嵌入式C++开发详解(一) 一、C++概述 1.嵌入式开发中为什么选择C++语言? (1)面向过程编程的特点 C语言特点:C语言是在实践的过程中逐步完善的 ·没有深思熟虑的设计过程 ·使用时存在很多“灰色地带” …… ·残留量过多低级语言的特征 ·直接利用指针进行内存操作 …… 面向过程的编程特点: 面向过程程序设计:数据结构+算法 ·主要解决科学计算问题,用户需求简单固定 ·特点:分析解决问题所需要的步骤 利用函数实现各个步骤 依次调用函数解决问题 ·问题:软件可重用性差 软件可维护性差 构建的软件无法满足用户需求 (2)面向对象编程的特点 面向对象的编程特点: 面向对象程序设计:由现实世界建立软件模型 ·将现实世界中的事物直接映射到程序中,可直接满足用户需求 ·特点:直接分析用户需求中涉及的各个实体 在代码中描述现实世界中的实体 在代码中关联各个实体协同工作解决问题 ·优势:构建的软件能够适应用户需求的不断变化 直接利用面向过程方法的优势而避开其劣势 C++语言特点:高效的面向对象语言,并且能够兼容已经存在的代码 2.C++为什么难学? C++支持的编程格式: ·过程式 ·数据抽象 ·基于对象 ·面向对象式 ·函数式 ·泛型形式 ·模板元形式 值语义和对象语义: 值语义可以拷贝与赋值,对象语义不可进行拷贝与赋值 3.C++相关基础知识点 (1)C++之父是谁? 本贾尼·斯特劳斯特卢普 1982年,美国AT&T公司贝尔实验室的Bjarne Stroustrup博士在c语言的基础上引入并扩充 了面向对象的概念,发明了—种新的程序语言。为了表达该语言与c语言的渊源关系,它被命名为C++。而Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)博士被尊称为C++语言之父。 (2)C++语言的标准 C++ 98 标准 C++标准第一版,1998年发布。正式名称为ISO/IEC 14882:1998[17] 。 C++ 03 标准 C++标准第二版,2003年发布。正式名称为ISO/IEC 14882:2003[18] 。 C++ 11 标准 C++标准第三版,2011年8月12日发布。正式名称为ISO/IEC 14882:2011[19] 。 C++11对容器类的方法做了三项主要修改。 首先,新增的右值引用使得能够给容器提供移动语义。其次,由于新增了模板类initilizer_list, 因此新增了将initilizer_list作为参数的构造函数和赋值运算符。第三,新增的可变参数模板(variadic template)和函数参数包(parameter pack)使得可以提供就地创建(emplacement)方法。 C++ 14 标准 C++标准第四版,2014年8月18日发布。正式名称为ISO/IEC 14882:2014[21] 。 C++14是C++11的增量更新,主要是支持普通函数的返回类型推演,泛型 lambda,扩 展的 lambda 捕获,对 constexpr 函数限制的修订,constexpr变量模板化等[22] 。 (3)C++11值得学习的新特性 ·智能指针如shared_ptr、weak_ptr等 ·rvalue reference ·function/bind ·lambda expression and closure 二、从C到C++的升级 1.声明定义 C++:C++中更强调语言的实用性,所有变量都可以在需要使用时再定义 如:for(int i = 0; i < 2; i++) C语言:变量必须在作用域开始时定义 2.register关键字的升级 经常被访问的变量我们就可以用register修饰为寄存器变量,请求编译器尽可能的将变量存在CPU的内部寄存器中,节省了CPU从内存中抓取数据的时间,从而提高了运行效率。 C语言:register只能修饰局部变量,不能修饰全局变量和函数; register修饰的变量不能通过取地址来获取寄存器变量; register修饰的变量一定是CPU能接受的数据类型。 C++:在C++中依然支持register关键字 C++编译器有自己的优化方式,不使用register中也可能做优化 C++中可以取register变量的地址(C++编译器发现程序中需要取register 变量的地址时,register对变量声明无效) 3.const关键字 C++:C++编译器对const常量的处理 ·当碰见常量声明时在符号表中放入常量 ·编译过程中若发现使用常量则以符号表中的值替换 ·编译过程中若发现对const使用了extern或者&操作符,则给对应的常量 分配存储空间 注意:C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中 的值。 C语言:C语言中const修饰变量,空间里的值可变,但是不能通过变量名来修改这个空间的对应值。 4.内存分配与释放 (1)C++中动态内存分配 ·C++中通过new关键字进行动态内存申请 ·C++的动态内存申请是基于类型进行的 ·delete关键字用于内存释放 变量申清: Type *pointer = new Type; …… delete pointer; 数组申清: Type *pointer = new Type[]; …… delete[] pointer; (2)new与malloc区别 ·new关键字是C++的一部分,malloc是由C库提供的函数 ·new以具体类型为单位进行内存分配,malloc只能以字节为单位进行内存 分配 ·new在申请单个类型变量时可进行初始化,malloc不具备内存初始化的特 性 5.引用VS指针 (1)引用 ·引用是给一个变量起别名 ·定义一个引用的一般格式: ·类型 &引用名 = 变量名 ·例如:int a = 1; int &b = a; //b是a的别名,因此a和b是同一个单元 注意:定义引用时一定要初始化,指明该引用变量是谁的别名 ·在实际应用中,引用一般用作参数传递与返回值 (2)const引用是指向const对象的引用 (3)函数传参:按引用传递 引用传递方式是在函数定义时在形参前面加上运算符“&” 例如:void swap(int &a,int &b); ·按值传递方式容易理解,但形参值得改变不能对实参产生影响 ·地址传递方式通过形参的改变使响应的实参改变,但程序容易产生错误且难以阅读 ·引用作为参数对形参的任何操作都能改变相应的实参的数据,又使函数调用显得方便、自然。 (4)函数返回值:引用作为函数返回值 ·引用的另一个作用是用于返回引用的函数 ·函数返回引用的一个主要目的是可以将函数放在赋值运算符的左边 ·注意:不能返回对局部变量的引用 (5)引用与指针的区别 ·引用访问一个变量是直接访问,而指针是间接访问 ·引用是一个变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间 ·引用一经初始化不能再引用其它变量,而指针可以 ·尽可能使用引用,不得已时使用指针 6.函数升级 (1)内联函数(inline):以空间换时间(频繁调用且简短) 内联函数的使用: inline int max(int a,int b) { return a>b?a:b; } #define MAX(a,b) (a)>(b)?(a):(b) 内联函数与带参数宏的区别: ·内联函数调用时,要求实参和形参的类型一致,另外内联函数会先对实 参表达式进行求值,然后传递给形参;而宏调用只用实参简单地替换形 参。 ·内联函数是在编译的时候、在调用的时候将代码展开的,而宏则是在预 处理时进行替换的 ·在C++中建议有用inline函数来替换带参数的宏 (2)函数重载(overload) 相同的作用域,如果两个函数名称相同,而参数不同,我们把它们称为重载overload 函数重载的条件:(函数的返回值不能作为条件) ·函数重载不同形式: ·形参数量不同 ·形参类型不同 ·形参顺序不同 ·形参数量和形参类型都不同 ·调用重载函数时,编译器通过检查参数的个数、类型和顺序来确定相应 的被调用函数 name managling与extern “C”: ·name managling 这里把它翻译为名字改编 ·C++为了支持重载,需要进行name managling ·extern“C”实现了C与C++混合编程 #ifdef__cpluscplus extern“C” { #endif …… #ifdef__cpluscplus } #endif (3)带默认参数的函数: ·函数没有声明时,在函数定义中指定形参的默认值 ·函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值 ·默认值的定义必须遵守从右到左的顺序,如果某个形参没有默认值,则 它左边的参数就不能有默认值 void funcl(int a,double b = 4.5,int c = 3) //合法 void funcl(int a = 1,double b ,int c = 3) //不合法 ·函数调用时,实参与形参按从左到右的顺序进行匹配 7.命名空间-namespace (1)命名空间 ·在C语言中只有一个全局作用域 ·C语言中所有的全局标识符共享一个作用域 ·标识符之间可能有冲突 ·C++中提出了命名空间的概念 ·命名空间将全局作用域分成不同的部分 ·不同命名空间中的标识符可以同名而不会发生冲突 ·命名空间可以相互嵌套 ·全局作用域也叫默认命名空间 (2)如何定义命名空间 namespace First { int i = 0; } namespace Second { int i = 1; } using namespace First; int main() { cout<<i<<endl; cout<<Second::i<<endl; return 0; } (3)如何使用命名空间 ·使用整个命名空间:using namespace name; ·使用命名空间中的变量:using name::variable; ·使用默认命名空间中的变量: ::variable 默认情况下可以直接使用 默认命名空间中的所有标识符 8.新的类型转换运算符 (1)static_cast<T>(expr) ·用于基本类型间的转换,但不能用于基本类型指针间的转换 ·用于有继承关系类对象之间的转换和类指针之间的转换 int main() { int i = 0; char c = ‘c’; int *pi = &i; char *pc = &c; c = static_cast<char>(i);//success pc = static_cast<char *>(pi);//error return 0; } static_cast是在编译期进行转换的,无法在运行时检测类型,所以类类型之间的转换可能存在风险 (2)const_cast<T>(expr) const_cast强制类型转换——用于去除变量的const属性 int main() { const int &j = 1; int & k = const_cast<int&>(j); const int x = 2; int & y = const_cast<int&>(x); k = 5; cout << j << endl; cout << k << endl; y = 3; cout <<x<<endl; cout<<y<<endl; cout <<&x<<endl; cout<<&y<<endl; return 0; } (3)reinterpret_cast<T>(expr) reinterpret_cast强制类型转换 ·用于指针类型间的强制转换 ·用于整数和指针类型间的强制转换 typedef void(pf)(int) int main() { int i = 0; char c = ‘c’; int * pi = reinterpret_cast<int *>(&c); char * pc = reinterpret_cast<char *>(&i); PF * pf = reinterpret_cast<PF*>(0x12345678); c = reinterpret_cast<char>(i); return 0; } reinterpret_cast直接从二进制位进行赋值,是一种极其不安全的转换。 (4)dynamic_cast<T>(expr) dynamic_cast强制类型转换 ·主要用于类层次间的转换,还可以用于类之间的交叉转移 ·dynamic_cast还具有类型检查的功能,比static_cast更安全
|