当前位置: 首页 > news >正文

零基础入门C语言之操作符详解1

建议阅读本文章前,先阅读一下专栏前面的文章。

目录

前言

一、二进制和进制转换

二、原码、反码和补码

三、移位操作符

四、位操作符

五、逗号表达式

六、下标访问操作符和函数调用操作符

总结


前言

我们在前面的文章中主要介绍了算术操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符和条件操作符,本文主要补充介绍一些前面没有讲到的操作符的知识。


一、二进制和进制转换

虽然我们在日常生活中都是使用十进制,但其实我们在编程过程中经常会听到二进制、八进制和十六进制,那它们分别表示什么意思呢?其实对于n进制来说,它们的规则都是满n进一,并且他们每一位上的数字都只能由0~n-1的数字构成。我们可以拿15举个例子:

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

那既然一个数字有这么多的表示方法,那么他们彼此之间是如何进行转化的呢?我们这里主要介绍几种转化。

首先是二进制转十进制,我们如果用十进制来表示123的话,那他是怎么表示出来的呢。在十进制中,它的每一位都是有权重的,从个位、十位、百位.......,它们分别对应10的0次方,10的1次方,10的2次方......。那么123就是这么来的:

其实二进制和十进制是相似的,只不过是权重是2的0次方,2的1次方,2的2次方。如果是二进制的1101,我们该如何理解呢?你可以先自己算一下,然后和下面的图片对比一下:

那么我们可以从二进制转为十进制,我们该如何从十进制转为二进制呢?很简单,就是我们上面这个过程的逆过程,具体如下图:

然后是二进制到八进制和十六进制的转换。八进制的数字每⼀位是0~7的数字,各自写成二进制,最多有3个二进制位就足够了,比如7的二进制是111,所以在二进制转八进制数的时候,从二进制序列中右边低位开始向左每3个二进制位会换算一个八进制位,剩余不够3个二进制位的直接换算。比如说我们将二进制的01101011换算成八进制就是0153(0开头的数字,会被当做八进制)。

二进制到十六进制的转换与之相似。十六进制的数字每一位是0-9,A-F的数字,各自写成二进制,最多有4个二进制位就够了,比如F的二进制位是1111,所以在二进制转为十六进制的时候,从二进制序列的右边低位开始在每4个二进制位会换算一个十六进制位,剩余不够4个二进制位的直接换算。比如说我们将二进制位的01101011换成十六进制就是0x6B(0x开头的数字会被当做十六进制)。

二、原码、反码和补码

整数的二进制表示方式有三种,即原码、反码和补码。对于有符号整数来说,三种表示方法均有符号位和数值位两部分。二进制序列之中,最高的一位会被当做是符号位,剩余的都是数值位。在符号位中,0表示正,1表示负。正整数的原码、反码、补码都相同,而负整数的三种表示方法各不相同。原码就是直接将数值按照正负数的形式翻译成二进制得到的;而反码则是将原码的符号位不变,其他位依次按位取反;补码则是将反码+1得到的。

我们举个例子:

#define _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
int main() {int a = -10;//-10是存放到a中,a是整型变量,4个字节,32bit//10000000 00000000 00000000 00001010 是-10的原码//11111111 11111111 11111111 11110101 是-10的反码//11111111 11111111 11111111 11110110 是-10的补码int b = 10;//00000000 00000000 00000000 00001010 是10的原码//00000000 00000000 00000000 00001010 是10的反码//00000000 00000000 00000000 00001010 是10的补码return 0;
}

对于整型来说,数据存放内存中其实存放的是补码。这样处理的话,可以将符号位和数值位统一处理;同时,加法和减法也可以统一处理。此外,补码和原码相互转换,其运算过程相同,不需要额外电路。

三、移位操作符

移位操作符包括左移操作符<<和右移操作符>>,但是需要注意的是移位操作符的操作数只能是整数,并且其移动的是二进制位。对于左移操作符来说,其规则是左边抛弃,右边补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;
}

其运行结果如下:

那么这个结果是怎么得到的呢?我们可以画出下面这个图来解决一下:

而针对于右移操作符,其规则就稍微复杂一些了。它分为逻辑右移,也就是左边用0填充,右边丢弃;和算术右移,也就是左边用原该值的符号位填充,右边丢弃。那么我们在编译器中使用右移操作符的时候,到底是逻辑右移,还是算术右移呢?这取决于你所使用的编译器,但是在市面上大部分的编译器使用的全都是算术右移。我们键入如下的代码:

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

其运行结果如下:

