C语言:混合运算与类型转换详解
内容提要
-
运算符
-
算术运算符
-
赋值运算符
-
关系运算符
-
逻辑运算符
-
逗号运算符
-
位运算
运算符
各类数值型数据间的混合运算
-
整型、浮点型、字符型数据可以进行混合运算,如:
10 - 'a' * 1.5
= 10 - 97 * 1.5 //保证参与运算符的都是数字
= 10.0 - 97.0 * 1.5//不同类型数据参与运算时,编译器会自动转换位同一类型后运算(隐式类型转换)(例:如果过程有浮点型数据,系统会自动全部转换称浮点型类型。类型提升:系统在内存中临时创建了一个内存空间)
解释:整型、浮点型、字符型能够进行混合运算,核心原因是它们都属于数值型数据——字符本质是特殊的数值型(参与数值计算时使用ASCII码,取值范围时0~127)
运算规则:若参与运算的两个数类型不同,会先将类型转换为一致后在运算。转换分为隐式转换和显示转换。
隐式转换又被称为自动类型转换由编译系统自动控制,规则时将低等级类型提升为高等级类型。
语法:
高等级类型 变量名 = 低等级类型变量

关键注意事项:
混合类型中的类型转换仅在运算过程中临时生效,不会改变原变量的类型和内存的存储形式(==所谓类型提升就是运行时系统在内存中临时创建了一个内存空间【temp】,将原本的数据产生一份副本,由副本数据参与计算,计算完毕后副本销毁,原数据不受影响==)。
举例:
int a = 10; //a的类型始终时int(数据类型一旦创建,不会发生改变)
double c = a = 22.5; //运算时a临时转换为double,等价于10.0 + 22.5,其实是a的double类型的副本参与计算
a = 21;(变量一旦申请,内存大小不会发生改变)
float c2 = a + 22.5f;//运算时a临时转换为float,等价于21.0f + 22.5f,其实是a的float类型的副本参与计算
a = 10;//a保持int类型不变
显示转换又被称作强制类型转换,由程序员手动指定转换类型。
语法:
(type)(表达式)
语法说明:
-
(double)a:将变量a强制转换为double类型。
-
(int)(x+y):将表达式x+y的结构转换为int类型(括号必须包含整个表达式,否则仅转换x)
-
(int) x+y:仅将变量x转换为int类型在与y进行计算(括号仅作用于x)
举例:
double a = 2, b = 3; //a,b均为double
double c = (int)a + b;//第一步:显示将a转换为int(值为2);第二步混合运算时int自动转换为double,最终等价于2.0 + 3.0
double num1 = 12.55; //num1为double类型
int num2 = (int)num1; //显示转换num1为int类型,浮点型转整型,会舍弃小数部分 printf("%d ",num2);
num1 = 15; //num1类型仍为double(值为15.0,平时用一位小数表示即可)
关键注意事项:
-
强制类型转换仅在运算过程中临时改变值的类型,不会修改原变量的类型和内存存储形式;
-
浮点型转整型时需直接舍弃小数部分,不进行四舍五入;
-
转换后的类型需与接受变量的类型匹配,避免数据溢出或精度丢失。
案例:强制类型转换实践
-
需求:验证强制类型转换对原变量类型的影响
-
代码:
#include
int main(int argc,char *argv[])
{
float x; int i; x = 3.6f; // float类型变量赋值时,同类型常量添加f/F后缀
i = (int)x;// 运行时强制将x转换为int,此时参与运算的实际上是x的副本
// 输出结果:x=3.600000,i=3,%f默认输出6位小数
printf("x=%f, i=%d ", x, i); return 0;
}
说明:变量x的类型始终为float,强制类型转换仅影响赋值给i的值,不改变x本身的类型和值,因为参与运算的是副本。
C运算符和C表达式
C运算符(完整列表)
| 序号 | 名称 | 符号 | 序号 | 名称 | 符号 |
|---|---|---|---|---|---|
| 1 | 算术运算符 | +、-、*、/、%、++、-- | 8 | 指针运算符 | *、& |
| 2 | 关系运算符 | >、<、>=、<=、==、!= | 9 | 字节数运算符 | sizeof |
| 3 | 逻辑运算符 | &&、||、! | 10 | 下标运算符 | [] |
| 4 | 位运算符 | <<、>>、~、|、^、& | 11 | 强制类型转换运算符 | (type) |
| 5 | 赋值运算符 | =、+=、-=、*=、/=、%= | 12 | 分量运算符 | .、-> |
| 6 | 条件运算符 | ?: | 13 | 函数调用运算符 | () |
| 7 | 逗号运算符 | , |
C表达式
定义:由运算数(常量、变量、表达式)和运算符按C语法规则链接而成的式子(表达式 = 运算数 + 运算符)
| 序号 | 名称 | 举例 | 结果类型与说明 |
|---|---|---|---|
| 1 | 算数表达式 | 2+6.7*3.5 | 数值类型(int或double等,取决于运算数类型) |
| 2 | 关系表达式 | x>0,y | 布尔类型(c语言用0表示假,非0表示真) |
| 3 | 逻辑表达式 | x > 0 && y > 0 | 布尔类型 |
| 4 | 赋值表达式 | a = 5.6,sum += i;a = b = c = d = 5; | 左侧变量的类型,运算顺序自右向左(将右边的值赋到左边) |
| 5 | 逗号表达式 | x = 3,y +=4, z -= 8 | ==类型是最后一个表达式的类型,结果是最后一个表达式的值== |
C语言运算符优先级与结合性
c语言通过优先级和结合性规定表达式的求值顺序:优先级高度的先执行;优先级相同时,按结合性顺序执行

