C与C++区别

来源:互联网 发布:炽热狙击无法连接网络 编辑:程序博客网 时间:2024/05/21 14:30

嵌入式C++编程指南

-----------------------------------------------------------------------------------

A. 移植:从C语言到C++

A.1 字符常量

注意

C语言中,字符常量的数据类型是int;在C++中,它的数据类型是char。



i = sizeof(\'a\');

在C语言中,该语句将sizeof(int)的值(通常大于1)赋给变量i;在C++中,该语句将sizeof(char)的值赋给变量i,该值总是等于1。

char ch = 'a';

在C和C++中,ch占用1个字节的空间。

要点

在进行从C语言到C++的代码移植时,应该重写那些与字符常量的存储长度相关的表达式,以便去除这种依赖性。

A.2 文件范围内的对象声明

注意

在C++的文件范围内,不带存储类型指定符(extern/static ..)的对象声明表示这就是对象的定义,且该对象具有外部连接。如果该定义不含初始化,则该对象的初始值为0(如同C语言一样,在C++程序里声明的对象必须且只能被定义一次)。

在C语言的文件范围内,不带存储类型指定符,且不含初始化的对象声明表示这只是试探性的定义,这种定义在一个编译单元之内可以出现多次。



int a; /* (1) */

int a = 10; /* (2) */

在C语言中,语句(1)是试探性的定义。由于语句(2)是明确的变量定义,C编译程序将把语句(1)当作纯粹的变量声明。

在C++中,语句(1)和(2)都是变量定义。由于语句(1)的存在,语句(2)就成了重复定义,因此将导致出错。

要点

在C++中,当且仅当文件范围内的对象声明带有显式的"extern"指定符并且不含初始化时,它才仅仅是对象声明(而非定义)。(注意区别类的声明)在文件范围内,每个被声明的对象都必须且只能被定义一次。除此之外,所有对该对象的声明都必须带有显式的"extern"指定符并且不含初始化。

A.3 常量类型限定符

注意

在C语言的文件范围内,不带显式存储类型指定符的常量对象都具有外部连接。在C++中,这种对象只具有内部连接。



+- file1 --------------------+

| extern const int n; |

+----------------------------+

+- file2 --------------------+

| const int n = 10; |

+----------------------------+

在C语言中,file2里的对象n具有外部连接,所以在file1里对n(也具有外部连接)的引用是成立的。在C++中,file2里的对象n只具有内部连接,所以在file1里不能引用n。

要点

要使C++中的常量对象具有外部连接,必须使用显式的"extern"指定符。

A.4 转换成void指针

注意

C语言能够自动地进行从void *到T *的标准类型转换(T是任意对象类型)。在C++中不存在这样的自动转换,而必须进行强制类型转换。

下列标准C库函数的返回值都是void *:

calloc, malloc, realloc, bsearch, memcpy, memmove, memchr, memset

当把上述函数的返回值赋给一个非void类型的指针时,C++要求进行显式的类型转换。



int *p;

p = malloc(10 * sizeof(int));

在C++中,对指针p的赋值必须经过显式的类型转换,如:

p = (int *)malloc(10 * sizeof(int));

要点

在C++中,应当使用new运算符来代替calloc, malloc, realloc等函数(参见A.12)。可以忽略memcpy, memmove, memset等函数的返回值(它们只是将其第一个参数转换成void *并返回之)。对于所有其它返回void *的函数(标准库函数或自定义函数),在将其返回值转换成其它类型的指针时,都应当进行显式的类型转换。

A.5 枚举类型

注意

枚举在C语言中相当于整型。程序可以将枚举类型作为整型使用或者相反,无需类型转换。在C程序里可以对枚举对象进行++和--运算。

在C++中,每个枚举都属于不同的类型。C++支持从枚举类型到整型的标准类型转换,反之则不行。在C++程序里不能对枚举对象进行内建的++和--运算,也不允许对其进行任何的复合赋值(如+=)。



enum RGB {red, green, blue} rgb;

++rgb;

如果这里的++是内建运算符的话,++rgb对C++来说就是错误的语句。它等价于:

rgb = rgb + 1;

除非使用如下的强制类型转换,否则上面的语句同样是错误的:

rgb = RGB(rgb + 1);

最佳的选择是在RGB类型里重载运算符++,如:

RGB &operator++(RGB &x)

{

return x = RGB(x + 1);

}

要点

在进行从C程序向C++程序的移植时,应当为运算符++和--提供类型安全的实现方式,以满足枚举类型的需求。

A.6 类型转换、参数声明或sizeof表达式里的类型定义

注意

在C语言的类型转换表达式、参数声明或sizeof表达式里可以进行类型定义。在C++中这是不允许的。



void func (struct TAG {int a;} st)

{

...

}

这里,类型TAG在参数声明里定义。

要点

参数声明里用到的类型应该在包含该函数声明的范围或更大的范围内定义。类型转换表达式或sizeof表达式里用到的类型应该在包含该表达式的范围或更大的范围内定义。

A.7 超越局部对象定义的控制转移

注意

在C语言中,goto或switch语句可以在代码块的范围内进行超越对象定义的控制转移,同时也可能绕过对该对象的初始化。在C++中这是不允许的。



goto LABEL;

{

int v = 0;

...

LABEL:

...

}

假设标号LABEL:之后的代码不要求变量v必须被初始化为0,则上面的程序在C语言中就是合法的。但在C++中,这必将产生错误。

要点

不要使goto或switch语句绕过对局部对象的定义和初始化。

A.8 字符数组的初始化

注意

在C语言中,可以使用比数组的容量还多一个字符的字符串(含串尾字符\'\\0\')来初始化字符数组。在C++中这是不允许的。



char s[3] = "abc";

数组的容量是3,而字符串的长度是4。这在C语言中是合法的,在C++中则不允许。

要点

不要使用比数组的容量更长的字符串(含串尾字符\'\\0\')来初始化字符数组,而必须以该字符串的确切长度作为数组容量(如char s[4] = "abc";)。

然而,如果希望即使在字符串的长度发生改变时也总是能够获得预期的结果,建议使用不指明数组容量的初始化方式(如char s[] = "abc";)。

A.9 原型声明

注意

C++程序要求在调用函数之前声明其原型。在C语言中,调用未经声明的函数是允许的。此外,C++程序认为函数声明"f()"等价于"f(void)",即无参函数。而在C语言中,该声明表示参数的个数和类型未定



extern void func();

....

sub();

func(0);

在C++中,对函数sub的调用是错误的,因为没有声明其原型。对函数func的调用同样出错,因为该函数的声明指出它是不带参数的。

要点

在调用函数之前一定要声明其原型。为了强调对函数f的调用是不带参数的,应该把它的声明写成"f(void)"。

A.10 C++增加的关键字

注意

下列C++关键字不是C语言的关键字:

asm bool catch class const_cast delete dynamic_cast explicit

false friend inline mutable namespace new operator private

protected public reinterpret_cast static_cast template this

throw true try typeid typename using virtual wchar_t



int class, new, old;

该声明在C语言中是合法的,在C++则不允许。

要点

不要把C++关键字作为标识符使用。

A.11 嵌套类型的作用域

注意

在C语言中,定义在结构或联合之内的类型名与该结构名或联合名有着完全相同的作用域。在C++中,嵌套类型名的作用域则局限在相应的结构或联合之内。



struct S

{

int a;

struct T

{

int t;

} b;

int c;

enum E {V1, V2} e;

};

struct T x;

enum E y;

对x和y的声明在C语言中是合法的,在C++中则不允许。在C++中,类型名T和E不能作用于结构S的定义之外。

要点

不要定义嵌套的类型名,除非所有对该名称的引用都位于相应的结构或联合之内。

A.12 动态内存管理

注意

C++不保证运算符new和delete对相同的内存采取与函数malloc和free相同的内存管理策略。因此,只有事先用"new"获取的内存才能用"delete"来释放;只有事先用malloc(或calloc, realloc)获取的内存才能用free来释放。



int (*p)[10];

p = (int (*)[10])malloc(sizeof(*p));

...

delete p;

该delete操作的行为是未定义的。

要点

不要在C++程序中使用malloc, calloc, realloc, free等函数;应该只用new和delete来管理内存。

A.13 \'/\'后面的\'/*\'

注意

紧跟在符号\'/\'之后的C语言风格的注释"/* */"将被解释为C++风格的注释"//..."。



i = j //* comment */ k;

字符序列"//"将被解释为注释分隔符。该语句将被解释为"i = j",而非"i = j / k;"。

要点

不要把C语言风格的注释"/* */"紧跟在符号\'/\'之后书写。

B. 代码长度

B.1 对象的初始化

注意

初始化一个对象有多种方式。其中某些方式会生成不必要的临时对象,从而导致代码长度的增加。



T x(i) // (1)

T x = i; // (2)

T x = T(i) // (3)

T x; // (4)

x = i;

(1) 该语句直接调用对象x的构造函数,不会生成临时对象,就如同调用:

x.T(i); // apply constructor to x

(2) 在某些实现中,该语句象(1)一样直接调用对象x的构造函数。在另一些实现中,它先构造一个临时对象,再用该对象来初始化x,就如同调用:

temp.T(i); // apply constructor to temp

x.T(temp); // apply copy constructor to x

temp.~T(); // apply destructor to temp

(3) 该语句等价于(2)。

(4) 该语句调用类型T的缺省构造函数来初始化x,然后使用赋值运算符为x赋新值。该赋值操作能够释放x当前占用的资源并为其分配新的资源。

x.T(); // apply default constructor to x

x.operator = i; // apply assignment operator to x

要点

优先使用上述方式(1)进行对象声明。

B.2 inline指定符

注意

内联展开能够减少函数调用和返回时的开销,但可能导致代码长度的增加。缺省情况下,在类内部定义的成员函数都将被内联展开。

要点

只对小函数使用inline指定符。不适合进行内联展开的成员函数应该在类之外加以定义,从而使其免于被内联展开。

B.3 返回值临时对象

注意

如果调用某个函数时,该函数返回某个对象的值,则为此将创建并销毁一个临时对象,从而导致代码长度和执行时间的增加。



class Matrix

{

int a, b;

public:

Matrix &operator += (const Matrix &);

friend:

Matrix operator +(const Matrix &, const Matrix &);

};

Matrix operator +(const Matrix &, const Matrix &)

{

...

}

void func()

{

Matrix a, b;

a = a + b; // (1)

a += b; // (2)

}

语句(1)调用了运算符+,该操作返回一个Matrix型对象的值。在某些实现中,这将创建(并随后销毁)一个Matrix型的临时对象。

语句(2)调用了运算符+=,该操作不会生成Matrix型的临时对象。

要点

对于类的对象,应当使用复合赋值运算符(比如用+=而不是+和=),以避免创建和销毁不必要的临时对象。

B.4 运算符new和delete

要点

为不同的类分别实现其所需的运算符new和delete,可以提高动态内存管理的速度,改善内存的使用。

B.5 全局对象的初始化

注意

对各全局对象进行初始化的次序与具体实现有关。然而,在单个编译单元之内的初始化次序必定与对象声明的次序相同。



/* file1 */

int a = f();

/* file2 */

int b = f();

/* file3 */

int f(void)

{

static int a = 0;

return a++;

}

程序可能将变量a初始化为0,将变量b初始化为1,或者相反,取决于具体实现所选择的初始化次序。为了得到确定的初始化次序,可以把变量b的声明从file2移到file1中,如:

/* file1 */

int a = f();

int b = f();

/* file3 */

int f(void)

{

static int a = 0;

return a++;

}

初始化次序将是先a后b。

要点

在C++中,应当避免使程序依赖于不同编译单元之内的各全局对象的初始化次序。

C. 运行速度

C.1 类对象数组的new和delete操作

注意

声明一个类对象数组时,程序将对每个数组元素调用其构造函数。同样,在该数组超出生存期之后,程序将对每个数组元素调用其析构函数。构造和析构函数所需的处理时间可能会长得超出预期,这在实时处理过程中将导致问题。

要点

如果处理时间是重要的考量因素,则在程序中应当避免创建和销毁较大的类对象数组。

C.2 循环程序中的对象声明

注意

如果在循环程序中声明一个类对象,则其构造和析构函数在每次循环中都将被调用。构造和析构的开销将降低循环程序的运行速度。



for (i = 0; i < 1000; i ++)

{

FOO a;

...

}

要点

不要在循环程序中声明类对象。应当把此类声明放在循环之外。

D. 基于ROM的代码

D.1 ROM中的常量对象

注意

一般而言,如果满足下列条件,常量对象就可以存放在ROM中:

-- 具有静态的存储期;

-- 由常量表达式进行初始化;

-- 是POD(plain old data)类型的对象。

POD类型是指:

-- 标量(数值、枚举或指针)类型;

-- 满足下列条件的类、结构或联合类型:所有数据成员都是公共的;所有数据成员都是POD类型的;没有自定义的构造或析构函数;没有基类;没有虚函数;

-- 由POD类型的元素组成的数组。



static const char lang[] = "EC++";

class A

{

int a;

public:

A();

~A();

};

const A x;

数组lang可以存放在ROM中,对象x则不行。

要点

常量对象如果需要存放在ROM中,则应当将其声明成POD类型,并用常量表达式进行初始化。


转自:http://hi.baidu.com/iruler/blog/item/ab5fea3502db591390ef3923.html#lastcmt

原创粉丝点击