今天在《C专家编程》中看到一段话:“由于设计哲学不同,C语言排斥强类型,它允许程序员需要时可以在不同类型的对象间赋值”,觉得比较奇怪,查了一下定义,强类型是指为所有变量指定数据类型,在没有强制类型转化前,不允许两种不同类型的变量相互操作。按这个定义,C语言应该算是强类型才对的啊?
确实,C语言在使用变量前,需要进行明确的类型定义,不同类型代表的数据存储方式不同,需要进行强制类型转化才能进行赋值。但是仔细翻翻C语言标准,还存在函数调用时的类型提升行为,C99标准中section 6.5.2.2 "Function calls" 的Paragraphs 6, 7:
Paragraphs6
1、默认参数提升:如果一个函数的形参类型未知,那么调用函数时要对相应的实参做“整数提升(integer promotions)”,除此以外,float类型的参数会被提升为double。2、如果形参和实参个数不相等的时候,行为未定义;3、 如果函数定义的时候指定参数原型,那么a)参数原型包含(...),即变参;b)形参类型和实参类型不符合。这两种行为都是未定义的;4、 如果函数定义的时候不指定参数原型,如果提升后的实参类型和形参类型不相符,则行为未定义;除了两种情况a)提升后一个是unsigned int一个是signed int,则值可以被表示成这两种的任何形式;b)实参或形参都是指针,分别指向限定和非限定(如const)的char或者void。Paragraphs7
1、 如果一个函数的形参类型已知,则实参的类型会被隐式的转换成形参的类型,并且转换成非限定的对应类型;2、如果函数原型中有(...)参数,那么对应的实参会被进行默认参数提升;除了函数调用时的类型提升外,在比较或赋值过程中还存在着隐式类型提升或转换,见ANSI C手册相关章节关于隐式类型转换的描述:
第6.2.1.1节 字符和整型(整型升级)
char,short int或int型位段(bit-field),包括它们的有符号或无符号变型,枚举类型,可以使用在需要int或unsigned int的表达式中,如果int可以完整表示源类型的所有值,那么该源类型的值就转换为int,否则转换为unsigned int,这称为整型升级。
第6.2.1.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,如果long int不能完整表示unsigned int的所有值,那么两个操作数都被转换为unsigned long int;再次,如果其中一个操作数的类型是long int,那么另一个操作数被转换为long int;如果其中一个操作数的类型是unsigned int,那么另一个操作数被转换为unsigned int;如果所有以上情况都不属于,那么两个操作数都为int。
浮点操作数和浮点表达式的值可以用比类型本身所要求的更大的精度和更广的范围来表示,而它的类型并不因此改变。
以上描述通俗点说,大致意思如下:当执行算术运算时,操作数的类型如果不同,就会发生转换。数据类型一般朝着浮点精度更高,长度更长的方向转换,整型数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。
C语言的类型转换,除了上述的这些标准,还涉及到很多很复杂的事情,比如有符号、无符号、浮点等,每一种类型转换都要定义一种转换规则,而且不同的编译器不同的体系结构往往会带来不同的结果,很多类型转换都是C标准中未定义的,很可能就导致错误的出现。因此,在C语言中,我们要尽量的避免隐式的类型转换。
在网上有人说C语言是弱类型,有人说强类型,其实争论这个本身没有意义。我们关注如何正确、安全的使用类型变量,避免隐式类型提升和转换才是正道。