前言
现在想,操作符确实让我刮目相看
本章内容
- 操作符介绍
- 关于表达式求值(颠覆)
1. 操作符介绍
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
1.1 算数操作符
+
-
*
/
%:返回整除后的余数
- %只能用于整形,其他几个操作符都可以用于整形或浮点数
- 对于 / ,如果 两个操作数都为整数 执行 整数除法 ;只要操作数中有浮点数,就执行浮点数除法
1.2 移位操作符
<< 左移操作符:*=2
>>右移操作符: /=2
注:移位操作符的操作数只能是整数,不能移动负位
1.2.1 左移操作符
规则:
左边抛弃,右边补0
修正:这里是 “ << 1 后”
1.2.2 右移操作符
规则:
1.逻辑移位:
左边补0,右边丢弃
2.算数移位
左边补原符号位,右边丢弃
更正:int n = -1;
1.3 位操作符
& - 按位与:有0则0
| - 按位或:有1则1
^ - 按位异或:同0异1
&
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
|
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
^
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
这玩意能有啥用?
来看看这道题:
不能创建临时变量(第三个变量),实现两个数的交换。
#include <stdio.h>
//这里用到: a^a = 0
//int num = 1
//00000000000000000000000000000001 ^
//00000000000000000000000000000001 =
//00000000000000000000000000000000
int main()
{
int a = 3;
int b = 5;
a = a^b;// a = 3^5
b = a^b;// b = 3^5^5 = 3
a = a^b;// a = 3^5^3 = 5
printf("a = %d b = %d\n", a, b);
return 0;
}
1.4 赋值操作符
=
a = b + 10;//把 (b+10) 赋给 a
- 语法规定可以连续赋值(a= b = c+10),但没必要,可读性不高
1.5 单目操作符
!
逻辑反操作:0->1 1->0
-
负值
+
正值
&
取地址
sizeof
操作数的类型长度(以字节为单位)
~
对一个数的二进制按位取反
–
前置、后置–
++
前置、后置++
*
间接访问操作符(解引用操作符) :通过操作数,找到其中的地址对应的变量
(类型)
强制类型转换
1.5.1 sizeof 和 数组 的那些事
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2):4
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4):4
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//(1):40
printf("%d\n", sizeof(ch));//(3):10
test1(arr);
test2(ch);
return 0;
}
(1)和(3)这里求的是整个数组的大小
(2)和(4)这里求的是形参arr(首元素地址)的大小,取决于电脑,32位的就是4bytes ; 64位的就是8bytes
1.5.2 前/后置 ++/–
规则:
前置:先++/–(自增/自减),后使用
后置:先使用,后 ++/–(自增/自减)
1.6 关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
- 对 == 和 = 需要敏感
1.7 逻辑操作符
它们只管 真假,而且还能控制求值顺序:能得出结果,后面就不计算
&& 逻辑与 : 得一个假就为假
| | 逻辑或:得一个真,就为真
来看看这段代码,输出的结果是?
#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 b=%d c=%d d=%d\n",a,b,c,d);
return 0;
}
//&&输出的结果是 a=1 b=2 c=3 d=4
//||输出的结果是 a=1 b=3 c=3 d=4
&& : a++,先使用:此时 a=0 , &&得一个假,直接得假 , 后面不计算
| | : b++,即使先使用, b也是2 ,为真 , | | 得一个真,直接得真,后面不计算
1.8 条件操作符
exp1 ? exp2 : exp3
可以读出来:exp1为真吗?为真就执行exp2,为假就执行exp3
#include<stdio.h>
int main()
{
int a = 10;
int b = (a > 15) ? 1 : 0;
printf("%d\n", b);
return 0;
}
//输出 0
a>15 为真吗? 为真:1 ; 为假:0
1.9 逗号表达式
exp1 , exp2 , … , expN
从左向右依次执行,整个表达式的结果是最后一个表达式的结果
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = (a + 5, b=10 , b = a + 5);
printf("%d\n", c);
return 0;
}
//结果是 15
1.10 下标引用、函数调用 和 结构成员
1.10.1 下标引用操作符
[ ]
操作数: 数组名 + 索引值
int arr[10];//创建数组
arr[9] = 10;//用下标引用操作符赋值
1.10.2 函数调用操作符
( )
操作数:函数名 + x个参数
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
Add(a, b);//函数调用
return 0;
}
1.10.3 结构成员
.
结构体.成员名
->
结构体指针->成员名
struct Student
{
char name[50];
int age;
double score;
};
#include<stdio.h>
int main()
{
struct Student stu;
struct Student * p_stu = &stu;//创建了一个 struct Student* 类型的结构体指针
stu.age = 10;//结构体变量.结构成员
p_stu->score = 100.0;//结构体指针->结构成员
printf("The age of stu is %d\nThe score of student is %.2lf",stu.age,p_stu->score);
return 0;
}
2.表达式求值
表达式求值过程中进行的操作:
隐式类型转换(可能进行)
算数转换(可能进行)
根据操作符属性计算(一定进行)
2.1 隐式类型转换
C语言是“强类型的语言”,所以不同类型相互替换使用需要经过转换
一般规则:
精度低的转换为精度高的
类型转换之一:整型提升
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算
说人话:整形运算器只算整形,类型转换是统一计算的条件
来看看如何进行整型提升
按照变量的数据类型的符号位提升
//负数
char c1 = -1;
c1的二进制位(补码)中只有8个比特位:
11111111
此处的 c1 是有符号的 char
高位补充符号位(1)
11111111111111111111111111111111
//正数
char c2 = 1;
c1的二进制位(补码)中只有8个比特位:
00000001
此处的 c2 是有符号的 char
高位补充符号位(0)
00000000000000000000000000000001
//无符号的整形提升:
高位补0
来证实一下:
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));//这里的+也是运算哦
printf("%u\n", sizeof(-c));//这里的-也是运算哦
//对此例中的c,只要发生运算,就会发生整形提升
return 0;
}
2.2 算术转换
还是“强类型”的事儿
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
算数转换又是如何转?
一样的 , 低精度转向高精度(多占空间是小事,至少不丢失数据了嘛)
long double
double
float
unsigned long int
long int
unsigned int
int
从上到小,精度由高到低
有符号 -> 无符号
< int 就 ->int
>int 就 ->更大的类型
*2.3 赋值类型转换
如:a=b b的类型会向a转换
float f = 3.14;
int num = f;//这时候的 float 转换成 int 就会丢失数据
2.4 操作符的属性
操作符的属性有3大块
- 操作符的优先级(操作符相邻才讨论)
- 操作符的结合性(优先级相同才讨论)
- 是否控制求值顺序(只有 “&&” 、“| |” 、“,”控制求值顺序)
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
*优先级的表格会在文末奉上
看一些问题表达式
例1:
#include<stdio.h>
int main()
{
int a, b, c, d;
a* b + b * c + c * d;
return 0;
}
解析:我们知道, * 的优先级比 + 高 , 但是不能保证第三个 * 比第一个 + 先执行
导致了两种可能
int a,b,c,d;
a*b
b*c
a*b + b*c
c*d
a*b + b*c + c*d
或者
a*b
b*c
c*d
a*b + b*c
a*b + b*c + c*d
例2:
c + --c;
解析: + 的左操作数是在 --之前求值 还是在 --之后求值?
例3:
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
解析:不同编译器结果不同,是全部 ++ 了再 + ,还是 ++ 一个 + 一个?
我们该写什么样的表达式?
通过操作符属性就能确定唯一的计算路径
今天的分享就到这里,不足之处望请斧正
这里是培根的blog,与你共同进步!