一,操作符分类
算术操作符:+,-,*,/,%(双目操作符)
移位操作符:< < > > (移动的是二进制的位)
位操作符:& | ^(使用二进制位进行计算)
赋值操作符:=,+=,-=,*=,/=,<<=,>>=,&=,|=,^=
(可见11.14)
单目操作符:!,++,--,&,*,+,-,~,sizeof(计算类型长度),
关系操作符:>,>=,<,<=,==,!=
逻辑操作符:&&,||
条件操作符:?:
逗号表达式:,(逗号操作符)
下标引用:[ ] (数组元素访问)
函数调用:()
结构成员访问:. ,->
C语言操作符特点:
1> 多
2> 灵活
二,二进制和进制的转换
例:数值15的各种进制的表示形式:
15的2进制:1111
15的8进制:17(读作:一七,不是十七)
15的10进制:15
15的16进制:F
10进制的数字每一位是0~9的数字
2进制的数字每一位是1~2的数字
8进制的数字每一位是0~7的数字
16进制的数字每一位是0~F的数字(0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f)
16进制的字母分别表示a-10,b-11,c-12,d-13,e-14,f-15。
8进制的:017;//0开头的是8进制数字
16进制:0xF;//0x开头的是16进制数字
2.1 二进制转十进制
十进制的每一位是有权重的,十进制的数字从右向左是个位,十位,百位......分别每一位的权重是10^0,10^1,10^2...
eg:123
10进制的位 1 2 3
权重 10^2 10^1 10^0
权重值 100 10 1
求值 1*100 + 2*10 + 3*1= 123
二进制的权重与十进制类似,从右向左是:2^0,2^1,2^2...
eg:1101
2进制的位 1 1 0 1
权重 2^3 2^2 2^1 2^0
权重值 8 4 2 1
求值 1*8 + 1*4 + 0*2 + 1*1 = 13
2.1.1 十进制转二进制数字
eg:125转化为二进制
125除2余数为 1
62除2余数为 0
31除2余数为 1
15除2余数为 1
7除2余数为 1
3除2余数为 1
1除2余数为 1
由下往上依次所得的余数就是十进制转换出的二进制
所以十进制的125转换的二进制:1111101
2.2 二进制转八进制和十六进制
2.2.1 二进制转八进制
八进制的数字每一位是0~7的,0~7的数字,各自写成二进制,最多有三个二进制位就足够了
八进制数:0 1 2 3 4 5 6 7
二进制数:0 1 10 11 100 101 110 111
当二进制转八进制数的时候,从二进制序列中右边低位开始向左边每三个二进制会换算一个八进制位,剩余不够三个二进制位的直接换算。
eg:二进制的:01101011,换算成八进制:0153,零开头的数字,会被当做八进制。
二进制:01 101 011
八进制:1 5 3
2.2.2 二进制转十六进制
十六进制的数字每一位是0~9,a~f 的,其中十六进制的数字写成二进制时最多有四个二进制位就足够了,比如f的二进制是1111。
十六进制数: 0 1 2 3 4 5 6 7 8 9 a b c d e f
二进制数(前七个与八进制相同,从第八个开始写)1000 1001 1010 1011 1100 1101 1110 1111
在二进制转十六进制数的时候,从二进制序列中右边低位开始向左每四个二进制位会换算一个十六进制位,剩余不够四个二进制位的直接换算。
eg:二进制的01101011,换成十六进制:0x6b,十六进制表示的时候前面加0x
二进制数: 0110 1011
十六进制数: 6 b
三,原码,反码,补码
整数的二进制表示方法有三种,原码,反码,补码
有符号整数的三种表示方法均有符号位和数值位两部分,二进制序列中,最高位的一位是被当做符号位,剩余的都是数值位。
符号位都是用0表示“正”,用1表示“负”。
正整数的原,反,补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就可以得到补码。
补码得到原码也是可以使用:取反,+1的操作
例:
对于整形来说:数据存放内存中其实存放的是补码。
四,移位操作符
< < 左移操作符
> > 右移操作符
注:移位操作符的操作数只能是整数。
4.1左移操作符
移位规则:左边抛弃,右边补0;
4.2右移操作符
移动规则:首先右移运算分为两种:
1>逻辑右移:左边用0补充,右边丢弃
2>算数右移:左边用原该值的符号位填充,右边丢弃
注:右移到底是算数右移还是逻辑右移,取决于编译器的实现,大部分的编译器上是算数右移
eg:
警告:对于移位运算符,不要移动负数位,这个是标准未定义的。
int num = 10;
num>>-1; //error
五,位操作符:&,|,^,~
5.1.& // 按位与
计算规则:只要有0就为0,同时为1才是1.
5.2.| // 按位或
计算规律:只要有1就是1,同时为0才为0.
5.3.^ // 按位异或
计算规律:相同位为0,相异位为1.
5.4.~ // 按位取反
注:以上操作符操作的都是二进制位,且操作数必须是整数。
5.5练习
例1:不能创建临时变量(第三个变量),实现两个整数的交换
例2: 求一个整数储存在内存中的二进制中1的个数。
例3:二进制位置0或者置1
编写代码将13的二进制序列的第5位修改为1,然后改回0.
六,单目操作符
(详细11.4回顾)
!,++,--,&,*,+,-,~,sizeof,(类型)
七,逗号表达式
exp1,exp2,exp3,...expN
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
例1;求c的值。
八,下标操作符 [ ], 函数调用( )
8.1 [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值(下标)
8.2( )函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
九,结构成员访问操作符
9.1结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量,数组,指针,甚至是其他结构体。
9.1.1结构的声明
struct tag
{
member - list; //成员列表,可以有1个或者多个成员
}variable - list; //变量列表
描述一个学生:
9.1.2结构体变量的定义和初始化
9.2结构成员访问操作符
9.2.1结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。
9.2.2结构体成员的间接访问
使用方法:结构体指针->成员名
十,操作符的属性:优先级,结合性
10.1优先级
优先级:相邻操作符,优先级高的先执行,优先级低的后执行。
eg: 3 + 4 * 5;
和数学的计算方法类似:先乘除后加减。
用操作符的方式来理解:由于乘法的优先级高于加法,所以会先计算4 * 5,而不是先计算3 + 4。
10.2结合性
如果两个运算符的优先级相同,就相比较两者的结合性根据运算符是左结合,还是右结合,决定执行顺序。
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [ ] | 数组下标 | 数组名[常量表达式] | 左到右 | - |
( ) | 圆括号 | (表达式)/函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | - | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | |
!= | 不等于 | 表达式!= 表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | |
10 | | | 按位或 | 表达式|表达式 | 左到右 | |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | |
13 | ?: | 条件运算符 | 表达式1? | 右到左 | 三目运算符 |
表达式2: 表达式3 | |||||
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | - |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
注:由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。
十一,表达式求值
11.1整形提升
C语言中整形算术运算至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符(char)和短整型(short)操作数在使用之前被转换为普通整形(int),这种转换称为整形提升
如何进行整形提升?
1.有符号整数提升是按照变量的数据类型的符号来提升的。
2.无符号整数提升,高位补0。
11.2算数转换
如果某个操作符的各个操作数属于不同的类型,除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
1 long double
2 double
3 float
4 unsingned long int
5 long int
6 unsigned int
7 int
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外一个操作数的类型后执行运算。
11.3问题表达式解析
11.3.1 表达式1
a*b + c*d + e*f;
表达式1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。
注:操作符的优先级是指的相邻的两个变量之间的操作符。
所以表达式的计算顺序可能为:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
也可以为:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
11.3.2 表达式2
c + --c;
同上,操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得知, +操作符的左操作数的获取在右操作数之前还是之后求职,所以结果是不可预测的,是有歧义的。
eg: c + --c;
int c = 5;
//--c == 4
当赋值的时候是给前面的c赋值5还是赋值4?
11.3.3 表达式3
不同的编译器会对此代码产生不同的数值。
11.3.4 表达式4
answer = fun() - fun() * fun();这句代码只知道操作符的优先级为:先算乘法,再算加减。
但是函数的调用先后顺序无法通过操作符的优先级确定。
11.3.5 表达式5
不同的编译器会对此代码产生不同的数值。
11.4总结
即使有了操作符的优先级和结合性,我们所写出的表达式依然有可能不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在风险的,建议不要写出特别复杂的表达式。