可以看到,我们的VS 2022使用的就是算术右移的方法。那么下面我们用图片来展示一下两者是如何进行操作的,首先是逻辑右移,然后是算术右移。

这时候读者会发现一个问题,我们在移动位的时候,都是移动的正整数,那我们如果移动负数的话,会发生什么?但这其实是标准未定义的,所以是不允许的操作。

四、位操作符

位操作符包括以下四种:&(按位与)、|(按位或)、^(按位异或)、~(按位取反)。它们的操作数也必须都要是整数,同样他们也都是对二进制位进行操作的。我们直接来上代码来讲解一下:

	int a = 3;int b = -5;int c = a & b; //按位与运算//00000000 00000000 00000000 00000011 3的补码//11111111 11111111 11111111 11111011 -5的补码//按位与要两者二进制位均为1时才为1,否则为0//00000000 00000000 00000000 00000011 printf("c = %d\n", c); //输出结果为3int d = a | b; //按位或运算//00000000 00000000 00000000 00000011 3的补码//11111111 11111111 11111111 11111011 -5的补码//按位或要两者二进制位有一个为1时就为1,否则为0//11111111 11111111 11111111 11111011printf("d = %d\n", d); //输出结果为-5int e = a ^ b; //按位异或运算//00000000 00000000 00000000 00000011 3的补码//11111111 11111111 11111111 11111011 -5的补码//按位异或要两者二进制位不同时为1,否则为0//11111111 11111111 11111111 11111000printf("e = %d\n", e); //输出结果为-8int f = ~a; //按位取反运算//00000000 00000000 00000000 00000011 3的补码//按位取反是将每个二进制位取反//11111111 11111111 11111111 11111100 printf("f = %d\n", f); //输出结果为-4return 0;

那么这时候会有读者问了,感觉这些东西没什么太大用处啊。那接下来我们来解决一道问题,也就是如何实现两个整数的交换?

如果想要实现交换两个整数数据,我们就必须创建新变量,然后借助这个新变量,来实现数据交换,例如下面这个代码:

#include <stdio.h>
int main()
{int a = 10;int b = 20;int tmp = 0;tmp = a;a = b;b = tmp;printf("a = %d\n", a); //输出结果为20printf("b = %d\n", b); //输出结果为10return 0;
}

这种方式是我们运用的最普遍的一种方式,但现在我们加上一个要求,就是禁止借助其他变量实现这个过程,那我们该如何操作呢?这时候,位操作符就可以派上用场了。

#include <stdio.h>
int main()
{int a = 10;//00000000 00000000 00000000 00001010 10的补码int b = 20;//00000000 00000000 00000000 00010100 20的补码a = a ^ b;//00000000 00000000 00000000 00011110 交换后a的补码b = a ^ b;//其实就是相当于a ^ b ^ b//00000000 00000000 00000000 00001010 交换后b的补码a = a ^ b;//00000000 0000000000000000 00010100 交换后a的补码printf("a = %d\n", a); //输出结果为20printf("b = %d\n", b); //输出结果为10
}

这种计算方法是非常非常巧妙的,它利用的就是两个基本的公式,a^a=0和a^0=a,并且其满足交换律和结合律,故而完成了交换。当然,我们在日常的编程过程中还是使用创建变量来实现数据交换。

接下来,我们来试一下求一个整数存储在内存中的二进制中1的个数。我们首先分析这个东西该怎么求呢,因为二进制属于是逢二进一,那么我们是不是可以先将数字对二取模,如果整除,说明有个0,否则则有1,然后我们就可以再进行除二,循环整个过程,写成代码大概是这个样子的:

#include <stdio.h>
int main()
{int num = 10;int count= 0;//计数while(num){if(num%2 == 1)count++;num = num/2;}printf("二进制中1的个数 = %d\n", count);return 0;
}

我们思考一下,这个代码有没有什么问题呢?我们可以改为-1试一试:

这很明显是错误的,仔细分析我们的代码我们可以看到在进入循环之后让-1除以2会出现问题,那么我们该如何解决呢?其实解决方法很简单,那就是初始化num的时候将其设置为无符号整数就可以了。

#include <stdio.h>
int main()
{unsigned int num = -1;int count= 0;//计数while(num){if(num%2 == 1)count++;num = num/2;}printf("⼆进制中1的个数 = %d\n", count);return 0;
}

最终结果如下:

但是我们可以想一下,其实乘2不就是相当于把二进制位左移1,除以2不就是相当于把二进制位右移1吗,那我们可不可以用位操作符来进行替换呢?答案显然是可以的。我给出一个示例代码:

