在之前的文章中,向大家简单介绍了一下操作符的大致分类。今天,本文将详细介绍各种操作符的实际用法。
操作符
1. 算术操作符
算数操作符包括:+ - * / %。
+、-、*想必大家都不陌生了,这里主要介绍一下除法操作符和取余操作符。
除法操作符 /
除法操作符两端操作数的类型不同,结果也就不尽相同:
当除号两端都是整数时,执行的是整数除法;
当除号两端至少有一个浮点数时,执行的是浮点数除法。
这里要注意的是:除数不得为0.
取余操作符 %
取余操作符也叫做取模操作符,得到的是两个操作数整除后的余数。
取余操作符的两个操作数必须都是整数。
2. 移位操作符
移位操作符包括:<< >>
移位操作符的操作数只能是整数
这里向大家介绍一下原码、反码以及补码的相关知识:
原码、反码、补码都是一个整数的二进制表达形式。最高位是符号位,0表示正,1表示负。
正整数的原码、反码以及补码都是相同的
负整数:
原码,符号位为1;
反码=原码的符号位不变,其余位按位取反;
补码=反码+1。
整数在计算机中存储的是补码形式,计算的时候也是使用补码计算的。
所以移位操作符移动的是整数补码的二进制序列
右移操作符 >>
右移分为算数右移和逻辑右移
算数右移:右边丢弃,左边补原来的符号位
逻辑右移:右边丢弃,左边直接补0
C语言中没有明确规定右移指的是算术右移还是逻辑右移,但一般编译器采取的是算数右移
//正数
int a=15; //00001111(原码==反码==补码
int b=a>>1; //00000111 ——>7
//负数
int a=-15; //11110001(补码)
int b=a>>1; //11111000(补码) ——>-8
左移操作符 <<
左移操作符相较于右移操作符,就要简单一些:
左边丢弃,右边补0
int a=6; //00000110
int b=a<<1; //00001100 ——>12
这里还要注意的是:对于移位操作符,不要移动负数位,这是标准未定义行为。
3. 位操作符
操作数必须是整数
按位与 &
对应二进制位,有0则为0,全1才为1
按位或 |
对应二进制位,有1则为1,全0才为0
按位异或 ^
对应二进制位,相同为0,不同为1
异或是不产生进位的,所以不会溢出
int a=3;
a^a==0;
a^0==a;
a^b^a==a^a^b; //异或支持交换律
4. 赋值操作符
赋值操作符包括:= += -= *= /= %= >>= <<= &= |= ^=
int a=1;
a+=1; //相当于a=a+1;
//其余复合赋值操作符同理
5. 单目操作符
单目操作符,顾名思义,就是只有一个操作数的操作符。
! | 逻辑反操作 |
- | 负 |
+ | 正 |
& | 取地址操作符 |
sizeof | 计算数据类型长度,以字节为单位 |
~ | 对一个数的二进制按位取反 |
++ | 前置、后置++ |
-- | 前置、后置-- |
* | 间接访问操作符/解引用操作符 |
(类型) | 强制类型转换 |
取地址操作符 & 与解引用操作符 *
int main()
{
int a = 10;
int* pa = &a;
//int* 是pa的类型 int 说明pa指向的对象是int型的,*说明pa是指针变量
//&是取地址操作符——取出a的地址
*pa = 20; //解引用操作符(间接访问操作符)——用来存放pa中的地址,找到指向的空间(内容)
return 0;
}
前置,后置++
//前置++
int b=++a;//先++,后使用
//相当于
int a=a+1; b=a;
//后置++
int b=a++;//先使用,后++
//相当于
int b=a; a=a+1;
前置--与后置--同理
计算数据类型长度 sizeof
sizeof是操作符,不是函数。计算的是类型创建变量的大小,其单位是字节。
6. 关系操作符
关系操作符包括:> >= < <= == !=
关系操作符只能运用到适合的类型上,不能运用到字符串或者自定义的结构体类型上。
7. 逻辑操作符
逻辑操作符包括:&&(且)、||(或)
进行逻辑运算时,逻辑运算符左右两边连接的布尔类型的操作数,最终表达式的结果是布尔值:True或False
&与&&,| 与 || 的区别
&&与 || :关注的是结果的真或假,计算的结果若为真,则为1
&与 | :关注的是二进制按位与或者按位或的计算
8. 逗号表达式
逗号表达式,从左向右计算,整个表达式的结果是最后一个表达式的结果
int a=5;
int b=(a++,++a,a) //7
9. 其他操作符
下标引用操作符: [ ]
函数调用操作符:( )
结构体成员访问操作符:. ->
表达式求值
表达式求值的顺序,一部分是由操作数的优先级和结合性决定的。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1. 整体提升
C语言的整型算术运算总是至少以缺省整型类型的精度来计算。为了获得这个精度,表达式中的字符和短整型操作数在使用之前要被转换为普通整型,这种转换称之为整型提升
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
如何进行整型提升
按照变量的数据类型的符号位来提升
int main()
{
//1.
char a = 0xb6; //10110110
short b = 0xb600; //1011010000000000
int c = 0xb6000000; //10110100000000000000000000000000
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
//2.
char m = 1;
printf("%u\n", sizeof(m));//1
printf("%u\n", sizeof(+m));//4
printf("%u\n", sizeof(-m));//4
return 0;
}
2.算术转换
如果某个操作符的各个操作数属于不同类型,那么除非一个操作数的类型转换为另一个操作数的类型,否则操作就无法进行。
下面的层次体系称为寻常算数转换:
3.操作符属性
复杂表达式的求值有三个影响的因素:
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序
结合以上内容,如果我们写出的表达式不能通过操作符的属性确定唯一一个计算路径,那么这个表达式就是存在问题的。