操作符
约 6829 字大约 23 分钟
2025-06-21
1.操作符的概念解释
C 语言 的操作符也被称为运算符,是用于执行特定操作的符号,可以拿来构建表达式并实现了程序中的各种计算和操作。
不过您首先需要知道,操作符一般是有对应的操作数的,例如 1 + a 这个表达式的操作数是常量 1 和变量 a,操作符为 +。而有些操作符只能操作更少或者更多的操作数,而根据操作数的个数,可以分为单目操作符、双目操作符、三目操作符。
不过我不按这个分类来讲,这种分类更多是强调操作符的使用规则,而不是作用,我以下回按照操作符的作用来分类讲解...
2.操作符的大致分类
我们从操作符的作用上来讲解,先不考虑多个操作符混杂的情况,只考虑一个操作符的使用。
2.1.算术操作符
+,-,*,/:常规的加减乘除操作符%:取模操作符
上述操作符中都是双目操作符,包含两个操作数。除了 % 左右的操作数必须是两个整数,其他的操作符左右的操作数可以是整型数据,也可以浮点型数据。
需要注意的是,对于 / 左右两个操作数在一个数是浮点数就执行浮点数的除法,否则都按照整型的除法。并且整型的除法只取商值而不取余数值,但是浮点型的除法和平时我们正常的除法一样。这是什么意思呢?请观察以下代码的现象。
// 使用不同数据类型作除法的一种现象
#include <stdio.h>
int main() {
int num1 = 2;
int num2 = 7;
double num3 = 7.0;
double num4 = 2.; // 这里 C 语言允许使用 "2." 的方式表示 "2.0"
double result1, result2, result3, result4; // 这里可以不初始化, 但是这不是很好的习惯, 我只是演示给您看这种做法在大部分情况下是没有问题的
// 操作数都是整数时作除法, 结果依旧为整数类型数据
result1 = num1 / num2; // 相当于 2 / 7 == 0...2 只把商的结果带走了, 但是余数直接丢弃, 整个结果不包含小数点以后的结果
printf("result1 = num1 / num2 结果: %d\n", result1); // 结果为 0
// 其中一个操作数是浮点数时做除法, 由于 num3 是浮点类型, num1 被转化为浮点类型的 2.0, 结果为浮点类型数据
result2 = num1 / num3; // 相当于 2 / 7.0 == 2.0 / 7.0 == 0.285714...
printf("result2 = num1 / num3 结果: %f\n", result2); // 结果为 0.285714
// 混合除法,第一个操作数是整数,第二个是浮点数
result4 = num3 / num4; // 相当于 7.0 / 2. == 7.0 / 2.0 == 3.500000
printf("result3 = num3 / num4 结果: %f\n", result4); // 结果为 3.500000
return 0;
}而实际上,所有的操作符在使用的时候,如果使用的所有操作数的类型不统一,C 语言 会保证所有类型在计算的时候发送隐式转换,使得所有的操作数在计算时的类型时统一的(这样方便统一计算机规则,虽然听起来不可思议,但是计算机中整数的加减法和浮点数的加减法的确时不同的,无法直接兼容)。
体现在上述代码中,就是 result2 = num1 / num3 中的 num1 和 num2 不统一,因此把 num1 转化为和 num2 一样的整型即可。但您一定会有一个问题,为什么不转化 num2 为 num1 的 int 类型呢?
这是因为转化的过程中有时会很危险,一般转化方向都是存储容量较小的数据类型转化为存储容量较大的数据类型,例如 int 转为 long,char 转为 int,flaot 转为 double,int 转为 double...
注
吐槽:如果有一个只能存储 1L 的容器装满了水,为了保证所有的水不被流失,并且我们决定更换更大的容器。您会选择使用 2L 的新容器来存储这些水,还是使用 0.5L 的容器来存储这些水?一般情况下都是选择 2L,毕竟人总有宁滥勿缺的想法...因此编译器选择把 int 类型的数据转化为存储容量更大的 double 也十分的合理。另外,这种行为实际是 C 语言标准要求编译器实现的。
重要
补充:如果取余涉及到负数怎么办?您可以先查看现象再来仔细归纳。
// 带负数操作数的求余结果
#include <stdio.h>
int main() {
printf("%d\n", 17 % 3); // 17 ÷ 3 = 5...2, 故结果为 2
printf("%d\n", -17 % 3); // -17 ÷ 3 = -5...-2, 故结果为 -2
printf("%d\n", 17 % -3); // 17 ÷ -3 = -5...2, 故结果为 2
printf("%d\n", -17 % -3); // -17 ÷ -3 = 5...2, 故结果为 -2
return 0;
}可以看到余数的结果是根据商来决定的,其实就和被除数的符号是一样的。
2.2.赋值操作符
2.2.1.普通赋值操作符
= 可以拿来初始化一个变量,也可以把字面量或变量中的值来赋值给另一个变量。
// 尝试使用普通赋值操作符
#include <stdio.h>
int main() {
int x = 10; // 字面量 10 赋值给 x 变量
int y = 20; // 字面量 20 赋值给 x 变量
int z = y; // 变量 y 赋值给 z 变量
y = z + 1;
x = y;
// 上述两句等价于 a = x = y + 1 这种写法, 但是这种写法有时会导致误会, 因此最好不要这么写
return 0;
}注
吐槽:赋值和初始化是有区别的,这是两个概念。
// 赋值合初始化的区别
int main() {
int a = 3; // 这句是初始化变量 a 为 3
int b;
b = 3; // 而 b = 3 则是对已有的变量 b 进行赋值
}2.2.2.复合赋值操作符
+=, -=, /=, *= 可以对变量进行运算的同时进行赋值。
// 尝试使用复合赋值操作符
#include <stdio.h>
int main() {
int x = 10;
x += 2; // 等价 x = x + 2
printf("%d\n", x);
x -= 2; // 等价 x = x - 2
printf("%d\n", x);
x *= 2; // 等价 x = x * 2
printf("%d\n", x);
x /= 2; // 等价 x = x / 2
printf("%d\n", x);
x %= 3; // 等价 x = x % 3
printf("%d\n", x);
return 0;
}2.3.关系操作符
>, <, >=, <=, !=, == 可以对两个量直接比较大小,并且整体返回一个数,零表示假,非零表示真。
// 尝试使用关系操作符
#include <stdio.h>
int main() {
int a = 5;
int b = 10;
printf("a > b : %d\n", a > b); // a > b 的结果是 0 (假)
printf("a < b : %d\n", a < b); // a < b 的结果是 1 (真)
printf("a >= b : %d\n", a >= b); // a >= b 的结果是 0 (假)
printf("a <= b : %d\n", a <= b); // a <= b 的结果是 1 (真)
printf("a == b : %d\n", a == b); // a == b 的结果是 0 (假)
printf("a != b : %d\n", a != b); // a != b 的结果是 1 (真)
return 0;
}警告
警告:和数学上的用法类似,但我还是想强调一下,等于 == 不是赋值 =,这一点经常会出错。
2.4.条件操作符
exp1 ? exp2 : exp3 的意义是若 exp1 为真,执行 exp2;若 exp1 为假,执行 exp3。这种操作符可以和 if-else 语句互换,但是通常是为了让代码更加简洁时使用。
// 尝试使用条件操作符
#include <stdio.h>
int main() {
int a = 5;
int b = 0;
// 分支语句写法
// if(a >= 5) {
// b = 3;
// } else {
// b = -3;
// }
// 使用条件/三目操作符写法(等价于上面的写法)
a >= 5 ? b = 3 : b = -3;
return 0;
}2.5.逗号操作符
exp1, exp2, exp3, ... 整体是使用逗号组合成的逗号表达式,将会从左向右依次执行 exp1 exp2 exp3,而整个表达式的最终结果就是最后一个表达式的结果。
// 尝试使用逗号操作符
#include <stdio.h>
int main() {
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1); // 整个表达式的值是最后一个表达式的值
// 先计算 a > b, 得到表达式整体结果 0
// 再计算 a = b + 10, a 被修改为 12, 得到表达式整体结果为 12
// 再计算 a, 得到表达式整体结果为 12
// 再计算 b = a + 1, b 被修改为 13, 得到表达式整体结果为 13
printf("%d\n", c); // 故结果为 13
return 0;
}2.6.指针操作符
可以通过 &变量 取得变量的地址,再使用 *地址 得到变量的值。
// 尝试使用指针操作符
#include <stdio.h>
int main() {
int a = 10;
int* ptr = &a;
printf("%p\n", ptr);
printf("%d\n", *ptr);
return 0;
}这个没什么好讲的,我们之前有提及过。
2.7.逻辑操作符
&&逻辑与操作||逻辑或操作!逻辑反操作
值得注意的是,逻辑操作符具有短路特性,在某些情况下可以减小运算。当 a&&b 中,a 表达式为假,b 表达式就不会被执行;当 a||b 中,a 表达式为真,b 表达式就不会被执行。
// 尝试使用逻辑操作符
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
int c = 30;
int d = 0;
if (a + b == c && a < b) { // a + b == c 并且 a < b 时整体表达式结果为非 0, 也就是真
printf("1\n");
}
if (a > b && printf("这里不会被执行")) { // printf("这里不会被执行")) 这个函数也会返回一个值, 也就是打印到屏幕上字符的个数, 因此就算真的打印结果也必为真, 但是这里很明显这个打印语句没有被执行, 因为 && 具有短路效应, 整体表达式的结果已经确定为假, && 左边的表达式为假那就没必要运行右边的表达式了
printf("2\n");
}
if (a < b || printf("这里不会被执行")) { // 由于 || 具有短路性, 已经确定 || 左侧的表达式为真, 整体表达式结果已经为真, 那右侧表达式就不必进行计算了
printf("3\n");
}
if (!(a == 10)) { // a == 10 本来整体结果为真, 但是使用 ! 后逻辑反转, (!(a == 10)) 整体结果为假
printf("4\n");
}
return 0;
}2.8.位操作符
&按位与操作|按位或操作^按位异或操作~按位取反操作<<左移操作符>>右移操作符
位操作符是对单独一个比特位做操作,一个比特位上的数字必然是 1/0。另外,浮点数是不能用移位操作的,位操作符的操作数都是整数。
警告
警告:不要和逻辑操作符混淆在一起,逻辑操作符是对真假逻辑的操作。
// 尝试使用位操作符
#include <stdio.h>
int main() {
// 定义几个操作数
printf("%zd\n", sizeof(int)); // 结果为 4 表示本程序运行在 32 位环境下
int a = 10; // 0000 0000|0000 0000|0000 0000|0000 1010 == a
int b = 18; // 0000 0000|0000 0000|0000 0000|0001 0010 == b
int c = 267; // 0000 0000|0000 0000|0000 0001|0000 1011 == c
int d = -246; // 1000 0000|0000 0000|0000 0000|1111 0110 == d
// 使用 &, |, ^, ~ 位运算符进行位运算(先不考虑负数的情况)
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// 0000 0000|0000 0000|0000 0000|0001 0010 == b
// -------------------------------------------- &
// 0000 0000|0000 0000|0000 0000|0000 0010 == 2
printf("%d & %d == %d\n", a, b, a & b);
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// 0000 0000|0000 0000|0000 0000|0001 0010 == b
// -------------------------------------------- |
// 26 == 0000 0000|0000 0000|0000 0000|0001 1010
printf("%d | %d == %d\n", a, b, a | b);
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// 0000 0000|0000 0000|0000 0001|0000 1011 == c
// ------------------------------------------- ^
// 0000 0000|0000 0000|0000 0001|0000 0001 == 257
printf("%d ^ %d == %d\n", a, b, a ^ c);
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// ------------------------------------------- ~
// 1111 1111|1111 1111|1111 1111|1111 0101(补码)
// 1111 1111|1111 1111|1111 1111|1111 0100(反码)
// 1000 0000|0000 0000|0000 0000|0000 1011 == -11(原码)
printf("~%d == %d\n", a, ~a);
// 使用 >>, << 位运算符进行位运算(先不考虑负数的情况)
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// ------------------------------------------- >> 3
// 0000 0000|0000 0000|0000 0000|0000 0001 == 1
printf("%d >> %d == %d\n", a, b, a >> 3); // 对于正数右移后, 缺失的位使用 0 进行补充
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// ------------------------------------------- << 3
// 0000 0000|0000 0000|0000 0001|0101 0000 == 80
printf("%d << %d == %d\n", a, b, a << 3);
// 但是出现负数的情况下可能有些不太一样, 需要将负数转化为补码再进行运算, 且运算结果可能还需要从补码转回原码
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// 1000 0000|0000 0000|0000 0000|1111 0110 == d
// -------------------------------------------- &
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// 1111 1111|1111 1111|1111 1111|0000 1001 == d(反码)
// -------------------------------------------- &
// 0000 0000|0000 0000|0000 0000|0000 1010 == a
// 1111 1111|1111 1111|1111 1111|0000 1010 == d(补码)
// -------------------------------------------- &
// 0000 0000|0000 0000|0000 0000|0000 1010 == 10
printf("%d & %d == %d\n", a, b, a & d);
// 1000 0000|0000 0000|0000 0000|1111 0110 == d
// ------------------------------------------- >> 3
// 1111 1111|1111 1111|1111 1111|0000 1001 == d(反码)
// ------------------------------------------- >> 3
// 1111 1111|1111 1111|1111 1111|0000 1010 == d(补码)
// ------------------------------------------- >> 3
// 1111 1111|1111 1111|1111 1111|1110 0001(补码)
// 1111 1111|1111 1111|1111 1111|1110 0000(反码)
// 1000 0000|0000 0000|0000 0000|0001 1111 == -31(原码)
printf("%d >> %d == %d\n", a, b, d >> 3); // 对于负数右移后, 缺失的位使用 1 进行补充(在大多数实现中, 右移有符号的数会以原符号位填充新的高位)
return 0;
}重要
补充:右移操作其实在计算机组成原理学科中会分为好几种
逻辑右移:左边缺失的位直接使用
0填充,右边丢弃算术右移:左边缺失的位需要用符号位来填充,右边丢弃
因此大部分 C语言 实现的都是算术右移。
重要
补充:+ 和 -,* 和 / 都是互逆运算,而实际上 ^ 自己和自己是互逆运算,因此有 0001^0101=0100 就有 0001=0100^0101。根据这个特点,有时可以设计出一些精妙的算法。
// 不能创建临时变量(第三个变量),实现两个数交换
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
printf("a = %d, b = %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d, b = %d\n", a, b);
return 0;
}// 求一个整数存储在内存中的二进制中 1 的个数
#include <stdio.h>
int main() {
int num = 246;
// // 方法 1
// // 0000 0000|0000 0000|0000 0000|1111 0110 == num
// int count = 0; // 计数器
// while (num) {
// if (num % 2 == 1) {
// count++;
// }
// num = num / 2;
// }
// printf("二进制中 1 的个数 = %d\n", count);
// // 方法 2
// int count = 0; // 计数器
// for (int i = 0; i < 32; i++) { // 反正 32 位下所有的整数都是 4 字节也就是 32 个比特位, 移位 32 次就可以了
// if (num & (1 << i)) {
// count++;
// }
// }
// printf("二进制中 1 的个数 = %d\n", count);
// 方法 3
int count = 0; // 计数器
while (num) {
count++;
num = num & (num - 1); // 是不是很无厘头的做法? 很难想到吧...
}
printf("二进制中 1 的个数 = %d\n", count);
return 0;
}警告
警告:左移和右移负数个位是 C 标准中未定义的,具体要看编译器怎么实现,因此不要使用负数来移位。
2.9.其他操作符
+/-负正值,加在整型字面量或浮点型字面量前,表示该字面量是负数,这个我们前面用过很多,也很容易理解,就不举例子了sizeof(变量/类型)求出变量或类型的类型长度,也就是类型所能存储的最大字节数(一般推荐使用变量作为操作数,而不使用类型作为操作数,这么做的原因是变量名的类型可能会被经常修改,但是变量名本身不经常被修改)- 前置、后置加加
++,自增1,会根据放在操作数的前面还是后面产生不同的行为 - 前置、后置减减
--,自减1,会根据放在操作数的前面还是后面产生不同的行为 - 下标引用操作符
[数组元素的索引],用来访问数组中的元素,这个我们前面用过很多,也很容易理解,就不举例子了 - 访问结构的成员的两种操作符 (1)
结构体.成员名(2)结构体指针->成员名 - 函数调用操作符
函数名(函数参数),这个()是在提醒编译器,前面的关键词是一个函数名 (类型)操作数对操作数的类型做强制转换,简称 强制类型转换
// 尝试使用 sizeof 关键字(在 32 位环境下)
#include <stdio.h>
void Test1(int arr[]) {
printf("%d\n", sizeof(arr)); // 8, 因为 arr 数组被传递过来后转换为指针类型 int *, 因此 int* arr 和 int [] arr 的写法是一样的, 而所有的指针类型最大存储大小都是一样的, 均为 8 字节
}
void Test2(char ch[]) {
printf("%d\n", sizeof(ch)); // 8, 这里的 char * 和上面的 int* 都是指针类型, 所以大小都是 8 字节
}
int main() {
int num = 10;
printf("sizeof(int) == %zd\n", sizeof(int)); // 4
printf("sizeof(num) == %zd\n", sizeof(num)); // 4
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%zd\n", sizeof(arr)); // 40
printf("%zd\n", sizeof(ch)); // 10
Test1(arr);
Test2(ch);
return 0;
}// 尝试使用前置加加和减减
#include <stdio.h>
int main() {
// 前置
int a = 10;
int x = ++a; // 先对 a(a ==10) 进行自增, 然后使用 a(a== 11) 进行赋值, 此时 x 为 11
int y = --a; // 先对 a(a ==11) 进行自减, 然后使用 a(a== 10) 进行赋值, 此时 y 为 10
printf("x = %d, y = %d\n", x, y); // x = 11, y = 10
// 后置
int b = 10;
int z = b++; // 先使用 b(b ==10) 完成赋值, 然后该语句结束后再对 b 进行自增(b== 11)
int i = b--; // 先使用 b(b ==11) 完成赋值, 然后该语句结束后再对 b 进行自减(b== 10)
printf("z = %d, i = %d\n", z, i); // z = 10, i = 11
return 0;
}// 使用 ++ 或 -- 的易错点
#include<stdio.h>
int main() {
int a, b, c;
a = 5;
c = ++a;
b = ++c, c++, ++a, a++;
b += a++ + c;//注意这里先用 a 再++
printf("a = %d b = %d c = %d\n:", a, b, c); // 9, 23, 8
return 0;
}注意
警告:尽可能只在一条语句中使用一次 ++/--,并且在包含 ++/-- 表达式内部不要进行过多的其他运算。
而 C 是一种弱类型的语言,对于类型可以进行非常自由的转换,转化的主要手段就包含隐式转换和显示转换(不过过于自由这通常不是一件好事),显示转换就会使用到 (类型) 进行强转,不过这点我们后面再来展开,因为这件事还挺复杂...
重要
补充:sizeof 关键字不算是一个函数,并且它的运算结果可能会根据编译环境(32/64 位)和编译器实现的差异而得到不同的值。例如指针类型在 32 位下的大小通常结果为 4,但在 64 下通常结果为 8。
3.操作符的求值顺序
在 C语言 中,多个操作符同时存在与一个式子中,就需要考虑优先计算谁的问题,也就是“求值顺序”如何安排的问题。而求值顺序主要由两个概念决定:操作符的 优先级 和 结合性。
- 优先级:当不同的运算符共用同一个变量时,多个运算符就需要规定哪个运算符先进行计算的问题,先进行运算的运算符优先级会比较高
- 结合性:如果同一个运算符共用一个变量时,就会根据结合方向决定先使用操作数左边的运算符还是右边的运算符,从左边的先开始就是左结合,从右边先开始就是右结合
因此可以总结出如下表格,请不要死记硬背,在代码实践中形成习惯即可。

