第五站:操作符(第一幕)

news2024/11/13 12:52:17

操作符相关的知识,在我们初识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字了。所以剩余内容放在后面的文章中继续讲解。本节主要讲解了算术操作符,计算机中二进制的表示,移位操作符,位操作符,赋值操作符,以及单目操作符,希望对大家有所帮助!

本站未完,欲知后事,请看下节

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/31028.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022亚太数学杯数学建模竞赛B题(思路、程序......)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

C妈妈加密返回值逆向分析

内容仅供参考学习 目标 网址&#xff1a;https://www.chanmama.com/promotionDetail/0IHXaJ1y7lRNaZyiTsKa6vHtvaZDe6zy/live 直播记录接口返回值加密 分析 打开调试工具&#xff0c;先截获一个请求&#xff1a; 进入Initiator 一般情况通过这边进入函数中挨着…

HTML CSS游戏官网网页模板 大学生游戏介绍网站毕业设计 DW游戏主题网页模板下载 游戏娱乐网页成品代码

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

#边学边记 必修5 高项:对人管理 第2章 项目沟通管理和干系人管理 2-5 项目干系人管理

导学 重要知识点&#xff1a;干系人分析&#xff0c;干系人分类、分析技术、项目干系人管理过程的输入、工具与技术、输出等。下午的案例题可能会涉及与项目干系人管理知识点相关的简答题。 干系人管理的过程 1. 识别干系人 识别能够影响项目决策、活动或结果的个人、群体或…

Lysozyme C (46-61) (chicken),62982-31-4

AMPs是由相对较小的分子组成的异质基团&#xff0c;通常含有不到100个氨基酸。 它们最初是在20世纪60年代由Zeya和Spitznagel 在多形核白细胞溶酶体中描述的。 迄今为止&#xff0c;已在数据库&#xff08;如数据库&#xff09;中 确定和登记了2600多个AMP。 它们是由几乎所有的…

Mysql语法三:表的约束和表与表之间的关系以及高级查询

目录 1.表的约束 1.1:约束类型 1.2&#xff1a;NULL约束 1.3&#xff1a;UNIQUE&#xff1a;唯一约束 1.4&#xff1a;DEFAULT &#xff1a;默认值约束 1.5&#xff1a;PRIMARY KEY&#xff1a;主键约束 1.5.1:联合主键 1.5.2&#xff1a;自增主键 1.6&#xff1a;FORE…

HOOPS 3DGS技术概述

1.什么是HOOPS 3D图像系统 HOOPS 3D图形系统&#xff08;HOOPS/3DGS&#xff09;是一款高性能3D图形工具包&#xff0c;适用于开发人员构建Windows和UNIX操作系统以及Internet应用程序。HOOPS/3DGS高度优化的数据结构和算法大大简化了基于CAD/CAM/CAE、科学可视化和地理信息系…

Flutter的三棵树

一、Flutter常见的家族成员 Widget常见的家族成员 Element常见的家族成员 Render常见的家族成员 二、示例代码对应的Flutter Inspector树 示例代码&#xff1a;MyApp->MyHomePage->ErrorWidget&#xff0c;包含了StatelessWidget、StatefulWidget、LeafRenderObjectWid…

远程医疗解决方案-最新全套文件

远程医疗解决方案-最新全套文件一、建设背景二、建设思路三、建设方案四、获取 - 远程医疗全套最新解决方案合集一、建设背景 针对当今社会医疗资源分布不均、看病难看病贵、医学单位间学术交流和研讨开展困难&#xff0c;华为公司推出了远程医疗解决方案&#xff0c;实现远程…

springboot(13):spring 过滤器和拦截器的区别

目录过滤器和拦截器的区别过滤器的使用1.使用spring boot提供的FilterRegistrationBean2.使用原生servlet注解定义Filter拦截器的使用在前面我们讲过拦截器怎么使用&#xff0c;参考&#xff1a;拦截器 和拦截器有个差不多的叫过滤器。 过滤器和拦截器的区别 首先看一下下面…

