- 操作符分类
- 算术操作符
- 移位操作符
- 左移操作符
- 右移操作符
- 位操作符
- 赋值操作符
- 单目操作符
- sizeof和数组
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用、和结构成员
- 表达式求值
- 隐式类型转换
- 算术转换
- 操作符的属性
- 操作符的属性
算术操作符算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
+ - * / %
同时也是双目操作符,意思是有两个操作数
- %操作符的两个操作数必须是整数,结果是整除之后的余数
- 除了%操作符之外,其它的几个操作符可以作用于整数和浮点数
- /操作符的两个操作数若都是整数,执行整数除法,结果是整除之后的商
- /操作符的两个操作数若至少有一个是浮点数,执行浮点数除法,结果是浮点数
<< 左移操作符 >> 右移操作符 //操作数只能是整数
移动的是该数的二进制位
左移操作符移位规则:
右移操作符左边抛弃、右边补0
移位规则:
首先右移运算分两种:
- 逻辑移位
左边用0填充,右边丢弃
算术移位
左边用原该值的符号位填充,右边丢弃
逻辑移位和算数移位是由编译器来决定的,但一般情况下,所能用到的编译器都是逻辑移位,道理很简单,一个负数经过移位后应该还是一个负数,所以用原符号位来填充,也就是算术右移。
注意:
对于移位运算符,不能移动负数位,这个是标准未定义的。
例如:
int num = 10; num >> -1//error位操作符
& //按位与 | //按位或 ^ //按位异或 //操作数只能是整数
&
两个数相同位上的二进制位都是1时,该位的结果为1,其余情况都为0
|
两个数相同位上的二进制位都是0时,该位的结果为0,其余情况都为1
^
两个数相同位上,相同的数该位的结果为0,相异的数该位的结果为1
赋值操作符可以对变量进行重新的赋值
int a = 10;//a初始化为10 a = 20;//对a重新赋值为20 a = 30;//对a重新赋值为30 赋值操作符可以连续使用: int a = 10; int b = 20; int c = 30; a = b = c;//把c的值也就是30赋值给b,b就变为30,b再把值赋给a,a就为30 但是这样写的格式风格是不良好的 建议这样写 b = c; a = b;
复合赋值操作符
+=
-=
*=
/=
%=
<<=
>>=
&=
|=
^=
效果
int a = 10; int b = 20; a = a + b;//a就变为了30 不同的写法,相同的效果 a += b;//a也变为30 //其它操作符一样的道理,这样写更加简洁单目操作符
! 逻辑反操作 - 负值 + 正值 & 取地址 sizeof 操作数的类型长度(以字节为单位) ~ 对一个数的二进制按位取反 -- 前置、后置-- ++ 前置、后置++ * 间接访问操作符(解引用操作符) (类型) 强制类型转换
!
在C语言中0为假,非0为真,而!就是对真和假进行取反,把假的变成真的,把真的变成假的
可以看到,当a == 1时,很显然表达式为真,那么就打印hello world!
但是加了一个!,那么!(a == 1) 表达式就为假,那么就不打印hello world!
- +
跟数学上是一样的,在一个数前面加一个负号,该数就变为相反数,在一个数前面加正号,无效果
10是一个正数,那么-10就是负数;+10还是原本的10 -10是一个负数,那么-(-10)也就是10,是一个正数;+(-10)还是原本的-10
&
可以把一个元素的地址给取出来
~
对一个数的二进制位整体取反
-- ++
//后置++、-- #includeint main() { int a = 10; int b = 10; int x = a++;//先使用a,a再自增1,也就是先把a的值10赋给x,然后a再变为11 //结果为x = 10 a = 11 int y = a--;//先使用b,b再自减1,也就是先把b的值10赋给y,然后b再变为9 //结果为y = 10 b = 9 return 0; } //前置++、-- #include int main() { int a = 10; int b = 10; int x = ++a;//a先自增1,再赋值给x,也就是a先变为11,然后再把11赋给x //结果为x = 11 a = 11 int y = --b;//b先自减1,再赋值给y,也就是b先变为9,然后再把9赋给y //结果为y = 9 b = 9 return 0; }
*
*一个地址,可以把该地址处的值取到,然后进行赋值,输出等操作
(类型)
可以使两个不同类型的值进行更好的运算等操作
sizeof
sizeof是一个操作符,不是函数
函数是必须要有函数名称和()的,而sizeof在计算大小时(不包括类型)可以不需要括号,这也证明了sizeof不是函数而是操作符
sizeof和数组#includevoid test1(int arr[]) { printf("%dn", sizeof(arr));//(1) } void test2(char ch[]) { printf("%dn", sizeof(ch));//(2) } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; test1(arr); test2(ch); printf("%dn", sizeof(arr));//(3) printf("%dn", sizeof(ch));//(4) return 0; }
- (1)的结果是4,是因为形参arr是一个指针,sizeof一个指针的大小,在32位平台上是4,64位平台上是8
- (2)的结果是4,是因为形参ch是一个指针,sizeof一个指针的大小,在32位平台上是4,64位平台上是8
- (3)的结果是40,是因为此时数组名arr是整个数组,sizeof数组名,计算整个数组元素的类型大小,arr里有10个int类型空间,所以结果是10 * 4 = 40
- (4)的解雇偶数10,是因为此时数组名ch是整个数组,sizeof数组名,计算整个数组元素的类型大小,ch里有10个char类型空间,所以结果是10 * 1 = 10
> >= < <= != ==
这里需要注意的是,=是赋值操作符,==才是判断相等的操作符
逻辑操作符&& 逻辑与 两个操作数都为真时,表达式结果为真 || 逻辑或 两个操作数都为假时,表达式结果为假
注意:
- 逻辑与&时,从前向后判断,若遇到为假的条件,则后面的表达式不再判断
因为有一个条件为假,则整个表达式结果为假,所以后面表达式就不再判断了
- 逻辑或|时,从前向后判断,若遇到为真的条件,则后面的表达式不再判断
因为有一个条件为真,则整个表达式结果为真,所以后面表达式就不再判断了
条件操作符格式:
exp1 ? exp2 : exp3
当exp1为真时,exp2的结果就是整个表达式的结果
当exp1为假时,exp3的结果就是整个表达式的结果
逗号表达式格式:
exp1, exp2, exp3, ..., expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
下标引用、函数调用、和结构成员1.[ ]下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组 //实用下标引用操作符 arr[5] = 8;//给数组下标为5的地方赋值为8 arr[8] = 9;//给数组下标为5的地方赋值为8
其实[ ]的两个操作数是可以互换位置的
推导过程如下
arr[5] -> *(arr + 5) -> *(5 + arr) -> 5[arr]
2.()函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#includevoid test1() { printf("hello world!n"); } void test2(int* str) { printf("%sn", str); } int main() { char* str = "hello world!"; test1(); test2(str); }
3.访问一个结构的成员
表达式求值. 结构体.成员名
-> 结构体指针->成员名
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其它类型。
隐式类型转换C的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算期间内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的元素相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)难以直接实现两个8比特位字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
char a, b, c; a = b + c;
b和c的长度小于int长度,所以先整型提升,转换为普通整型,然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中。
如何进行整型提升
整型提升分为两种
- 有符号,高位补符号位
- 无符号,高位直接补0
有符号整型提升 //负数的整型提升 char c1 = -1; 变量c1的二进制位(补码)中只有8个比特位 11111111 所以运算时进行整型提升,转换为普通整型长度, char是有符号的char,符号位为1,所以高位补1 整型提升之后的结果是: 11111111 11111111 11111111 11111111 //正数的整型提升; char c2 = 1; 变量c2的二进制位(补码)中只有8个比特位 00000001 所以运算时进行整型提升,转换为普通整型长度, char是有符号的char,符号位为0,所以高位补0 整型提升之后的结果是: 00000000 00000000 00000000 00000001 无符号整型提升 直接高位补0即可
实例1
先看a,a的二进制位(补码)是 10110110 然后在a == 0xb6表达式中进行整型提升变为 11111111 11111111 11111111 10110110 然后与0xb6进行比较,而0xb6的二进制位(补码)是 00000000 00000000 00000000 10110110 所以,a == 0xb6为假,也就不输出a
再看b,b的二进制位(补码)是 10110110 00000000 然后在b == 0xb600表达式中进行整型提升变为 11111111 11111111 10110110 00000000 然后与0xb600进行比较,而0xb600的二进制位(补码)是 00000000 00000000 10110110 00000000 所以,b == 0xb600为假,也就不输出b
最后看c,c的二进制位(补码)是 10110110 00000000 00000000 00000000 与0xb6000000的长度一样,所以不发生整型提升 而0xb6000000的二进制位(补码)是 10110110 00000000 00000000 00000000 所以,c == 0xb6000000为真,输出c
实例2
sizeof(c)并没有进行整型提升,所以c此时长度为1个字节 sizeof(+c)以及sizeof(-c),都分别进行了+c和-c的运算,所以进行整型提升,长度变为4个字节算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double double float unsigned long int long int unsigned int int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后再执行运算。
注意:
但是算术转换要合理,要不然会有一些潜在的问题。
float f = 3.14; int num = f;//隐式转换,会有精度丢失操作符的属性
复杂表达式的求值有三个影响的因素
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于它们的优先级。如果两者的优先级相同,取决于它们的结合性。
实例1
//表达式的求值部分由操作符的优先级决定。 a * b + c * d + e * f 由于*的优先级高于+,所以只能保证先进行*,再进行+,但是并不能决定第三颗*比第一颗+早执行 //可能的顺序有 //顺序一: a * b c * d e * f (a * b) + (c * d) (a * b) + (c * d) + (e * f) //顺序二: a * b c * d (a * b) + (c * d) e * f (a * b) + (c * d) + (e * f)
实例2
c + --c; //只能保证--的运算在+号之前,但是不能保证+左操作数的获取是在--之前还是之后
注意:*
但是算术转换要合理,要不然会有一些潜在的问题。
float f = 3.14; int num = f;//隐式转换,会有精度丢失操作符的属性
复杂表达式的求值有三个影响的因素
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于它们的优先级。如果两者的优先级相同,取决于它们的结合性。
实例1
//表达式的求值部分由操作符的优先级决定。 a * b + c * d + e * f 由于*的优先级高于+,所以只能保证先进行*,再进行+,但是并不能决定第三颗*比第一颗+早执行 //可能的顺序有 //顺序一: a * b c * d e * f (a * b) + (c * d) (a * b) + (c * d) + (e * f) //顺序二: a * b c * d (a * b) + (c * d) e * f (a * b) + (c * d) + (e * f)
实例2
c + --c; //只能保证--的运算在+号之前,但是不能保证+左操作数的获取是在--之前还是之后
**总结:**我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。



