第三章:C++中的C

在使用C++编写程序的时候,我们会发现其中很多地方所使用的语句与C语言十分相似,其实从其命名可以看出来,C++与C语言有着不一般的关系。C++可以认为是为了使C语言更容易被开发,具有更强大的工程能力而被制造出来的。其中的基础语句以及函数的调用等等,都与标准C非常的相似,但是也有很多不同的地方,这些细节也都是C++为了解决一些问题而改进的地方,正因为”++”在C++里边有着自增的含义,所以C++也可以理解为在C的基础上更上一层楼。其实在使用C++编译器时,几乎可以将C语言代码原封不动的放置进来编译,不过C++也加入了很多独有的特性,同时也改进了很多C语言当中的问题,所以这里仅着重介绍一些两者之间的不同以及编写程序的时候值得注意的地方。

首先关于函数的创建,在C和C++当中,有一个特征叫函数原型,或者叫函数接口,它的意思是对函数返回值类型和参数列表的一个声明。而编译器则可以利用这个原型去检查函数的调用及返回并及时发现错误。在声明函数原型的时候,可以只依次写出函数参数列表的参数类型,而不必使用标识符,当然使用标识符可以使程序员获得更多信息有助于编写程序,但是编译器并不会检查函数声明和定义时的标识符是否匹配。而在函数定义时,标准C认为所有参数都必须具有标识符才可以被调用,所以在标准C当中定义函数时必须给所有变量指定标识符,而C++认为在函数开发过程中可能需要具有一些保留位来提高兼容性,所以其参数列表允许不使用标识符,仅做占位用途,当然该位置输入的参数也不能被直接调用,不过,删去不使用的标识符可以消除编译器的警告。在不清楚具体需要输入几个参数的时候,C语言提供了一种解决方法,即在参量表中输入省略号(…)或者直接将参量表留空,这两者在C中表示的意思都是可变参数参量表,即编译器不会对输入的参数类型进行检查。但是在C++当中,由于有函数重载这样的方法,可以尽量避免使用可变参量表,因为可变的参量表总是会导致很多bug。在C和C++当中,声明一个函数都需要声明其返回值类型,如果不进行声明编译器将会按照int型进行检查。

与C语言非常相似的是,在C++当中的几乎所有执行控制语句都与C当中的一模一样,比如if-else语句、switch-case语句、while语句、do-while语句、for语句甚至goto语句,说到goto语句,这是一个十分不推荐使用的语句,因为goto语句在C++里边可以引起严重的混乱。由于C++具有实时定义的特点,所以在C++当中处处都有着严格的作用域控制,即一个变量在声明之后,只会在离它最近的包裹着它的花括号范围内起作用,脱出这个范围之后,这个变量即会失去作用,也就是说它被释放掉了。但是如果使用goto跨越了作用域的边界,其并不会触发变量的释放,因此就会引发一系列错误。

再来说说实时定义,这是较C语言方便太多了的一个特性,它允许程序员在代码中任何地方声明定义变量,不必像C语言那样必须将变量在作用域的开始位置定义。一方面在编写代码的过程中不停的来回插入所需变量十分的麻烦,另一方面在读者阅读代码的时候由于变量的定义与使用地方距离太远,不容易对应观察其含义。所以实时定义是一个非常重要且好用的特性。

在变量方面C++与C语言并没有什么显著的区别,不过在常量方面,C++与C具有很大差距,在C语言当中,建立一个常量有时只能使用C语言的预处理器特性,使用#define来定义一个宏。而在C++当中则还可以选择使用const关键字。在这个方面C++要显得人性化很多,因为它的编译器会对const类型的数据进行很多的优化,比如如果程序所有的地方都只是对const的值进行使用,则编译器会将const进行变量折叠,即不给它进行内存分配,而是直接写入到程序的符号表里边去。而如果有对其地址的请求的话,编译器则会将其放在内存中,并且保证其不被改变,任何改变行为都会引发编译错误。还有一点就是编译器会对const类型数据进行类型检查,保证const在作为参数被传递的时候的安全性。而#define则只是简单的文字替换,容易引发很严重的问题。后来在C语言当中也引入了const的用法,不过不同的是,在C语言当中,const只能表示一段不能被改变的内存,由编译器来保证其不被改变。

