操作符详解
目录
✨ 引言:
操作符是 C 语言的核心语法元素,贯穿于每一行代码的编写与执行。无论是基础的算术运算、高效的位操作,还是复杂的表达式求值,掌握操作符的底层逻辑和使用技巧,都是写出简洁、高效、无 Bug 代码的关键。本文将系统梳理 C 语言所有常用操作符,从分类、底层原理到实战案例,搭配详细代码和通俗解释,让你彻底掌握操作符的方方面面!
一、🔧 操作符的分类
C 语言操作符按功能可分为 12 大类,覆盖编程中的各类需求,一目了然:
- 算术操作符:
+,-,*,/,%(基础数学运算) - 移位操作符:
<<,>>(二进制位直接移位) - 位操作符:
&,|,^,~(二进制位逻辑运算) - 赋值操作符:
=,+=,-=,*=,/=,%=,<<=,>>=,&=,|=,^=(变量赋值与运算结合) - 单目操作符:
!,++,--,&,*,+,-,~,sizeof,(类型)(仅需一个操作数) - 关系操作符:
>,>=,<,<=,==,!=(比较大小或相等性) - 逻辑操作符:
&&,||(逻辑判断,支持短路求值) - 条件操作符:
?:(三目运算符,简洁分支逻辑) - 逗号表达式:
,(多表达式串联执行) - 下标引用:
[](数组元素访问) - 函数调用:
()(函数执行调用) - 结构成员访问:
.,->(结构体成员操作)
二、🔢 进制和进制转换
操作符(尤其是位运算、移位操作)的底层依赖二进制,先掌握进制转换,后续学习会更轻松:
常见进制及表示
| 进制 | 组成元素 | C 语言标识 | 示例(数字 15) |
|---|---|---|---|
| 二进制 | 0, 1 | 无(直接书写) | 1111 |
| 八进制 | 0-7 | 前缀0 | 017 |
| 十进制 | 0-9 | 无 | 15 |
| 十六进制 | 0-9, A-F(a-f) | 前缀0x/0X | 0xF |
常用进制转换方法
-
十进制转二进制:除 2 取余,逆序排列例:将 10 转为二进制
10 ÷ 2 = 5 余 0 5 ÷ 2 = 2 余 1 2 ÷ 2 = 1 余 0 1 ÷ 2 = 0 余 1结果:从下往上读取余数 → 1010
-
二进制转十进制:按权展开,求和二进制从右向左,第 n 位的权值为 2^(n-1),将每一位与权值相乘后求和:例:将 1010 转为十进制
1×2³ + 0×2² + 1×2¹ + 0×2⁰ = 8 + 0 + 2 + 0 = 10 -
二进制与八进制 / 十六进制转换:
- 二进制转八进制:每 3 位一组(不足补 0),每组对应一个八进制数(利用 421 权值法:3 位二进制对应权值 4、2、1)例:101101 → 010(2) 110(6) 001(1) → 0261
- 二进制转十六进制:每 4 位一组(不足补 0),每组对应一个十六进制数(利用 8421 权值法:4 位二进制对应权值 8、4、2、1)例:101101 → 0010(2) 1101(13→D) → 0x2D
三、💾 底层核心:原码、反码、补码
计算机中整数以补码形式存储和运算,这是理解位操作、移位操作的关键,必须彻底掌握:
核心概念
- 符号位:二进制最高位用于表示正负,0 = 正数,1 = 负数(int 类型占 4 字节 = 32 位)
- 原码:直接将十进制数转换为二进制,最高位为符号位,是最直观的表示形式
- 反码:
- 正数:反码与原码完全相同
- 负数:符号位保持不变,其他位按位取反
- 补码:
- 正数:补码与原码、反码完全相同
- 负数:补码 = 反码 + 1(计算机存储和运算的最终形式)
代码示例(int 类型,32 位)
#include <stdio.h>
int main()
{
int a = -10;
// 原码:10000000 00000000 00000000 00001010(直接转换十进制,最高位为1)
// 反码:11111111 11111111 11111111 11110101(符号位不变,其他位取反)
// 补码:11111111 11111111 11111111 11110110(反码加1)
int b = 10;
// 原码:00000000 00000000 00000000 00001010(正数符号位为0)
// 反码:00000000 00000000 00000000 00001010(与原码相同)
// 补码:00000000 00000000 00000000 00001010(与原码、反码相同)
return 0;
}
为什么要用补码?
- 统一加减法运算:CPU 只有加法器,减法运算可转化为 “加负数的补码”,无需额外硬件支持例:计算 1-1(等价于 1+(-1))
- 用原码计算:00000000 00000000 00000001 + 10000000 00000000 00000001 = 10000000 00000000 00000010 → 结果为 - 2(错误)
- 用补码计算:00000000 00000000 00000001 + 11111111 11111111 11111111 = 100000000 00000000 00000000 → 舍弃溢出进位,结果为 00000000 00000000 00000000 → 0(正确)
- 避免正负零歧义:原码中 + 0(00000000)和 - 0(10000000)是两个不同的表示,补码中仅存在唯一的零(00000000),避免计算混乱
补码与原码的转换技巧
- 负数补码 → 原码:补码减 1 后按位取反(或按位取反后加 1,两种方法结果一致)
- 例:补码 11111111 11111111 11111111 11110110(-10 的补码)减 1 得反码:11111111 11111111 11111111 11110101 → 按位取反(符号位不变)得原码:10000000 00000000 00000000 00001010
四、⚡ 移位操作符
移位操作符直接操作整数的二进制补码,效率极高,操作数只能是整数:
1. 左移操作符 <<
- 规则:左边的二进制位被直接抛弃,右边补 0
- 数学意义:正数左移 n 位,等价于乘以 2ⁿ(效率远高于乘法操作)
#include <stdio.h>
int main()
{
int a = 6;
// 原码:00000000 00000000 00000000 00000110
// 反码:00000000 00000000 00000000 00000110
// 补码:00000000 00000000 00000000 00000110
int b = (a << 1);
// 左移1位后补码:00000000 00000000 00000000 00001100
// 反码:00000000 00000000 00000000 00001100
// 原码:00000000 00000000 00000000 00001100 → 12
printf("%d\n", a); // 6(原变量值不变)
printf("%d\n", b); // 12(6×2¹)
return 0;
}
#include <stdio.h>
int main()
{
int a = -6;
// 原码:10000000 00000000 00000000 00000110
// 反码:11111111 11111111 11111111 11111001
// 补码:11111111 11111111 11111111 11111010
int b = (a << 1);
// 左移1位后补码:11111111 11111111 11111111 11110100
// 反码:11111111 11111111 11111111 11110011(补码减1)
// 原码:10000000 00000000 00000000 00001100 → -12
printf("%d\n", a); // -6(原变量值不变)
printf("%d\n", b); // -12(-6×2¹)
return 0;
}
2. 右移操作符 >>
右移操作符分为两种模式,具体采用哪种由编译器决定(主流编译器对有符号数采用算术右移):
- 逻辑右移:右边的二进制位被抛弃,左边补 0(多用于无符号数)
- 算术右移:右边的二进制位被抛弃,左边补原符号位(多用于有符号数)
示例(以 - 1 为例,int 类型 32 位)
#include <stdio.h>
int main()
{
int num = -1;
// 原码:10000000 00000000 00000000 00000001
// 反码:11111111 11111111 11111111 11111110
// 补码:11111111 11111111 11111111 11111111
num >>= 1; // 采用算术右移,左边补符号位1
// 右移后补码:11111111 11111111 11111111 11111111(与原补码相同)
// 反码:11111111 11111111 11111111 11111110
// 原码:10000000 00000000 00000000 00000001 → -1
printf("%d\n", num); // 输出-1(验证算术右移)
return 0;
}
重要注意事项
- 不要对整数进行负数位的移位(如
a << -1),这属于未定义行为,不同编译器可能产生不同结果 - 移位操作不会改变原变量的值,需将结果赋值给新变量或原变量(如
a = a << 1)
五、🔍 位操作符
位操作符直接操作二进制补码的每一位,是底层开发、高效编程的核心工具,核心规则如下:
| 操作符 | 名称 | 运算规则(两位对比) | 记忆口诀 | |
|---|---|---|---|---|
& | 按位与 | 全 1 为 1,有 0 为 0 | 有 0 则 0 | |
| ` | ` | 按位或 | 有 1 为 1,全 0 为 0 | 有 1 则 1 |
^ | 按位异或 | 不同为 1,相同为 0 | 相同为 0,不同为 1 | |
~ | 按位取反 | 0 变 1,1 变 0 | 逐位翻转 |
1. 按位与 &
#include <stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a & b;
// 3的补码:00000000 00000000 00000000 00000011
// -5的原码:10000000 00000000 00000000 00000101
// -5的反码:11111111 11111111 11111111 11111010
// -5的补码:11111111 11111111 11111111 11111011
// 按位与运算:
// 00000000 00000000 00000000 00000011
// 11111111 11111111 11111111 11111011
// &-----------------------------------
// 00000000 00000000 00000000 00000011 → 补码(正数原码与补码相同)→ 3
printf("%d\n", c); // 输出3
return 0;
}
常见用途:判断整数奇偶性(n & 1,结果为 1 则为奇数,0 则为偶数)、清 0 特定位(与 0 按位与)
2. 按位或 |
#include <stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a | b;
// 3的补码:00000000 00000000 00000000 00000011
// -5的补码:11111111 11111111 11111111 11111011
// 按位或运算:
// 00000000 00000000 00000000 00000011
// 11111111 11111111 11111111 11111011
// |-----------------------------------
// 11111111 11111111 11111111 11111011 → 补码
// 反码:11111111 11111111 11111111 11111010(补码减1)
// 原码:10000000 00000000 00000000 00000101 → -5
printf("%d\n", c); // 输出-5
return 0;
}
常见用途:置 1 特定位(与 1 按位或)
3. 按位异或 ^
#include <stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
// 3的补码:00000000 00000000 00000000 00000011
// -5的补码:11111111 11111111 11111111 11111011
// 按位异或运算:
// 00000000 00000000 00000000 00000011
// 11111111 11111111 11111111 11111011
// ^-----------------------------------
// 11111111 11111111 11111111 11111000 → 补码
// 反码:11111111 11111111 11111111 11110111(补码减1)
// 原码:10000000 00000000 00000000 00001000 → -8
printf("%d\n", c); // 输出-8
return 0;
}
常见用途:交换两个整数(无临时变量)、翻转特定位
4. 按位取反 ~
#include <stdio.h>
int main()
{
int a = 0;
// 原码:00000000 00000000 00000000 00000000
// 反码:00000000 00000000 00000000 00000000
// 补码:00000000 00000000 00000000 00000000
int b = ~a;
// 按位取反后补码:11111111 11111111 11111111 11111111
// 反码:11111111 11111111 11111111 11111110(补码减1)
// 原码:10000000 00000000 00000000 00000001 → -1
printf("%d\n", b); // 输出-1
return 0;
}
注意:按位取反会翻转所有位(包括符号位),结果必然改变原数的正负性
位操作实战技巧(面试高频)
1. 无临时变量交换两个整数
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d, b=%d\n", a, b);
a = a ^ b; // a = 3 ^ 5
b = a ^ b; // b = (3 ^ 5) ^ 5 = 3(利用a^a=0,0^a=a)
a = a ^ b; // a = (3 ^ 5) ^ 3 = 5
printf("交换后:a=%d, b=%d\n", a, b); // 输出:a=5, b=3
return 0;
}
2. 统计二进制中 1 的个数(高效方法)
#include <stdio.h>
int main()
{
int n = 0;
int count = 0;
scanf("%d", &n);
// 核心原理:n & (n-1) 会清除n二进制中最右边的1
while (n)
{
n = n & (n - 1);
count++;
}
printf("二进制中1的个数:%d\n", count);
return 0;
}
示例:n=15(1111)
- 15 & 14 = 14(1110)→ count=1
- 14 & 13 = 12(1100)→ count=2
- 12 & 11 = 8(1000)→ count=3
- 8 & 7 = 0 → count=4 → 循环结束,结果为 4
3. 判断一个数是否是 2 的幂次方
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
// 核心原理:2的幂次方二进制中只有一个1,n & (n-1) 结果为0
if (n > 0 && (n & (n - 1)) == 0)
{
printf("是2的幂次方\n");
}
else
{
printf("不是2的幂次方\n");
}
return 0;
}
4. 二进制位置 0 或置 1
#include <stdio.h>
int main()
{
int n = 13; // 二进制:00000000 00000000 00000000 00001101
int bit_pos = 4; // 操作第4位(从0开始计数)
// 1. 将第bit_pos位置1(与1按位或)
n |= (1 << bit_pos); // 1 << 4 = 16(00000000 00000000 00000000 00010000)
printf("%d\n", n); // 13 | 16 = 29(00000000 00000000 00000000 00011101)
// 2. 将第bit_pos位置0(与~(1<<bit_pos)按位与)
n &= ~(1 << bit_pos); // ~(1<<4) = 0xFFFFFFEF(11111111 11111111 11111111 11101111)
printf("%d\n", n); // 29 & 0xFFFFFFEF = 13(恢复原值)
return 0;
}
六、📌 单目操作符
单目操作符仅需一个操作数,功能丰富,包括逻辑判断、自增自减、地址操作等:
| 操作符 | 名称 | 功能说明 |
|---|---|---|
! | 逻辑非 | 对表达式结果取反:!0=1,! 非 0=0 |
++ | 自增 | 前置 ++:先自增 1,再使用;后置 ++:先使用,再自增 1 |
-- | 自减 | 前置 --:先自减 1,再使用;后置 --:先使用,再自减 1 |
& | 取地址 | 获取变量的内存地址(后续指针章节详细讲解) |
* | 解引用 | 通过地址访问内存中的变量(后续指针章节详细讲解) |
+ | 正号 | 对数值无影响(仅用于强调正数,可省略) |
- | 负号 | 改变数值的符号 |
~ | 按位取反 | 对二进制补码逐位翻转(已在第五章讲解) |
sizeof | 计算字节数 | 计算变量或数据类型所占的字节大小,返回值类型为 size_t(用 % zu 格式符打印) |
(类型) | 强制类型转换 | 将表达式结果转换为指定类型(慎用,可能丢失精度或导致数据异常) |
关键示例
1. sizeof 操作符
#include <stdio.h>
int main()
{
int a = 10;
printf("sizeof(a) = %zu\n", sizeof(a)); // 输出4(int类型占4字节)
printf("sizeof(int) = %zu\n", sizeof(int)); // 输出4
printf("sizeof(a + 3.14) = %zu\n", sizeof(a + 3.14)); // 输出8(a提升为double类型)
char arr[10] = {0};
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 输出10(数组总字节数=元素个数×单个元素字节数)
return 0;
}
2. 自增自减操作符
#include <stdio.h>
int main()
{
int a = 5;
int b = ++a; // 前置++:a先自增为6,再赋值给b → b=6,a=6
printf("a=%d, b=%d\n", a, b); // 输出:a=6, b=6
int c = 5;
int d = c++; // 后置++:c先赋值给d(d=5),再自增为6 → d=5,c=6
printf("c=%d, d=%d\n", c, d); // 输出:c=6, d=5
return 0;
}
3. 强制类型转换
#include <stdio.h>
int main()
{
float f = 3.14f;
int a = (int)f; // 强制将float类型转换为int类型,丢失小数部分
printf("a=%d\n", a); // 输出3
int b = 10;
double d = (double)b / 3; // 强制转换后进行除法,结果为浮点数
printf("d=%lf\n", d); // 输出3.333333
return 0;
}
注意:强制类型转换仅在表达式计算时临时改变类型,不会改变原变量的类型和值
补充说明
单目操作符中的 &(取地址)和 *(解引用)是指针操作的核心,后续指针章节会详细讲解,此处暂不展开。
七、📝 逗号表达式
逗号表达式的形式为 exp1, exp2, exp3, ..., expN,核心规则如下:
- 从左到右依次执行所有表达式
- 整个逗号表达式的结果为最后一个表达式的值
- 逗号的优先级是所有操作符中最低的
示例
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
// 逗号表达式:先执行a>b(结果为0),再执行a=b+10(a=12),再执行a,最后执行b=a+1(b=13)
int c = (a > b, a = b + 10, a, b = a + 1);
printf("c=%d\n", c); // 输出13(最后一个表达式b=a+1的结果)
printf("a=%d, b=%d\n", a, b); // 输出a=12, b=13
// 逗号表达式在循环中的应用(简化代码)
int x = 0, y = 0;
while (x++, y++, x < 5)
{
printf("x=%d, y=%d\n", x, y);
}
// 循环执行过程:x从1到4,y从1到4,每次循环先执行x++和y++,再判断x<5
return 0;
}
应用场景
- 简化代码:在需要执行多个操作但语法只允许一个表达式的场景(如 for 循环的初始化、条件判断)
- 批量赋值或计算:依次执行多个赋值或计算操作,取最后一个结果
八、🔍 下标引用 [] 与函数调用 ()
1. 下标引用操作符 []
下标引用操作符用于访问数组元素,格式为 arr[n],核心规则:
- 操作数为数组名和索引值(n 为索引,从 0 开始)
- 等价于
*(arr + n)(数组名本质是数组首元素的地址)
示例
#include <stdio.h>
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("%d\n", arr[2]); // 输出3(访问索引为2的元素)
printf("%d\n", *(arr + 2)); // 输出3(与arr[2]等价)
printf("%d\n", 2[arr]); // 输出3(等价于arr[2],语法允许但不推荐)
return 0;
}
说明:arr[n] 之所以等价于 *(arr + n),是因为数组名 arr 代表数组首元素的地址,arr + n 表示首元素地址向后偏移 n 个元素的地址,* 解引用后即可访问该地址对应的元素。
2. 函数调用操作符 ()
函数调用操作符用于执行函数,格式为 函数名(实参1, 实参2, ...),核心规则:
- 操作数为函数名和实参列表(实参个数与函数形参一致)
- 函数调用时,实参的值会传递给形参,函数执行后返回结果(无返回值的函数返回 void)
示例
#include <stdio.h>
// 定义加法函数
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("Hello World!\n"); // 函数调用,无实参
int a = 3, b = 5;
int ret = Add(a, b); // 函数调用,传递两个实参
printf("a + b = %d\n", ret); // 输出8
return 0;
}
说明:函数调用操作符的优先级较高,在表达式中会优先执行。
九、🏗️ 结构体与成员访问操作符 . 和 ->
C 语言中的结构体是自定义数据类型,用于将不同类型的数据组合成一个整体,访问结构体成员需使用 . 或 -> 操作符。
1. 结构体的定义与初始化
#include <stdio.h>
// 结构体声明:描述学生的属性
struct Student
{
char name[20]; // 姓名
int age; // 年龄
int high; // 身高
float weight; // 体重
char id[16]; // 学号
}; // 分号不能省略(结构体声明结束标志)
// 定义全局结构体变量并初始化
struct Student s1 = {"张三", 20, 180, 75.5f, "20230901022"};
// 定义全局结构体变量(未初始化,成员为随机值)
struct Student s2, s3, s4;
int main()
{
// 定义局部结构体变量并初始化(指定成员顺序)
struct Student s5 = {"李四", 22, 175, 68.0f, "20230901023"};
// 定义局部结构体变量并指定成员初始化(C99及以上支持)
struct Student s6 = {
.age = 21,
.name = "王五",
.weight = 72.3f,
.high = 178,
.id = "20230901024"
};
return 0;
}
2. 结构体成员访问
.操作符:通过结构体变量名访问成员,格式:结构体变量.成员名->操作符:通过指向结构体的指针访问成员,格式:结构体指针->成员名
示例
#include <stdio.h>
#include <string.h>
struct Student
{
char name[20];
int age;
};
int main()
{
// 定义结构体变量并初始化
struct Student s = {"张三", 20};
printf("姓名:%s,年龄:%d\n", s.name, s.age); // 用.访问成员 → 输出:姓名:张三,年龄:20
// 修改成员值
strcpy(s.name, "张三三"); // 字符串赋值需用strcpy函数
s.age = 21;
printf("修改后:姓名:%s,年龄:%d\n", s.name, s.age); // 输出:修改后:姓名:张三三,年龄:21
// 定义结构体指针并指向结构体变量
struct Student *ps = &s;
// 用->访问成员
printf("指针访问:姓名:%s,年龄:%d\n", ps->name, ps->age); // 输出:指针访问:姓名:张三三,年龄:21
// -> 等价于 (*指针).成员
printf("等价形式:姓名:%s,年龄:%d\n", (*ps).name, (*ps).age); // 输出同上
return 0;
}
3. 嵌套结构体的成员访问
如果结构体成员也是结构体类型,需逐层访问:
#include <stdio.h>
// 定义嵌套的结构体
struct Date
{
int year;
int month;
int day;
};
struct Person
{
char name[20];
struct Date birthday; // 成员为结构体类型
int height;
};
int main()
{
struct Person p = {
.name = "赵六",
.birthday = {2000, 9, 10},
.height = 180
};
// 访问嵌套结构体的成员
printf("姓名:%s\n", p.name);
printf("生日:%d年%d月%d日\n", p.birthday.year, p.birthday.month, p.birthday.day);
printf("身高:%dcm\n", p.height);
return 0;
}
十、⚖️ 操作符的优先级与结合性
复杂表达式的求值顺序由操作符的优先级和结合性决定,这是避免表达式求值错误的关键:
1. 优先级
优先级决定了不同操作符在表达式中的执行顺序,优先级高的操作符先执行。例如:
- 乘法
*、除法/的优先级高于加法+、减法- - 赋值操作符
=的优先级较低,通常最后执行
常见操作符优先级排序(从高到低):
- 圆括号
()(强制改变优先级) - 单目操作符(
!,++,--,~,sizeof等) - 算术操作符(
*,/,%高于+,-) - 移位操作符(
<<,>>) - 关系操作符(
>,>=,<,<=高于==,!=) - 位操作符(
&高于^高于|) - 逻辑操作符(
&&高于||) - 条件操作符
?: - 赋值操作符(
=,+=,-=等) - 逗号表达式
,(优先级最低)
2. 结合性
当多个优先级相同的操作符连用时,求值顺序由结合性决定:
- 左结合:从左到右依次执行(大部分操作符,如
+,-,*,/) - 右结合:从右到左依次执行(少数操作符,如赋值操作符
=, 单目操作符)
示例
#include <stdio.h>
int main()
{
// 优先级示例:先算乘法,再算加法
int a = 3 + 4 * 2;
printf("a=%d\n", a); // 输出11(3 + (4×2))
// 结合性示例1:左结合(加法)
int b = 3 + 4 + 5;
printf("b=%d\n", b); // 输出12((3+4)+5)
// 结合性示例2:右结合(赋值)
int c, d, e;
c = d = e = 10;
printf("c=%d, d=%d, e=%d\n", c, d, e); // 输出10,10,10(c=(d=(e=10)))
// 优先级示例:括号改变顺序
int f = (3 + 4) * 2;
printf("f=%d\n", f); // 输出14((3+4)×2)
return 0;
}
重要建议
- 即使熟悉优先级和结合性,也建议使用括号
()明确指定求值顺序,提高代码可读性 - 避免编写过于复杂的表达式(如多个操作符嵌套),可拆分为多个简单语句
- 参考:http://zh.cppreference.com/w/c/language/poerator_precedence
十一、🔢 表达式求值的关键规则
表达式求值过程中,除了优先级和结合性,还需注意整型提升和算术转换,否则可能出现意想不到的结果:
1. 整型提升
- 定义:表达式中的字符型(
char)、短整型(short)操作数,在参与运算前会自动转换为普通整型(int) - 原因:CPU 的整型运算器(ALU)通常以
int类型为最小运算单位,转换为int可提高运算效率 - 提升规则:
- 有符号数:按符号位进行符号扩展(正数高位补 0,负数高位补 1)
- 无符号数:高位补 0
示例
#include <stdio.h>
int main()
{
char a = 3; // 二进制:00000011(signed char)
char b = 127; // 二进制:01111111(signed char)
// a和b会进行整型提升
// a提升后:00000000 00000000 00000000 00000011
// b提升后:00000000 00000000 00000000 01111111
char c = a + b;
// a + b的结果:00000000 00000000 00000000 10000010(int类型)
// 截断为char类型:10000010(signed char,符号位为1,是负数)
// 打印时c会再次进行整型提升(符号扩展)
// 提升后补码:11111111 11111111 11111111 10000010
// 原码:10000000 00000000 00000000 01111110 → -126
printf("%d\n", c); // 输出-126
return 0;
}
注意:char类型在不同编译器中可能是signed char或unsigned char,会影响整型提升的结果,编程时需注意兼容性。
2. 算术转换
- 定义:如果表达式中不同类型的算术操作数参与运算,编译器会按规则将它们转换为同一类型后再运算
- 转换规则(从高到低,按优先级转换):
long doubledoublefloatunsigned long longlong longunsigned longlongunsigned intint
示例
#include <stdio.h>
int main()
{
int i = 10;
float f = 3.14f;
double d = i + f; // i(int)→ float → 与f相加 → 结果转换为double
printf("d=%lf\n", d); // 输出13.140000
unsigned int u = 20;
int j = -10;
if (u + j > 0) // j(int)→ unsigned int → 相加结果为unsigned int(无负数)
{
printf("u + j > 0\n"); // 会执行此语句
}
else
{
printf("u + j <= 0\n");
}
return 0;
}
注意:无符号数与有符号数混合运算时,有符号数会转换为无符号数,可能导致负数被解释为大数,需格外小心。
十二、⚠️ 问题表达式解析(避坑指南)
有些表达式看似正确,但由于 C 语言标准未明确规定求值顺序或存在副作用,会导致未定义行为(不同编译器结果不同),需坚决避免:
1. 同一表达式中多次修改同一变量
// 未定义行为:i被多次修改,求值顺序不确定
int i = 10;
i = i++ - --i * (i = -3) * i++ + ++i;
printf("i=%d\n", i); // 不同编译器结果不同
2. 函数参数的求值顺序不确定
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
// 未定义行为:fun()的调用顺序不确定
int answer = fun() - fun() * fun();
printf("%d\n", answer); // 可能是2-3*4=-10,也可能是其他结果
return 0;
}
3. 逻辑操作符的短路求值被忽略
int a = 0, b = 5;
// a为假,&&右边的++b不会执行(短路求值),b仍然是5
if (a && ++b)
{
// 不会执行
}
printf("b=%d\n", b); // 输出5
4. 赋值操作符与相等判断混淆
int x = 5;
// 错误:将赋值操作符=误写为相等判断==
if (x = 10) // x被赋值为10,条件为真
{
printf("x=10\n"); // 会执行
}
避坑总结
- 避免在同一表达式中对同一变量进行多次读写操作
- 函数参数中避免使用有副作用的表达式(如
i++) - 明确逻辑操作符的短路求值特性,不依赖未执行的表达式
- 严格区分赋值操作符
=和相等判断== - 复杂表达式拆分为多个简单语句,提高可读性和正确性
十三、🎉 总结
本文覆盖了 C 语言所有常用操作符的知识点,从基础到进阶,从原理到实战,搭配详细代码示例和解释。如果遇到具体问题(如指针操作、结构体复杂应用),欢迎在评论区交流~ 祝大家编程之路少踩坑,多高效!
901

被折叠的 条评论
为什么被折叠?



