嵌入式C++开发详解

267 阅读10分钟

一、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更安全