1.操作符的定义
操作符是什么?操作符相当于标点符号之类的一系列符号。
在我们日常的语法结构中,
有像+ - * / 这种运算符号;
有表示逻辑关系的一系列术语,例如并且(and),或者(or);
有表示动作的动词,像平移,插入;
C语言中的操作符是用来进行各种数学运算、逻辑运算、位运算、赋值等操作的符号,那么看着是不是就和我们日常生活中的这些语法结构很像呢?
其实可以把操作符类比为生活中用于不同用途的工具,例如锤子用来敲打物品,筷子用来吃饭夹菜。
操作符是为了方便工作的,并且用途十分广泛,所以它具有多和灵活的特点。
2.原码 反码 补码
在介绍众多操作符之前,需要知道三个概念。
我们知道,在计算机中,常用的数制是二进制,那么在整数中,二进制的表示方法有三种,它们分别是:原码 反码 补码
有符号整数的表达式包括符号位和数值位,其中最高位也就是最左边那一位是符号位,其他都是数值位。
并且规定:对于符号位,0表示正,1表示负。
那么在我们知道了这些基本知识之后,接下来正式介绍这三种表示方法。
这三种码是数值的表达方式
原码
是打印在屏幕上我们所能看到的
反码
相当于原码的相反数
补码
是用于存储和计算的(因为另外两种计算可能会出现错误,补码可以将符号位和数值位统一处理)
同时这三种表达方式都是以比特位的格式来表达的。
在正整数和负整数中,三种表达方法的定义各不相同。
在正整数中:
原码 反码 补码都相同
在负整数中:
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
并且在负整数中,三码满足如图的转换方式,并且需要记住:符号位是不会跟着取反的。
3.操作符的分类
操作符分为
• 算术操作符: + 、- 、* 、/ 、%
• 移位操作符: > • 位操作符: & | ^ `
• 赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、>= 、&= 、|= 、^=
• 单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)
• 关系操作符: > 、>= 、< 、<= 、 == 、 !=
• 逻辑操作符: && 、||
• 条件操作符: ? :
• 逗号表达式: ,
• 下标引用: []
• 函数调用: ()
• 结构成员访问: . 、->
接下来一一介绍各个操作符。
a.移位操作符:
用来移动操作数在存储中的位置。
表达式:n<<a
n表示要被执行的对象
a表示要移动的位数
我们知道,在存储中空间是固定的,那么当我们移动操作数的时候,必定会有抛弃和进入来使得存储空间足够稳定。那么移位操作符的具体使用也是根据这个原理来的。
<< 左移操作符
移位规则:左边抛弃、右边补0
>> 右移操作符
移位规则:首先右移运算分两种:
1. 逻辑右移:左边⽤0填充,右边丢弃
2. 算术右移:左边⽤原该值的符号位填充,右边丢弃
选择取决于编译器的实现,但大部分是算数右移。
不难看出,箭头指向哪个方向就是哪个方向移操作符。
我们需要注意的是:
移位操作符的操作数只能是整数。
移位操作符只移位正数位,不移负数位。其实<<1其实就相当于>>(-1),但是这是不被允许这么写的。
B.位操作符:
(操作的都是二进制位)特殊的运算方式,具有自己的逻辑运算规律
&:按位与
运算规则:有0就0,全1才1
| :按位或
运算规则:全0才0,有1就1
^:按位异或
运算规则:相同为0,相异为1
~:按位取反
运算规则:所有位都取反,包括符号位
需要知道的是,上述所有的计算进制都是二进制,而所有的运算都是基于二进制的运算规则。
但是在编译器上我们输入的时候是十进制,在计算过程中会自动换算成二进制,计算完之后再以十进制的形式输出。
注意:针对位操作符的名称来源是具有逻辑原因的。比如按位或为什么叫做按位或可以如此解释:“或”操作符的名称来源于逻辑运算中的“或”运算。在逻辑运算中,“或”运算表示只要有一个条件为真,整个表达式就为真。在位操作中,按位或操作也符合这个逻辑,只不过它是按进制的位来进行的:只要对应位置上有一个为1,结果就为1。因此,按位或操作符被称为“或”运算是因为它的逻辑行为类似于逻辑运算中的“或”运算,这种命名方式使得它更容易理解和记忆。
同时,以上的操作符是支持交换律的。例如a^(b^c)可以写成(a^b)^c。
C.单目操作符:
!、++、--、&、*、+、-、~ 、sizeof、(类型)
单目操作符的特点是只有一个操作数,例如a++表达就是a自加的意思。
D.逗号表达式:
就是⽤逗号隔开的多个表达式
逗号表达式,从左向右依次执行。整个表达式的结果是最后⼀个表达式的结果。也就是说遵循从左往右的逻辑顺序。
那么像在if语句中,假若出现这样的代码:
if(a,b,a=b,a+c=0)
实际上我们只看a+c=0这一个条件是否成立。不过逗号表达式中所提到的一些条件还是要根据逻辑关系先运算出结果。
E.下标访问[]
操作数:⼀个数组名 + ⼀个索引值
举例:int arr[10];//创建数组
arr[9] = 10;//实⽤下标引用操作符。
[ ]的两个操作数是arr和9。
F.函数调用()
接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
函数调用操作符往往直接跟在函数的后面,例如主函数main()的()就是函数调用操作符。
G.结构成员访问操作符
结构体:
C语言提供了一系列内置类型:int、char、float...
但是它们只能单一描述一个对象。
如果我们想要从各个角度,从多方面来描述一个对象的话,就需要用到结构体。
结构体是一种自定义的数据类型,结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体。
结构体的声明和定义以及初始化
在C语言中,struct是用来定义结构体的关键字。通过struct关键字可以定义结构体类型,并在程序中使用该类型来创建结构体变量。
如图中p1与p2,直接写在函数体后和拿出来单独确立,这两种定义方式都是可行的。
结构成员访问操作符
直接访问
直接访问用到.操作符
格式是:
结构体变量.成员名
间接访问
如果我们得到的不是一个变量,而是结构体的一个指针,就要用到间接访问
间接访问用到->操作符
格式是:结构体指针->成员名
操作符的属性
优先级
如果在一个表达式中包含多个运算操作符,那么它们的优先级会影响到表达式的运算结果。
优先级是相邻的操作符进行运算。
这个概念和数学中加减乘除的优先级概念是类似的。
结合性
而如果当运算符的优先级相同时,就要考虑到结合性,也就是看结合顺序是从左往右还是从右往左。
而对于()括号这个运算符来说具有最高的优先级,所以被它括住的运算符也同样享有最高优先级的运算。这个跟数学中的括号是类似概念。
对于不同的运算符的优先级和结合性,可以查表:
4.表达式求值
在了解操作符的分类之后,我们就需要把操作符代入到表达式中了。
整型提升
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节⻓度⼀般就是int的字节长度,同时也是CPU的通⽤寄存器的长度。
也就是说,像char类型的字节长度,也就是低于int类型的字节长度是不够用来表达式的整形运算的,这个时候为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整型提升。
在例子中,a和b的类型需要进行整型提升到和c一样的整型才能进行运算。
1. //正数的整形提升 2. char c2 = 1; 3. //变量c2的⼆进制位(补码)中只有8个⽐特位: 4. 00000001 5. //因为 char 为有符号的 char 6. //所以整形提升的时候,⾼位补充符号位,即为0 7. //提升之后的结果是: 8. 00000000000000000000000000000001
1. //负数的整形提升 2. char c1 = -1; 3. //变量c1的⼆进制位(补码)中只有8个⽐特位: 4. //1111111 5. //因为 char 为有符号的 char 6. //所以整形提升的时候,⾼位补充符号位,即为1 7. //提升之后的结果是: 8. //11111111111111111111111111111111
算术转换
整型提升其实就相当于改变了类型。
相同的:
如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long
double
double float
nsigned long
int
long int
unsigned int int
注意:如果某个操作数的类型在上⾯这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。