一、操作符分类
算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员。
二、算术操作符
cpp
+ - * / %
1.除了%操作符外,其他算术操作符都可以作用于整数和浮点数。
2.对于/操作符来说,如果两个操作数都为整数,则执行整数除法,只要有一个操作数是浮点数那么就会执行小数除法。
3.%操作符的两个操作数必须是整数,返回的结果是整除后的余数。
4.除法中除数不能为0。
三、移位操作符
cpp
<< 左移操作符;
>> 右移操作符;
注:移位操作符的操作数必须是整数,移位操作符移动的是二进制位。
整数的二进制表达形式有三种:原码,反码,补码。并且只适用于整数。
1.正整数的原码,反码和补码是相同的。
2.负整数的原码,反码和补码需要计算。
3.不管是正整数还是负整数都可以直接写出二进制原码,根据正负写出的二进制序列就是原码。
cpp
int a = 5; //0101 -- 二进制序列
//一个整型是4个字节,也就是32个bit位
//00000000000000000000000000000101 原码
//00000000000000000000000000000101 反码
//00000000000000000000000000000101 补码
int b = -15; //1111 -- 二进制序列
//10000000000000000000000000001111 原码
//11111111111111111111111111110000 反码(原码符号位不变,其他位按位取反后得到的就是反码)
//11111111111111111111111111110001 补码(反码加一就是补码)
注:第一个bit位是符号位,符号位是0表示正数,符号位是1表示负数。整数在内存中存储的是补码。移位计算的也是补码。
3.1 左移操作符
移位规则:左边丢弃,右边补0。

3.2 右移操作符
移位规则:首先右移运算分为2种
1.算术右移(右边丢弃,左边补符号位)
2.逻辑右移(右边丢弃,左边直接补0)
C语言没有明确规定到底是按算术右移还是逻辑右移,一般编译器上采用的是算术右移。

