总言
C语言:各种操作符的使用介绍。
文章目录
- 总言
- 1、算术操作符
- 2、移位操作符
- 2.1、整体介绍
- 2.2、左移操作符
- 2.3、右移操作符(逻辑右移、算术右移)
- 3、位操作符
- 3.1、整体介绍
- 3.2、演示实例
- 3.2.1、按位与
- 3.2.2、按位或
- 3.2.3、按位异或
- 3.2.4、按位异或的基本性质
- 3.3、相关练习
- 3.3.1、不能创建临时变量(第三个变量),实现两个数的交换。
- 3.3.2、求一个整数存储在内存中的二进制中1的个数。
- 4、赋值操作符
- 5、单目操作符
- 5.1、逻辑真假
- 5.2、sizeof运算符
- 5.3、按位取反
- 5.4、自增自减运算符
- 6、关系操作符、逻辑操作符
- 6.1、关系操作符
- 6.2、逻辑操作符
- 7、条件操作符、逗号表达式
- 7.1、条件操作符(三目操作符)
- 7.2、逗号表达式
- 8、下标引用、函数调用和结构成员
- 8.1、下标引用操作符
- 8.2、函数调用操作符
- 8.3、结构体成员访问
- 9、表达式求值
- 9.1、隐式类型转换
- 9.1.1、整型提升
- 9.1.1.1、实例演示一
- 9.1.1.2、实例演示二
- 9.1.1.3、演示实例三
- 9.1.2、算术转换
- 9.2、操作符的优先级
- 9.2.1、基础说明
- 9.2.2、一些问题:子表达式求值
1、算术操作符
1)、整体介绍
+ - * / %
1、 除了 %
操作符之外,其他的几个操作符可以作用于整数和浮点数。
2、对于 /
操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3、 %
操作符的两个操作数必须为整数。返回的是整除之后的余数。
2)、小数除法和整数除法
int main()
{
//整数除法:除法两端操作数皆为整型
int a = 3 / 2;
float b = 3 / 2;
printf("a=%d\nb=%f\n\n", a, b);
//小数除法:需要操作符有一端操作符为小数
float c = 3.0 / 2;
float d = 3 / 2.0;
printf("c=%f\nd=%f\n\n", c, d);
//强制类型转换
float e = (float)3 / 2;
printf("e=%f\n",e);
return 0;
}
3)、取模运算符两端操作数和范围说明
int a = 9 % 2 :
1、两端操作数需要为整型;
2、%n,其得到的取值范围为0~n-1;
2、移位操作符
2.1、整体介绍
1)、基本介绍
<< 左移操作符
>> 右移操作符
需要注意:
1、移位操作符的操作数只能是整数。
2、移位操作符作用在二进制位上。
2)、整数二进制
整数的二进制有三种表述形式:原码、反码、补码。
对正整数:原码、反码、补码相同;
对负整数:原码、反码、补码需要计算。
正整数二进制举例:
正整数,原反补相同。
对有符号位整型,其最高位是符号位,0表示整数、1表示负数。
int类型,在32位下为4个byte,即32个bite位。
int a = 5;
原码:00000000 00000000 00000000 00000101
反码:00000000 00000000 00000000 00000101
补码:00000000 00000000 00000000 00000101
负整数二进制举例:
对于负整数
反码:原码的符号位不变,其它位按位取反。
补码:反码+1即为补码。
int a = -5;
原码:10000000 00000000 00000000 00000101
反码:11111111 11111111 11111111 11111010
补码:11111111 11111111 11111111 11111011
其它一些问题说明
1、问题: 一个整型数据存入内存中,存放的是原码、反码、还是补码?
回答:整数在内存中存储的是补码(VS编译器在内存窗口中使用十六进制展示,实则依旧是二进制),运算也为补码,打印或使用时显示原码值。
2、说明: 移位操作符在不对自身赋值的情况下,不改变原来的变量。
int a = 5;
int b = a << 5;
在不自身赋值的情况下,a的值并没有因为使用移位操作符而改变。
3、注意: 对于移位运算符,不要移动负数位,这个是标准未定义的。
int num = 10;
num>>-1;//error
2.2、左移操作符
1)、左移操作符演示
规则说明:
左移操作符,左边抛弃,右边补0。
实例演示一:正数
相关代码如下:
int main()
{
int a = 5;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
分析:
左移操作符丢弃最高位,在有符号整型中,其丢弃的是最高位的符号位。
输出结果如下:
实例演示二:负数
相关代码如下:
int main()
{
int a = -5;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
分析:
需要注意:从补码回到原码,一种方法是,对补码-1,对反码非符号位按位取反。
另外一种方法是,对补码按位取反,对得到的结果再加1。
11111111 11111111 11111111 11110110//补码
10000000 00000000 00000000 00001001//补码非符号位按位取反
10000000 00000000 00000000 00001010//得到结果再+1
输出结果如下:
2.3、右移操作符(逻辑右移、算术右移)
1)、右移操作符演示
右移运算分两种,逻辑右移和算术右移。
对逻辑右移:左边用0填充,右边丢弃。
对算术右移:左边用原该值的符号位填充,右边丢弃。
使用右移操作符默认为哪种模式,取决于编译器,常见编译器一般为算术右移。(VS2019下也是默认算术右移)
2)、逻辑右移演示
实例演示一:正数
相关代码如下:
int main()
{
int a = 5;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
分析:
实例演示二:负数
相关代码如下:
int main()
{
int a = -5;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
分析:
3)、算术右移演示
实例演示一:正数
相关代码如下:
int main()
{
int a = 5;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
分析:对于正数,算术右移和逻辑右移结果一致。
实例演示二:负数
相关代码如下:
int main()
{
int a = -5;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
分析:
3、位操作符
3.1、整体介绍
1)、概述
& //按位与
| //按位或
^ //按位异或
需要注意:
1、位操作符的操作数只能是整数。
2、位操作符作用在二进制位上。即,按二进制位与&
,按二进制位或|
,按二进制位异或^
。
3、注意区分取地址操作符和按位与操作符。虽然符号一致,但含义不同。
&a;//取地址操作符,是单目操作符
a&b;//按位与操作符,双目操作符
a&&b;//逻辑与操作符
整体规则:
按位与:有0为0,全1为1
按位或:有1皆为1
按位异或:相同为0,相异为1
3.2、演示实例
3.2.1、按位与
相关规则:
按位与:对应的二进制位,有0为0,全1为1
相关代码如下:
int main()
{
int a = 3;
int b = -5;
int c = a & b;
printf("%d\n", c);
return 0;
}
分析:
int a = 3;
a为整型,其补码如下:
00000000 00000000 00000000 00000011
int b = -5;
b为整型,其补码如下:
10000000 00000000 00000000 00000101 原码
11111111 11111111 11111111 11111010 反码
11111111 11111111 11111111 11111011 补码
int c = a & b;
00000000 00000000 00000000 00000011 //3的补码
11111111 11111111 11111111 11111011 //-5的补码
------------------------------------
00000000 00000000 00000000 00000011 //得补码,高位为0是正数,原反补相同,打印结果为3
输出结果:
3.2.2、按位或
相关规则:
按位或:对应的二进制位,有1皆为1
相关代码如下:
int main()
{
int a = 3;
int b = -5;
int c = a | b;
printf("%d\n", c);
return 0;
}
分析:
int a = 3;
a为整型,其补码如下:
00000000 00000000 00000000 00000011
int b = -5;
b为整型,其补码如下:
10000000 00000000 00000000 00000101 原码
11111111 11111111 11111111 11111010 反码
11111111 11111111 11111111 11111011 补码
int c = a | b;
00000000 00000000 00000000 00000011 //3的补码
11111111 11111111 11111111 11111011 //-5的补码
------------------------------------
11111111 11111111 11111111 11111011 //得补码,高位为1是负数,打印要转换为原码
11111111 11111111 11111111 11111011 //补码
11111111 11111111 11111111 11111010 //反码
10000000 00000000 00000000 00000101 //原码,输出结果为-5
输出结果:
3.2.3、按位异或
相关规则:
按位异或:对应的二进制位,相同为0,相异为1
相关代码如下:
int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
printf("%d\n", c);
return 0;
}
分析:
int a = 3;
a为整型,其补码如下:
00000000 00000000 00000000 00000011
int b = -5;
b为整型,其补码如下:
10000000 00000000 00000000 00000101 原码
11111111 11111111 11111111 11111010 反码
11111111 11111111 11111111 11111011 补码
int c = a ^ b;
00000000 00000000 00000000 00000011 //3的补码
11111111 11111111 11111111 11111011 //-5的补码
------------------------------------
11111111 11111111 11111111 11111000 //得补码,高位为1是负数,打印要转换为原码
11111111 11111111 11111111 11111000 //补码
11111111 11111111 11111111 11110111 //反码
10000000 00000000 00000000 00001000 //原码,输出结果为-8
输出结果:
3.2.4、按位异或的基本性质
1)、按位异或的基本性质
1、交换律: A ^ B = B ^ A
2、结合律: A ^ (B ^ C) = (A ^ B) ^ C
3、恒等律: X ^ 0 = X
4、归零律: X ^ X = 0
5、自反: A ^ B ^ B = A ^ 0 = A
6、对于任意的X:X ^ (-1) = ~ X
7、如果 A ^ B = C 成立,那么 A ^ B = C ,B ^ C = A
2)、衍生小运用
一、
问题:用异或运算找出一串已知数字中没有重复的数。
例如:1、2、3、4、5、1、2、3、4
二、
问题:一个数组存放若干整数,一个数出现奇数次,其余数均出现偶数次,找出这个出现奇数次的数。
解法:将所有的数全部异或,得到的结果就是那个数。
三、
问题:1-1000放在含有1001个元素的数组中,只有唯一的一个元素重复,找出这个重复的数字。要求不能使用辅助存储空间并且数组的每个元素只能访问一次。
解法一:将这1001个元素加起来的和减去1+2+……+1000,所得的值就是重复的数字(数据过大容易溢出)
解法二:将所有的数全部异或,得到的结果与1 ^2 ^3 ^ …^1000的结果进行异或,得到的结果就是重复数。
四、
一个整形数组里除了两个数字之外,其它的数字都出现了两次。找到这两个只出现一次的数字。
解析:剑指offer,40
3.3、相关练习
3.3.1、不能创建临时变量(第三个变量),实现两个数的交换。
1)、通常情况下的两数交换
创建临时变量的方法,实例开发中,多用此方法。
int a = 3, b = 5;
int tmp = a;
a = b;
b = tmp;
printf("a=%d,b=%d\n", a, b);
2)、一种相对投机的写法
此方法只是单纯打印a、b,而非使得a、b内存储的数据发生交换
int a = 3, b = 5;
printf("a=%d,b=%d\n", a + b - a, a + b - b);
3)、常规方法一
此法面临的问题是,当a、b两数都很大时,相加得到的和可能会超出整型所表示的范围,即溢出。
int a = 3, b = 5;
a = a + b;
b = a - b;//(a+b)-b = a
a = a - b;//(a+b)-a = b
printf("a=%d,b=%d\n", a, b);
4)、改进方法二
此方法虽解决了上述问题,但也有其局限性,因为位操作符只能作用于整型变量。
int a = 3, b = 5;
a = a ^ b;
b = a ^ b;//(a^b) ^ b = a
a = a ^ b;//(a^b) ^ a = b
printf("a=%d,b=%d\n", a, b);
3.3.2、求一个整数存储在内存中的二进制中1的个数。
1)、方法一:位操作符+移位操作符结合
int main()
{
int num = 0;
scanf("%d", &num);
int count = 0;
for (int i = 0; i < 32; ++i)
{
if (1 == ((num >> i) & 1))
{
count++;
}
}
printf("%d\n", count);
return 0;
}
细节说明:
1、对于右移运算符有逻辑右移、算术右移,若是高位补1,会导致数据多出1,上述方法还奏效吗?
回答:可行。只要我们确定了机型,对应整型变量位数,那么for循环移位时并不会统计到因位运算生成的1。
4、赋值操作符
分为简单赋值操作符和复合赋值操作符,可连续赋值。
=
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
5、单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
5.1、逻辑真假
1)、概述
1、C语言中,0为假,非零为真。
2、C语言中,C99前没有表示真假的类型,C99中引入了布尔类型。布尔类型中,true为真,false为假。
#include<stdbool.h>
int main()
{
//写为_Bool和bool是一样的
_Bool flage1 = true;
bool flage2 = false;
return 0;
}
5.2、sizeof运算符
1)、sizeof运算符的使用
①sizeof
可与普通变量、变量类型结合,计算变量类型的长度,以字节为单位,注意:sizeof
不是函数!函数后的形式参数必须有括号,而sizeof后不一定需要有括号。
int main()
{
int a = 10;
printf("%d\n", sizeof(a));//可以填变量
printf("%d\n", sizeof(int));//也可以填变量的类型
printf("%d\n", sizeof a);//类型不能省略括号,变量可以
//printf("%d\n", sizeof int);//error
return 0;
}
②sizeof
与数组名结合,数组名非首元素地址的特殊形式之一,表示整个数组元素总长度,单位为字节。(前提条件为数组没有传参)
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));
问题:数组作为参数时,使用sizeof运算后的结果是什么?(见后续)
2)、sizeof运算符中表达式
如下:下述代码结果问什么?
int main()
{
int b = 10;
short c = 5;
printf("%d\n", sizeof(c = b + 2));
printf("%d\n", c);
return 0;
}
输出结果如下:
相关解释:
③sizeof
运算符中的表达式不参与计算。c = b + 2
,我们无需知晓c的具体计算结果,直接就能知道其为短整型,即大小可明确。
原因:从.c
文件到.exe
可执行程序,要经过编译、链接两步骤。sizeof(表达式)
在编译期间已经被处理了,即将sizeof(c = b + 2)
直接替换为2
(这是明确跑不掉的),故后期.exe运行时,printf
直接输出替换后的值。
3)、sizeof和数组
如下述代码,请问1、2、3、4其输出结果为?
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
输出结果如下:
相关解释:
1、sizeof内部单独放数组名,表示整个数组
2、数组传参时,传递的是首元素地址,形式参数接收到的实际上是指针,指针在不同机位上有固定的大小。32位机器上,有32bit位,而8个bit位1byte,则使用sizeof运算符求取数组首元素地址时,固定为4字节。
5.3、按位取反
1)、基本介绍
同位操作符一样,其作用于二进制位上,对每一个二进制位进行按位取反,0变1,1变0,包括符号位。
int main()
{
int a = 0;
printf("%d\n", ~a);
return 0;
}
00000000 00000000 00000000 00000000 //a
11111111 11111111 11111111 11111111 //~a(补码)
11111111 11111111 11111111 11111110 //~a反码
10000000 00000000 00000000 00000001 //~a原码:-1
2)、作用演示
m
问题引入1:如何将整数11的二进制低位第三个0变为1?
对11:
00000000 00000000 00000000 00001011
要求:将其变为如下
00000000 00000000 00000000 00001111
解决方案如下:
00000000 00000000 00000000 00001011 //11
00000000 00000000 00000000 00000100 //进行按位或
00000000 00000000 00000000 00001111 //就可以得到结果
int a = 11;
int b = a | (1 << 2);
问题引入2:那么,如何再将得到的值返回为原先的11呢?
解决方案如下:
00000000 00000000 00000000 00001111 //15
11111111 11111111 11111111 11111011 //得到这串数值,与15进行按位与
00000000 00000000 00000000 00001011 //就能返回到11
问题,11111111 11111111 11111111 11111011
如何得到?
11111111 11111111 11111111 11111011
00000000 00000000 00000000 00000100 //按位取反后
int a = 11;
int b = a | (1 << 2);
int c = b & (~(1 << 2));
演示结果如下:
3)、解释多组输入时的while(~scanf(“%d”,&n))
while(~scanf("%d",&n))
while(scanf("%d",&n) != EOF)
scanf
读取失败时,会返回EOF
,EOF
被定义为-1
,~(-1)
即0
,则while
循环读取结束。
5.4、自增自减运算符
#include <stdio.h>
int main()
{
int a = 10;
int x = ++a;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
int y = --a;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
return 0;
}
#include <stdio.h>
int main()
{
int a = 10;
int x = a++;
//先对a先使用,再增加,这样x的值是10;之后a变成11;
int y = a--;
//先对a先使用,再自减,这样y的值是11;之后a变成10;
return 0;
}
6、关系操作符、逻辑操作符
6.1、关系操作符
1)、基本介绍
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
注意事项:
1、注意不要将==
和=
混淆写错,导致不必要的错误。
2、不能用关系运算符来判断字符串是否相等,关于字符串是否相等有专门的函数,即strcmp
,但其规律也不是简单的比较字符串长度。
if ("abcdef" == "abcd")//error
{
//这种写法比较的是这两个字符串首字符的地址
}
6.2、逻辑操作符
1)、基本介绍
&& 逻辑与
|| 逻辑或
2)、逻辑操作符的短路现象演示例题
程序输出的结果是什么?
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;//(一)
//i = a++||++b||d++;//(二)
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
printf(" i = %d\n", i);
return 0;
}
对于&&
:左边为假,则得假,故右边不必执行
对于||
:左边为正,则得真,故右边不必执行
7、条件操作符、逗号表达式
7.1、条件操作符(三目操作符)
1)、基本说明
exp1 ? exp2 : exp3
2)、演示实例
演示一:下述代码如何转换成条件表达式?
if (a > 5)
b = 3;
else
b = -3;
b = (a > 5 ? 3 : -3);
演示二:使用条件表达式实现找两个数中较大值
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", (a > b ? a : b));
7.2、逗号表达式
1)、基本说明
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。其从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
2)、演示实例
演示一:以下结果输出为?
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf(" a=%d\n b=%d\n c=%d\n", a, b, c);
return 0;
}
演示二:
if (a = b + 1, c = a / 2, d > 0)
上述这个if
语句,其起到判断作用的是最后一句d > 0
,但前面的a = b + 1, c = a / 2,
也会依次计算执行,若其中d
参与运算,那么会影响后续d
值。
演示三:
如下,有一段代码,其处理逻辑是①a = get_val();
②count_val(a);
③a > 0
。重复上述操作,直到满足循环结束条件。
a = get_val();
count_val(a);
while (a > 0)
{ //业务处理
a = get_val();
count_val(a);
}
如果使用逗号表达式,可将其修改为:
while (a = get_val(), count_val(a), a > 0)
{
//业务处理
}
8、下标引用、函数调用和结构成员
8.1、下标引用操作符
1)、概述
基础说明:
[ ]
操作数:一个数组名 + 一个索引值
例子如下:
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
2)、下标引用操作符:关于数组的一些小扩展
arr[i]
:对数组使用下标引用操作符,操作数为数组名arr、索引值i
arr[i]
等价于*(arr+i)
:以i=7为例,arr为首元素地址,对编译器而言,其首先把arr[7]
处理成*(arr+7)
,以这种指针的形式再进行运算。由于加法支持交换律,则上述表达式也可写成*(7+arr)
即7[arr]
的形式,其代表的含义和arr[7]
一致。
即printf("%d %d",arr[7],7[arr]);
得到效果一致。(打印地址也一样)
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%d\n", arr[7]);
printf("%d\n", *(arr + 7));
printf("%d\n", 7[arr]);
return 0;
}
8.2、函数调用操作符
1)、概述
()
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
思考问题:函数调用操作符的操作数至少有几个?
回答:一个,即函数名。
例子:
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello world.");//实用()作为函数调用操作符。
return 0;
}
8.3、结构体成员访问
1)、概述
. 结构体变量.结构体成员
-> 结构体指针->结构体成员
例子演示:
#include <stdio.h>
struct Stu
{
char name[10];
int age;
double score;
};
void print1(struct Stu ss)//如果是结构体变量,就可以使用.操作符访问
{
printf("name:%s age:%d score:%.2lf\n", ss.name, ss.age, ss.score);
}
void print2(struct Stu* ps)//如果是结构体指针,可以使用->操作符访问
{
printf("name:%s age:%d score:%.2lf\n", ps->name, ps->age, ps->score);
}
int main()
{
struct Stu s = { "王朝",20,93.5f };
print1(s);
print2(&s);
return 0;
}
注意事项:
1、上述结构体中,不能直接修改结构体成员的名字s.name = "马汉";
,因为name
是数组名,非特例下,数组名是数组首元素地址,不能任意修改。若要修改结构体成员名称,需要使用字符串复制的函数strcpy
。
struct Stu s = { "王朝",20,93.5f };
print1(s);
//s.name = "马汉";//error
strcpy(s.name, "马汉");
print1(s);
scanf("%s", s.name);//使用scanf也行,s.name得到的是数组名
print1(s);
2、->
和*
的关系:ps->name
,也可以写成(*ps).name
。但在上述举例的结构体中,仍然不能用(*ps).name=“马汉”
该种方法修改数组名,因为对name
解引用后只访问了一个字节,而此处人物名称不只是一个字节。
9、表达式求值
9.1、隐式类型转换
9.1.1、整型提升
1)、是什么和为什么
是什么: 表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
为什么:
9.1.1.1、实例演示一
相关代码如下:
int main()
{
char c1 = 3;
char c2 = 127;
char c3 = c1 + c2;
printf("%d\n", c3);
return 0;
}
说明:如上述代码,c1、c2
的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于c3
中。
如何进行整型提升?
1、整型提升是按照变量的数据类型的符号位来提升的。
2、有符号整型:对负数整型提升,高位补充符号位,即1,对整数整型提升,高位补充符号位,即0。
3、无符号整型:整型提升,高位补0。
上述代码分析如下:
char c1 = 3;
Ⅰ、3是整型,32位下如下:
00000000 00000000 00000000 00000011 (原反补相同)
Ⅱ、存放入char类型的变量中,发生截断:
c1 = 00000011
char c2 = 127;
Ⅰ、同理,127是整型,32位下如下:
00000000 00000000 00000000 01111111 (原反补相同)
Ⅱ、存放入char类型的变量中,发生截断:
c2 = 01111111
char c3 = c1 + c2;
Ⅰ、c1 + c2要进行算术运算,需要提升为普通整型。按规则,二者结果如下(有符号char,正数,高位补0)
00000000 00000000 00000000 00000011 (c1)
00000000 00000000 00000000 01111111 (c2)
00000000 00000000 00000000 10000010 (c1+c2)
Ⅱ、c1+c2计算完的结果要存储到char类型的C3中,发生截断:
c3 = 10000010
printf("%d\n", c3);
Ⅰ、printf要打印c3,同理需要提升为普通整型,且打印的是原码(有符号char,高位负数补1)
11111111 11111111 1111111 10000010 (补码)
11111111 11111111 1111111 10000001 (反码)
10000000 00000000 0000000 01111110 (原码)
最终结果为-126
输出结果如下:
9.1.1.2、实例演示二
相关代码如下:
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
此题快速判断的方法:b6
,b
转换为二进制是1011
。char、short
:a、b需要整型提升,有符号整型,高位补符号位1。补码到原码符号位不变,仍旧是1,故参与比较时,a、b为负数,而判等运算符右侧的数值为正数,故if语句对a、b不执行。
9.1.1.3、演示实例三
相关代码如下:
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
分析:c
只要参与表达式运算,就会发生整形提升,表达式 +c
发生提升,所以sizeof(+c)
是4个字节。表达式 -c
也会发生整形提升,所以sizeof(-c)
是4个字节,但是 sizeof(c)
是1个字节。
9.1.2、算术转换
1)、是什么
概念说明:如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
以下为寻常算术转换:
long double
double
float
unsigned long int
long int
unsigned int
int
注意事项:
1、算术转换要具有合理性,否则会有一些潜在的问题。
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
2、算术转换也是隐式类型转换中的一种。
9.2、操作符的优先级
9.2.1、基础说明
1)、基础说明
复杂表达式的求值有三个影响的因素。
1、操作符的优先级
2、操作符的结合性
3、是否控制求值顺序
注意:
1、相邻的操作符才讨论优先级。
2、两个相邻的操作符谁先执行?①取决于他们的优先级。②如果两者的优先级相同,取决于他们的结合性。
3、关于操作符优先级此处不做详细说明,这里附上一个查看链接:运算符的优先级
9.2.2、一些问题:子表达式求值
1)、实例演示一
//表达式的求值部分由操作符的优先级决定。
a*b + c*d + e*f
问题分析:上述代码在计算的时候,由于*
比+
的优先级高,只能保证*
的计算是比+
早,但是优先级并不能决定第三个*
比第一个+
早执行。
所以表达式的计算机顺序就可能是:
1、a*b
2、c*d
3、a*b + c*d
4、e*f
5、a*b + c*d + e*f
1、a*b
2、c*d
3、e*f
4、a*b + c*d
5、a*b + c*d + e*f
将其延伸,假如此处的abcdef
不是单纯的变量,而是许多表达式呢?那么其执行顺序会导致结果未定义。
2)、实例演示二
c + --c;
问题分析:同上,操作符的优先级只能决定自减--
的运算在+
的运算的前面,但是我们并没有办法得知+
操作符的左操作数的获取,在右操作数之前还是求值之后,所以结果是不可预测的。
3)、实例演示三
//非法表达式《C和指针》
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
不同编译器下的结果:
4)、实例演示四
int fun()
{
static int count = 1;//静态变量
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);//输出多少?
return 0;
}
问题分析:上述代码 answer = fun() - fun() * fun();
,我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。
5)、实例演示五
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
问题分析:这段代码中的第一个 +
在执行的时候,第三个++
是否执行是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 +
和第三个前置 ++
的先后顺序。