1535. 找出数组游戏的赢家

给你一个由 不同 整数组成的整数数组 arr 和一个整数 k 。 每回合游戏都在数组的前两个元素&#xff08;即 arr[0] 和 arr[1] &#xff09;之间进行。比较 arr[0] 与 arr[1] 的大小&#xff0c;较大的整数将会取得这一回合的胜利并保留在位置 0 &#xff0c;较小的整数移至数组…

[附源码]计算机毕业设计JAVA竞价拍卖系统

[附源码]计算机毕业设计JAVA竞价拍卖系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis M…

Spring 源码阅读 04:BeanFactory 初始化

本篇要阅读的是 BeanFactory 初始化的部分&#xff0c;也就是 refresh 方法中的这一行方法调用&#xff1a; // Tell the subclass to refresh the internal bean factory. // 这里会调用模版方法&#xff0c;通过子类的实现&#xff0c;初始化 BeanFactory 并解析 XML 配置 C…

企业快速构建可落地的IT服务管理体系的五大关键点

随着数字化转型的发展&#xff0c;IT运维管理环境日益复杂&#xff0c;对管理的要求也随之增高如何提升运维效率&#xff0c;快速落地做好运维管理&#xff0c;搭建一套IT服务管理必不可少&#xff0c;以往我们也对IT服务管理框架进行过总结&#xff0c;当下&#xff0c;面对很…

Linux ALSA 之三:简单的 ALSA Driver 实现

简单的 ALSA Driver 实现一、概述二、Linux ALSA 音频设备驱动实例1、注册 Platform Device & Platform Driver2、创建 card3、PCM 设备相关设定3.1 创建 PCM Device3.2 设置 PCM 操作3.2 PCM HW 初始化4、Control 设备相关设定4.1 定义 snd_kcontrol_new4.2 构造 control5…

C语言实现冒泡排序(图解)

目录 一、冒泡排序是什么&#xff1f; 二、图解冒泡排序过程 三、代码实现 3.1易错点&#xff08;切记切记&#xff09; 四、优化 4.1优化代码 一、冒泡排序是什么&#xff1f; int arr[]{9,8,7,6,5,4,3,2,1,0} &#xff0c;像这样的数组&#xff0c;升序排序。 冒泡排序…

Dynamic Potential-Based Reward Shaping将势能塑形奖励函数拓展为F(s,t,s‘,t‘)

摘要 基于势能的奖励塑形可以显著降低学习最优策略所需的时间&#xff0c;并且在多agent系统中&#xff0c;可以显著提高最终联合策略的性能。已经证明&#xff0c;它不会改变一个agent单独学习的最优策略或多个agent一起学习的纳什均衡。 ------然而&#xff0c;现有证明的一…

正厚软件-软件测试用例设计方法之二-边界值

正厚软件-刘老师的干货分享 上一课我们学习测试用例的等价类划分法&#xff0c;今天我们看下边界值法。 一、方法简介 1、定义&#xff1a;边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充。 2、边界值与等价类…

Linux零拷贝原理学习

文章目录一、为什么要有 DMA 技术?二、传统的文件传输有多糟糕&#xff1f;三、如何优化文件传输的性能&#xff1f;四、 如何实现零拷贝&#xff1f;mmap writesendfile使用零拷贝技术的项目五、PageCache 有什么作用&#xff1f;六、大文件传输用什么方式实现&#xff1f;一…

c++动态创建二维数组和释放

动态创建二维数组和释放 文章目录创建参考博客&#x1f60a;点此到文末惊喜↩︎ 创建 指针数组的方式 使用malloc和free可以兼容c相比于使用STL可以更加灵活但是debug可能数组显示不全 // 初始化int **arr;int row 5;//用于表示行数int col 5;//用于表示列数arr new int…