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数据类型。
表1 三、引用(Reference)的出现与函数调用 引用的出现主要是为了支持运算符的重载。不过他在函数调用方面也有不小的好处。通常,在C中,想要改变参数的值可以通过传递指针来实现,现在,有了引用,这样的做法显得很方便。比如: int f(int &x) 上面,当f(y);执行完,y的值增加了1,在C中,我们会这么做: 这只是一个很小的例子,但是,用引用的函数“干净”很多。在不涉及面向对象编程的时候,我更喜欢把引用当作C++给我们的“点心”。 四、C++定义块的消除 C规定,变量的声明块必须在语句执行块的前面。但是C++的作者认为,这样会降低程序的可读性,于是,C++的声明和执行语句可以混合起来。比较典型的例子是: for (int i = 0; …;…)…[2] 五、强制转换 C提供了一种类型的强制转换,形如(type)的方法,C++中保留了C的强制转换的方法,对于相同功能的强制转换,C++还增加了一种很像函数调用的强制转换的方法,例如可以这样写: int x; 在C++中,也可以写成: x = int(y); 或许,强制转换可以体现C的灵活性,但是,它也是一种很不安全的做法。C++增加了与强制转换有关的四个关键字,如表2:
表2
C++的转换在类上有很多应用,比如,dynamic_cast是主要应用于类的转换(本文不讨论)。而其他三种转换,不涉及类时,与C对应转换含义上没有多少区别,但是,由于用了这样的关键字,所以,C++程序员很容易在程序里找到相应的转换,并且清楚是怎么转换的。
六、struct含义的变化 对struct来说,struct在C++中已经变成了class,只不过,struct默认成员是公用的(public),而class默认成员是私用的(private)。于是,struct在C和C++中的含义也不同了。比如: struct 这个定义在C中是不合法的,而在C++中是合法的。 之所以把它称之为“点心”,是因为即使在面向过程的编程中,也可以利用一点点类的方法来使编程方便一些。有兴趣的读者可以用类写一些递归(如线段树的构造)程序。 struct str_test 在C++中,是允许的,但是在ANSI C中不允许这样做。 C++对C的不向下兼容 C++:尽可能地与C靠近,但又不过分地近——《C++语言的设计和演化》 一、C++字符常量的出现 在字符的处理上,C和C++也是有不同的。在C中没有字符常量,而在C++中有字符常量,可以用这样的代码来验证: printf(“%d/n”, sizeof(‘x’)); 用C编译,输出的结果是sizeof(int)的值,而用C++编译,输出的是1。
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++中,一次是合法的,另外一次是不合法的。
在指针方面,可以体现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} 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。 int f(int x) 在C中是允许的,但是,C++编译的时候会说: return-statement with no value, in function declared with a non-void return type 简单地说,在数据类型这方面,C++要比C严格很多,而这种严格(特别是参数的一些方面),保证了函数重载可以出现在C++中(下文将提到)。 int main() 这样的程序在C中是不会出现编译错误的,只不过,如果这个函数在连接的库中没有定义,连接的时候会说: undefined symbol _f in module ……[3] 但是,在C++中,编译器会说: 'f' undeclared (first use this function) 关于函数,C++提供了两个功能,重载与默认参数,下文会提到。 C功能的C++新方法 一、#define与const #define是C和C++的预处理指令,在C中,用#define可以用来定义常量,由此增加程序的可读性,降低维护的成本。 例如: #define b 10 会出现这样的编译错误: parse error before numeric constant 对于初学者来说,可能对这个编译错误莫名奇妙。因此,尤其是在C++编程中,用const被认为一种好的编程习惯。 const int a = 10; 在C中,这样就把a的值改变了,虽然这样做是危险的。但是用C++编译这个程序的时候,会出现这样的编译错误: invalid conversion from `const int*' to `int*'
那么如果这样“调用”: y = 2 + 1 * 2 + 1;
另外一个更加限制#define定义函数的使用的是,这样的文本替换,如果“参数”本身是一个表达式,那么,这个表达式所产生的作用很可能是无法预料的,比如,对上例 #define f(x) (x) * (x) 如果程序员这样调用: y = f(++x); 就变成了: y = (++x) * (++x); 一般程序员都只想让x加1,但是调用完f(x)之后,x的值增加了2。 inline int f(int x) 值得注意的是,inline函数的声明对inline与否并没有作用。另外,代码是否被嵌入使用的地方(即inline与否),也并不是完全取决于inline关键字,由于某些函数是不可能做到inline的(比如,递归调用的函数),因此,inline关键字只是告诉编译器,如果可能的话,inline吧。 二、函数重载,运算符重载和默认参数 重载是否要加入到C++中,设计者犹豫了一下。实现(语言设计)困难、教学困难、代码阅读困难和执行效率会低是主要原因,后来,在证明了没有这些不利因素之后,他把重载加入了C++中。 int sqr(int x) 在C中,如果要实现这样的功能,则需要两个不同的函数名字,调用的时候写需要注意,是以什么数据类型作为参数,从而调用不同的函数。[6] int f(int x) 那么,有了默认参数,我们可以这样做: int f(int x, int y = 1) 这样可以只写一个函数,效果是相同的。调用的时候,可以有一个参数,也可以有两个。
iostream和stdio的差异实际上并不小,也涉及到C和C++的库,但是由于它们太常用了,因此这里提一下。 printf(“%d”, x);
using namespace std开始说起 现在的程序设计基础的教学,用什么语言呢?刚开始的时候,教员会告诉学生,在写程序之前,写上这样的两句: 小结 通过上面的比较,我们可以看出,C++不仅仅是由C发展而来的面向对象程序设计语言,C并不是C++的真子集。另外,C++对C所做的改动也都是有原因的。从事物发展的角度看,C++并不完美(比如强制转换),但是可以从这个角度看出程序设计语言发展的一些规律:在进步,但难以做到完美。 参考文献 Al Kelley & Ira Pohl. A book on C: Programming in C (Forth Edition). 机械工业出版社 2004 |