一些人声称C语言不是一个强类型语言[1],但我认为它是——只是因为C编译器在类型检查时,通常表现微弱。换言之,C允许程序员在这些规则上表现怠慢。我喜欢把C称为一个‘弱化的强类型’语言。其他人可能只称它为静态类型语言。不管怎样,跟踪类型非常重要。
强类型语言的定义是,每一个变量、函数调用和每一个常量,都具有一些在编译时指定,并且由编译器强制执行的类型[2]。在比C更严格的语言中,你将不能把一个字符赋值给一个整数变量,或者把一个整数赋值给一个浮点数变量;并且你肯定不能做很多你能对指针做的事。尽管如此,C确实强加了不同的类型规则,像我们将看到的。
C的类型被分为三个主要组:对象类型,函数类型和不完整类型。(不完整类型可能会与对象类型归为一组,但是C标准把它们分开。) 对象类型基本上用来声明变量,函数类型用于声明函数。不完整类型是对象类型在一些重要信息,未被提供时的一种特殊情形。一个不完整类型通常在被补充上所需信息后达到完整。
这些类型可以按不同方式分组。我喜欢分为‘基本’和‘派生’类型。基本类型简单地是在列表中的:
char
signed char
unsigned char
short int
unsigned short int
int
unsigned int
long int
unsigned long int
long long int (new in C99)
unsigned long long int (new in C99)
float
double
long double
float complex (new in C99)
double complex (new in C99)
long double complex (new in C99)
float,double,long double和三个complexes之外的那些类型是整型。三种char变种都称为字符型。最后六种是浮点数类型,分为实数和复数浮点数类型。
许多整型有简略的名字,因为在short和long之后的int关键字可以被省略。(在C89中你甚至能在更多的情形下省略它,但是C99几乎去除了‘隐含int’。) 类型char(有时称为‘简单的char’)确切地具有相同的特点——最小值,最大值和sizeof——对于实现决定的signed char或unsigned char。int类型通常是‘相似的’,对short或long。但是所有这些类型都被认为是不同的,甚至在特征匹配时混淆使用(比如与下面讲到的指针)它们是也是一个错误(这是C被视为强类型语言的一个理由)。
作为一个特殊情况,C具有与一些整型‘兼容’的枚举类型,即使如此却是截然不同的类型。作为另一种特殊情况,C提供void类型,它仅仅是一种不能被完整的不完整类型。不存在真正的void类型的值,这有时是一种自我矛盾。(把它看作空值集合。因为所有空集合都是一致的,它有或者没有哪些值都不要紧。)
这些基本类型被用于构建派生类型。派生类型有时也指不完整类型和函数类型。派生类型是:
指针可以非常令人难解,因为C的指针如此非同小可,并且与数组具有密切的关系。我们稍后将了解更多。
所有值都有类型。常量具有一个由其语法和值决定的类型。最明显的是单个整数和浮点数常量,比如42或3.14159。常量42具有int类型,3.14159具有double类型。更大的常量在必要时,会自动使用比int更大的类型,整数和浮点数常量加上后缀以给它们另一个类型。十六进制和八进制常量,使用与十进制常量略有不同的规则。因此2.71828F的类型是float而不是double,0xFFUL的类型是unsigned long int。(完整的规则列表可在其它文献中找到。)
与其它类似语言(甚至包括C++)不同,像'a'这样的字符常量的类型是int。
运算可以在任意值上进行,并且这些运算的含义完全由值的类型决定。(这是把C称作强类型语言的另一个主要条件。) 例如向右移位运算>>的确切行为,可能因被移位的值是有符号还是无符号而发生改变。这就是为什么跟踪每个值的类型如此至关重要的原因。如果你不知道类型和值,就不能预测运算和结果。
因为这个原因,当在C语言中分析任何特定的表达式时,你应该记下每个子表达式的类型和值。我喜欢把它们成对写在尖括号里:
42 is an <int, 42>
3.14159F is a <float, 3.14159>
稍后我们将向其中加入第三个元素,以区分对象和值。
C提供相当自由的转换。这不仅改变类型而且改变了值。底层机器的位模式甚至字节尺寸,经常在这个过程中改变。把3.14从double改变为int,会丢掉小数部分。如果sizeof(double)是8,并且sizeof(int)是2或4,从一个改变为另一个甚至改变字节个数。
许多C的转换自动发生。即是,对如下的代码:
int i = 3;
double d = 3.14159;
赋值i = d;和d = i;完全合法,并且导致一个自动类型转换。因为字符常量的类型是int,甚至这样的代码:
char c = 'A';
包含一个自动类型转换。所以类型转换十分普通和频繁(这即是一些人说所的,C不是一个强类型语言)。
显式类型转换(cast)是一个强制转换的语法结构。其语法是一个在圆括号内的类型名,显式类型转换的结果是一个新值和一个新类型。新类型恰好是括号内的类型名,通常新值恰好是可从自动转换得到的值。即是:
(double)i
把i的值到double,就像把它赋值给d一样。然而有许多转换即使不完全错,至少是有疑问的。例如转换一个‘指向char的指针’类型的值,为一个‘指向int的指针’类型的值,不被保证能做任何有用的事情。这样的转换不会自动发生——如果你制造一个它们会自动产生的条件,编译器必须产生一个诊断。显式类型转换是一种‘更强烈’的转换,实际上告诉编译器:闭嘴马上就做,即使它充满危险。
这导致一条通用的规则:怀疑任何显式类型转换,尤其是指针转换。不幸的是,一些C编译器在类型转换上太过挑剔,比如从int到short,如果程序员未使用显式类型转换叫它闭嘴,它就产生警告。
同样不幸的是,许多C程序包含有疑问的——完全错误的——指针转换,许多程序员学会了,通过无拘无束地撒播cast来禁止这些警告。毕竟一个典型的诊断‘warning:整数到指针转换未见cast’,实际上是在请求程序员盲目地插入一个显式类型转换,而不是要弄清楚,为什么编译器认为其中一个值是一个整数。
在C89之前,C没有任何特殊指针的例子。这导致了一个问题:没有‘通用的’指针类型,可是一个像malloc()的函数调用不得不返回一个有效的指针,却不知道它应该是什么类型。K&R-1 C中的解决方案是返回一个char*类型的值,依仗其把任何对象分解为字节的能力。不幸的是,那意味着大多数malloc()调用需要一个显式类型转换。ANSI C通过添加一个新的通用指针类型,去掉了这个转换的需要。逻辑上它应该是一个‘anyptr’或者类似的拼写,但是ANSI委员会不想增加一个新关键字。既然void类型已经是特别的——表示‘什么都没有’——他们使用‘指向void的指针’,或void*代表一个通用指针。不是‘不指向什么’,而是‘指向任意对象’。
特别古怪的是,void**不可以指向任意对象。特殊类型void*用作通用指针,自由地转换为任何其它指针类型或从任何其它指针类型转换而来,但是它是独一无二的——一个‘anyptr’,代表它自己。类型void**只能指向这些特殊指针。即使void*能指向任意其它类型(包括其它void*),void**仅能指向void*类型。
(This Chinese translation isn't confirmed by the author, and it isn't for profits.)
Translator : jhlicc@gmai1.c0m
Origin : http://www.torek.net/torek/c/types.html