简化优先级表(由高到低):
| 优先级 | 运算符类别 | 运算符示例 | 结合性 |
|---|---|---|---|
| 1 | 括号、下标、成员访问 | ()、[]、.、-> | 从左向右 |
| 2 | 后缀自增 / 自减 | a++、a– | 从左向右 |
| 3 | 前缀自增 / 自减、逻辑非等 | ++a、–a、!、sizeof、&、-(负号) | 从右向左 |
| 4 | 强制类型转换 | (type) | 从右向左 |
| 5 | 算术乘除、取余 | *、/、% | 从左向右 |
| 6 | 算术加减 | +、- | 从左向右 |
| 7 | 位左移 / 右移 | <<、>> | 从左向右 |
| 8 | 关系运算符(比较大小) | >、<、>=、<= | 从左向右 |
| 9 | 关系运算符(相等判断) | ==、!= | 从左向右 |
| 10 | 位与 | & | 从左向右 |
| 11 | 位异或 | ^ | 从左向右 |
| 12 | 位或 | | | 从左向右 |
| 13 | 逻辑与 | && | 从左向右 |
| 14 | 逻辑或 | || | 从左向右 |
| 15 | 条件运算符 | ?: | 从右向左 |
| 16 | 赋值运算符(含复合赋值) | =、+=、-=、*=、/= 等 | 从右向左 |
| 17 | 逗号运算符 | , | 从左向右 |
算术运算符
| 运算符 | 功能 | 类型 | 注意事项 |
|---|---|---|---|
| + | 加法/正值 | 双目/单目 | 双目表示运算如:a+b/单目时表示+5 |
| - | 双法/负值 | 双目/单目 | 双目表示运算如:a-b/单目时表示:-5 |
| * | 乘法 | 双目 | |
| / | 除法 | 双目 | 除数不为0,整型相除结果为整型,如1/2=0(float变int小数部分省略) |
| % | 取余(模运算) | 双目 | 仅适用于整型:姐u哦符号与被除数一致 |
| ++ | 自增 | 单目 | 使变量值增1 |
| – | 自检 | 单目 | 使变量值减1 |

