文章目录
操作符详解
1.操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
2.算术操作符
+ - * / %
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
- 对于 / 操作符,如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
加减乘都属于常规运算,这里特意讲解一下除法和取模运算
2.1除法运算
int a1 = 7 / 2;
printf("a1 = %d\n",a1);
double a2 = 7 / 2;
printf("a2 = %d\n",a2);
double a3 = 7.0 / 2;
printf("a3 = %d\n",a3);
可以发现除法的结果不在意指定的类型,只要有一个数字是小数,结果执行的就是小数除法,除法中除数不可以为0.
2.2 取模操作
取模意思就是取余,取模操作符的两个操作符都必须是整数,其操作符符号为%
比如3 / 2 = 1.5(1余5),所以3 % 2 = 1
3.移位操作符
3.1二进制的原反补码
想要弄懂移位操作符,首先先明白二进制的原反补码。
整数的二进制表示形式有三种表示形式:原码、反码、补码。
1.正整数的原码、反码、补码是相同的
2.负整数的原码、反码、补码是要计算的,计算规则如下:
原码不变
原码除符号位之外,取余部分全部按位取反(0变1,1变0),得到的就是反码。
反码加1,得到的就是补码。
首先1个整数是4个字节,一个字节有8个bit位。所以,一个整数就有32个bit位。其中第一个比特位0/1(也叫符号位)表示的是数字的正负,其中0表示正,1表示负,
例如整数15,在二进制的表示中14 = [1 * 2^3] + [1 * 2^2] +[1 * 2^1] + [0 * 2^0],所以14的二进制就是可以表示为1110.
所以:整数15的原码 —>> 00000000000000000000000000001111
反码—>> 00000000000000000000000000001111
补码—>> 00000000000000000000000000001111
负整数15的原码 —>> 10000000000000000000000000001111
反码—>> 11111111111111111111111111110000(原码除符号位之外,预取按位取反)
补码—>> 11111111111111111111111111110001 (反码加1)(二进制满2进1)
其中,整数在内存中存储的是补码,计算的时候也是使用补码计算的。
3.2右移移位操作
移位本质上移动的其实就是补码的二进制序列。
右移分为算术右移和逻辑右移。c语言中没有明确规定是算术右移还是逻辑右移,一般编译器上采用的是算术右移。
逻辑移位:左边用0填充,右边丢弃
算术移位:左边用原该值的符号位填充,右边丢弃
举例:
#include <stdio.h>
int main()
{
int a = -15;
int b = a >> 1;
printf("%d\n",b);
return 0;
}
上述代码可知,a的补码为 11111111111111111111111111110001(32位),经过右移一位,原来的补码就变为了 1111111111111111111111111111000(31位,最右边的1丢失了),此时发现对比原来的补码,符号位缺失了,根据右移规则,符号位用原该值的符号位补充,补充后的结果为: 11111111111111111111111111111000,该二进制表示的数值为 【1 * 2^3】+【0 * 2^2】+【0 * 2^1】+【0 * 2^0】 = 8,又因为符号位为1,所以数值为负数。由此可得该二进制表示的结果为-8.
从运行结果来看,我们的分析是正确的。为了更加清晰的看出bit位的变化,下面贴上数值的变化前后对比:
注意:移位不可以移动负数位,比如 a >> -2不能代表左移2位,移动负数位的这种写法c语言本身并未定义
3.3左移操作符
移动和计算规则与右移方法基本一致。
移位规则:
左边抛弃、右边补0
4.位操作符
& 按位与
| 按位或
^ 按位异或
注:他们的操作数必须是整数。
4.1按位与&
举例:
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011 - 补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a & b;
//a:00000000000000000000000000000011
//b:11111111111111111111111111111011
//c:00000000000000000000000000000011 ---> 计算值为 [1 * 2^1]+[1 * 2^0] = 3
printf("c = %d\n",c);
return 0;
}
&符号的运算规则就是对应二进制位有0则为0,两个同时为1才是1,所以上述代码运行结果就是为3.
4.2按位或 |
按位或| 的运算规则与按位与正好相反:对应二进制位有1则为1,两个同时为0才是0。
举例:
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011 - 补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a | b;
//a:00000000000000000000000000000011
//b:11111111111111111111111111111011
//c:11111111111111111111111111111011 --补码
// 11111111111111111111111111111010 --反码
// 10000000000000000000000000000101 --原码 ---> 计算值为 [1 * 2^2]+[0 * 2^1]+[1 * 2^0] = -5
printf("c = %d\n",c);
return 0;
}
结果发现c的值就是-5.
这时可能会有小伙伴产生了疑问,为什么按位与是直接计算,而按位或则是算出原码才计算呢?
当然,至于为什么也是很简单的,一开始就提及到正整数的原反补码是一样的,所以按位与直接获得的补码就等于原码,可以直接计算,而按位或得到的补码是负整数,负整数的原反补码并不相同,所以需要额外计算出原码,在进行计算求值。
4.3按位异或^
按位异或^的运算规则为:对应二进制位相同为0,相异为1。
#include <stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011 - 补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a ^ b;
//a:00000000000000000000000000000011
//b:11111111111111111111111111111011
//c:11111111111111111111111111111000 --补码
// 11111111111111111111111111110111 --反码
// 10000000000000000000000000001000 --原码 ---> 计算值为 [1 * 2^3]+[0 * 2^2]+[0 * 2^1]+[0 * 2^0] = -8
printf("c = %d\n",c);
return 0;
}
从运行结果可以发现,c的值的确为-8.
4.4 练习
看到这里,可能会有小伙伴疑惑,这个操作符有什么用呢?接下来,让我们来看一道题:
题目:不能创建临时变量(第三个变量),实现两个数的交换
解答:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
运行结果如下:
5.赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值,也就是你可以给自己重新赋值。
例如:
#include <stdio.h>
int main()
{
int w = 12;
printf("Before w is: %d\n",w);
w = 89;
printf("After w is: %d\n",w);
double s = 1000.0;
printf("Before w is: %lf\n",s);
s = 20000.0;
printf("Before s is: %lf\n",s);
return 0;
}
运行结果如下:
除了单一赋值符外,还有复合赋值符:
+= -= *= /= %= >>= <<= &= |= ^=
例举其中一个,其他赋值符均一样。比如a+=b,其运行过程就是a = a + b:
#include <stdio.h>
int main()
{
int a = 3;
int b = 6;
a+=b;
printf("%d\n",a)
return 0;
}
6.单目操作符
6.1单目操作符的介绍
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
– 前置、后置–
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
单目操作符顾名思义就是只有一个操作数,其中关于sizeof其实我们之前已经见过,可以求变量(类型)所占空间的大小。
6.2前后置的++和–
这里用代码演示一下前置和后置的++与–,以下对前后置+±-进行举例:
//++和--运算符
//前置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = ++a;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
printf("x = %d\n",x);
printf("a = %d\n",a);
int y = --a;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
printf("y = %d\n",y);
printf("a = %d\n",a);
return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = a++;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
printf("x = %d\n",x);
printf("a = %d\n",a);
int y = a--;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
printf("y = %d\n",y);
printf("a = %d\n",a);
return 0;
}
7.关系操作符
举例:
> >= < <=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,就不一一举例了,但是我们要注意一些运算符使用时候的陷阱。
警告:
在编程的过程中== 和=不小心写错,导致的错误
8.逻辑操作符
逻辑操作符有哪些:
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与
1&2 -----> 0
1&&2 ----> 1
区分逻辑或和按位或
1|2 -----> 3
1||2 ----> 1
下面来看一道习题:
#include <stdio.h>
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
运行结果如下:(看看自己算对了吗)
9.条件操作符
exp1 ? exp2 : exp3
语法意思就是exp1为真吗,如果为真,结果为exp2;如果为假,结果为exp3.
举例:
正常的表达式书写:
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
int m = 0;
scanf("%d %d",&a,&b);
if (a > b)
printf("m = %d\n",a);
else
printf("m = %d\n",b);
return 0;
}
利用条件操作符书写:
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
int m = 0;
scanf("%d %d",&a,&b);
m = (a > b ? a : b);
printf("m = %d\n",m);
return 0;
}
10.逗号表达式
exp1, exp2, exp3, ……epN
其结果就是expN
逗号表达式,就是用逗号隔开的多个表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果!
举一个简单的例子:
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
printf("a = %d\n",a);
printf("b = %d\n",b);
printf("c = %d\n",c);
return 0;
}
11.下标引用、函数调用和结构成员
11.1下标引用操作符
操作数:一个数组名 + 一个索引值
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5};
printf("%d\n",arr[2]); // [ ]的两个操作数是arr和2
return 0;
}
11.2 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数.
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");
return 0;
}
11.3 访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
举例:
#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
int set_age1(struct Stu stu)
{
stu.age = 18;
return stu.age;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
int a = set_age1(stu);
printf("stu = %d\n",a); //打印结果就为18
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
12 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
12.1隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
//实例1
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于a中。
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位来提升的
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
12.2算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:
但是算术转换要合理,要不然会有一些潜在的问题
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
12.3操作符的属性
复杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级: