C语言操作符汇总(上)

news2024/9/23 13:28:28

目录

前言

一、操作符的分类

二、⼆进制和进制转换

1. 二进制转10进制

2. 10进制转2进制数字

3.  2进制转8进制和16进制

3.1 2进制转8进制

3.2 二进制转16进制 

三、原码、反码、补码 

四、移位操作符

1. 左移操作符

2. 右移操作符 

五、位操作符:&、|、^、~

1.按位与:

2.按位或:

 3.异或

4.按位取反 

 5.趁热打铁

5.1⼀道变态的⾯试题:

5.2 练习一

方案一:

 方案二

方案三

总结


前言

这期我们总结一下C语言操作符,有我们前面学过的,也有没学过的,但是后面我们也会详细讲解;


正文开始

一、操作符的分类

  • 算术操作符: + 、- 、* 、/ 、%
  • 移位操作符: << >>
  • 位操作符: & | ^ `
  • 赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
  • 单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)
  • 关系操作符: > 、>= 、< 、<= 、 == 、 !=
  • 逻辑操作符: && 、||
  • 条件操作符: ? :
  • 逗号表达式: ,
  • 下标引⽤: []
  • 函数调⽤: ()
  • 结构成员访问:->

上述的操作符,我们已经讲过算术操作符、赋值操作符、逻辑操作符、条件操作符和部分的单⽬操作符,今天继续介绍⼀部分,操作符中有⼀些操作符和⼆进制有关系,我们先铺垫⼀下⼆进制的和进制转换的知识。 

二、⼆进制和进制转换

其实我们经常能听到2进制、8进制、10进制、16进制这样的讲法,那是什么意思呢?其实2进制、8进制、10进制、16进制是数值的不同表⽰形式⽽已。
比如:数值15的各种进制的表⽰形式:

15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F

我们重点介绍⼀下⼆进制:
⾸先我们还是得从10进制讲起,其实10进制是我们⽣活中经常使⽤的,我们已经形成了很多尝试:
• 10进制中满10进1
• 10进制的数字每⼀位都是0~9的数字组成
其实⼆进制也是⼀样的
• 2进制中满2进1
• 2进制的数字每⼀位都是0~1的数字组成
那么 1101 就是⼆进制的数字了。 

1. 二进制转10进制

其实10进制的123表⽰的值是⼀百⼆⼗三,为什么是这个值呢?其实10进制的每⼀位是权重的,10进制的数字从右向左是个位、⼗位、百位....,分别每⼀位的权重是10零次方 , 10一次方 , 10二次方 ... 

如下图:

2进制和10进制是类似的,只不过2进制的每⼀位的权重,从右向左如下图所示:
如果是2进制的1101,该怎么理解呢? 

2. 10进制转2进制数字

3.  2进制转8进制和16进制

3.1 2进制转8进制

8进制的数字每⼀位是0~7的,0~7的数字,各⾃写成2进制,最多有3个2进制位就⾜够了,⽐如7的⼆进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算⼀个8进制位,剩余不够3个2进制位的直接换算。

如:2进制的01101011,换成8进制:0153,0开头的数字,会被当做8进制。

int main()
{
	printf("%d\n", 123);
	printf("%d\n", 0123);  //1 * 8^2 + 2 * 8^1 + 3 * 8^0 = 83;
	return 0;
}

运行结果:

3.2 二进制转16进制 

16进制的数字每⼀位是0~9,a ~f 的,0~9,a ~f的数字,各⾃写成2进制,最多有4个2进制位就⾜够了,比如 f 的⼆进制是1111,所以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算。

进制的01101011,换成16进制:0x6b,16进制表⽰的时候前⾯加0x 

int main()
{
	printf("%d\n", 345);
	printf("%d\n", 0x345); //3 * 16^2 + 4 * 16^1 + 5 * 16^0 = 837
	return 0;
}

运行结果:

三、原码、反码、补码 

整数的2进制表⽰⽅法有三种,即原码、反码和补码
有符号整数的三种表⽰⽅法均有符号位数值位两部分,2进制序列中,最⾼位的1位是被当做符号
位,剩余的都是数值位。
符号位都是⽤0表⽰“正”,⽤1表⽰“负”。

正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同。

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。

反码得到原码也是可以使⽤:取反,+1的操作

int main()
{
	int a = -10;
	//-10是存在a中,a是整形变量,是4字节, 32bit
	//10000000000000000000000000001010 - 原码
	//11111111111111111111111111110101 = 反码
	//11111111111111111111111111110111 - 补码  反码+1就得到补码。

	int b = 10;
	//10000000000000000000000000001010 - 原码
	//10000000000000000000000000001010 - 反码
	//10000000000000000000000000001010 - 补码
	return 0;
}

对于整形来说:数据存放内存中其实存放的是补码。 

为什么呢?

在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。 

//1-1
//1+(-1)
//原码计算:
//00000000000000000000000000000001
//10000000000000000000000000000001
//10000000000000000000000000000010  结果不对
//用补码计算:
//00000000000000000000000000000001  1的补码
//11111111111111111111111111111110  -1的反码
//11111111111111111111111111111111  -1的补码
//00000000000000000000000000000000  最高位舍去(结果)

四、移位操作符

<< 左移操作符
>> 右移操作符

1. 左移操作符

移位规则:左边抛弃、右边补0

#include <stdio.h>
int main()
{
 int num = 10;
 int n = num<<1;
 printf("n= %d\n", n);
 printf("num= %d\n", num);
 return 0;
}

注意我们左移的是数的补码,不是原码,上面由于是正数,正数的原码和补码一样,如果我们换成负数的例子:

int main()
{
	int a = -6;
	//10000000000000000000000000000110  原码
	//11111111111111111111111111111001  反码
	//11111111111111111111111111111010  补码
	int b = (a << 1);
	//11111111111111111111111111110100  左移后的补码
	//10000000000000000000000000001011  取反
	//10000000000000000000000000001100  加一(得到左移后的原码)-12
	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

运行结果: 

2. 右移操作符 

移位规则:⾸先右移运算分两种:

1. 逻辑右移:左边⽤0填充,右边丢弃
2. 算术右移:左边⽤原该值的符号位填充,右边丢弃

右移到底是算术右移,还是逻辑右移是取决于编译器的实现,常见的是编译器都是算术右移(我使用的VS2022是算术右移)

int main()
{
	int num = -1;
	//10000000000000000000000000000001
	//11111111111111111111111111111110
	//11111111111111111111111111111111
	int b = num >> 1;
	//11111111111111111111111111111111  算术右移不变
	printf("%d\n", b);
	printf("%d\n", num);
	return 0;
}

运行结果: 

算术右移示意图: 

因为小编使用的是VS2022是使用的算术右移,所以无法演示逻辑右移,在这里我们也画一下示意图:

警告⚠️:对于移位运算符,不要移动负数位,这个是标准未定义的。 

例如:

int num = 10;
num>>-1;//error

 其实这个操作符也可想+-一样赋值简写:

a >>= 1;
a = a >> 1;  //两者表达意思一致

五、位操作符:&、|、^、~

位操作符有:

& //按位与
| //按位或
^ //按位异或
~ //按位取反

注:他们的操作数必须是整数。 

我们看到这个是不是有点熟悉呢:

&(按位与)-->&&(逻辑与)

|(按位或)--> ||(逻辑或)

按位与,按(2进制): 

话不多说,直接上代码:

1.按位与:

int main()
{
	int a = 3;
	int b = -5;

	int c = a & b;
	//00000000000000000000000000000011 3的补码
	//10000000000000000000000000000101 -5的补码
	//11111111111111111111111111111010 -5的反码
	//11111111111111111111111111111011 -5的补码
	// 将两个数的补码按位与:
	//00000000000000000000000000000011 3的补码
	//11111111111111111111111111111011 -5的补码
	//00000000000000000000000000000011 3&-5(补码)
	//00000000000000000000000000000011 正数原反补码相同

	printf("%d\n", c);  //3
	
	return 0;
}

 运行结果:

2.按位或:

int main()
{
	int a = 3;
	int b = -5;

	int c = a | b;
	//将两个数的补码按位或:
	//00000000000000000000000000000011 3的补码
	//11111111111111111111111111111011 -5的补码
	//11111111111111111111111111111011 3 | -5(补)
	//10000000000000000000000000000101 原码(-5)
	printf("%d\n", c);
	return 0;
}

 运行结果:

 3.异或

异或规则:相同为0,不同为1

//异或
//异或规则:相同为0,不同为1

int main()
{
	int a = 3;
	int b = -5;

	int c = a ^ b;
	//将两个数的补码按位异或:
	//00000000000000000000000000000011 3的补码
	//11111111111111111111111111111011 -5的补码
	//11111111111111111111111111111000 3 ^ -5(补)
	//10000000000000000000000000001000 原码(-8)
	printf("%d\n", c);
	return 0;
}

运行结果:

4.按位取反 

我们是不是想起了“!”,我们来想想他两个的区别是:

//按位取反~

int main()
{
	int a = 0;
	int b = ~a;
	//00000000000000000000000000000000  0的原码也就是补码
	//11111111111111111111111111111111  补码
	//10000000000000000000000000000001  ~a的原码(-1)
	printf("%d\n", b);
	return 0;
}

 运行结果:

 5.趁热打铁

5.1⼀道变态的⾯试题:

不能创建临时变量(第三个变量),实现两个数的交换。

其实这个问题解决方法不止一种:

方案一:

int main()
{
	int a = 3;
	int b = 5;

	printf("交换前:a=%d b=%d\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;


	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

运行结果:

 这个方案看似已经解决了我们的需求,但是存在一个潜在的问题:

如果a,b很大,但没超过整型,a+b超过整型的最大值,所以这种方法不可取;

我们来看另一种解决方法;

 方案二:

我们直接来看代码:

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a = %d  b = %d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后:a = %d  b = %d\n", a, b);
	return 0;
}

 我们先来看看结果和我们需要的一不一样:

结果如我们所见,和我们要求一样,但是why?

 在讲这个问题前我们先来将问题具象化:

假设 a = 3,也就是0011;那么a^a是什么?

不难想出,都一样那么结果就是0;

那a^0等于多少?

0011

0000   -->0011(结果不变还是3)

好,上面的两个小问题想明白了,我们接着看这个代码:

    a = a ^ b;-----------1
	b = a ^ b;
	a = a ^ b;

第二步的b = a ^ b中的a已经经过第一步的赋值变成了a ^ b的结果,代入1式子,也就是b = a ^ b ^ b,刚刚在上面讲过了,两个相同的数异或等于0;而一个数与0异或等与本身;

所以也就是                                           b = a;------------------------------------2

此时已经将a赋值给b了,接着执行第三步,a = a^b,代入1,2,也就是a = a ^ b ^ a;同理也就是a = b;也就完成了交换;

因为异或不存在有进位的运算,所以也就不可能存在溢出,所以这个程序也就完美的规避了方案一的缺陷;

5.2 练习一

编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数

这个问题也可用多种方法解决,我们来分析一下各类方法的优缺点:

方案一:

我们想要二进制中的1的个数,我们可以利用获取最后的一位数,用if判断是否为1,如果为1,那就加一,再将最后一位去除,形成一个循环:

代码展示:

int main()
{
	int num = 0;
	scanf("%d", &num);
	int count = 0;
	while (num)
	{
		if (num % 2 == 1)
			count++;
		num /= 2;
	}
	printf("num的二进制中1的个数是%d\n", count);
	return 0;
}

 我们运行一下看看结果:

 但是这个程序有个漏洞,它不能统计负数中的1的个数,比如:

输入-1

-1%2 !=0,-1/2 = 0;

这就无法得出正确答案

其实我们也可以在这个代码上直接进行补救:

我们将输入的数直接看成无符号数:

int main()
{
	unsigned int num = 0;
	scanf("%d", &num);
	int count = 0;
	while (num)
	{
		if (num % 2 == 1)
			count++;
		num /= 2;
	}
	printf("num的二进制中1的个数是%d\n", count);
	return 0;
}

也可得出正确答案:

 方案二

我们还可以利用上面讲的按位与和右移操作符进行确定最低位和往高位推进:

代码如下:

int main()
{
	int n = 0;
	int count = 0;
	scanf("%d", &n);
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if ((n >> i) & 1 == 1)
			count++;
	}

	printf("%d\n", count);
	return 0;
}

 这个思路就不会因为你输入的是负数而出错:

方案三

其实还有一种不容易想到的算法: 

我们利用这种算法来对1进行计数,去除一个1就记一次数:

int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	printf("%d\n", count);
	return 0;
}

运行结果:

5.3 练习三

我们前面学了很多符号,其实每种符号都有用:

例:⼆进制位置0或者置1

编写代码将13⼆进制序列的第5位修改为1,然后再改回0

13的2进制序列:  00000000000000000000000000001101
将第5位置为1后:00000000000000000000000000011101
将第5位再置为0:00000000000000000000000000001101

int main()
{
	int n = 13;
	//把第5为改为1
	n |= (1 << 4);

	printf("%d\n", n);

	//把第5位改成0
	n &= (~(1 << 4));
	printf("%d\n", n);
	return 0;
}

 运行结果

一个问题:如何判断一个数是否是2的n次方

利用上面的知识想想,why,不会私信,评论都ok 


总结

我们在这期主要总结了一下C语言中的操作符,但是由于篇幅的问题,还没有总结完成,下期见!26考研加油!

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

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

相关文章

10-1RT-Thread动态内存管理

10-1RT-Thread动态内存管理 在嵌入式系统中&#xff0c;变量和中间数据一般存放在系统存储空间中。只有在实际使用时&#xff0c;才将它们从存储空间读取到CPU进行运算。存储空间可分为两种&#xff0c;内部存储空间rem和外部存储空间rome或flash。其中ram或称之为内存&…

【Linux详解】命令行参数|环境变量

目录 一、命令行参数 二、环境变量 1.环境变量的基本概念 2.查看环境变量的方法 3.环境变量相关命令 4.环境变量的组织方式以及获取环境变量的三种方法 环境变量具有全局属性 一、命令行参数 【示例1】main函数也是函数&#xff0c;main函数可以带参吗&#xff1f; 没…

Python教程(二十) : 十分钟入门【PyQt6】

文章目录 专栏列表环境准备1 安装 Python2 安装 PyQt6 PyQt6 中的模块使用模块创建一个窗体&#xff1a; PyQt6 常用的控件1. QPushButton&#xff08;按钮&#xff09;2. QLabel&#xff08;标签&#xff09;3. QLineEdit&#xff08;文本输入框&#xff09;4. QTextEdit&…

(4)SVG-path中的椭圆弧A(绝对)或a(相对)

1、概念 表示经过起始点(即上一条命令的结束点)&#xff0c;到结束点之间画一段椭圆弧 2、7个参数 rx&#xff0c;ry&#xff0c;x-axis-rotation&#xff0c;large-arc-flag&#xff0c;sweep-flag&#xff0c;x&#xff0c;y &#xff08;1&#xff09;和&#xff08;2&a…

FFMpeg环境搭建(WIN10)

0、前期准备 软件环境&#xff1a;Win10 qtcreator 软件准备&#xff1a;MSYS2 安装包、 FFmpeg源码 1、软件安装 通过MSYS2安装编译工具 1、打开MSYS2安装包&#xff0c;一路next即可 &#xff08;注&#xff1a;如果需要更改路径可以自行更改&#xff09; 2、安装完成…

虚拟现实辅助工程技术助力多学科协同评估

在当今高速发展的经济环境中&#xff0c;制造业面临着多重挑战&#xff0c;包括提高产品性能、压缩设计周期、实现轻量化设计和降低成本。为了有效应对这些挑战&#xff0c;多学科协同评估成为缩短研发周期和提升研制质量的关键手段。 传统的多学科评估面临着数据孤立与融合困难…

Android 系统源码项目加载预编好的so库

Android 系统源码项目加载预编好的so库 文章目录 Android 系统源码项目加载预编好的so库一、前言二、源码中加载so1、Android.mk加载so加载so的主要相关代码&#xff1a; 2、Android.bp加载so&#xff08;1&#xff09;Android.mk使用源码命令编译成Android.bp&#xff08;2&am…

Java灰度发布

有没有在北京面试java的小伙伴&#xff0c;每家公司面试问的问题都不一样&#xff0c;昨天面试官问到了灰度发布&#xff0c;一脸懵&#xff0c;好像在哪儿听说过&#xff0c;毕竟我都没发布过&#xff0c;之前都是项目组长在干这些事儿&#xff0c;所以聊聊&#xff0c;了解一…

驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结

前言 嘿&#xff0c;小伙伴们&#xff0c;今天我们来一场 Autofac 的学习之旅吧&#xff01; Autofac 是一个轻量级的依赖注入框架&#xff0c;专门为 .NET 应用程序量身定做&#xff0c;它就像是你代码中的 “魔法师”&#xff0c;用它来管理对象的生命周期&#xff0c;让你…

828华为云征文|华为云Flexus X实例docker部署最新gitlab社区版,搭建自己的私人代码仓库

828华为云征文&#xff5c;华为云Flexus X实例docker部署最新gitlab社区版&#xff0c;搭建自己的私人代码仓库 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Ng…

大数据采集迁移工具

Flume Sqoop kafka框架 MQ&#xff1a;消息队列 broker相当于服务器 消息队列

栈和队列(1)

空栈先移动栈顶再加数据&#xff0c;满栈先插入数据再移 栈的基本概念栈是一种后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09;的数据结构。栈支持两种主要的操作&#xff1a;•压栈&#xff08;Push&#xff09;&#xff1a;向栈中添加一个元素。•弹栈&…

Kubernetes v1.28.0安装详解

Kubernetes v1.28.0安装详解 一.环境初始化 要在所有节点执行命令进行配置 1、检查操作系统的版本 此部署环境为CentOS 7.9 [rootCentOS7 ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootCentOS7 ~]#2、主机名解析 为了方便集群节点间的互相调…

活动系统开发之采用设计模式与非设计模式的区别-数据库设计及代码设计

1、数据库ER图 2、应用框架选用 PHP语言对应的thinkphp6.1应用框架 3、功能代码设计(后端) a、父类Base.php i&#xff1a;控制登录&#xff0c;只能登录后管理员才能操作&#xff1b; ii&#xff1a;控制按钮权限&#xff0c;管理员不仅要登录&#xff0c;且必须要有对应菜单…

报错处理:超过Uobject最大数量

处理方式 一、打包时项目中设置游戏中UObject的最大数量为100000000 二、打包后的配置文件中设置 打包路径&#xff1a; 一厅统管\Windows\YZ_YTTG\Saved\Config\Windows\Engine.ini文件下添加配置文件 [/Script/Engine.GarbageCollectionSettings] gc.MaxObjectsInEditor1000…

API 网关 OpenID Connect 实战:单点登录(SSO)如此简单

作者&#xff1a;戴靖泽&#xff0c;阿里云 API 网关研发&#xff0c;Higress 开源社区 Member 前言 随着企业的发展&#xff0c;所使用的系统数量逐渐增多&#xff0c;用户在使用不同系统时需要频繁登录&#xff0c;导致用户体验较差。单点登录&#xff08;Single Sign-On&a…

2024最新!Facebook手机版和网页版改名教程!

Facebook作为全球最大的社交平台之一&#xff0c;允许用户自定义名字和昵称。在Facebook更新姓名可以帮助您更好的展现账号形象。本文将为您提供详细的步骤指导&#xff0c;帮助您在手机APP和网页版上轻松完成Facebook改名操作。 Facebook手机版改名 打开Facebook APP并登录账号…

区块链ARC如何能让节点能够大规模处理交易数据

​​发表时间&#xff1a;2024年8月7日 TAAL技术主管Michael Bckli表示&#xff0c;TAAL公司一直在对ARC进行测试&#xff0c;并准备在今年年底全面发布。因TAAL在区块链交易处理方面具备深厚的专业知识&#xff0c;BSV区块链委托TAAL进行ARC开源参考落地方案的开发。 ARC是一个…

魔珐科技受邀参与外滩大会:以3D数字人AIGC产品赋能大资管行业,重塑金融服务边界

在人工智能浪潮的推动下&#xff0c;金融行业正经历着前所未有的场景革命。2024年Inclusion外滩大会作为行业交流的盛会&#xff0c;汇聚了众多学者、专家及企业领袖&#xff0c;共同探讨AI技术在多领域的深度应用&#xff0c;特别是其在金融行业中的革新潜力。 在外滩大会上&…

在桌面商业分析应用程序中启用高级 Web UI

挑战 Mercur Business Control 应用程序在企业界&#xff0c;尤其是金融领域&#xff0c;拥有悠久的应用历史。它帮助企业处理、可视化和分析海量数据&#xff0c;从而做出明智的商业决策。 随着产品的不断演进和现代化&#xff0c;Mercur Solutions AB 为该应用创建了 Web 客…