整型、浮点型、字符型数据可以进行混合运算,如:
10 - 'a' * 1.5
= 10 - 97 * 1.5 // 保证参与运算的都是数字
= 10.0 - 97.0 * 1.5 // 不同数据类型可以参与运算,编译器会自动将其转换为同一数据类型后再运算(隐式类型转换)
解释:整型、浮点型、字符型之间都可以参与混合运算,是因为它们都是数值型,字符是特殊的数值型(字符参与数值计算使用的是ASCII码==(0~127)==)
运算时,参与运算的两个运算数如果类型不同,则首先将其类型转换为一致再运算。转换规则分为隐式转换和显示转换。
将低等级类型自动提升为高等级类型,又被称作自动类型转换。是由编译系统在控制类型转换。
语法:
高等级类型 变量名 = 低等级类型变量;
转换关系:
① (低等级类型)int → unsigned int → long → double(高等级类型)
注意:int 与 unsigned int 的转换顺序可能因为平台(如位数)和编译器不同而有差
异,需结合具体环境判断。
② (低等级类型)char /short → int(高等级类型)
③ (低等级类型)float → double(高等级类型)
注意:
在混合运算过程中,系统所进行的类型转换并不会改变原数据的类型(不会改变原数据在内存的存储),只是在运算过程中将其值变成同类型后运算,举例:
int a = 10; // a 是 int
double c = a + 22.5;// 此时 a在参与运算的时候临时转换为double类型,就变成了 double c = 10.0 + 22.5
a = 21; // a 是 int
float c2 = a + 21.5f; // 此时 a在参与运算的时候临时转换为float类型,就变成了 float c2 = 21.0f + 21.5f
a = 10; // a 是 int
需要我们手动指定转换类型,又被称作强制类型转换。是由程序员自己来控制转换。
语法:
(type)(表达式)
举例:
举例:
double a = 2, b = 3; // a,b都是double类型
double c = (int)a + b;// 首先将a转换为int类型,此时为显示转换;然后混合运算的时候a转换为double类型,此时隐式转换
double num1 = 12.55; // num1是double类型
int num2 = (int)num1; // num1原本是double类型,在赋值运算时,显示转换为int类型
printf("%d\n", num2); // 12 注意:小数转整数,会舍弃掉小数部分,保留整数部分
num1 = 15; // 此时num1依然是double
注意:类型转换只是发生在程序运行过程中,并不会改变其在内存中的存储形式。在强制类型转换的过程中,并不改变原变量的类型,只是在运算过程中将其值转换类型后再运算。
需求:强制类型转换案例
代码:
#include
int main(int argc,char *argv[])
{
float x;
int i;
x = 3.6f; // float类型的变量,如果赋值是同类型常量,常量需要跟上F/f
i = (int)x;// 将x在程序运行阶段,强制转换为int类型
printf("x=%f,i=%d\n",x,i); // x=3.600000,i=3 浮点型数据转换为整型,会丢失小数部分
return 0;
}
说明:x仍然为float类型,可见强制类型转换并不会改变变量原本的类型。
序号 | 名称 | 符号 | 序号 | 名称 | 符号 |
---|---|---|---|---|---|
1 | 算术运算符 | + - * / % ++ – | 8 | 指针运算符 | * & |
2 | 关系运算符 | > < >= <= == != | 9 | 字节数运算符 | sizeof |
3 | 逻辑运算符 | && || ! | 10 | 下标运算符 | [] |
4 | 位运算符 | << >> ~ | ^ & | 11 | 强制类型转换运算符 | (type) |
5 | 赋值运算符 | = += -= *= /= %= | 12 | 分量运算符 | . -> |
6 | 条件运算符 | ?: | 13 | 其他 | 函数调用运算符() |
7 | 逗号运算符 | , |
所谓表达式就是将操作对象用运算符连接起来的符合C语法规则的式子。(表达式 = 运算数 + 运算符)
序号 | 名称 | 举例 | |
---|---|---|---|
1 | 算术表达式 | 2 + 6.7 * 3.5 | 计算结果是数值类型 |
2 | 关系表达式 | x > 0, y < z + 6 | 计算结果是布尔类型,0-假 和 非0-真 |
3 | 逻辑表达式 | x > 0 && y > 0 | 计算结果是布尔类型,0-假 和 非0-真 |
4 | 赋值表达式 | a = 5.6, sum += i | 规则:由右往左 |
5 | 逗号表达式 | x = 3,y+=4,z-=8 | 分隔,并列,计算结果就是最后一个表达式的值 |
C语言规定了运算符的优先级和结核性。在表达式求值时,按运算符的优先级的高低次序执行。如果运算符对象两侧的运算符优先级相同,如a +b +c,则按照规定的“结合方向”处理。
+,-
正负值运算符(单目/一元运算符:只有一个运算数),举例:
+5 // 正5
5 // 正5
-5 // 负5
+,-,*,/,%
加减乘除取余运算符(双目/二元运算符:有两个运算数)注意:除法运算中,除数不能为0
这些算数运算符的运算顺序与数学上的运算顺序是相同。*,/,%
的优先级高于+,-
**定义:**用算数运算符和括号将运算对象链接起来,符合C语言规范的算式,例如:
a * b / c - 1.5 + 'A'
表达式中各种运算符的运算顺序,必要时需要添加括号,例如:
((a+b) / (c+d)) != (a+b / c+d)
表达式中各种运算对象的数据类型,特别是整型相除,**C语言规定,两个整型相除,其结果仍然为整型。**例如:
7/6 值 1
4/7 值 0
1/2 值 0
面试题:
1. 1/2 + 1/2的结果是多少? 正确答案:0
2. 1.0/2 + 1.0/2的结果是多少? 正确答案:1
**定义:**在表达式求解的时候,先按运算符的优先级别的高低次序执行。若一个运算对象两侧的运算符的优先级相同,则按规定的结合方向处理。
① 算数运算符的结合方向:自左向右,也就是运算对象先与左边的运算符结合,例如:
a - b + c; // 先计算 a - b,然后用其减出来的结果 + c
② 特殊运算符的结合方向:自右向左,也就是运算对象先与右边的运算符结合,例如:
i++;
③ 若一个运算符两侧的数据类型不同,会自动转换为同类型后计算。
12 + 12.5 + 'A'
= 12.0 + 12.5 + 'A'
= 24.5 + 65
= 24.5 + 65.0
= 89.5
**作用:**使变量的值增1或者减1
结合方向:自左向右 / 自右向左
结合方向:自左向右
表示在使用该运算符对象之前,先让运算数i
自身增1或者减1,然后再使用它,也就是使用增1或者减1后的值。
先计算,后使用
说明:
① 先计算:先让计算数 i = i + 1
② 后使用:将计算后的运算数进行赋值、比较等操作
举例:
int i = 1; // i = 1
int x = ++i;// ++i可以看做是一个没有名字的新变量,
// 上面两行代码等价于:
// int i = 1;
// i = i + 1;
// int x = i;
printf("i=%d,x=%d\n",i,x); // i=2,x=2
printf("i=%d,x=%d\n",++i,x);// i=3,x=2
int a = 1;
printf("a=%d\n",++a); // a=2
结合方向:自右向左
表示在使用该运算符对象之后,先使用它,然后再让运算数自身增1或者减1,也就是使用增1或者减1前的值。
先使用,后计算
说明:
① 先使用:将计算前的运算数进行赋值、比较等操作
② 后计算:再让计算数 i = i + 1
举例:
int i = 1; // i = 1
int x = i++;// i++可以看做是一个没有名字的新变量,
// 上面两行代码等价于:
// int i = 1;
// int x = i;
// i = i + 1;
printf("i=%d,x=%d\n",i,x); // i=2,x=1
printf("i=%d,x=%d\n",i++,x);// i=2,x=1
int a = 1;
printf("a=%d\n",a++); // a=1
注意:
①
++
与--
运算符只适用于整型变量或者字符型变量,而不能用于其他类型的变量,如:int i = 0; i++; // i = 1, (i++) = 0 ++i; // i = 2, (++i) = 2 char a = 'A'; // 65 a++; // a = 66, (a++) = 65
②
++
与--
运算符不能用于常量或者表达式,如:--5; // 非法的 不能用于常量 int i = 0, j = 0; (i+j)++; // 非法的 不能用于表达式 #define MAX_VAL 1 NAX_VAL++; // 非法的 不能用于常量
// 第1题:
int i = 1;
int n = i++ + ++i - --i + i--;
// 分解步骤:
// 1. i++ → 1(i=2)
// 2. ++i → 3(i=3)
// 3. --i → 2(i=2)
// 4. i-- → 2(i=1)
// 结果:1 + 3 - 2 + 2 = 4
// 第2题:
int i = 10;
int j = 5;
int k = i++ + ++i - --j - i--;
// 分解步骤:
// 1. i++ → 10(i=11)
// 2. ++i → 12(i=12)
// 3. --j → 4 (j=4)
// 4. i-- → 12 (i=11)
// k = 10 + 12 - 4 - 12 = 6
注意:正式的编程中,切记不能有上面的写法,因为这种代码有风险,属于未定义行为(UB)
=
称之为赋值运算符,其作用是将一个数据赋值给一个变量,如:
int a = 5; // 将常量赋值给变量
int b = a; // 将变量赋值给变量
int c = a + b; // 将表达式赋值给变量
执行赋值运算的结果,是将右边的数据存入到左边变量对应的内存单元,赋值运算的顺序:自右向左
如果赋值运算符两侧的类型不一致,则在赋值时进行类型转换,转换规则:
浮点型 → 整型变量:舍弃小数部分。如:int a = 5.5 → 5;
整型 → 浮点型变量:数值不变,以浮点型存储。如:double a = 5 → 5.000000...
字符型 → 整型变量:放在整型的低8位,保持原值不变规则。如:int a = 'A'
实现赋值运算的表达式,由赋值运算符+运算数构成。
语法:
变量 = 表达式;
案例:
a = 5; // 将字面量赋值给变量(字面量就是常量的一种)
y = 2 * x + 3;// 将表达式赋值给变量
a = a + 1; // 将表达式赋值给变量
作用:
将右边表达式的值赋值给左边的变量。赋值表达式的取值取自左边变量的值。(赋值表达式的值就是变量的值)
什么是表达式:
表达式 = 运算数 + 运算符
什么是运算数:
常量、变量、表达式都可以是运算数。
说明:+=,-=,*=,/=,%=....&=,|=,>>=,<<=,^=
左右两侧操作完毕后,赋值给左侧的变量,例如:
int a = 1;
a += 3; // 等价与 a = a + 3
a -= 3; // 等价与 a = a - 3
a *= 3; // 等价与 a = a * 3
...
a = a + 1
如何表示?①
a++
或者++a
②
a+=1
注意:
① 在运算时:不能将(=)写作(==)
② 赋值运算符的优先级属于最低(除了逗号运算符以外),一般都是留到最后运算
说明:>,<,>=,<=,==,!=
所有的关系运算符都是双目运算符(二元运算符),运算符的左侧和右侧可以是变量、常量(字面量、符号常量、使用const修饰的变量)、还可以是表达式,举例:
a > b; // 变量
5 > 6; // 常量
a+b>c; // 表达式
关系运算符运算的结果是布尔类型,C语言中实际上没有布尔类型,我们用整型的0和非0来表示成立(真)或者不成立(假)。
int a = 5, b = 4;
printf("%d,%d\n", a > b, a + b > 10);// 1,0 注意:计算机给我们返回的真-1,假-0
C99标准引入stdbool.h
,本质上是对0和1进行了封装。其实很简单,就是定义了两个符号常量:
#define true 1
#define false 0
注意:
避免链式调用(如:0 <= score <= 100
,应改为逻辑与:score >= 0 && score <= 100
)
在C语言中,上面的链式写法并不会编译报错,为什么不能用链式调用?请看下面例子:
int score1 = 45, score2 = -65, score3 = 110;
// 预测:0 <= 45 <= 100 结果为1
printf("%d\n",0 <= score1 <= 100);//实际结果:0 <= score1 返回1,1 <= 100返回1,最终结果1,成立
// 预测:0 <= -65 <= 100 结果为0
printf("%d\n",0 <= score2 <= 100);//实际结果:0 <= score2 返回0,0 <= 100返回1,最终结果1,不成立
// 预测:0 <= 110 <= 100 结果为0
printf("%d\n",0 <= score3 <= 100);//实际结果:0 <= score3 返回1,1 <= 100返回1,最终结果1,不成立
经过以上测试,我们发现链式比较语法上没问题,可以通过编译,但是逻辑上有问题,所以不能用作条件判断。
浮点型比较需要用差值法:fabs(a-b)<1e-6
。使用fabs取绝对值函数,需要引入math.h
在C语言中,为什么浮点型不用==
符号做等值判断?请看下面例子:
#include
int main(int argc,char *argv[])
{
float a = 1.1f + 1.2f; // a 预测结果:2.3f
float b = 2.3f; // b 预测结果:2.3f
printf("1.1f + 1.2f = %.20f\n",a); // %.20f 意思是保留小数点后20位
printf(" 2.3f = %.20f\n",b);
double a1 = 1.1 * 2;
printf("%d\n",a1 == 2.2);
return 0;
}
运行结果:
测试发现:跟预期结果有出入,所以不能使用==
总结:
操作 | 正确方式 | 错误方式 |
---|---|---|
浮点数相等比较 | 使用误差范围(fabs(a - b) < epsilon ) |
a == b |
浮点数大小比较 | 直接使用 > 或 < (误差不影响顺序) |
- |
零比较 | fabs(a) < epsilon |
a == 0.0 |
运算的结果为布尔值,要么为真-非0
,要么为假-0
!
:==非(逻辑非)==单目运算符,并且只能在操作数的左侧;非真即为假,非假即为真。(取反)
!(a % 2 != 0)
)是1次取非,结果:a%2==0
!!(a % 2 == 0)
)是2次取非,结果:a%2==0
&&
:==与(逻辑与)==双目运算符,当左右两侧的数据都为真时,最终的结果才为真(有假则为假)
当逻辑与运算时,左侧为假,右侧结果不会影响最终结果,右侧根本不会执行,最终的结果就是左侧的结果(假),这种现象称之为短路效果(短路与),这是C语言中提供的一种惰性计算,就是为了减少运算次数。
案例:
// 需求:要求成绩在0~100以内
int score = 90;
printf("%d\n", score >= 0 && score <= 100); // 1
||
:==或(逻辑或)==双目运算符,当左右测数据都为假时,最终的结果才为假(有真则为真)
当逻辑或运算时,左侧为真,右侧结果不会影响最终结果,右侧根本不会执行,最终的结果就是左侧的结果(真),这种现象称之为短路效果(短路或),这是C语言中提供的一种惰性计算,就是为了减少运算次数。
案例:
// 需求:闰年计算公式
int year = 2025;
printf("%d\n", ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0));
**作用:**将若干个表达式“串联起来”,如:a = 4, a+=3;
**别称:**顺序求值运算符
语法:
表达式1,表达式2,...表达式n;
**求解过程:**按从左到右的顺序分别计算各个表达式的值,其中最后一个表达式n的值就是整个逗号表达式的值。
案例:
#include
int main()
{
int a = 0, b = 0; // 这不是逗号表达式,这里称作逗号分隔符。
// 逗号表达式
int result = (a = 3, b = 5, a + b);// 8 整体是赋值表达式,=右侧是逗号表达式
// 怎么判断一个表达式是逗号表达式还是赋值表达式,要看它最终的运算是赋值操作还是逗号操作
// 结合条件判断
int x = 10, y = 20;
int max = (x++, y++, (x > y) ? x : y);// x=10, y=20, max = 21
printf("result=%d, max=%d\n", result, max);// result=8, max=21
return 0;
}
**说明:**按位(bit)来进行运算操作的运算符,更多时候用于定制化应用开发和嵌入式开发。
语法:~、&、|、<<、>>
说明:单目运算符,数据的每一个bit位按位取反,也就是二进制数据位上,0变1,1变0
演示:
举例:
printf("%d\n",~5);
说明:双目运算符,对于运算符左右的两个数,对应的二进制位数据都为1时,结果为1,否则为0
演示:
举例:
printf("%d\n", 5 & 6); // 4
说明:双目运算符,对于运算符左右的两个数据,对应的二进制位数据有一个为1,结果为1,否则为0
演示:
举例:
printf("%d\n",5 | 6);// 7
说明:双目运算符,对于运算符左右的两个数据,对应二进制位数据相同,结果为0,不同为1
演示:
举例:
printf("%d\n",5 ^ 6);// 3
`
说明:单目运算符,数据的每一个bit位按位取反,也就是二进制数据位上,0变1,1变0
演示:
[外链图片转存中…(img-ewAt2WuF-1752147662898)]
举例:
printf("%d\n",~5);
说明:双目运算符,对于运算符左右的两个数,对应的二进制位数据都为1时,结果为1,否则为0
演示:
[外链图片转存中…(img-ZQzrAIqp-1752147662898)]
举例:
printf("%d\n", 5 & 6); // 4
说明:双目运算符,对于运算符左右的两个数据,对应的二进制位数据有一个为1,结果为1,否则为0
演示:
[外链图片转存中…(img-yotkMprQ-1752147662898)]
举例:
printf("%d\n",5 | 6);// 7
说明:双目运算符,对于运算符左右的两个数据,对应二进制位数据相同,结果为0,不同为1
演示:
[外链图片转存中…(img-iqK5s0vZ-1752147662898)]
举例:
printf("%d\n",5 ^ 6);// 3