环境:CLion2021.3;64位macOS Big Sur
文章目录
「地表最强」C语言(一)基本数据类型
「地表最强」C语言(二)变量和常量
「地表最强」C语言(三)字符串+转义字符+注释
「地表最强」C语言(四)分支语句
「地表最强」C语言(五)循环语句
「地表最强」C语言(六)函数
「地表最强」C语言(七)数组
「地表最强」C语言(八)操作符
「地表最强」C语言(九)关键字
「地表最强」C语言(十)#define定义常量和宏
「地表最强」C语言(十一)指针
「地表最强」C语言(十二)结构体、枚举和联合体
「地表最强」C语言(十三)动态内存管理,含柔性数组
「地表最强」C语言(十四)文件
「地表最强」C语言(十五)程序的环境和预处理
「地表最强」C语言(十六)一些自定义函数和宏
「地表最强」C语言(十七)阅读程序
八、操作符
- 算术操作符:+ 、-、*、/、%
- 移位操作符:>>、<<
- 位操作符:按位与&、按位或|、按位异或^、按位取反~
- 赋值操作符:=、+=、*=、%=、<<= 、>>=、&=、|=、^= 等
- 单目操作符:+、-、++、–、!、强制类型转换、&、sizeof、*(解引用)、~(按位取反)
- 关系操作符:==、!=、>=、<= 等。
- 逻辑操作符:&&、||
- 条件操作符:exp1 ? exp2 : exp3;
- 逗号表达式:exp1,exp2,exp3,…,expn;
- 下标引用、函数调用和结构成员操作符:[]:数组访问元素 、():实际上,函数名后边的圆括号就是操作符。
几个易错操作符:
8.1 算术操作符
注意两点即可:
(1)/,操作数至少有一个为浮点数时,结果才是小数。否则会取商得到整数,即使以%f打印。
(2)%,两个操作数必须是整数。
8.2 移位操作符
移位操作符移动的是二进制位。
整数在内存中以补码的形式存储。
正整数:原码 = 反码 = 补码
负整数:
原码:根据数值写出来的二进制序列。
反码:原码的符号位不变,其余位按位取反。
补码:反码+1
int a = 10;
int b = a << 2;//b=40
int c = a >> 2;//c=2
位操作符操作的是二进制位,先将用int存储的10用二进制表示,共4*8=32bit:
00000000 00000000 00000000 00001010
左移两位:
00000000 00000000 00000000 00101000 左边丢弃,右边补0
b = 1*2^5 + 1*2^3 = 40
右移两位:
00000000 00000000 00000000 00000010 右边丢弃,左边补原数字符号位
c = 2
左移:左边丢弃,右边补0
右移:
1.算术右移:右边丢弃,左边补符号位,运算时一般是算术右移
2.逻辑右移:右边丢弃,左边补0
对于正整数,左移n位相当于乘2^n;右移n位相当于除以2^n
8.3 位操作符
位操作符的操作数必须是整数!
8.3.1 异或
交换两个数字,不能定义新的变量:
int a = 3;
int b = 5;
a = a + b; //数值太大可能会溢出
b = a - b;
a = a - b;
上述代码的问题:a,b均在int范围之内,但是和是可以超过int的范围的,就会溢出。
采用异或:
a = a ^ b;
b = a ^ b;//b = a ^ b ^ b = a ^ 0 = a
a = a ^ b;//a = a ^ b ^ a ^ b ^ b = a ^ a ^ b ^ b ^ b = 0 ^ 0 ^ b = b
// 0 ^ a = a a ^ a = 0
8.3.2 按位反操作符
按位取反包括符号位
int a = 0;
printf("%d\n",~a);
结果为:-1。
整数在内存中是以补码的形式存储,而打印出来的是原码,因此需要将补码转化为原码。
正数的原码、反码、补码相同;负数将其原码除符号位以外按位取反得到其反码,反码+1得到补码。
a:00000000 00000000 00000000 00000000
~a:11111111 11111111 11111111 11111111
此时的~a为补码形式,将其-1得到其反码:
[~a]反:11111111 11111111 11111111 11111110
符号位为1,负数,除符号位将其按位取反得到原码:
[~a]原:10000000 00000000 00000000 00000001 即 -1
8.3.3 小练习
求一个整数在内存中二进制位为1的个数:
int numOfBin(int n)
{
int count = 0;
int flag = 1;
for (int i = 0; i < 32; i++)
{
flag = (n >> i) & 1;
if (1 == flag)
count++;
}
return count;
}
8.4 赋值操作符
I have nothing to say.
8.5 单目操作符
&:取地址。
*:解引用/间接访问,通过地址找到其所指向的对象。
sizeof:不是函数,括号内为变量时,可省略括号,为数据类型时不可省略。
int a = 10;
int arr[10] = { 0 };
printf("%d\n", sizeof a);
printf("%d\n", sizeof arr);//sizeof计算变量的时候可以省略括号,但计算关键字的时候不可以省略。
printf("%d\n", sizeof(int [10]));//int [10]为数组的类型(即数组类型为去掉数组名剩下的部分)
short s = 5;
int a = 10;
printf("%d\n", sizeof(s = a + 2));//sizeof括号中放的表达式不参与运算!sizeof是在编译期间处理的,而表达式是在运行期间处理的
//sizeof编译过后,此段代码就已经无效了,编译只是看一下括号内的最终类型,而并没有实际运算。
printf("%d\n", s);
8.6 关系运算符
就是比较大小。
注意,有时比较相等不能使用==,例如比较字符串。
8.7 逻辑操作符
&&(若左边为假,右边不参与计算)
||(若左边为真,右边不参与计算)
int i = 0, a = 0, b = 2, c = 3, d = 4;
//i = a++ && ++b && d++;//1 2 3 4
i = a++ || ++b || d++;//1 3 3 4
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
注意区分逻辑操作符和位操作符:
- &&是逻辑运算,其两个操作数表示“真(非0)”或“假(0)”,将真假作与运算;
- &是位运算,将两个操作数按位作与运算。
if (1 && 2) {
printf("true");
}
else {
printf("false");
}
1和2均为真,结果为真,即ture:
if (1 & 2) {
printf("true");
}
else {
printf("false");
}
1:00000000 00000000 00000000 00000001
2:00000000 00000000 00000000 00000010
1&2:00000000 00000000 00000000 00000000 即 0 ,为假
8.8 条件操作符
也叫三目操作符
exp1 ? exp2 : exp3;//若表达式1的值为真,那么整个表达式的值就是表达式2的值,否则就是表达式3的值。
8.9 逗号表达式
从左到右依次计算,整个表达式的值为最后一个表达式的结果。
8.10 下标引用、函数调用和结构成员
(1)下标引用[]:例如数组取元素arr[4]; 操作数:arr、4
(2)函数调用():sum(a,b); 操作数:sum、a、b
(3)结构成员访问操作符 ->和.
struct Book
{
char name[20];
char id[20];
double price;
};
// . 通过结构体变量名访问结构体成员变量:结构体变量名.成员名
struct Book book = { "理想国","A21546",80.25 };
struct Book* pb = &book;
printf("书名:%s\n", book.name);
printf("书号:%s\n", book.id);
printf("价格:%lf\n", book.price);
printf("书名:%s\n", (*pb).name);
printf("书号:%s\n", (*pb).id);
printf("价格:%lf\n", (*pb).price);
// -> 通过指针访问结构体的成员变量:结构体指针->成员名
printf("书名:%s\n", pb->name);
printf("书号:%s\n", pb->id);
printf("价格:%lf\n", pb->price);
8.11 表达式求值
表达式求值的顺序一部分是有操作符的优先级和结合性决定,有些表达式的操作数在求值的过程中可能需要转换为其它类型。
8.11.1 隐式类型转换
1.整型提升
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general - purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
char a = 3;//char有符号
//00000011 - a 符号位为0 - 没有达到int型,需要高位补0提升为整型
//00000000 00000000 00000000 00000011
char b = 127;
//01111111 - b 符号位为0 - 没有达到int型,需要高位补0提升为整型
//00000000 00000000 00000000 01111111
char c = a + b;
//a和b都没有达到int大小,这时会发生整型提升,整型提升是按照变量的数据类型的符号位来提升的(符号位是什么就全补什么)
//若数据类型为无符号,高位补0.
//00000000 00000000 00000000 00000011 - a
//00000000 00000000 00000000 01111111 - b
//00000000 00000000 00000000 10000010 - a+b
//10000010 - c char只有8个比特位,因此截断低8位,但要打印%d,因此也需要整型提升,此时符号位为1
//11111111 11111111 11111111 10000010 补
//11111111 11111111 11111111 10000001 反
//10000000 00000000 00000000 01111110 原 = -126
printf("%d\n", c);//-126
char a = 0xb6;
//10110110 - a
//11111111 11111111 11111111 10110110 - 整型提升后的a
//00000000 00000000 00000000 10110110 - 0xb6 != a
short b = 0xb600;
//10110110 00000000 - b
//11111111 11111111 10110110 00000000 - 整型提升后的b
//00000000 00000000 10110110 00000000 - 0xb600 != b
int c = 0xb6000000;
//10110110 00000000 00000000 00000000 - c
//10110110 00000000 00000000 00000000 - 0xb6000000 = c
if (a == 0xb6)//假
printf("a");
if (b == 0xb600)//假
printf("b");
if (c == 0xb6000000)//真
printf("c");
char c = 1;
printf("%u\n", sizeof(c));//sizeof返回无符号整数,%u用于打印无符号整数
printf("%u\n", sizeof(+c));//+c虽然未参与运算(sizeof虽然是编译时,括号内表达式不参与运算,但是可以推导出其类型属性),但计算机知道其类型属性,发生了整型提升
printf("%u\n", sizeof(-c));//-c虽然未参与运算,发生了整型提升
//任何一个表达式都有两种属性:1.值属性(就是最后的值) 2.类型属性,计算机可以推导出来
2.算术转换
下列操作符的操作数若是不同的类型,需要向上转换,否则无法计算。
int - unsigned int - long int - unsigned long int - float - double - long double
8.11.2 操作符属性
1.优先级 2.结合性(L-R || R-L 从左到右或从右到左) 3.是否控制求值顺序
C语言操作符优先级等属性点击下边链接即可下载使用:
C语言操作符的优先级,结合性,用法示例,结果类型等属性.pdf