操作符相关的知识,在我们初识C语言(第三幕)这篇文章中其实已经讲到过了。但是那一次讲解仅仅只是一些粗略的知识讲解,我们在那里面已经提到过,后续会专门超级详细的讲解操作符的知识,我们现在就来攻下操作符相关的知识吧。
这是之前讲解操作符的链接初识C语言(第三幕)
目录
一、操作符分类
二、算术操作符
1./号操作符
2.%操作符
三、移位操作符
1.整数的二进制表示
1.计算机中的正数和负数的二进制序列
2.原码,反码和补码
3.原码反码补码三码的相互转换图解
2.数据在内存中的存储
3.左移操作符详解
(1)正数的左移
(2)负数的左移
4.右移操作符详解
(1)两种右移
四、位操作符
1.按位与&
2.按位或|
3.按位异或 ^
(1)详解异或操作符
(2)一道经典的题目
五、赋值操作符
1.赋值操作符(=)
2.复合赋值符
六、单目操作符
1.单目操作符的概念
2.单目操作符的种类
3.单目操作符详解
(1)!逻辑反操作
(2)+ -操作符
(3)&取地址 *解引用
(4)sizeof
(5)~对一个数的二进制按位取反
(6)++ --操作符
(7)强制类型转换
4.sizeof与数组的一个经典题目
总结:
一、操作符分类
操作符一般分为以下几类:
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
二、算术操作符
以下是五种算术操作符
+ - * / %
其中前三种相信大家都了解的很清楚。跟数学中的运算一致。需要注意的就是/和%这两个操作符
1./号操作符
我们直接看这样一段代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int ret = 10 / 3;
printf("%d", ret);
return 0;
}
他的运行结果是
这是因为对于除法操作符而言,两边的操作数都是整数,则执行的是整数除法,而如果想要计算出小数,则需要两边的操作数至少有一个是浮点类型的。
计算出浮点数的案例如下
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
//int ret = 10 / 3;
//printf("%d", ret);
double ret = 10.0 / 3;
printf("%lf", ret);
return 0;
}
运行结果为
但是我们这块是六位小数,如果只想要一位小数。则将第八行中的lf改为.1lf,这个代表的是打印小数点后一位。如下所示
2.%操作符
我们看下面一段代码
#include<stdio.h>
int main()
{
int ret = 10 % 3;
printf("%d", ret);
}
运行结果为
其实%操作符(取模操作符)可以计算的是两个操作数的余数。并且两个操作数必须为整数,否则报错。
三、移位操作符
>>右移操作符
<<左移操作符
注意:移位操作符只能作用于整数
首先我们得知道,这个移位操作符,移的是二进制位,所以说这就要求我们要熟练掌握进制转换,我们在之前出过一篇进制转换的文章,这里附上链接:进制转换
其次是他们的使用,他们的使用是这样的,并且他们的移位并不会改变自身的大小
#include<stdio.h>
int main()
{
int a = 10;
int b = a << 2;
int c = a >> 1;
return 0;
}
1.整数的二进制表示
这是我们了解移位操作符的基础,首先我们必须了解的是,整数的二进制表示形式一共有三种。分别是原码,反码,补码。
1.计算机中的正数和负数的二进制序列
比如说我们看这一段很简单的代码
上图中a是10,b是-10,他们的区别就是一个是整数一个是负数
a是10的话,我们不难得出他的二进制序列是1010
而a又是一个整型类型。一个int类型是32个bit位,所以我们得出
1010的前面需要补0。
所以二进制序列其实应该为00000000 00000000 00000000 00001010
但是呢我们如何区分他是+10还是-10呢?
我们是用最高位来进行区分的
最高位是1,他就是负数,最高位是0,它就是正数。
所以a,也就是10,他的二进制序列为00000000 00000000 00000000 00001010
b,也就是-10,他的二进制序列为10000000 00000000 00000000 00001010
我们将上面这两种,按照他的正负,直接就能写出来的二进制序列称为原码。
2.原码,反码和补码
我们在上面说过,整数的的二进制表示形式一共有三种,分别是原码,反码和补码。而我们在上面也解释了如何求出一个原码。原码其实就是按照一个数的正负,直接就能写出来的二进制序列。
那么反码和补码又是如何求出来的呢?
我们这里给出一条结论,正数的原码,反码,补码相同;负数的反码,补码是需要经过计算的,反码是符号位不变,其他位按位取反,得到的就是反码,补码是反码+1得到的。
我们这里举出一个例子
对于上面的a而言,他是一个正数10
他的原码是00000000 00000000 00000000 00001010
他的反码是00000000 00000000 00000000 00001010
他的补码是00000000 00000000 00000000 00001010
也就是原码,反码,补码均相同
对于上面的b而言,他是一个负数-10
他的原码是10000000 00000000 00000000 00001010
他的反码是11111111 11111111 11111111 11110101
他的补码是11111111 11111111 11111111 11110110
原码是需要将最高位,也就是符号位改为1得到的
反码是需要将原码的符号位不变,其他位均按位取反得到的
补码是反码+1得到的
3.原码反码补码三码的相互转换图解
对于正数而言,他的三码统一,所以无需进行转换
需要注意的是负数的三码转换,下面的负数转化的三码转换图解:
对于一个已知的原码,符号位不变,其他位按位取反可以得到反码,然后反码+1可以得到补码。(如图中黑色线所示)
相反的,如果补码想要得到原码可以直接按照上面相反的步骤即可得到(如图中绿色线所示)
当然还有另外一种方法,也可以从补码转换成原码,那就是和原码转换成补码的方法一样。补码的符号位不变,其他位按位取反,然后+1.这样也可以得到原码 (如图中橙色线条所示)
为了方便我们理解,我们举一个例子
对于上面的b而言,他是一个负数-10
他的原码是10000000 00000000 00000000 00001010
他的反码是11111111 11111111 11111111 11110101
他的补码是11111111 11111111 11111111 11110110
这是我们刚刚走过一个流程,是-10的原码如何得出补码的
那么从补码到原码的方法,按照逆运算,也就是绿色线条的方法,肯定是可以得到原码的
我们来看一下橙色线条的方法
-10的补码是11111111 11111111 11111111 11110110
符号位不变,其余按位取反得:
10000000 00000000 00000000 00001001
+1得 10000000 00000000 00000000 00001010
最终的结果恰好就是原码。
2.数据在内存中的存储
为什么我们突然要讲一下原码,反码和补码呢?他们有什么用呢?其实这是因为内存中存储的其实是补码。所以在参与移位操作符运算的时候均需要按照补码来移位。而绝不是对原码进行操作,这一点也是很多人在移位操作符上经常犯得经典的错误,标准的零分。
3.左移操作符详解
(1)正数的左移
我们看这样一段代码
#include<stdio.h>
int main()
{
int a = 10;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
这段代码中使用了一个左移操作符。我们先来分析一下,左移操作符是如何操作的?
我们在前文中说过,数据在内存中存储的是补码,所以移位操作符应该作用于补码
如下图所示,假设这是a在内存中的存放
我们让其进行左移一位。就变成下图所示
左边有一位被多出去了,右边少了一位。此时计算机就会将左边多出去的扔掉,不要了,然后和右边自动补0,如下图所示,紫色的0被扔掉,补充绿色的0
此时我们的二进制序列就变为了
00000000000000000000000000010100,当然这是补码,以上所有的操作都是对于补码的操作
我们打印出来的时候,我们用的是原码,但是由于正数三码统一,所以上述补码转换为原码的结果仍然是它
而它转换成10进制就是20
这就是左移操作符的图解了
我们按照上面的代码往下走,这个a<<1以后变为了20赋值给了b,所以b其实就是20了,但是这个过程中a并没有发生改变
最终运行结果就是
事实上,我们会发现,左移操作符,他使得二进制序列左移一位,他会使得我们每一位的权重都变为了原来的二倍。所以说左移操作符会使得原来的数扩大十倍。
所以左移操作符的运算规则是左边丢弃,右边补零
(2)负数的左移
我们继续来看一下负数的案例,我们看这个代码
#include<stdio.h>
int main()
{
int a = -10;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
对于此时的这个a而言
他的原码是10000000 00000000 00000000 00001010
他的反码是11111111 11111111 11111111 11110101
他的补码是11111111 11111111 11111111 11110110
假设它在内存中是这样放的
我们对其进行左移,而左移的规则是左边丢弃,右边补零
所以最终的补码结果为
11111111 11111111 11111111 11101100
转化为反码为(补码-1)
11111111 11111111 11111111 11101011
转化为原码为(符号位不变,按位取反)
10000000 00000000 00000000 00010100
而此时正好就是-20
我们运行一下
而此时我们会发现仍然符合我们上面的规则,左移一位,扩大2倍。
4.右移操作符详解
(1)两种右移
与左移不同的是,右移要比左移复杂一点,因为它有两种右移方式,而左移只有一种
右移有如下两种方式。具体采用哪种方式是取决于编译器的
1.算术右移(绝大多数编译器,平常见到的)
2.逻辑右移
我们看这样一段代码
#include<stdio.h>
int main()
{
int a = -1;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
我们先写出-1的原码,进而推导出补码
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110(符号位不变,其他按位取反)
补码:11111111 11111111 11111111 11111111(反码+1)下图是-1在内存中的存储
我们右移一位,得到下图
可是这里出现一个一个问题,是补0还是1呢?
这块就回到了我们一开始所说的右移的两种方式了
算术右移:右边丢弃,左边补原来的符号位
逻辑右移:右边丢弃,左边直接补0
而我们目前的visual studio2022编译器采用的是算术右移
所以我们补1
补了1以后呢,我们发现,这和我们之前的一模一样
所以这个补码转换成原码是
10000000 00000000 00000000 00000001
转换成十进制数就是-1
所以我们编译器应该的结果就是两个-1
注意:对于移位运算,不要移动负数位,也不要超出数据的大小。这是c语言标准未定义的。是很危险的操作。
四、位操作符
位操作符有
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须都是整数,同时也是针对二进制位进行计算的
1.按位与&
我们看这样一段代码
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a & b;
printf("%d", c);
return 0;
}
这段代码中
由于在内存中数据都是补码,所以&两端的数据都应该先转化成补码
a是3
所以它的原码是00000000 00000000 00000000 00000011
而正数三码统一,所以补码就是上面的二进制序列
b是-5
所以它的原码是10000000 00000000 00000000 00000101
反码为11111111 11111111 11111111 11111010
补码为11111111 11111111 11111111 11111011
而按位与(&)的运算是,每一个对应的二进制位依次进行计算,只有对应的二进制位都为1,结果才为1,否则为0
所以上述两个反码的运算过程见下图
所以也就是说计算出来的c的补码为00000000 00000000 00000000 00000011
而这个c的符号位为0,也就是正数,因此三码统一,所以它的原码等于补码
所以得出c的值为3
我们在总结一下&的运算规则:
对应的二进制有0,则为0,都为1,才为1
2.按位或|
我们看这样一段代码
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a | b;
printf("%d", c);
return 0;
}
对于这段代码而言
我们仍然是将a和b的二进制补码形式计算出来
a的补码为00000000 00000000 00000000 00000011
b的补码为11111111 11111111 11111111 11111011
而按位或( | )运算是,每一个对应二进制位依次进行计算,只要出现1,则为1,两个都为0,才为0
所以计算过程为
也就是c的补码为11111111 11111111 11111111 11111011
有了c的补码就可以计算出它的反码,进而推导出原码
所以c的原码为10000000 00000000 00000000 00000101, 也就是-5
我们在总结一下|的运算规则:
对应的二进制有1,则为1,都为0,才为0
3.按位异或 ^
(1)详解异或操作符
我们来看这样一段代码
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
printf("%d", c);
return 0;
}
对于^这个操作,异或的计算是这样的,将每一个二进制数依次进行计算,相同为0,相异为1
所以计算过程为
也就是c的原码为10000000 00000000 00000000 00001000 ,而这个正好是-8
所以运行结果为
我们总结一下^的运算规律
对应的二进制序列,相同为0,相异为1
(2)一道经典的题目
题目描述:
不创建第三个变量,实现两个整数的交换
我们最容易想到的就是创建一个临时变量来进行交换,这样是可以交换,但是违背了题意,那么我们应该怎么做呢?
请看下面的代码
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("%d %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d\n", a, b);
return 0;
}
运行结果为
我们发现这段代码居然可以实现交换,那这又是为什么呢?
我们仔细分析一下这段代码,如下图所示
但是这种写法存在一种问题,如果a和b的值太大了,导致他们的和溢出了,但是他们本身是不溢出的。这样就会出现问题。所以这样写是存在局限性的。那么我们还有更好的办法吗?我们说当然由。别忘记我们正在讲解异或的知识,所以我们考虑用异或去解决这个问题
请看这段代码
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("%d %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d\n", a, b);
return 0;
}
运行结果为
可见可以实现交换,那么为什么可以进行交换呢?我们来详细分析一下
第一步操作中,a^b赋给了a
第二步操作中,用现在的a^b,我们将第一步的a代入得:(a^b)^b,也就是a^b^b,而异或是满足交换律,结合律的,式子中正好由两个b,所以两个b异或的结果正好就是0,而0^a的结果是什么呢?
异或的运算规则是依次比较对应二进制(补码),相异为1,相同为0。而二进制只有0和1这两个,0和0异或结果为0,0和1异或,结果为1。所以我们得出结论:0与任意数异或结果为该任意数,这一点似乎与小学数学中所学的1乘以任意数结果为该任意数有着异曲同工之妙。其实,看到这块,我们也可以试着推测一下1异或任意数的值是多少呢?我们知道1异或0等于1,1异或1等于0。这样我们变可以想到任意一个数异或1以后。它的二进制补码中的1变为0,0变为1。而这些计算出来的是补码,最终我们还要-1,然后符号位不变,其他按位取反。所以我们得出一个结论:1异或任意整数,若该任意数大于等于0且不等于1,则结果为该任意数+1;若该任意数等于1,则结果为0;若该任意数小于0,则结果为该任意数-1。这个结论读者可自行验证
因此有了上面的分析,我们可以得到a^b^b的结果就是a,所以第二步的操作实际上就是把b赋给了a
第三步的操作就与第二步的操作分析过程类似,结果就是将原来的a赋给了b。
当然我们上面讲了两种方式,但是要注意,在实际的应用中,以上两种方式交换都不如直接使用第三个变量合适。因为对于异或而言,它的可读性差,并且效率也不如使用第三个变量高,而且它只能针对整数进行交换。这里仅仅只是作为一种思维题考查。在实际应用中很少见。
五、赋值操作符
1.赋值操作符(=)
赋值操作符,是一个很常见的操作符,它可以修改我们以前不想要的一个值。
int weight=12;
weight=120;//直接进行赋值修改即可
同时赋值操作符也可以连续使用
int x=10;
int y=20;
int a=0;
a=x=y+1;
这个代码的意思就是,先将y+1的值赋给x,然后将x赋给a,这样写的话其实不利于我们进行调试,而且可读性差,所以我们一般都将其拆开
x=y+1;
a=x;
这样写就更加清晰了
2.复合赋值符
复合赋值符顾名思义,就是将赋值操作符和一些其他的操作符结合起来。由如下几种
+= -= *= /= %= >>= <<= &= |= ^=
举个例子,如下图所示,这些复合赋值符都是可以进行展开的,这样就可以实现改变我们原来的值,所有的操作符都是同理的
六、单目操作符
1.单目操作符的概念
什么是单目操作符呢?要想了解这个,我们先看下面的讲解
a + b 这个操作中,a和b是操作数,+是操作符。这个操作符其实是一个双目操作符,所谓双目操作符,就是有两个操作数。
那么根据这个我们可以得到,所谓单目操作符,就是只有一个操作数。
2.单目操作符的种类
有如下几种单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
3.单目操作符详解
(1)!逻辑反操作
我们直接看这个代码
#include<stdio.h>
int main()
{
int flag = 5;
if (flag)
{
printf("1\n");
}
if (!flag)
{
printf("2\n");
}
printf("%d\n", flag);
printf("%d\n", !flag);
printf("%d\n", !!flag);
return 0;
}
运行结果为
第一次打印出来1是因为,只有0为假,非零为真
第二次打印出来5,是因为flag就是5
第三次打印出来0,是因为使用了!,导致5变成了假,也就是0
第四次打印出来1,是因为使用了两次!,使用一次变为0,第二次使用变为真,而这个真默认为1
除此之外,我们还有一点需要说明一下
就是布尔类型,在c语言中也有布尔类型,当然这个是在c99中引入的,所以很多编译器不支持,布尔类型其实是判断真假的类型
我们看这个代码
#include<stdio.h> #include<stdbool.h> int main() { _Bool flag = true; if (flag) { printf("1"); } return 0; }
运行结果为
或者说这段代码,可以用来判断闰年
#include<stdio.h> #include<stdbool.h> _Bool is_leap_year(int y) { if ((y % 4 == 0) && (y % 100 != 0) || (y % 400==0)) { return true; } else { return false; } } int main() { int y = 0; scanf("%d", &y); int ret=is_leap_year(y); printf("%d", ret); }
当然,这个这个布尔类型除了可以使用_Bool以外,还可以使用bool来说明类型,两者是等价的
(2)+ -操作符
这两个操作符就很简单了,就是我们初中数学所学的运算。完全一致,这里给出一个例子
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", a);
printf("%d\n", +a);
printf("%d\n", -a);
return 0;
}
运行结果为
其实在这里,有一部分人会跟unsigned的用法混淆!这两个本应该是没有关系的两个东西,不过既然提到,在这里也提及一下
unsigned 是无符号整型
我们一般的类型前面其实应该加一个signed 意思是有符号类型,但是省略了
我们知道,在我们计算一个数的原码时候,第一位是符号位,这就是有符号类型的意思
而无符号类型,第一位是不作为符号位的。
也就是说,对于一个(unsigned int)10而言
它的原码是00000000 00000000 00000000 00001010,所以补码也是这个,而补码的第一位的零是不作为符号位,直接按补码转换成二进制来进行打印。
对于(unsigned int )-10而言
我们还是先写出它的原码
10000000 00000000 00000000 00001010
反码为
11111111 11111111 11111111 11110101
补码为
11111111 11111111 11111111 11110110
-10在内存中存储的是补码,由于第一位不是符号位,所以,直接按补码的大小直接转化为十进制就是它真正的值
代码为
#include<stdio.h> int main() { unsigned int a = 10; unsigned int b = -10; printf("%u\n%u", a, b); return 0; }
注意:unsigned类型需要以%u来打印
结果为
(3)&取地址 *解引用
对于取地址操作符而言,一般都是对一个变量取地址的,这里给出代码,详细说明取地址的各种情形
#include<stdio.h>
int main()
{
//对于变量
int a = 10;
int* p = &a;//取出a的地址,放入p这个指针中
char ch = 'a';
char* pc = &ch;//取出ch的地址
char arr[10] = { 0 };
char* p2 = arr;//取出数组首元素的地址
char* p3 = &arr[0];//取出数组首元素的地址
//对于常量而言
char* p4 = "abcdef";//取出来的是a的地址
printf("%c", *p4);
}
当然还有解引用操作符,如下代码所示,可以通过*去访问a,进而修改它的值
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
*p = 20;
printf("%d", a);
return 0;
}
(4)sizeof
首先sizeof是关键词也是操作符,这两个是不冲突的,不是必须选择一个的。是用来计算一个类型,一个变量的大小的
我们看一段代码
#include<stdio.h>
int main()
{
int a = 0;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a );
printf("%d\n", sizeof(int));
return 0;
}
运行结果为
需要注意的是,sizeof计算一个变量的大小时,括号可以省略,但是计算类型的大小时,括号不可以省略。sizeof int 这种行为就是报错的,但是sizeof(int)不会
而且根据这一点我们还可以证明sizeof不是一个函数,因为我们sizeof一个变量时候,括号是可以省略的,而函数调用的时候,是不能省略括号的。
类似的,sizeof也可以对数组进行操作
#include<stdio.h>
int main()
{
int arr[10] = {0};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof arr );
printf("%d\n", sizeof(int [10]));
return 0;
}
运行结果为
需要注意的是,int [10]是arr数组的类型,要知道,数组其实也是有类型的。这一点在后面还会提到
然后还需要注意的就是这一段代码
#include<stdio.h>
int main()
{
int a = 5;
short s = 3;
printf("%d\n", sizeof(s = a + 3));
printf("%d\n", s);
}
这段代码的运行结果为
第一个打印出2是因为假设里面的发生了,则a+3放到了s里面,s的大小是2
第二个打印出来的是3的原因是sizeof内部的表达式是不进行计算的!!!
而它不进行计算的原因是我们的程序由.c文件变成.exe可执行程序文件时候需要经过几个阶段
而sizeof是在编译阶段就已经计算出来了,所以内部的表达式不会进行计算
(5)~对一个数的二进制按位取反
与计算反码不同的是,这个波浪号是使全部位都按位取反。而计算反码时符号位不会发生改变,当然这个也只能作用于整数。我们举一个例子,如下代码所示
#include<stdio.h>
int main()
{
int a = 0;
printf("%d", ~a);
return 0;
}
运行计算过程为
运行结果为
当然在我们说到这块的时候,我们可以试着应用一下我们的位操作符做一个经典题目
如下图所示,这段代码中,想要使得a的二进制序列中,从右往左数第五个二进制数改为1,该如何实现呢?
这道题有很多人连思考都没有,就想要使用指针,这是一个经典的错误,标准呢的零分,因为指针操作的内存最小单位都是字节,而这个是一个比特位,要知道一个比特位是四个字节。所以使用指针是肯定不可以的。那么该如何解决呢?其实这是一个针对改变比特位的问题,我们自然而然就想到使用位操作符。而位操作符我们截至到目前总共学习了>> 右移<< 左移,&按位与,|按位或,^按位异或,以及~,二进制取反。这几种,而此时我们想要解决我们可以这样想,任何一个数或上1都会变成1,或上0还是原来的数,所以我们可以给这个二进制数或上一个只有第五个比特位是1的一个数,那个数又该如何获得呢?其实这里我们就可以使用左移操作符了,我们只要使用左移操作符,问题就迎刃而解了
#include<stdio.h>
int main()
{
int a = 9;
//00000000 00000000 00000000 00001001
//00000000 00000000 00000000 00010000(可由1<<4得到)
//00000000 00000000 00000000 00011001(按位或得到了目标)
a |= (1 << 4);
printf("%d", a);
return 0;
}
运行结果为
那么问题来了,如果我们又想要吧第五位改回去该如何做呢?
其实我们思考一下,这个问题应该与前一个问题是对称的。所以我们推测,应该是需要使用&,而我们知道,任何一个数&0,都为0,按位与1,仍为原来的数,所以我们得到,这个数应该按位与一个只有第五位是0,其余全为1的二进制序列。那么这个该如何得到呢?其实我们不难想象,在上面中,我们是按位或一个第五位为1,其余全为0的数,而这个数不正好是我们现在需要的那个数的二进制刚好反了过来吗,由此我们得到,使用一个~操作符即可
#include<stdio.h>
int main()
{
int a = 9;
//00000000 00000000 00000000 00001001
//00000000 00000000 00000000 00010000(可由1<<4得到)
//00000000 00000000 00000000 00011001(按位或得到了目标)
a |= (1 << 4);
printf("%d\n", a);
//00000000 00000000 00000000 00011001
//11111111 11111111 11111111 11101111(由~(1<<4)得到)
//00000000 00000000 00000000 00001001(按位与可以得到目标)
a &= (~(1 << 4));
printf("%d\n", a);
return 0;
}
运行结果为
(6)++ --操作符
我们看这样一段代码
#include<stdio.h>
int main()
{
int a = 0;
int b = a++;//后置++,先使用后++
printf("%d", a);
printf("%d", b);
return 0;
}
这个是一个后置++操作符,先使用后++,所以结果应该为11,10
而前置++,也是同理的,只不过他是先++,后使用
#include<stdio.h>
int main()
{
int a = 0;
int b = ++a;//后置++,先引用后使用
//等价于 a = a + 1,b=a;
printf("%d", a);
printf("%d", b);
return 0;
}
运行结果为两个11
还有一种场景是这样的
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", a++);
printf("%d\n", a);
return 0;
}
此时的第一个打印是先使用后++。所以结果应该为10 11
如果换成前置++
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", ++a);
printf("%d\n", a);
return 0;
}
则结果为 11 11
而如果在函数中使用的话,也是会有前置和后置的区别的
#include<stdio.h>
void test(int b)
{
printf("%d\n", b);
}
int main()
{
int a = 10;
test(a++);
test(++a);
return 0;
}
结果为
以上就是++操作符的使用场景的总结,而--操作符和++操作符是完全类似的。我们可以看出++,--这些操作符是带有副作用的,使用之后会改变它自身的值
(7)强制类型转换
这个也是属于一种操作符,我们看一下使用场景
#include<stdio.h>
int main()
{
int a = (int)3.14;
return 0;
}
在这段代码中,我们知道a是一个整型,3.14是一个double类型,我们非要将3.14放到a中,那么就会发生强制类型转换
还有我们之前在三子棋小游戏,扫雷,猜数字小游戏中都使用到了一个srand和time函数
#include<stdio.h>
int main()
{
srand((unsigned int)time(NULL));
return 0;
}
这是因为,time 返回的是一个time_t类型的数据,而srand需要的是unsigned int类型的数据,所以需要强制类型转换
4.sizeof与数组的一个经典题目
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
我们在第四站:数组时候就讲过,数组名一般情况都代表首元素地址,只有两种情况不是,一种是&数组名,这是整个数组的地址,另外一种就是sizeof(数组名),这计算出来的是整个数组的大小。
所以说我们(1)处打印的是4*10=40 (2)处打印的是10*1=10
由于函数传一个数组名传的其实是地址,虽然它形参写的看起来是一个数组,但实际上是一个指针,所以(3)(4)计算的结果都是4/8,32位平台位4,64位平台为8
运行结果为
总结:
好了本节内容就先暂时讲解到这块,本篇篇幅已经较长了,已经讲解了1.3w字了。所以剩余内容放在后面的文章中继续讲解。本节主要讲解了算术操作符,计算机中二进制的表示,移位操作符,位操作符,赋值操作符,以及单目操作符,希望对大家有所帮助!
本站未完,欲知后事,请看下节