前言
别把生活过的一团糟
详解C的操作符
操作符
1.算数操作符
• +
• -
• *
• /
• %(取模/取余)
如何用 / 得到一个浮点数?
- 变量类型:浮点数类型
- / 两边的操作数至少一个浮点数
#include<stdio.h>
int main()
{
float a = 5 / 2.0;
printf("%d", a);
}
取模操作符 % 两边的操作数必须都为整型
2.移位操作符
• <<
• >>(移动二进制位)
基本概念理解
-
按二进制位取反
-
整数的二进制表示形式(三种)
-
整数在内存中的存储
代码理解
<< 左移操作符:左边丢弃,右边补0
#include<stdio.h>
int main()
{
int a = 2;
int b = a << 1;//左移操作符
printf("%d\n", b); //b = ?
return 0;
}
分析:
1、 局部变量 a 的类型 - int
2、 int类型所占空间大小 - 4byte == 32bit
3、 2 的二进制表示
>> 右移操作符:通常采用算数右移
1) 算数右移: 右边丢弃,左边补原符号位(0 / 1)
2) 逻辑右移: 右边丢弃,左边补0
#include<stdio.h>
int main
{
int a = -1;
int b = a >> 1;//右移操作符
printf("%d\n", b); //b = ?
return 0;
}
分析:
注意:
i. << 和 >> 并不改变 a 的值
ii. 只是把 a >> 1的结果存入变量 b 中
3.位操作符
按二进制位
• &
• |
• ^
代码理解
& 按位与:真真 - 真,假假 - 假,真假 - 假
1 1 - 1, 0 0 - 0, 1 0 - 0
| 按位或: 真真- 真, 假假 - 假,真假 - 真,
1 1 - 1, 0 0 - 0, 1 0 - 1
int main()
{
int a = 3;
int b = 5;
int c = a & b;
int d = a | b;
//a:00000000000000000000000000000011 - 3
//b:00000000000000000000000000000101 - 5
//c:00000000000000000000000000000001 - 1
//d:00000000000000000000000000000111 - 7
return 0;
}
^ 按位异或:规则:相同取0 , 不同取1
性质:a ^ a = 0 (相同取0)
0 ^ a = a (不同取1) - 是1的地方还是1 - 不变
交换两个变量的值,不使用第三个变量
#include<stdio.h>
//交换两个变量的值,不使用第三个变量
int main()
{
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;//(3 ^ 5 ^ 5 == 3 ^ 0 == 3) - 成功交换
//此时,b == 3; a == 3 ^ 5;
a = a ^ b;//(3 ^ 5 ^ 3 == 5 ^ 0 == 5)
printf("a = %d, b = %d", a , b);
}
4.赋值操作符
• =
• +=
• -=
• *=
• /=
• &=
• |=
• ^=
•>>=
代码理解
复合赋值符的含义
int main()
{
int a = 0;
a += 1;
a = a + 1;//语义相同
return 0;
}
连续赋值
int main()
{
int a = 0, x = 1, y = 2;
a = x = y + 1;//连续赋值(不推荐)
//理解(同语义)
x = y + 1;
a = x;// 这样写更清晰,易于调试
return 0;
}
= 赋值
== 判断相等
比较两个字符串时不能用 ==
5.单目操作符
只有一个操作数
• !
• -
• +
• &
• Sizeof
• ~
• - -
• ++
• *
• (类型)
代码理解
!逻辑反操作
#include<stddio.h>
int main()
{
//规定:0为假,非 0 为真
int a = 0;
printf("a = %d", !a);//打印出 a = 1
if (a)
{
printf("不打印\n");
}
if (!a)
{
printf("打印\n");
}
return 0;
}
– 负值
+ 正值
& 取地址
* 间接访问操作符 (解引用操作符)
(类型) 强制类型转换
#include<stdio.h>
int main()
{
int a = 0;
a = +5;//一般 + 省略
a = -5;//表负值
int* pa = &a;//&取地址
*pa = 5;//解引用操作符 - 通过 a 的地址找到 a
a = (int)3.14;//强制把3.14浮点型转化成整型
return 0;
}
sizeof 计算类型 / 变量所占内存大小 (单位:byte)
基本理解
#include<stdio.h>
int main()
{
//sizeof计算类型 / 变量大小
printf("%d\n",sizeof(a));
printf("%d\n",sizeof a);//计算变量大小时()可省略 - 说明sizeof不是函数,是关键字
printf("%d\n",sizeof(int))//计算类型大小不能省略()
//sizeof 计算数组大小
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(int [10]));
}
深层理解
int main()
{
short s = 5;
int a = 2;
printf("%d\n", sizeof(s = a + 2));//sizeof() - ()中的表达式不参与运算
//翻译 + 链接 - 运行
//表达式的计算发生在运行程序中 - 但翻译时sizeof(s = a + 2)直接翻译成了 2
//打印结果是 2
//类型由 s 决定
//a + 2的值并没有赋给 s
a + 3;//值属性 + 类型属性
printf("%d\n", s);//s == 5;
return 0;
}
#include<stdio.h>
void test_1(int arr[])
{
//数组传参传首元素地址
printf("1:%d\n", sizeof(arr));// 4或8(32位系统/64位系统)
}
void test_2(char ch[])
{
printf("2:%d\n",sizeof(ch));// 4或8
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d %d\n", sizeof(arr), sizeof(ch));//40 10
test_1(arr);
test_2(ch);
}
- - 前置、后置
++ 前置、后置
前置:先++,后使用
后置:先使用,后++
#include<stdio.h>
int main()
{
int a = 10;
int b = a++;//b = 10(a的值赋给 b后,a++,a = 11)
printf("a = %d, b = %d", a, b);//a = 11, b = 10
b = ++a;// b = 12(a++,a = 12后,把a的值赋给b)
printf("a = %d, b = %d", a, b);//a =12, b = 12
printf("%d", a--);//打印a = 12后(先使用), a--,a = 11
printf("%d", a);//a = 11
return 0;
}
~ 按位取反(二进制位)
基本理解
int main()
{
int a = -1;
printf("%d", ~a);
// a:11111111111111111111111111111111(-1在内存中存储的补码)
//~a:00000000000000000000000000000000
return 0;
}
操作一个数的二进制位
#include<stdio.h>
int main()
{
int a = 13;
//00000000000000000000000000001101
//a 的二进制第五位换成1
a = a | (1 << 4);
printf("%d\n", a);
//a 换回去
a = a & ~(a << 4);
printf("%d\n", a);
return 0;
}
6.逻辑操作符
• && 逻辑与
• || 逻辑或
逻辑与: 同时满足为真
逻辑或: 至少一个满足为真
代码理解
#include<stdio.h>
int main()
{
int a = 0;
int b = 5;//0为真,非0为假
int c = 0;
c = a && b;
printf("%d\n", c);//c == 0
c = a || b;
printf("%d\n", c);//c == 1
}
#include<stdio.h>
int main()
{
int i = 0, a = 0,b = 2, c = 3, d = 4;
i = a++ && ++ b && ++d;
printf("a = %d b = %d c = %d d = %d\n", a, b, c , d);//a = 1, b = 2, c =3, d = 4
//分析:
//1、判断a == 0为假后,a++, a == 1
//2、a 为假,后面表达式不在执行
i = a++ || ++b || ++d;
printf("a = %d b = %d c = %d d = %d\n", a, b, c , d);//a = 2, b = 2, c = 3, d = 4
//分析:
//1、判断a == 1为真后,a++, a == 2
//2、a 为真,后面表达式不在执行
a = 0;
i = a++ || ++b || ++d;
printf("a = %d b = %d c = %d d = %d\n", a, b, c , d);//a = 1, b = 3, c = 3, d = 4
//分析:
//1、判断a == 0为真假,a++, a == 1
//2、b++, b == 3
//3、b == 3为真,后面表达式不在执行
return 0;
}
7.条件操作符
三目操作符
• exp1 ? exp2 : exp3
规则:
Exp1 – 真 – exp2计算 – 结果为exp2
Exp1 – 假 – exp3计算 – 结果为exp3
8.逗号表达式
• exp1 , exp2 , exp3 ,… ,expN
规则:
从左向右依次计算,最后的结果可能受前面计算的影响整个表达式的结果是 - 最后一个表达式的结果
9.下标引用操作符
• [ ]
10.函数调用操作符
• ( )
11.结构成员访问操作符
• .
• ->
#include<stdio.h>
struct student//创建结构体类型
{
char name[20];
int age;
char school_name[20];
};
int main()
{
struct studen Y = {"Eunoiay", 19, "XXX大学"};
printf("%s %d\n", Y.nmae, Y.age);//访问结构体成员变量
struct studen* p = &Y;
printf("%s %d\n",p->name, p->age);//访问结构体成员变量
}
12.操作符的属性
• 操作符的优先级
• 操作符的结合性
• 操作符是否控制执行顺序
优先级:大类分为15个等级,小类自上而下递减
控制相邻两个操作符执行顺序
结合性:控制操作符L – R 和 从R – L 的顺序
控制执行顺序的操作符( 4个 ):
- && 逻辑与
- || 逻辑或
- :? 条件操作符
- , 逗号
优先级 | 操作符 | 描述 | 结合性 |
---|---|---|---|
1 | ( ) | 聚合 | N / A |
( ) | 函数调用 | L — R | |
[ ] | 下标引用 | ||
. | 访问结构成员 | ||
-> | 访问结构指针成员 | ||
2 | 后置++ | 后缀, 自增 | |
后置-- | 后缀, 自减 | ||
! | 逻辑反 | R — L | |
~ | 按位取反 | ||
+ | 表正值 | ||
- | 表负值 | ||
前置++ | 前缀, 自增 | ||
前置-- | 前缀, 自减 | ||
* | 间接访问 | ||
& | 取地址 | ||
sizeof | 长度运算 | ||
(类型) | 强制类型转换 | ||
3 | * | 乘 | L — R |
/ | 除 | ||
% | 取模 / 取余 | ||
4 | + | 加 | |
- | 减 | ||
5 | << | 左移位 | |
>> | 右移位 | ||
6 | > | 大于 | |
>= | 大于等于 | ||
< | 小于 | ||
<= | 小于等于 | ||
7 | == | 等于 | |
!= | 不等于 | ||
8 | & | 按位与 | |
9 | ^ | 按位异或 | |
10 | | | 按位或 | |
11 | && | 逻辑与 | |
12 | || | 逻辑或 | |
13 | ?: | 条件操作符 | N / A |
14 | = | 赋值 | R — L |
+= | 复合赋值 | ||
-= | |||
*= | |||
/= | |||
%= | |||
<<= | |||
>>= | |||
&= | |||
^= | |||
|= | |||
15 | , | 逗号 | L — R |
13.表达式求值
1. 求值顺序 – 操作符优先级和结合性
- 两个相邻操作符的执行顺序,取决于他们的优先级。若两者优先级相同,则取决于结合性
- 除此之外,由编译器自行决定(不违反控制执行顺序的操作符的前提下)
2.求值过程可能需要转换类型
- 常见 – 隐式类型转换
隐式类型转换:
1、整形提升
i. 表达式中char / short 类型的操作数在使用前转换为int类型 - 为获得精度
ii. 按照变量数据类型的符号位提升 (0 / 1)
iii. 无符号 - 高位补0
2、寻常算数转换
i. 某个操作符的操作数属于不同的类型时,其中一个操作数进行寻常算数转换
ii. 向精度更高(浮点型),长度更长的方向转换
iii. 无符号类型可以存放的正数范围比有符号类型 中的范围大一倍 (signed二进制第一位为符号位)unsigned 向 signed转换可能会丢失信息
iv. 整型数如果转换为signed不会丢失信息,就会转换为signed,否则转换为unsigned。
整型提升
1.为什么要整型提升?
整型操作器 (ALU) - CUP中执行整型运算的器件
一般,ALU的操作数的字节长度 == int的字节长度 == CPU通用寄存器的长度
因此,CPU难以直接进行(char / short)1 / 2byte的运算
所以,表达式中各种长度 小于 int长度的整型值,都必须先转换为int或unsigned int,才能送入CPU执行运算。
2.如何进行整型提升?
- 表达式中char / short 类型的操作数在使用前转换为int类型 - 为获得精度
- 按照变量数据类型的符号位提升 (0 / 1)
- 无符号 - 高位补0
3.代码理解
#include<stdio.h>
int main()
{
char a = 3;
char b = 127;
char c = a + b;
//1. char类型长度 1 byte = 8 bit
//2. a: 00000011
// b: 01111111
//3. 整型提升
// a:00000000000000000000000000000011
// b:00000000000000000000000001111111
// c:00000000000000000000000010000010
//4. 截断
// c:10000010(第一位是符号位)
printf("%c\n", c);
//10000010 - ASCII码值无对应的符号
printf("%d\n", c);//注意:%d的格式打印
//5. %d的格式打印 - 整型提升
// c:11111111111111111111111110000010(补码)
// 11111111111111111111111110000001(反码)补码 - 1
// 10000000000000000000000001111110(原码)反码符号位不变,其他位取反
// c = -126
return 0;
}
int main()
{
//0x表示后面的数以16进制计算
char a = 0xb6; //b6 == 182
short b = 0xb600; //b600 == 46592
int c = 0xb6000000; //b6000000 == 3053453312
if (a == 0xbb6) //char 类型和 int 类型比较 -- 整型提升
printf("a");
if (b == 0xb600) //short类型和 int 类型比较 -- 整型提升
printf("b");
if (c == 0xb6000000)
printf("c");
//代码运行结果为c
}
![](https://img-blog.csdnimg.cn/5ada98fa88e04d29a66140e77491adaa.png)
寻常算数转换
1.为什么要算数转换?
某个操作符的操作数属于不同的类型时,其中一个操作数进行寻常算数转换
2.如何进行算数转换?
- 向精度更高(浮点型),长度更长的方向转换
- 无符号类型可以存放的正数范围比 有符号类型 中的范围大一倍 (signed二进制第一位为符号位)unsigned 向 signed转换可能会丢失信息
- 整型数如果转换为signed不会丢失信息,就会转换为signed,否则转换为unsigned。
3.代码理解
14.问题表达式
有不确定因素,有歧义
① 1 * 2 + 3 * 4 + 5 * 6 的运算顺序
a、相邻操作符:* + *
b、确定的顺序:2 + 12 + 5 * 6
c、不确定的顺序:14 + 5 * 6 == 44
2 + 12 + 30 == 44
② int c = 2; c - ++c中 操作数c的值的获取顺序
a、相邻操作符:- ++
b、确定的顺序:c - 3
c、不确定的顺序:2 - 3 == -1
3 - 3 == 0
③ 问题代码
《C和指针》中证明表达式的求值顺序只是部分由操作符的优先级决定的程序
#include<stdio.h>
int main()
{
int i = 0;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
《C和指针》中不同编译器下的值
值 | 编译器 |
---|---|
-128 | Tandy 6000 Xenix 3.2 |
-95 | Think C 5.02(Macintosh) |
-86 | IBM PowerPC AIX 3.2.5 |
-85 | Sun Sparc cc(K&C编译器) |
-63 | gcc,HP_UX 9.0,Power C 2.0.0 |
4 | Sun Sparc acc(K&C编译器) |
21 | Turbo C/C++ 4.5 |
22 | FreeBSD 2.1 R |
30 | Dec Alpha OSF1 2.0 |
36 | Dec VAX/VMS |
42 | Microsoft C 5.1 |
④ 问题代码
Linux环境:ret == 10, i == 4
VS2019环境:ret == 12, i == 4
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
//相邻操作符:(++) 优先级只控制相邻两个操作符
// 先执行() 和 ++ 都一样
// 因此(++i) + 相当于 ++i +
// 原式:++i + ++i + ++i
// 相邻操作符:++ + ++
// 确定的顺序:先++i ++i(两次确定)
// 不确定的顺序:3 + 3 + ++i(Linux环境)
// 再++i后,4 + 4 + 4(VS2019)
//Linux环境: 3 + 3 + 4 = 10,i = 4 ++两次,赋值,++,赋值
//VS2019环境:4 + 4 + 4 = 12,i = 4 ++三次,赋值
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
⑤ 问题代码 - 函数调用顺序不确定
操作符仅决定了运算的执行顺序,函数调用的顺序并没有规则加以限定
int fun()
{
static int count = 1;//生命周期同全局变量 - 不自动销毁
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);
return 0;
}
共勉!