C与C++两个关系亲密的编程语言,它们本质上是两中语言,只是C++语言设计时要求尽可能的兼容C语言特性,因此C语言中99%以上的功能都可以使用C++完成。本文探讨那些存在于C语言中的特性,但是在C++中缺失或者表现出不同行为的特性。了解这些特性能让你更深入地理解这两个语言,但是,本文中所罗列的每一项特性都不是建议你在程序开发中采用的技巧,而恰恰相反是应该避免使用的特性。为了你的代码可读性更好,移植性更强,请不要在你的代码中的任何地方秀出这样的特性。了解它是为了更好地避免她。
C++语言类型检查更强
C语言的类型检查比C++要弱,比如在C语言中void*类型就可以被赋值给char*类型,虽然二者不是同一个类型,甚至二者实际为完全不同的类型,如下图1中的代码,首先定义一个整型变量a,之后使用void指针p1指向整型变量a的地址;再通过char指针p2指向p1,在下图中的第19和20行代码,C编译器是没有提供任何错误的,对于C语言来说,一切都很合理。
但是对于C++编译器来说,void类型的指针是不能够直接赋值给char类型指针的,如上图1中的做法,C++编译器会毫不留情地抛出错误,如下图2所示
对于C++来说,将void*类型的变量赋值给char*的变量,必须显式地指定转换意图,编译器是不会自动为你转换的,他只知道你要把两个不同的东西搞到一起,对他来说是不可接受的。显式地转换类型可以实现你的意图,如图3第20行代码所示,通过使用static_cast类型转换表达式实现转换为目标变量的相同类型。
指针限定符(qualifier)
对于无限定符的指针来说,它不允许或不建议将无限定符的指针赋值给含有限定符的指针,图4中代码,第4行代码为无限定符的指针,第5行代码为含有一个限定符的指针,第6行代码为含有两个限定符的指针。
使用C编译器进行编译图4中的代码时,编译器给出了如下的警告信息,它提示了你进行了两种不兼容类型的指针转换,使用的时候要小心了,出现什么问题自己要负责。
但是,对于C++编译器来说,使用如图6所示的代码。
编译时给出的错误就是如下图7的情景了,图6中第5行代码是合法的,但是第6行代码对于C++编译器来说就是个错误了,它不允许你将int**转换为const int**类型。
枚举类型处理方式
C语言中枚举类型和整型之间的变量进行赋值时,直接使用就是了,整型值可以直接赋值给枚举型变量,同样,枚举值也可以直接赋值给整型变量,如下图8所示的代码。
第6行代码定义一个int类型变量c1,并将其初始化为一个枚举类型的枚举值green,之后通过第9行代码,又将枚举型变量c2赋值给整型变量c3。这两条语句对于C语言来说都是正确的。
但对于C++来说,编译器则不可接受!两种不同类型的变量怎么能够相互赋值呢!如图9所示,它不支持直接将一个整型类型的数值赋值给一个枚举类型的变量。
如果想要在C++中正常地将整型数组赋值给一个枚举变量,那么它需要显示地指定转换目标变量的类型,同样我们可以使用static_cast转换表达式,如图10所示,显示地指定转换后的类型为color枚举值。
常量初始化
C语言中通过const限定符修饰的变量是可以不用指定初始值的,比如下面的代码声明了一个变量i,但是它在类型int前面使用了限定符const,使用C语言编译器编译此代码时,一切正常。
但是如果使用C++的话,使用const限定符的变量必须指定初始化值,否则编译器报如下错误。
虽然不知道C语言这样设计是为什么,也不知道没有初始化的变量以后如何设置它的值,但是目前编译器的行为就是这个样子的,怪怪的,不像是个精心设计的功能,反而给人的感觉倒像是个失误似的。
交叉初始化(crossing initialization)
在C语言中你是可以在goto或switch语句中进行交叉初始化(crossing initialization),但是在C++中你是不能够这样做的。如图13中,采用了goto语句(没事不要使用它,破坏结构化编程逻辑),C语言可以正常编译的。
但是C++是禁止这种用法的,它会给出奇怪的错误,如下图14所示。
全局变量的临时定义
C语言支持在同一个翻译单元(translation unit)中临时性地定义一个全局变量,之后你可以再次定义一个同名的变量,而编译器是不会报错的。这个功能又是一个看上去像个Bug的功能,不知道如何使用,也尽量不要使用这偏门左道技术。
图15中第3行代码和第7行代码都定义了一个整型变量age,C编译器会欣然接受它这种写法,但C++编译器就不客气了,认为它是个语法错误,你重复定了这个age变量,直接抛出下图16中的错误,告诉你不要重复定义之前的变量。
struct、union或enum可以同名
你可以使用enum定义一个枚举类型color,之后再通过typedef定义一个类型为color的结构体,这两种类型是可以同时存在的。见图17中的代码
图17中第2行代码定义了一个color枚举类型color,代码第30-34行又定义了一个color类型的结构体,通过第28行代码定义了一个枚举型变量c1表示颜色枚举值,而第36行代码定义了一个结构体颜色变量,两个color表示不同的类型。但是对于C++来说,就是违反了语法规则,编译器会无情地给出如下图18的错误信息
K&R函数定义
虽然C语言的K&R编程风格不被推荐使用,但是,现在的C编译器依旧保留了C语言的这种写法,比如定义一个函数时,可以采用如图19所示
但是,这种写法对于C++编译器来说是不被允许的,会抛出下图中的错误,它可不认识K&R风格的语法。
函数声明
在C语言中,如果函数原型未指定参数,那么它表示这个函数可以接收一个或多个参数,如果表示这个函数没有参数,则使用关键字void表示。如下图21所示
图21中第3行代码定义了一个名称为f的函数,其参数列表为空,它表示此函数对应的定义可以是接收一个或多个参数,本例中为2个参数,它定义在第12-14行代码之间。第4行代码声明了一个函数,其参数为void,表明这个函数的定义中无需任何参数。
对于C++语言来说,这就是个错误,函数的声明与函数的定义类型不匹配!如图22所示编译器给出的错误。但是C++对于参数列表为空和为void的情况处理方式一致,都表示没有输入参数,其与C语言的行为也是不一样的。
嵌套struct
C语言和C++语言中嵌套struct的作用域范围也是不一样的。在C语言中,嵌套C结构的作用范围与其外层定义的结构相同,但是对于C++来说,它的嵌套结构作用域只存在于这个外层作用域之内。如下图23所示的代码
在C语言中,嵌套结构color为第7-11行代码,在使用color结构时,可以与使用其外层的结构person相同,图23中第15行代码和第23行代码分别定义了一个person和color结构,person为外层结构,color则为嵌套结构,虽然color定义在person结构之内,但这并不影响第23行的用法。但它对于C++来说也是错误,如下图所示
变长数组(Variable-Length-Array)
变长数组是在运行时刻分配数组的大小,而不是在编译时刻决定的。C语言中通过在函数中定义一个数组,这个数组的大小可以是一个参数变量,如图25所示
在图25中,第5行代码定义了一个int型的数组,数组的大小为n,注意这个n是一个变量,数组data就是一个变长数组。第15和16行代码,分别使用不同的参数数值调用f函数,f函数第一次调用中data数组的长度为10,f函数第二次调用中它的长度变为了20。虽然说这是C标准中定义的内容,但是测试使用的gcc和g++都可以编译图25中的代码。而微软visual studio 2022对于图26中C代码或C++代码的代码编译时,都给出了编译错误,如图27所示。
从测试的结果来看,微软提供的C/C++编译器目前是不支持变长数组功能的。
至此,我们介绍了C语言中的一部分特性,但是C++语言没有或者表现不相同的实例代码,并给出了C++编译器提供的错误信息,除此之外,C语言中还有一些其它内容与C++语言表现不一致,下一篇文章我们继续聊C语言的个性,谢谢各位,我们下一篇文章见!