自增、自减运算符(++、–)
作用:使变量值增1或者减1,适用于算数类型(整型、字符型、浮点型等)的可修改左值,实践中常用整型的字符型变量。
核心区别:运算符位置决定变量值的更新时机。
++i/–i(前缀自增/自减)
运算规则:先更新变量值,后使用变量(==先计算,后使用==,自己计算,别人使用)
-
变量值自增1(i=i+1)或自减1(i=i-1)
-
使用更新后的变量值参与计算、赋值、比较等操作。
举例
int i = 1; //将常量1赋值给变量i
int x =++i;//等价于 i = i + 1 (i=2), x = i (x=2)
printf("i=%d,x=%d ",i, x);i = 2,x = 2
printf("i=%d,x=%d ", ++i, x);// i=3,x=2,i = i + 1 = 2 + 1 = 3, ++i 可以理解,int temp = ++i int a = 1;
printf("a=% ",++a,a);//a = 2
i++/i–(后缀自增/自减)
运算规则:先使用变量后更新变量(==先使用,后计算==)
-
步骤1:使用变量当前值参与赋值、计算、比较的等操作。
-
步骤2:变量值自增1(i=i+1)或自减(i=i-1)
举例:
int i = 1; // 变量i int x = i++;// 等价于 x = i (x=1),
i = i + 1 (i=2) printf("i=%d,x=%d ", i, x); // i=2,x=1
printf("i=%d,x=%d ", i++, x);// i=2,x=1, ++i 可以理解,
int temp = i++(先用旧值输出再变成新的)
int a = 1; printf("a=%d ", a++); // a=1
// 以上两行代码等价于
// int a = 1
// int temp = a++;
// printf("a=%d ", temp);
// a=1
总结与注意事项:
-
无论前缀还是后缀,变量本身的最终值都会增1或者减1,差异仅在于参与其他运算时使用的是更新前还是更新后的值。
-
==进制用于常量或者表达式:如–5(常量)、(i+j)++(表达式)、MAX_VAL++(宏定义)等都是错的==
-
避免复杂嵌套:如i++ + ++i - –i + i–属于未定义行为(UB),不同编译器可能给出不同结果,正式开发中严禁使用。
赋值运算符
基本赋值运算符(=)
作用:将右侧运算数(常量、变量、表达式)的值存入左侧变量的内存单元。
语法:变量 = 表达式
举例:
int a = 5; //将常量5赋值给a(初始化)
int b = a; //将变量a赋值给变量b
int c = a + b;//将表达式a+b的值赋值给变量c
a = a + 1; //将表达式a+1的值赋值给变量a(a的值增1),可以写作a++,++a
核心规则:
核心规则:
-
运算顺序:==自右向左==(先计算右侧表达式,再赋值给左侧变量)
-
左侧必须是可修改的变量(左值),不能是常量或表达式,如5 = a、a + b = 3均非法
-
==赋值表达式的值等于左侧变量的最终值==,如b = a = 5中,a = 5的值为5,再赋值给b,最终b = 5.
类型转换规则(赋值时)
若赋值运算符两侧类型不一致,会自动进行类型转换,转换规则如下:
| 源类型 | 目标类型 | 转换规则 | 举例 |
|---|---|---|---|
| 浮点型(double、float) | 整型(short、int、long) | 舍弃小数部分,仅保留整数部分 | int a = 5.9→ a 5 |
| 整型(short、int、long) | 浮点型(double、float) | 数值不变,以目标浮点型格式存储 | double b = 5 → b = 5.000000 |
| 字符型(char) | 整型(short、int、long) | 字符的ASCII码存入整型低8位,高位补0 | int c = ‘a’ → c = 65 |
| 长整型 | 短整型 | 截取低字节数据,可能导致数据溢出 | short d = 32768(int)→ 溢出 |
复合赋值运算符
是基本赋值运算符与算数运算符/位运算符的结合,主要是为了简化代码的书写。
语法:
变量 运算符 = 表达式;//等价于 变量 = 变量运算符(表达式)
注意:复合赋值运算符优先级低,右侧表达式会优先计算。
常见复合赋值运算符:
| 运算符 | 等价形式 | 举例 | 结果 |
|---|---|---|---|
| += | a = a + b | int a=1; a+=3; | a=4(1+3) |
| -= | a = a - b | int a=5; a-=2; | a=3(5-2) |
| *= | a = a * b | int a=3; a*=4; | a=12(3*4) |
| /= | a = a / b | int a=8; a/=2; | a=4(8/2) |
| %= | a = a % b | int a=7; a%=3; | a=1(7%3) |
| &= | a = a & b | int a=5; a&=6; | a=4(5&6) |
| |= | a = a | b | int a=5; a|=6; | a=7(5|6) |
| ^= | a = a ^ b | int a=5; a^=6; | a=3(5^6) |
| <<= | a = a << b | int a=2; a<<=1; | a=4(2<) |
| >>= | a = a >> b | int a=4; a>>=1; | a=2(4>>1) |
注意事项:
-
复合赋值运算符的优先级与基本赋值运算符相同(仅高于逗号运算符);
-
避免混淆=(赋值)和==(相等判断):a=5表示将 5 赋值给 a,a==5表示判断 a 是否等于 5;
-
表达式两侧若有复杂运算,需加括号明确优先级(如a += (b*c),而非a += b*c,虽结果一致但可读性更佳)。
关系运算符
作用:比较2个运算数的大小或相等关系,结果为布尔类型(C语言用0表示假,非0【除0外其他数】表示真,常用1表示真)。
常用关系运算符(均为双目【2个运算数】运算符):
| 运算符 | 功能 | 举例 | 结果(假设a = 5,b = 4) |
|---|---|---|---|
| > | 大于 | a > b | 1(true-真) |
| < | 小于 | a < b | 0(false-假) |
| >= | 大于等于 | a>=5 | 1(true-真) |
| <= | 小于等于 | b<=3 | 0(false-假) |
| == | 等于 | a==5 | 1(true-真) |
| != | 不等于 | a!=b | 1(true-真) |
注意:C标准给我们返回的真值为1,但使用时可以用任意非0作为真值。
运算数支持类型:
变量、常量(字面量(3、3.14)、符号常量(宏定义)、const修饰变量)、表达式,如:
a > b; //变量 vs 变量
5 > 6; //常量 vs 常量
(a + b)>(c * 2) //表达式 vs 表达式
//笼统讲,变量常量都可以看成表达式
关键注意事项:
1.避免链式比较:==C语言不推荐0 <= score <=100这类链式写法,逻辑上会报错。==
错误原因:编译器按左结合性依次计算,如0 <= score <=100,等价于(0 <= score )<=100,结果用于为真,无论score是否再0~100范围内。
2. 浮点型相等比较需要用插值法:由于浮点型存储精度限制(如1.1无法精确存储),直接用==判断相等会导致逻辑错误。
错误示例:
float a = 1.1f + 1.2f; // 2.300000
float b = 2.3f;
printf("a=%.20f,b=%.20f,%d ",a,b,a==b);// a=2.30000019073486328125,b=2.29999995231628417969,0
正确写法:判断两数差值的绝对值是否小于极小值(如1e-6,即0.000001),需引入math.h头文件使用fabs函数(取绝对值)
#include
// C语言数学库 #define EPS 1e-6 // 定义误差允许范围
if (fabs(a - b) < EPS) // 为什么要用绝对值函数,要求两个数相减的差值为正值 4 - 5 = |-1| = 1 5 - 4 = |1| = 1
{
// 两数相等 double a = 56, b = 99; if (fabs(a - b) < 100)
}
浮点型比较:
| 操作类型 | 正确方式 | 错位方式 |
|---|---|---|
| 浮点数相等比较 | fabs(a - b) < EPS(EPS=1e-6) | a == b |
| 浮点数大小比较 | 直接使用>、<、>=、<= | - |
| 浮点数零值比较 | fabs(a) < EPS | a == 0.0 |
逻辑运算符
作用:对布尔类型的运算数进行逻辑运算,结果仍然为布尔类型(0为假,非0为真)
常用逻辑运算符:
| 运算符 | 名称 | 类型 | 运算规则 | 短路效果 |
|---|---|---|---|---|
| ! | 逻辑非 | 单目 | 真变假,假变真 | 无(仅一个运算数) |
| && | 逻辑与 | 双目 | 全真则真,有假则假 | 左侧为假时右侧不执行,结果为假 |
| || | 逻辑或 | 双目 | 有真为真,全假为假 | 左侧为真时右侧不执行,结果为真 |
逻辑非!
-
仅作用于右侧运算数,优先级较高(高于与/或)
-
仅同一运算数去非奇数次,结果与原值相反;取非偶数次,结果与原值相同。
逻辑与&&
-
运算顺序:自左向右
-
短路效果:左侧运算数(表达式)为假,则右侧运算数不在执行(因最终结果必为假,无需计算右侧)。
逻辑或||
-
运算顺序:自左向右
-
短路效果:左侧运算数(表达式)为真,则右侧运算数不在执行(因最终结果必为真,无需计算右侧)。
短路效果的应用场景:
-
避免非法操作:如ptr != NULL && *ptr == 5(先判断指针非空,再访问指针指向的值,避免空指针异常);
-
优化性能:无需计算所有运算数即可确定结果时,短路效果可减少运算次数。
逗号运算符
作用:将多个表达式串联成1个表达式(又称作顺序求值运算符),按从左到右的顺序执行每个表达式。
语法:表达式1,表达式2,…表达式n
核心规则:
-
求值顺序:从左往右依次计算每个表达式的值
-
最终结果:==整个逗号表达式的值等于最后一个表达式的值。==
-
优先级:是所有运算符中,优先级最低的。需要注意括号的使用。
关键注意事项:
区分逗号表达式与逗号分隔符:变量声明(如int a=0, b=0)、函数参数(如printf("%d,%d", a, b))中的逗号是分隔符,而非逗号表达式;
括号的重要性:若省略括号,逗号运算符的低优先级会导致逻辑错误。
例如int result = a=3, b=5, a+b等价于(int result = a=3), b=5, a+b,result 的值为 3(而非 8)。如果表达式int result = (a=3, b=5, a+b),此时result的值为8。
位运算
作用:直接对数据的二进制位(bit)进行运算,常用于嵌入式开发、底层编程、数据加密等场景。
前提:参与位运算的操作数需先转换为二进制(signed类型按补码存储,unsigned类型按照原码存储)。
按位取反(~)
-
类型:单目运算符
-
规则:对数据的每一个二进制位取反(0→1,1→0)
-
注意:整数在内存中以补码形式存储,取反需要结合补码规则转换为十进制(如:~5 = -6)
演示:以8位二进制为例
| 十进制 | 二进制原码 | 按位取反 | 十进制结果 |
|---|---|---|---|
| 5 | 0000 0101 | 1111 1010 | -6 |
(按位取反后按补码转换为原码输出。)
按位与(&)
-
类型:双目运算符
-
规则:对应二进制位均为1时,结果为1;否则为0(有0则0),按补码计算后转为原码。
演示:以8位二进制数为例
| 十进制 | 二进制补码 | 运算 | 十进制 | 二进制补码 | 结果(二进制) | 结果(十进制) |
|---|---|---|---|---|---|---|
| 5 | 0000 0101 | & | 6 | 0000 0110 | 0000 0100 | 4 |
常用场景:
-
按位清零:将特定位设为0(如:num & 0xFFFFFFF将num的最低位设置为0,后续展开讲解)
-
提取特定位:获取数据的某几段(如:num & 0x0F提取num的低4位)
按位或(|)
-
类型:双目运算符
-
规则:对应二进制位有1个为1时,结果为1;否则为0(有1则1)。
演示:以8位二进制数为例
| 十进制 | 二进制补码 | 运算 | 十进制 | 二进制补码 | 结果(二进制) | 结果(十进制) |
|---|---|---|---|---|---|---|
| 5 | 0000 0101 | | | 6 | 0000 0110 | 0000 0111 | 7 |
常用场景:
-
按位置1:将特定位设为1(如:num | 0x01将num的最低为设为1)
-
合并数据:将两个数据的不同位合并为一个数据。
按位异或(^)
-
类型:双目运算符
-
规则:对应二进制位相同则为0,不同则为1(相同为0,不同为1)
演示:以8位二进制数为例
| 十进制 | 二进制补码 | 运算 | 十进制 | 二进制补码 | 结果(二进制) | 结果(十进制) |
|---|---|---|---|---|---|---|
| 5 | 0000 0101 | ^ | 6 | 0000 0110 | 0000 0011 | 3 |
常用场景:
-
数据交换:无需临时变量交换整数(a = a ^ b;b = a ^ b;a = a ^ b)
-
按位反正:将特定位翻转(0 → 1,1 → 0)
-
加密解密:与同一个密钥疑惑2次可以还原数据。
按位左移(<<)
说明:将原操作数的所有二进制位整体向左移动指定的位数。移位规则为”高舍低补“(高位溢出的二进制位直接丢弃,低位补0,不用纠结符号位).该规则与存储模式(大/小端)无关,仅取决于移位操作本身。
无符号左移
-
语法:
操作数 << 移动位数
-
核心公式:无溢出时结果等价于操作数移动位数操作数∗2移动位数(溢出后公式失效)
-
举例:
unsigned char a = 3 << 3;//结果为24(3*2^3=24)
unsigned char a = 5 << 4;//结果为80(5*2^4=80)
有符号左移
-
语法:同上
-
核心公式:同上
-
举例:
char a = -3 << 3;//结果为-24(-3*2^3=-24)
int b = 240 << 2;//结果为960(240*2^2=960)
-
注意:
-
有符号数的移位基于补码进行(运算过程:原→反→补→移位→反码→原码→结果)
-
若移位后符号位被覆盖(如正数左移溢出变为负数),公式不在适用,结果由补码规则决定。
按位右移(>>)
说明:将原操作数的所有二进制位整体向右移动指定的位数。移位规则位“高补低舍”(低位溢出的二进制位直接丢失,高位由操作数类型决定补值)
| 操作数 | 高位补值规则 | 移位类型 |
|---|---|---|
| 无符号数 | 补0 | 逻辑右移 |
| 有符号数 | 补符号位(1补1,0补0) | 算数右移 |
注意:大部分编译器(如GCC,Clang,MSVC)对有符号数均采用算数右移,仅极少数特殊平台例外。
语法:
操作数 >> 移动位数
核心公式:结果等价于操作数移动位数操作数/2移动位数(向下取整)
无符号右移
-
举例:
unsigned char a = 3 >> 3;//结果为0(3 / 2^3 = 0.375)向下取整为0
有符号右移
-
举例:
char a = -3 >> 3;//结果为-1(-3 / 2^3 = -0.375)向下取整为-1
移位运算完整流程:
原数据→二进制原码→二进制反码→二进制补码→执行移位操作→二进制反码(移位后补码逆运算)→二进制原码→目标进制结果









