C++与C语言细节差异分析
2007-11-10 10:08
399 查看
C和C++是很流行的语言,目前大学程序设计基础课大部分用的是C或者C++,但是由于种种原因,使用者对这两种语言的了解不够,本文指出,C++虽然是从C发展过来的,但是除了一些明显的不同,他们存在着细节上的差异。本文通过简要分析这些差异来说明这个观点。 C++是从“带类的C”发展过来的。在此基础上发展成为面向对象的语言,这个可以说是C++与C最大的差别。面向对象的讨论是一个很大的话题,涉及面向对象的讨论将使问题复杂化,也超出了本文的范围,在后面的介绍中,涉及面向对象的东西将尽量避开。 C++的“点心” 一、注释 C++的注释允许采取两种形式。第一种是传统C采用的/*和*/,另一种新采用的则是//。同时允许这两种注释风格,程序员可以选用他们所喜欢的形式。大部分的程序员,包括C++的设计者更喜欢//的注释风格。在小段代码的注释,//显然比/* */方便很多。然而,在注释掉很大段代码的时候,/* */就要比//方便了。 设计者认为,这种注释只是C++增加的一个次要的功能。 增加//的注释风格,会导致一些C与C++的不兼容,例如: x = a //* divide */ b 在C中表示x = a / b,在C++中表示x = a。这个例子被大部分程序员认为没有什么实际价值。但它的确表现了由//引起的一些不兼容。 二、布尔类型与逻辑运算 在C中没有布尔类型,一般布尔值就用整型代替,C++增加了布尔类型bool,同时也增加常量true和false。实际上,逻辑类型的变量在程序设计中是一个无法避免的东西,而C的程序员也经常通过整型用各种方法“造出”“布尔”类型。于是,C++索性就增加了bool数据类型。 而在一些人用的键盘上,没有如&,|,^等键,于是,C++增加了一些关键字,见表1:
三、引用(Reference)的出现与函数调用 引用的出现主要是为了支持运算符的重载。不过他在函数调用方面也有不小的好处。通常,在C中,想要改变参数的值可以通过传递指针来实现,现在,有了引用,这样的做法显得很方便。比如: int f(int &x) { return ++x; } f(y); 上面,当f(y);执行完,y的值增加了1,在C中,我们会这么做: int f(int *x) { return ++(*x); } 这只是一个很小的例子,但是,用引用的函数“干净”很多。在不涉及面向对象编程的时候,我更喜欢把引用当作C++给我们的“点心”。 四、C++定义块的消除 C规定,变量的声明块必须在语句执行块的前面。但是C++的作者认为,这样会降低程序的可读性,于是,C++的声明和执行语句可以混合起来。比较典型的例子是: for (int i = 0; …;…)…[2] 这样,变量i在这层for循环结束后就结束后就被释放,不会与前面或者后面用到的某个变量i混淆起来。 五、强制转换 C提供了一种类型的强制转换,形如(type)的方法,C++中保留了C的强制转换的方法,对于相同功能的强制转换,C++还增加了一种很像函数调用的强制转换的方法,例如可以这样写: int x; long y; x = (int)y; 在C++中,也可以写成: x = int(y); 或许,强制转换可以体现C的灵活性,但是,它也是一种很不安全的做法。C++增加了与强制转换有关的四个关键字,如表2:
[align=center] C++的转换在类上有很多应用,比如,dynamic_cast是主要应用于类的转换(本文不讨论)。而其他三种转换,不涉及类时,与C对应转换含义上没有多少区别,但是,由于用了这样的关键字,所以,C++程序员很容易在程序里找到相应的转换,并且清楚是怎么转换的。[/align] 六、struct含义的变化 对struct来说,struct在C++中已经变成了class,只不过,struct默认成员是公用的(public),而class默认成员是私用的(private)。于是,struct在C和C++中的含义也不同了。比如: struct { int x, y; int fun(int); }; 这个定义在C中是不合法的,而在C++中是合法的。 之所以把它称之为“点心”,是因为即使在面向过程的编程中,也可以利用一点点类的方法来使编程方便一些。有兴趣的读者可以用类写一些递归(如线段树的构造)程序。 在用struct定义变量的时候,C与C++也有一点区别,在C中,定义变量时struct是不能省略的,但是C++中是可以省略struct的(包括class也能省略)。比如: struct str_test { //definition }; str_test b; 在C++中,是允许的,但是在ANSI C中不允许这样做。 C++对C的不向下兼容 C++:尽可能地与C靠近,但又不过分地近——《C++语言的设计和演化》 一、C++字符常量的出现 在字符的处理上,C和C++也是有不同的。在C中没有字符常量,而在C++中有字符常量,可以用这样的代码来验证: printf(“%d/n”, sizeof(‘x’)); 用C编译,输出的结果是sizeof(int)的值,而用C++编译,输出的是1。 二、C++对enum严格的类型检查 C++的类型检查要比C严格很多,这点在下文还会有体现。我们先以数据类型enum为例,在C中,enum类型会直接转化成为int,实际上,在C++中也是,但是,为了保证运算时类型的对应,C++禁止了一些操作,比如: enum {a, b, c, d, e, f, g, h, i} x; x = a; x++; 在C中是允许的,但是在C++中是不允许的。因为增加运算需要两次类型转换,在C++中,一次是合法的,另外一次是不合法的。 把enum类型与int区分开来,这在实现函数重载上也会有很大的好处(下文会讲到重载)。 三、C++对指针运算的一些限制 在指针方面,可以体现C++对类型的严格要求,C++中void*不能直接被赋值给某个指针,而需要强制转换。如: int *p; void *a; p = a; 在C中是可以编译通过的,在C++编译器处理的时候会出现编译错误。另外,C++中void *类型是不能进行指针算术运算的。比如上例中,表达式: a + 1; 在C++中是不合法的,而在C中是合法的,与 (void *)((int)a + 1); 等价。 C++种禁止的另外一项是指针的减法,而在C中,指针的减法是被允许的。例如: int a, b; 在C中,&b - &a的值为((int)&b – (int)&a) / sizeof(int),在C++中,这是不允许的。 四、函数使用的一些不兼容 在函数的一些规则细节方面,C++与C是有些不同的。通常,我们这样定义函数: type function_name (parameter list) { declarations statements} 这种方法不管在C或者C++中都适用。但是C语言有一些是可以省略的,比如,type,默认为int,但是C++中不可以省略。另外,parameter list可以不指定变量的数据类型,而在parameter list与declarations之间定义参数的数据类型,例如: f(x) int x;{…} 这在C中是允许的,但是,C++里会出现这样的编译错误: `x' was not declared in this scope ISO C++ forbids declaration of `f1' with no type syntax error before `int' 另外一个数据类型规则上的不同是,对于没有parameter list的为空的解释,C认为,这样的parameter list表示,这个函数可以有任意多个任意类型的参数。但是C++则认为,这样的parameter list表示,这个函数没有任何参数。当然,C++的这种解释,C可以把parameter list定为void。 还有一点不同的是,虽然函数定义了返回类型(即type),但是在C中,函数可以不返回任何类型。比如: int f(int x) { return ; } 在C中是允许的,但是,C++编译的时候会说: return-statement with no value, in function declared with a non-void return type 简单地说,在数据类型这方面,C++要比C严格很多,而这种严格(特别是参数的一些方面),保证了函数重载可以出现在C++中(下文将提到)。 除了数据类型,函数方面,C++还有一些比C严格的地方。在C中函数可以没有声明而直接调用,但是C++保证,调用函数之前必须声明函数。即C很相信程序员,但是C++却不。 比如这样: int main() { f(); return 0; } 这样的程序在C中是不会出现编译错误的,只不过,如果这个函数在连接的库中没有定义,连接的时候会说: undefined symbol _f in module ……[3] 但是,在C++中,编译器会说: 'f' undeclared (first use this function) 关于函数,C++提供了两个功能,重载与默认参数,下文会提到。 C功能的C++新方法 一、#define与const #define是C和C++的预处理指令,在C中,用#define可以用来定义常量,由此增加程序的可读性,降低维护的成本。 但是const的出现,使得#define定义常量变得不受欢迎。因为#define只是做简单的文本替换,从定义的那刻起,一直到程序的末尾或者有指令#undef停止#define继续文本替换。但是const具有清晰的作用域的概念,因此使用const定义常量被认为是更好的方法。 例如: #define b 10 int f() { int b; } 会出现这样的编译错误: parse error before numeric constant 对于初学者来说,可能对这个编译错误莫名奇妙。因此,尤其是在C++编程中,用const被认为一种好的编程习惯。 不过,对于关键字const的处理方法,C与C++也是不同的,C中const的作用域默认是全局的,而C++中const的作用域默认是内部的。如果要在C++中使const的作用域是全局,那么可以用extern来声明。另外,在C中,const是有存储空间的,但是C++中,一个const有没有存储空间取决于这个const怎么用,如果没有必要,就不会给这个const开辟空间,比如仅仅需要用某个值来代替这个变量名(和C的#define很像)。 但是,一个变量被声明为const,并不是说这个变量的值不能被改变,而只是,编译器尽可能不让程序员改变变量的值。但是,由于C的类型检查非常宽松[4],所以,改变一个const的值是很容易的。如下的程序: const int a = 10; int *p = &a; (*p)++; 在C中,这样就把a的值改变了,虽然这样做是危险的。但是用C++编译这个程序的时候,会出现这样的编译错误: invalid conversion from `const int*' to `int*' 另外的一些不同是,C中,const可以不赋初值,但是C++中const一定要被初始化。如 const int a; 在C中是被允许的,但是在C++中是不允许的。而且,C中的const可以不指定类型,默认为int,而C++在定义或者声明const的时候,必须制定变量的类型。 二、#define与inline[5] 在C中,利用#define增加程序可读性的另外一个应用是用#define来“定义函数”,例如: #define f(x) (x) * (x) 在以后的程序中,如果可以这样“调用”: y = f(3); 它的好处是,碰到比较复杂的表达式,写程序的时候方便,读的时候也方便;另外一方面,由于这里只是作文本替换,而不是另外一个函数来实现,所以,速度比用函数快。 但是,有几点是需要小心的,首先,由于#define只是作文本替换,所以写这样的“函数”的时候要格外小心,如上例,如果写成: #define f(x) x * x 那么如果这样“调用”: y = f(2 + 1); 那么就变成了 y = 2 + 1 * 2 + 1; 并不是想要的 y = (2 + 1) * (2 + 1); 另外一个更加限制#define定义函数的使用的是,这样的文本替换,如果“参数”本身是一个表达式,那么,这个表达式所产生的作用很可能是无法预料的,比如,对上例 #define f(x) (x) * (x) 如果程序员这样调用: y = f(++x); 就变成了: y = (++x) * (++x); 一般程序员都只想让x加1,但是调用完f(x)之后,x的值增加了2。 在C中,这两个问题是很严重的,因为这样的错误很难查出来。在C++中,inline函数的出现很好得解决了这些问题。 inline在C++中出现的必要性并不是因为以上所说的原因,而是因为类的使用的需要。然而,由于涉及面向对象,本文并不讨论inline在类上的应用。本文讨论的是,inline函数的出现将很好得解决。 inline函数和普通的函数差不多。区别是,普通的函数通过调用来实现函数的功能,而inline函数直接把函数的代码编译,嵌入使用的地方。inline函数的定义与#define一样简单,只要在定义在使用之前。例如: inline int f(int x) { return ++x; } 值得注意的是,inline函数的声明对inline与否并没有作用。另外,代码是否被嵌入使用的地方(即inline与否),也并不是完全取决于inline关键字,由于某些函数是不可能做到inline的(比如,递归调用的函数),因此,inline关键字只是告诉编译器,如果可能的话,inline吧。 inline函数的优点在于,执行速度比普通的函数快,没有#define“定义函数”带来的烦恼。它的缺点是会加大编译后的目标文件,所以inline函数也是不能不加节制地使用的。 二、函数重载,运算符重载和默认参数 重载是否要加入到C++中,设计者犹豫了一下。实现(语言设计)困难、教学困难、代码阅读困难和执行效率会低是主要原因,后来,在证明了没有这些不利因素之后,他把重载加入了C++中。 函数重载可以让程序员使用名字相同的函数,然后实现类似的功能,比如,要计算一个数(整型和实型)的平方,C++中可以这样写: int sqr(int x) { return x * x; } double sqr(double x) { return x * x; } 在C中,如果要实现这样的功能,则需要两个不同的函数名字,调用的时候写需要注意,是以什么数据类型作为参数,从而调用不同的函数。[6] 重载的另一方面是运算符的重载,但实际上,运算符的重载是用函数来实现的,运算符的重载使得程序阅读起来很方便。因此有人把运算符的重载称为“语法点心”。但是,为了避免不必要的麻烦,C++对运算符的重载加了很大的限制,比如,只能对已经存在的运算符进行重载,另外,重载运算符不能违反该运算符原来的含义。 函数重载的功能有时候可以用另外的一种方法来实现,即默认参数。[7]比如这样的例子: int f(int x) { return x; } int f(int x, int y) { return x * y; } 那么,有了默认参数,我们可以这样做: int f(int x, int y = 1) { return x * y; } 这样可以只写一个函数,效果是相同的。调用的时候,可以有一个参数,也可以有两个。 默认参数也有严格的规定,有默认值的参数只能放在参数表的最后。 支持重载是好事吗?Java并没有引入重载。有这样的一种观点:重载是危险的。因为这样会使程序员不知道自己调用的是什么函数,特别是C++中含有的自动类型转换,这样使编程变得需要很小心翼翼。 三、C++的iostream与C的stdio iostream和stdio的差异实际上并不小,也涉及到C和C++的库,但是由于它们太常用了,因此这里提一下。 C的stdio通过让程序员对文件指针的操作(stdin和stdout可以认为是特殊的指针),一个个函数来实现对文件的操作和数据的输入输出。 stdio似乎已经足够了,因为它很高效。而且,也是比较方便的。但是C++设计者认为,它不够类型安全。因此设计了类型安全且高效的iostream,一部分原因是因为它是用类做的,至少有constructors和destructors保证一些操作(比如,不必担心忘了把文件关掉,constructor和destructors是面向对象的内容,本文不讨论)。 不过,我们可以这样认为,大部分时候,iostream不只是类型安全,而且,很方便,比如,我们要输入变量x的值。在c中我们需要知道他的数据类型,至少要知道,以什么样的数据类型输出,比如,我们要输出整数x,用stdio,这样写: printf(“%d”, x); 但是,用iostream,连x的类型都不需要管,这样写就可以了: cout << x; 这是个简单的例子,但是已经看出来iostream这种情况下的方便了。 另外一个问题是,iostream真的高效吗、足够高效吗?答案是,它很高效,但是与stdio比起来,它是低效的,在大规模的数据输入输出的时候,这种差距就显示出来了。 C语言,还是C++语言? using namespace std开始说起 现在的程序设计基础的教学,用什么语言呢?刚开始的时候,教员会告诉学生,在写程序之前,写上这样的两句: #include <iostream> using namespace std; 在刚开始的时候,似乎不需要去理解这是为什么,但这也就是C++增加的内容——名字空间(namespace),这是C语言没有的。 名字空间的由来是因为大型程序设计中,名字(变量、函数等)会很多,一不小心就会引起混乱、冲突。于是要寻求一种解决方案,如果让程序员来刻意注意这个问题,就增加了编程成本,也不方便。于是由C++语言本身来解决这个问题,于是出现了名字空间。让程序员可以把名字放在不同的名字空间里,方便引用,也避免冲突。using namespace std;就是指能在当前的程序中可以直接用名字空间std里面的名字;否则,可以通过std::name的方法使用std里面的名字,std是很多C++系统的库的提供的一个名字空间,包含了一些常用的名字。 程序设计基础课程一般不会在乎所用的语言,于是索性将C与C++混淆,实际上,一般程序设计基础课程用的是C++的编译器,但是教的都是面向过程的内容,换句话说没,用的大部分是C++的C真子集。 小结 通过上面的比较,我们可以看出,C++不仅仅是由C发展而来的面向对象程序设计语言,C并不是C++的真子集。另外,C++对C所做的改动也都是有原因的。从事物发展的角度看,C++并不完美(比如强制转换),但是可以从这个角度看出程序设计语言发展的一些规律:在进步,但难以做到完美。 参考文献 Al Kelley & Ira Pohl. A book on C: Programming in C (Forth Edition). 机械工业出版社 2004 Bruce Eckel. Thinking in C++ (Second Edition) 机械工业出版社2002 Scott Meyers. Effective C++ HTML版 Bjarne Stroustrup 著, 裘宗燕 译《C++程序设计语言》 机械工业出版社 2002 Bjarne Stroustrup 著, 裘宗燕 译《C++语言的设计和演化》机械工业出版社 2002 |
相关文章推荐
- C++与C语言细节差异分析
- C++与C语言细节差异分析
- C++、Java和C#语言在处理“虚拟私有方法”上的差异
- C++ 语言特性的性能分析
- Java和C++在细节上的差异(四)
- C++ 语言特性的性能分析
- C++与C语言容易忽视的几个差异
- Java和C++在细节上的差异
- C++、Java和C#语言在处理“虚拟私有方法”上的差异
- C++ 语言特性的性能分析
- 基本语言细节--《The C++ Programming Language 》--(3)基本功能
- C++ 语言特性的性能分析
- Java和C++在细节上的差异(二)
- 基本语言细节--inline函数的几点分析
- 基本语言细节--《The C++ Programming Language 》--(4)剑指新类型
- Java和C++在细节上的差异(转)
- C++ 应用程序性能优化--第 2 章:C++ 语言特性的性能分析
- 基本语言细节---C++ 虚函数表解析 陈皓
- C++ 语言特性的性能分析 第 2 章:C++ 语言特性的性能分析
- C++ 语言特性的性能分析