注:对于移位操作符,不要移动负数,这种是标准未定义行为。
四、位操作符
位操作符有:
cpp
& -- 按位与;
| -- 按位或;
^ -- 按位异或;
注:位操作符操作的必须是整数。
& :对补码进行操作,对应二进制位有0则0,两个同时为1才为1。
| :对应二进制位有1则为1,同时为0才是0。
^ :对应二进制位相同为0,相异为1。
按位异或的一个功能:异或是支持交换律的。
cpp
int a=4;
int b=7;
a^a=0;
a^0=a;
a^b^a=7;
a^a^b=7;
//交换前:a=4,b=7
a = a ^ b;
b = a ^ b;
a = a ^ b;
//交换后:a=7,b=4
五、赋值操作符
赋值操作符:=
赋值操作符可以对不满意的值进行重新赋值,也可以连续赋值。
cpp
int a = 0;
int b = 1;
int c = 2;
a = b = c + 1; //连续赋值
复合赋值符:
cpp
+=,-=,*=,/=,%=,>>=,<<=,&=,|=,^=
六、单目操作符
6.1 单目操作符
单目操作符只有一个操作数
cpp
! //逻辑取反操作
- //负值
+ //正值
& //取地址
sizeof //求操作数的类型长度(单位是字节)
~ //对一个数的二进制位进行按位取反
-- //前置--,后置--
++ //前置++,后置++
* //间接访问操作符(解引用操作符),通过存放的地址找到指向的空间(内容)
(类型) //强制类型转换操作符
前置--:先--,后使用
后置--:先使用,后--
前置++:先++,后使用
后置++:先使用,后++
注:sizeof是一个操作符,而不是函数。
七、关系操作符
关系操作符比较简单
cpp
>
<
>=
<=
!= //用于测试不相等
== //用于测试相等
注:不要把==(等于)和=(赋值操作符)搞混淆了。
八、逻辑操作符
cpp
&& //逻辑与
|| //逻辑或
! //逻辑非
逻辑与&&和按位与&的区别:
cpp
1&&2 --> 1
1&2 --> 0
逻辑或||和按位或|的区别:
cpp
1||2 --> 1
1|2 --> 3
&&、||、!的计算结果如果是真则返回1,如果是假则返回0。
注:&&如果左边为假,则右边不再继续计算;||如果左边为真,则右边不再继续计算。
九、条件操作符
cpp
exp1 ? exp2 : exp3
1.当表达式1(exp1)为真时,表达式2(exp2)计算,表达式3(exp3)不计算。
2.当表达式1(exp1)为假时,表达式2(exp2)不计算,表达式3(exp3)计算。
写成if语句的话类似下面的例子:
cpp
if (exp1)
{
exp2;
}
else
{
exp3;
}
十、逗号表达式
cpp
(exp1,exp2,exp3,...,expn)
逗号表达式就是用逗号隔开的多个表达式。
逗号表达式从左往右计算,整个表达式的结果是最后一个表达式计算后的结果。
十一、下标引用、函数调用和结构成员
1. [] 下标引用操作符
操作数:一个数组名+一个索引值
cpp
int arr[10];
arr[0] = 0;
//arr和0是[]的两个操作数
2.() 函数调用操作符
接受一个或多个操作数:函数名+函数参数
cpp
strlen("abcd");
//strlen和"abcd"就是()的两个操作数
注:对于函数调用操作符来说,最少有一个操作数。
3.访问结构体成员操作符
cpp
. 结构体变量.结构体成员名
-> 结构体指针->结构体成员名
例如:
cpp
struct Book
{
char name[30];
char author[20];
float price;
};
void Print(struct Book* p)
{
printf("%s %s %.1f\n", p->name, p->author, p->price);
}
struct Book b1 = { "百年孤独","马尔克斯",66.5 };
Print(&b1);
printf("%s %s %.1f", b1.name, b1.author, b1.price);
十二、表达式求值
12.1 隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来计算的。
为了获得这个精度,表达式中的字符型(char)和短整型(short)操作数在使用前会被转化为普通整型,这种转换被称之为整型提升。
整型提升的意义:
CPU内整型运算器(ALU)的操作数字节长度一般就是int的字节长度(8个字节长度),同时也是CPU通用寄存器的长度。
因此,即便是两个字符型数字的相加,在CPU执行运算过程中也要先转化为CPU内整型操作数的标准长度。
所以表达式中各种可能小于int长度的整型值,都需要先转化为int或unsigned int,然后再送到CPU内去执行运算。
整型提升是按照变量的数据类型的符号位来进行提升的。
cpp
//负数的整型提升
char c = -1;
//10000001 原码
//11111110 反码
//11111111 补码
//c是一个有符号的char,整型提升高位补充为符号位,即补1
//提升后:11111111111111111111111111111111
//正数的整型提升
char c1 = 2;
//00000010 原码
//00000010 反码
//00000010 补码
//c1是一个有符号的char,整型提升高位补充为符号位,即补0
//提升后:00000000000000000000000000000010
注:有符号类型的整型提升,高位补充符号位,无符号类型的整型提升,高位补充0。
12.2 算术转换
如果某个操作符的各个操作数都属于不同类型,除非其中的一个操作数类型转化为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
cpp
long double;
double;
float;
unsigned long int;
long int;
unsigned int;
int;
如果某个操作数在上面的表格排名较低,那么首先要转化成另一个操作数的类型后运算才会继续执行。
12.3 操作符的属性
复杂表达式的求值有三个影响因素:
1.操作符优先级
2.操作符的结合性
3.是否控制求值顺序
操作符优先级列表可以参考这位大佬的文章:C语言运算符优先级列表(超详细)_c语言运算符优先级表-CSDN博客
注:相邻操作符优先级相同的情况下,结合性起作用。相邻操作符优先级高的先运算,优先级低的后运算。
如果我们写出的表达式不能通过操作符的属性来确定唯一的计算路径,那么这个表达式就是存在问题的。