#include <stdio.h>
int main()
{int num = -1;int i = 0;int count = 0;//计数for(i=0; i<32; i++){if( num & (1 << i) )count++; }printf("⼆进制中1的个数 = %d\n",count);return 0;
}

这个代码的思路就是如果想要去确认这个位上的数字是不是1,我就让它去和1来按位与,如果说最后得到的是1,就说明这位数字是1,反之则为0。但是这段代码又面临着一个问题,就是无论这个数字里有多少个1,它都会原封不动地机械执行32次,这不仅耗费时间而且十分麻烦,那么我们能不能想办法把它简化一下呢?你可以来看一下下面这个代码:

#include <stdio.h>
int main()
{int num = -1;int i = 0;int count = 0;//计数while(num){count++;num = num&(num-1);}printf("⼆进制中1的个数 = %d\n",count);return 0;
}

这个代码是最简单并且最高效的求二进制中1数量的代码,其本质原理就是对于任意整数num,num - 1的二进制会把num最右边的一个1变成0,同时将该1右边的所有0变成1(如果右边有 0 的话)。

五、逗号表达式

逗号表达式就是用逗号隔开的多个表达式,它由左到右依次执行,整个表达式结果是最后一个表达式的结果。其结构如下:

exp1, exp2, exp3, …expN

我们键入如下代码:

#include <stdio.h>
int main()
{int a = 1;int b = 2;int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式printf("%d", c); //输出结果为13return 0;
}

其结果如下:

所以很明显,我们就是从左向右计算,然后输入了最后一个表达式的结果。

那这个逗号表达式到底有什么作用啊,感觉不是很有用。那你可以看下下面的伪代码:

a = get_val();
count_val(a);
while (a > 0)
{//业务处理//...a = get_val();count_val(a);
}

很明显,我们有代码重复。这时候我们通过逗号表达式就可以简化这个过程:

while (a = get_val(), count_val(a), a>0)
{//业务处理
}

六、下标访问操作符和函数调用操作符

下标访问操作符就是[],它的操作数是一个数组名和一个索引值,也就是下标,具体如下:

int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
[ ]的两个操作数是arr和9。

函数调用操作符则是(),它接受一个或者多个操作数,第一个操作数是函数名,剩余的则是传递给函数的参数。

#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;
}


总结

本文系统介绍了编程中常用的操作符知识,包括二进制和进制转换、原码/反码/补码、移位操作符、位操作符、逗号表达式以及下标访问和函数调用操作符。重点讲解了二进制与十进制的相互转换方法,整数在内存中的三种表示形式,以及位操作符的巧妙应用(如交换两个整数和统计1的个数)。文章还通过实例解释了逗号表达式和常用操作符的使用场景,为读者全面理解编程中的操作符提供了详细指导。这些基础知识对理解计算机底层原理和编写高效代码具有重要意义。

http://www.dtcms.com/a/517368.html

相关文章:

  • 元隆盛建设集团有限公司网站青海企业网站建设开发
  • 福建网站建建设房地产销售流程详细
  • 面试学校网站开发平台公司债务风险
  • 工程建设教育网首页梧州网站优化价格
  • 建设银行陕西分行网站简单的网站首页模板
  • 方微商城网站开发wordpress内容主题模板下载失败
  • 一个主做海贼王的网站建设网站需要哪些硬件
  • 网站建设项目实训报告书设计官网和推广的公司
  • 代做网站转账截图新做的网站如何备案
  • 网站建设设计 飞沐高端网站建设公司服务好吗
  • 2024 ICPC 沈阳(JDBEM)
  • OS:使用ffmpeg从视频文件提取音频文件
  • 企业网站的内容模块常州网站建设方案书
  • 软件公司网站建设做淘宝客为什么要做网站
  • 微网站一键导航一站式的手机网站制作
  • AWQ量化
  • 四川禾力建设工程质量检测有限公司网站宣传片拍摄制作多少钱
  • 自动化文件管理:分类、重命名和备份
  • 长沙网站seo推广东莞网站制作建设公司
  • 大型网站建设哪个好网络营销专业背景
  • 如何在Elasticsearch中设置召回率优先的搜索策略?
  • 泉州网站建设工作室涿州住房和城乡建设局网站
  • 上海网站专业制作怎么在百度上推广自己
  • a站插画ss网站代码
  • 操作系统-内存寻址
  • 面试Spring全家桶(一)
  • 快速上手TypeScript,TS速通
  • 建设电影网站视频素材资源管理器
  • 数据结构——拓扑排序(2)
  • 天津大邱庄网站建设公司fontawesome 网站