目录

最近又读了一下基本 C 语言经典著作,特此整理下一些 C 语言的知识点。参考书籍有:C程序设计语言、C专家编程、C缺陷与陷阱

1. 整型提升

在一个表达式中,凡是可以使用整型的地方都可以使用带符号或无符号的字符、短整型或整型位字段,还可以使用枚举类型的对象。如果原始类型的所有值都可用int类型表示,则其值将被转换为int类型;否则将被转换为unsigned int类型。这一过程称为整型提升。

以下面的代码为例:

printf(" %d ", sizeof'A');

这个代码本想打印出一个字符字面值类型的长度,但你会发现结果是4(或是你机器上int的长度)。这里就根据提升规则,由char转换为int。

2. 整型转换

将任何整数转换为某种指定的无符号类型数的方法是:以该无符号类型能够表示的最大值加1为模,找出与此整数同余的最小的非负值。在对二的补码表示中,如果该无符号类型的为模式比较窄,这就相当于左截取;如果该无符号类型的为模式比较宽,这就相当于对带符号值进行符号扩展和对无符号值进行0填充。

将任何整数转换为带符号类型时,如果它可以在新类型中表示出来,则其值保持不变,否则它的值同具体的实现有关。

3. 整数和浮点数

当把浮点类型的值转换为整型时,小数部分将被丢弃。如果结果值不能用整型表示,则其行为是未定义的。特别是,将负的浮点数转换为无符号整型的结果是没有定义的。

当把整型值转换为浮点类型时,如果该值在该浮点类型可表示的范围内但不能精确表示,则结果可能是下一个较高或较低的可表示值。如果该值超出可表示的范围,则其行为是为定义的。

4. 浮点类型

将一个精度较低的浮点值转换为相同或更高精度的浮点类型时,它的值保持不变。将一个较高精度的浮点类型值转换为较低精度的浮点类型时,如果它的值在可表示范围内,则结果可能是下一个较高或较低的可表示值。如果结果在可表示范围之外,则其行为是为定义的。

表达式中的float类型的操作数不会自动转换为double类型。一般来说,数学函数使用double类型的变量。使用float类型主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间(double算术运算特别费时)。

5. 算术类型转换

许多运算符都会以类似的方式在运算过程中引起转换,并产生结果类型。其效果是将所有操作数转换为同一公共类型,并以此作为结果的类型。这种方式的转换称为普通算术类型转换。

首先,如果任何一个操作数为long double类型,则将另一个操作数转换为long double类型。

否则,如果任何一个操作数为double类型,则将另一个操作数转换为double类型。

否则,如果任何一个操作数为float类型,则将另一个操作数转换为float类型。

否则,同时对两个操作数进行整型提升;然后,如果任何一个操作数为unsigned long int类型,则将另一个操作数转换为unsigned long int类型。

否则,如果一个操作数为long int类型且另一个操作数为unsigned int类型,则结果依赖于long int类型是否可以表示所有的unsigned int类型的值。如果可以,则将unsigned int类型的操作数转换为long int;如果不可以,则将两个操作数都转换为unsigned long int类型。

否则,如果一个操作数为long int类型,则将另一个操作数转换为long int类型。

否则,如果任何一个操作数为unsigned int类型,则将另一个操作数转换为unsigned int类型。

否则,将两个操作数都转换为int类型。

用通俗语言,大致意思如下:

当执行算术运算时:操作数的类型如果不同,就会发生转换。数据类型一般朝着浮点精度更高,长度更长的方向转换,整型数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。

例如,如果f为float类型,n为int类型,那么表达式

(n > 0) ? f : n

是float类型,与n是否为正值无关。

6. 参数传递中的类型转换

在参数传递过程中会发生隐式类型转换,这是需要注意的地方。由于函数的参数也是表达式,所以也会发生类型提升。如果使用了适当的函数原型,类型提升便不会发生,否则也会发生。在被调用函数的内部,提升后的参数被裁剪为原先声明的大小。

