Thinking in C++ ----第三章 C++中的C
一、函数的声明和定义
1. 函数重载
在C语言中函数不能被重载。
2. 函数使用前的函数声明
在C语言中:
如果函数在使用之前没有进行声明,那么编译器会对其进行隐式声明:
假设这个函数的返回类型为int类型,但不对其参数做任何假设。
在C++中:
一个函数在使用之前必须进行声明。
3. 在函数声明中没有参数列表
在C语言中:
如果一个函数声明没有参数列表,如下:int func();
那么编译器不会对func的参数做任何假设,并且不会检查func的参数是否合法。这时,
func可以定义为:int func(int i){}
上述func函数的定义不会出错,因为编译器已经关闭了对其参数的检查。
在C++中:
如果一个函数声明没有参数列表,则表明这个函数没有参数,等价于:int func(void);
4. 函数定义中未命名的参数
在C语言中:
每一个参数都必须指定名字,若函数定义为如下形式:int func(int ){}
则,编译器就会报错
在C++中:
允许出现未命名的参数。当然,如果一个参数没有名字,那么也就无法使用它。
5. 忽略返回值类型
在C语言中:
如果一个函数声明或函数定义忽略返回值类型,如:fun(void);
那么这个函数的返回类型默认为是int类型。
在C++中:
上述情况不合法,一个函数声明或定义必须显示的指定它的返回类型。
6. 函数中的return
在C语言中:
编译器不会检查函数的返回类型,如:int func(){}
上述定义是不会出错的,但是它的返回值是没有意义的。
在C++中:
编译器会强制检查函数的返回值类型。
二、内部链接 和 外部链接
在一个程序中标识符代表存放变量或被编译过的函数体的存储空间。链接这些变量或函数的方式有两种:内部链接(internal linkage)和外部链接(external linkage)。
如果一个变量的链接方式是内部链接,那么意味着此变量只在定义它的文件中可见,对于这个变量的引用仅限于这个文件;在其他的文件中可以使用相同的名字定义标识符,这不会引起命名冲突。在c/c++中,内部链接是由关键字static指定的。
全局变量(C++中的const常量除外)和函数 默认为外部链接。在其他文件中可以使用extern关键字来访问具有外部链接的标识符。在全局变量定义时可以使用static关键字修饰,使它们成为内部链接方式;也可以使用extern关键字进行修饰,显示指定标识符为外部链接。
对于局部变量来说,它们不存在链接方式。因为局部变量只是临时存在于堆栈,链接器看不到它们。
C++中的const常量(不是C语言)是一个特例,它默认为内部链接的方式,若想使它变为外部链接,则必须使用extern关键字进行修饰。
三、const
C语言中的const
在C语言中对于表达式:const int i = 0;
它的含义是定义了一个变量i,并且变量i的值不能被修改。
你不能使用一个const变量定义一个数组,如下:
const int size = 3;
char arr[size]; // 错误,因为size是一个变量而不是常量。
在C语言中定义常量的方式有两种,
1. 使用宏,如:#define size 3
2. 使用enum,如:enum{size = 3};
C++中的const
在C++中,const有许多种用法,这里只讨论使用它定义常量的情况。
在C++中对于表达式:const int size = 0;
它的含义是定义了一个常量size,它可以被这样使用:
char arr[size];
对于表达式:const int size = str.size();
此时,这个size就不是一个常量了,因为它的值不能再编译期间确定。
而const在这里的含义只是表示:size的值不能被改变。
如果你这样使用size:
char arr[size]; // 错误,此时size不是常量
所以说在C++中使用const修饰的标识符可能是常量也可能是变量。
使用const常量替换宏常量
宏常量只是简单的进行值替换,它无法进行类型检查,这可能会导致一些问题;而使用const常量则可以避免这些问题。
要在多个文件中使用const常量,可以把const常量的定义放在头文件中。要使用这个常量,只需包含头文件即可。因为const常量的链接方式是内部链接,所以这不会造成命名冲突。
const常量的存储空间
先给出这样一个结论:如果在程序中不需要const常量的地址,那么编译器不会为const常量分配存储空间。
如果程序中只使用了const常量的值,而没有使用它的地址的话,那么这个const常量会被保存在符号表中,不用在堆或栈中为其分配存储空间。
为const常量分配存储空间的情况:
1. 示例代码如下:
const int i = 10; // 定义了一个整型的常量
void *addr = (void *)&i; // 取i的地址
分析:第一行定义了一个编译期的常量i,第二行需要i的地址,此时编译器被迫为i分配了内存空间,因为只有这样,i才会具有一个地址。
2. 示例代码如下:
extern const int i = 10; // 使用extern修饰i,使之成为外部链接的方式
分析:此时i成为了外部链接的方式,这意味着其他的编译单元也可以对它进行引用,因此编译器必须为它分配空间。
四、volatile关键字
volatile关键字告诉编译器,这个变量是“易变的”,不要对它做任何优化。这样做的好处是,一旦某个变量发生变化,程序会立刻知道它。例如在多线程 程序中A线程根据某个变量的值而进行某些操作,将这个变量定义为volatile的,这样这个变量一旦被修改,线程A就会检测到。
五、逗号运算符
一个例子:
2 a = (b++, c++, d++); // 相当于 (a = b++), c++, d++, e++;
也就是说,逗号运算符返回的是最后一个表达式的值。
六、宏
宏的两个特殊的用法:
先看如下示例:
2 #define DEFINT(x) int int_##x // 定义一个int类型的变量
3
4 DEFINT(1); // 产生如下定义:int int_1;
5 int_1 = 1;
6 OUT(int_1); // 输出:int_1 = 1
1. 字符串定义
如上例所示,使用#x可以产生一个字符串,
如果传递进来的x是abc,那么将产生”abc”,
如果传递进来的x是123,那么将产生”123”
这个特性在某些情况下非常有用,比如可以利用上例中的OUT宏来跟踪一个变量的值。
2 int b = 2;
3 OUT(a); // 输出:a = 1
4 OUT(b); // 输出:b = 2
2. 标志粘贴
如上例所示,使用 ## 可以将其两边的符号连接起来
比如:a ## b,将会产生标识符:ab,##会忽略第一次遇到的空格
在某些情况你可能想要创建许多以某种前缀开头的变量,那么就可以如上例中的DEFINT一样定义自己的宏。
以后会关注的
1. C/C++中调用其他语言,如汇编,Python
2. makefile