文章目录
- 一,自动类型转换
- 1,赋值运算
- 1.1,浮点数赋值给整型变量-不安全
- 1.2,整数赋值给浮点数变量-安全
- 1.3,窄类型赋值给宽类型-安全
- 1.4,宽类型赋值给窄类型-不安全
- 2,混合类型的运算
- 2.1,整形和浮点数混合
- 2.2,不同的浮点数类型混合
- 2.3,不同的整数类型混合
- 3,整数类型的运算
- 4,函数
- 二,强制类型转换
当不同数据类型的数据出现在同一个表达式中时,就会涉及数据类型转换,C语言中的数据类型转换有两种:
- 自动类型转换
- 强制类型转换
类型转换可能是安全的,即不会丢失数据;也可能是不安全的,即出现丢失数据的情况。
窄类型转换为宽类型,是安全的,不会丢失数据。
宽类型转换为窄类型,是不安全的,虽然不是百分百丢失数据,但有丢失数据的可能。
一,自动类型转换
自动类型转换是指在特点情况下,编译器将一种数据类型自动转换为另一种类型,自动类型转换可能是安全的,也可能是不安全的,即可能出现数据丢失。
1,赋值运算
赋值运算符左右两边的数据类型不一致时,会以变量的类型为准,将右边的值转成变量的类型。
1.1,浮点数赋值给整型变量-不安全
浮点数赋予整型变量时,C语言的转换过程简单粗暴,保留整数部分,丢弃小数部分。
int x = 3.14159627;
如上,变量x被声明为整型,然后赋一个double类型值。
编译器会把3.14159627转为整形,小数部分0.14159627
会被丢弃(注意,不是四舍五入),保留整数3,3被赋值给变量x,因此变量x的值是3。
显然,这种情况下的自动类型转换导致数据丢失,是不安全的类型转换,我们在编写代码时,要避免类似的赋值语句,左右两边的类型一致才是最佳实践。
1.2,整数赋值给浮点数变量-安全
整型赋值给浮点数变量时,会自动转为浮点数。
float y = 12 * 2;
上面示例中,变量y的值不是24,而是24.0,因为等号右边的整数自动转为了浮点数。
C语言中的浮点数遵循了IEEE 754标准,使用科学计数法来存储浮点数,其数组范围被整形要大得多,所以整形赋值给浮点数,不会出现数据丢失,是安全的自动类型转换。
1.3,窄类型赋值给宽类型-安全
字节宽度较小的整数类型,赋值给字节宽度较大的整数变量时,会发生类型提升,即窄类型自动转为宽类型。
比如,char或short类型赋值给int类型,会自动提升为int。
char x = 100;
int i = x + y;
上面示例中,变量x的类型是char,由于赋值给int类型,所以会自动提升为int。
1.4,宽类型赋值给窄类型-不安全
字节宽度较大的类型,赋值给字节宽度较小的变量时,会发生类型降级,自动转为后者的类型。这时可能会发生截断
,系统会将移除的高位二进制,从而出现意料之外的情况。
int i = 321;
char ch = i; // ch 的值是 65 (321 % 256 的余值)
上面例子中,变量ch是char类型,宽度是8个二进制位。
变量i是int类型,将i赋值给ch,后者只能容纳i(二进制形式为101000001,共9位)的后八位,前面多出来的二进制位被丢弃,保留后八位就变成了01000001(十进制的65,相当于字符A)。
之前介绍的浮点数赋值给整型变量,也属于宽类型自动转换为窄类型,也会发生截断,丢弃小数部分。
double pi = 3.14159;
int i = pi; // i 的值为 3
上面示例中,i等于3,pi的小数部分被截去了。
2,混合类型的运算
不同类型的值进行混合计算时,必须先转成同一个类型,才能进行计算。转换规则如下:
2.1,整形和浮点数混合
整数与浮点数混合运算时,整数转为浮点数类型,与另一个运算数类型相同。
3 + 1.2 // 4.2
上面示例是int类型与float类型的混合计算,int类型的3会先转成float的3.0,再进行计算,得到4.2。
2.2,不同的浮点数类型混合
运算时,宽度较小的类型转为宽度较大的类型,比如float转为double,double转为long double。
2.3,不同的整数类型混合
运算时,宽度较小的类型会提升为宽度较大的类型。
比如short转为int,int转为long等,有时还会将带符号的类型signed转为无符号unsigned。
下面例子的执行结果,可能会出人意料。
int a = -5;
if (a < sizeof(int))
do_something();
上面示例中,变量a是带符号整数,sizeof(int)是size_t类型,这是一个无符号整数。
按照规则,signed int 自动转为 unsigned int,所以a会自动转成无符号整数4294967291(转换规则是-5加上无符号整数的最大值,再加1),导致比较失败,do_something()不会执行。
所以,最好避免无符号整数与有符号整数的混合运算。因为这时 C 语言会自动将signed int转为unsigned int,可能不会得到预期的结果。
3,整数类型的运算
两个相同类型的整数运算时,或者单个整数的运算,一般来说,运算结果也属于同一类型。
但是有一个例外,宽度小于int的类型,运算结果会自动提升为int。
unsigned char a = 66;
if ((-a) < 0) printf("negative\n");
else printf("positive\n");
上面示例中,变量a是 unsigned char 类型,这个类型不可能小于0,但是-a不是 unsigned char 类型,会自动转为 int 类型,导致上面的代码输出 negative。
再看下面的例子。
unsigned char a = 1;
unsigned char b = 255;
unsigned char c = 255;
if ((a - 5) < 0) do_something();
if ((b + c) > 300) do_something();
上面示例中,表达式a - 5和b + c都会自动转为 int 类型,所以函数do_something()会执行两次。
4,函数
函数的参数和返回值,会自动转成函数定义里指定的类型。
int dostuff(int, unsigned char);
char m = 42;
unsigned short n = 43;
long long int c = dostuff(m, n);
上面示例中,参数变量m和n不管原来的类型是什么,都会转成函数dostuff()定义的参数类型。
下面是返回值自动转换类型的例子。
char func(void) {
int a = 42;
return a;
}
上面示例中,函数内部的变量a是int类型,但是返回的值是char类型,因为函数定义中返回的是这个类型。
二,强制类型转换
最佳实践是,我们在编写代码时,应该避免自动类型转换,因为自动类型转换可能导致出现意料之外的情况。
代码的行为始终在程序员的预料之中,是程序员必须追求的目标。
对于必不可少的类型转换,最好是使用强制类型转换。
强制类型转换是指在一个值或变量的前面,使用圆括号指定类型(type),称之为casting。
(unsigned char) ch
上面示例将变量ch转成无符号的字符类型。
char c = (char)266;
上面示例中,(char)将266强制转换为char类型。
首先,虽然从语法上看,这种转换是没有必要的,因为对于赋值运算来说,编译器会把右边的值自动转换为左边的类型。但是,这样的代码是我们推荐的写法,更直观,便于阅读。
其次,把整型266强制转换为char类型,会出现数据截断,导致部分数据丢失,但由于这是我们意料之中的情况,就不会出现安全问题。