嵌入式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更安全