这里举一些实际的例子辅助您了解操作符的优先级和结合性。
// 第一种场景
a*b + c*d + e*f
// 可能的计算顺序 1
// a*b
// c*d
// a *b + c* d
// e*f
// a *b + c* d + e*f
// 可能的计算顺序 2
// a*b
// c*d
// e*f
// a *b + c* d
// a *b + c* d + e*f
// 虽然实际的计算顺序是依赖编译器实现的, 但上述两种计算顺序不影响最终的结果// 第二种场景
c + ++c; // 无法明确前一个 c 用的是加加之前的 c 值还是之后的 c 值// 第三种场景
int fun() {
static int count = 1;
return ++count;
}
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer); // 输出多少?
// 上述代码中的 answer = fun() - fun() * fun(); 只能通过操作符的优先级得知, 先算乘法, 再算减法, 但是函数的调用先后顺序无法通过操作符的优先级确定4.类型转化
4.1.整型提升
4.1.1.整型提升的概念
C 的整型算术运算总是至少以缺省(默认)整形类型的精度来进行的。而为了获得这个精度,表达式中的“字符操作数”和“短整型操作数”等在计算之前就被转换为“普通整型”,这种转换称为整型提升。
// 尝试观察整型提升的现象(在 32 位环境下)
int main() {
int a = 10;
char b = 1;
printf("%zd", sizeof(a + b)); // 4, 和 sizeof(int) 的结果一样
// b 从 char 类型提升到了 int 类型, 以获取跟多的比特位和 a 进行运算, 这样就可以很大程度上减小数据溢出的情况
return 0;
}4.1.2.整型提升的意义
- 表达式的整型运算要在
CPU的相应运算器件内执行,CPU内整型运算器ALU的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度(寄存器可以拿来存储比特数据)。因此即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。 - 通用
CPU通常不直接实现两个1字节直接相加的运算(虽然机器指令中可能有这种字节相加指令,不过这总情况我们不考虑)。所以表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行计算。 - 因此整型提升符合计算机的设计原理,也能提高运算精度
4.1.3.整型提升的例子
// 正数整型提升的例子
int main() {
char a = 0, b = 3, c = 127; // b 和 c 都被提升为整型类型, 然后执行加法运算
a = b + c;
// 0000 0000 | 0000 0000 | 0000 0000 | 0000 0011 = 3
// 0000 0000 | 0000 0000 | 0000 0000 | 0111 1111 = 127
// --------------------------------------------------- +
// 0000 0000 | 0000 0000 | 0000 0000 | 1000 0010 = 130
// 然后再截断存储在 char a 中为 1000 0010 = 130
// 由于整个过程没有负数和溢出问题, 所以平时使用运算的时候, 整型提升会观察不出来, 是自动进行的
return 0;
}为什么说没有负数的问题呢?
因为实际上整型提升是用符号位来提升的,上述正数用 0 来提升(例如 0000 0011 变成 0000 0000 | 0000 0000 | 0000 0000 | 0000 0011 ) ,而负数的符号位是 1,负数整型提升需要用 1 来补位。
// 负数整型提升的例子
#include <stdio.h>
int main() {
char a = -1;
char b = 1;
printf("%d\n", a + b);
// 1000 0001 == a(原码)
// 1111 1110(反码)
// 1111 1111(补码)
// 1111 1111 | 1111 1111 | 1111 1111 | 1111 1111(整型提升)
// 0000 0001 == b
// 0000 0000 | 0000 0000 | 0000 0000 | 0000 0001(整型提升)
// ------------------------------------------------------ +
// 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 == 0(这里的计算过程种溢出一位比特位 1)
return 0;
}4.1.4.整型提升的验证
// 整型提升的验证
#include <stdio.h>
int main() {
char a = 0xb6; // 字面量的整数默认类型为 int, 0xb6 = 0000 0000 | 0000 0000 | 0000 0000 | 1011 0110, 截断存储为 a == 1011 0110, 由于是直接存储的, 编译器会认为这是一个负数的补码, 转化为反码为 1011 0101, 转化为原码 1100 1010, 也就是 -74
short b = 0xb600; // 0xb6 = 0000 0000 | 0000 0000 | 1011 0110 | 0000 0000, 截断存储为 b == 1011 0110 | 0000 0000, 由于是直接存储的, 编译器会认为这是一个负数的补码, 转化为反码为 1011 0101 | 1111 1111, 转化为原码 1100 1010 | 0000 0000, 也就是 -18944
int c = 0xb6000000; // 0xb6 = 1011 0110 | 0000 0000 | 0000 0000 | 0000 0000, 由于是直接存储的, 编译器会认为这是一个负数的补码, 转化为反码为 1011 0101 | 1111 1111 | 1111 1111 | 1111 1111, 转化为原码 1100 1010 | 0000 0000 | 0000 0000 | 0000 0000, 也就是 -1241513984
printf("a==%d, b==%d, c==%d\n", a, b, c); // 输出 a ==-74, b==-18944, c ==-1241513984
// 但按符号位整型提升后 (a == 1111 1111 | 1111 1111 | 1111 1111 | 1011 0110) != (0xb6 == 0000 0000 | 0000 0000 | 0000 0000 | 1011 0110)
if (a == 0xb6) {
printf("a == 0xb6\n");
} else if ((char)a == (char)0xb6) {
printf("(char)a == (char)0xb6\n");
}
// 但按符号位整型提升后 (b == 1111 1111 | 1111 1111 | 1011 0110 | 0000 0000) != (0xb6 == 0000 0000 | 0000 0000 | 1011 0110 | 0000 0000)
if (b == 0xb600) {
printf("b == 0xb600\n");
} else if ((short)b == (short)0xb600) {
printf("(short)b == (short)0xb600\n");
}
// 这里没有使用整型提升, 因此就会相等, 没有什么意外情况发生
if (c == 0xb6000000) {
printf("c == 0xb6000000\n");
}
return 0;
}重要
补充:上面这个例子非常的经典,需要反复牢记和复习,这个验证过程更多揭示了 C 对字面量的解释和对整型提升的理解。
4.2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法直接运算,C 为我们自动做了算术转化工作,如果 C 没这么做的话,就需要程序员自己使用 (类型) 进行类型强转,而一般自动的算术转化有以下的转化规则。
long doubledoublefloatunsigned long intlong intunsigned intint
如果操作符的某个操作数的对于类型所能存储的大小在上面这个列表中排名较低,那么首先要转换为另外一个类型大小较高操作数的类型后,再来执行运算。C 充分相信程序员,不会在这方面要求程序员自己转换,但是程序员必须承担自动转换所相应的责任(有危险您也要自己担责,这是您必须支付的代价)。例如下面的例子,就展示了算术转换一些潜在风险。
// 隐式转换和显式转换
int main() {
float f = 3.14;
int num = f; // 隐式转换, 会有精度丢失
// 上述等价于 int num = (int)f, 这种也被称为显示转换
return 0;
}注意
注意:强制转化是一种临时转化,不是永久转化。