C语言与C++的运算符在基础类型操作中表现的一模一样,其也是由右至左进行等式的赋值等等,其中几乎所有的逻辑运算符、位运算符、关系运算符以及特殊的运算符在基础应用情况下均是相同的。不过不同的是,在C++当中运算符似乎不仅仅再是运算符那么简单。在C++当中,运算符是可以被重载的,比如在使用C++的标准输出cout的时候,用法是cout << “str”;其中”<<“即是移位操作符,不过在此处它并不是移位符的意思,而是一种函数调用,对于上面那个表达式来说,cout和”str”分别为函数的两个参数,而表达式的值则是函数的返回值。而重载的意思就是在C++当中,可以用同一个名字来作为多个函数的名字,只要他们的参量表不完全相同,编译器就可以对函数的调用进行区分。具体的细节在之后的内容会专门讲到。而运算符也可以作为一类特殊的函数。

当然在进行运算编写的过程中,我们经常由于各种原因需要对不同类型的变量进行转换,在C语言当中,一些相对比较常见而且一般不会导致数据丢失等现象的转换会由编译器隐式完成,不过在一些较为容易出现问题或者转换目的不明确的时候,编译器会提示错误,并且需要程序员专门输入转换方式进行显式转换即用括号在变量名前冠上需要转换到的类型。而在C++当中,虽然它支持相同的转换方式,不过它也给出了一种显式转换的方法来替替代旧的风格,即static_cast, const_cast, reinterpret_cast, dynamic_cast这几种转换方式分别控制了几种不同的转换过程,static_cast是静态转换,就是进行一些定义明确的但是可能具有危险的,或者甚至编译器都会进行自动转换的类型转换。而const_cast则是将const类型的常量转换为非const类型的变量,或者将volatile类型的变量转换成非volatile类型的变量。(这里volatile变量指的是“不知何时会改变的变量”,它在内存当中可能会被除了本段代码以外的其它东西所改变,所以编译器会对这个变量不进行任何优化,比如将其加载到寄存器中进行连续赋值操作等。)reinterpret_cast指的是按照位拷贝的方式进行转换,一般情况下这种转换非常少用,除非在进行一些特殊的操作或算法时,因为按位拷贝转换成其他变量之后,大多数情况下需要再转换回来才能正常使用该变量,当然如果进行一些位操作的话倒没有什么太大的影响。最后dynamic_cast则是用于对类的派生与其基类之间的转换,在多态方面有着重要的应用,这些概念在书中的15章有详细的阐述。值得注意的一点是,在编写程序的时候尽量还是应该少使用类型转换,它们极易引发程序的漏洞,而C++将类型转换的方法设计的尤为复杂,也是在时时刻刻提醒着程序员,尽量少使用类型转换,而且一旦出现了问题,只需要再程序中检索cast关键字便可以迅速排查程序中出现的类型转换问题。

最后,C++的结构体联合体以及枚举等特殊用法也和C语言几乎一模一样,不过有一点不同的是,在C++当中结构体中的内容不一定必须是数据的集合,也可以将函数添加进去,而类(class)的概念也是从这个基础之上诞生的,将数据和函数封装成为一个集合,并且由内部的函数完成一些数据的操作,最后达到一种黑箱的效果,这也是所谓面向对象的一个最直观形象的一个解释。

总而言之,C++其中几乎包含了C语言的所有用法,并且对其中很多不甚妥当的地方进行了改进,使它在代码性能丝毫不逊色C语言的情况之下,更加的贴近程序员的思路,并且加速了开发的流程,而且使得可以由更多的人进行同一个项目的开发不会变得混乱不堪。这些都是C++的优点,也是它流行起来的原因之一。其相较C语言更上一层楼可谓当之无愧。