ANSI C的函数原型就是采取一种新的函数声明形式,把参数的类型也包含于声明之中。

在C语言中,我们没有办法可以将一个数组作为参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向数组第一个元素的指针。因此,将数组作为函数参数毫无意义。所以,C语言中会自动地将作为参数的数组声明转换为相应的指针声明。

实参                                所匹配的形式参数
数组的数组      char c[8][10];      char(*c)[10];    数组指针
指针数组        char *c[15];        char **c;       指针的指针
数组指针        char (*c)[64];      char (*c)[64];  不改变
指针的指针      char **c;           char **c;       不改变

你之所以能在main()函数中看到char **argv这样的参数,是因为argv是个指针数组(char *argv[])。这个表达式被编译器改写为指向数组第一个元素的指针,也就是一个指向指针的指针。

7. 指针和整数

指针可以加上或减去一个整型表达式。在这种情况下,整型表达式的转换按照加法运算符的方法进行。

两个指向同一数组中同一类型的对象的指针可以进行减法运算,其结果将被转换为整型;转换方式按照减法的方式进行。

值为0的整型常量表达式或强制转换为void *类型的表达式可以通过强制类型转换、赋值或比较操作转换为任意类型的指针。其结果将产生一个空指针,此空指针等于指向同一类型的另一空指针,但不等于任何指向函数或对象的指针。

还允许进行指针相关的其他某些转换,但其结果依赖于具体实现。这些转换必须由一个显示的类型转换运算符或强制类型转换来指定。

指针可以转换为整型,但此整型必须足够大;所要求的大小依赖于具体的实现。映射函数也依赖于具体的实现。

整型对象可以显式地转换为指针。这种映射总是将一个足够宽的从指针转换来的整数转换为同一个指针,其他情况依赖于具体的实现。

指向某一类型的指针可以转换为指向另一类型的指针,但是,如果该指针指向的对象不满足一定的存储对齐要求,则结果指针可能会导致地址异常。指向某对象的指针可以转换为一个指向具有更小或相同存储对齐限制的对象的指针,并可以保证原封不动地再转回来。“对齐”的概念依赖于具体的实现,但char类型的对象具有最小的对齐限制。指针也可以转换为void * 类型,并可原封不动地转换回来。

一个指针可以转换为同类型的另一个指针,但增加或删除了指针所指的对象类型的限定符除外。如果增加了限定符,则新指针与原指针等价,不同的是增加了限定符带来的限制。如果删除了限定符,则对底层对象的运算仍受实际声明中的限定符的限制。

最后,指向一个函数的指针可以转换为指向另一个函数的指针。调用转换后指针所指的函数的结果依赖于具体的实现。但是,如果转换后的指针被重新转换为原来的类型,则结果与原来的指针一致。

8. void

void对象的值不能够以任何方式使用,也不能被显式或隐式地转换为任一非空类型。因为空(void)表达式表示一个不存在的值,这样的表达式只可以用在不需要值的地方,例如作为一个表达式语句或作为逗号运算符的做操作数。

可以通过强制类型转换将表达式转换为void类型。例如,在表达式语句中,一个空的强制类型转换将丢掉函数调用的返回值。

9. 指向void的指针

指向任何对象的指针都可以转换为void * 类型,且不会丢失信息。如果将结果再转换为初始指针类型,则可以恢复初始指针。之前讨论过,执行指针的转换时,一般需要显式的强制转换,这里不同的是,指针可以被赋值为void * 类型的指针,也可以赋值给void * 类型的指针,并可与void * 类型的指针进行比较。

10. 强制类型转换

之前都在说隐式类型转换,在任何表达式中都可以使用一个称为强制类型转换的一元运算符强制进行显式类型转换。

(类型名) 表达式

参考文献

  • C程序设计语言

  • C专家编程

  • C缺陷与陷阱