☆Welcome to House's blog!☆
本人主页:神王豪斯(重拾基础期)-CSDN博客
本文为C语言操作符部分内容的学习分享,文章中有哪些地方说的不对、代码有哪些可以改进的地方或是文章有哪些方面需要改进就有劳大家打在评论区啦!那咱们废话不多说,直接开始吧!
在编程和数学运算的世界里,操作符扮演着至关重要的角色。它们就像是精准的工具,帮助我们对数据进行各种处理和计算。操作符通过特定的符号和规则,使得我们能够执行加、减、乘、除等基本运算,以及更复杂的逻辑判断、位操作和赋值等操作。了解操作符的工作原理和分类,对于我们编写高效、准确的代码和解决数学问题具有十分关键的意义~
1.操作符分类
I. 算术操作符: + 、- 、* 、/ 、%
II.移位操作符: << 、>>
III.位操作符: &、 |、 ^
IV.赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
V.单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)
VI.关系操作符: > 、>= 、< 、<= 、 == 、 !=
VII.逻辑操作符: && 、||
VIII.逗号表达式: ,
X.下标引⽤: [ ]
XI.函数调⽤: ( )
XII.结构成员访问: . 、->
(转载自鹏哥C语言~)
上述操作符中,移位操作符与位操作符在使用的过程中都会涉及二进制的计算,因此为了更清晰地分享这部分内容,就让我们先从二进制入手,对其有一个更深刻的认识吧~
2.二进制与进制转换
2.1 二进制
2.1.1进制
在深入了解二进制前,我们先要知晓进制这个概念,举个例子:
在做数学时,数字大小满10进1,这便是我们熟知的10进制;
1分钟有60秒,满60秒进1的分钟数,这可理解为60进制;
一周7天,天数满7便进1的周数,这可理解为7进制;
总而言之,进制就是进位计数制,是咱们人为规定的用来更好的表示数值进位方法~
想必正在看这篇文章的你也一定对2进制、8进制或16进制都不陌生了,在学习变程度过程中我们时常会听到它们的名字,可这些个名称的背后究竟隐藏着那些不为人知的秘密呢?接下来就让我们揭开它们神秘的丝绸面纱,一睹纱后的醉人芳容~
2.1.2 2进制、8进制、16进制
事实上,无论是那种进制,就如我们刚刚所说,都只是一种数值的表示方法而已,我们千万不要自己给他们加太多的高难度滤镜、妄自菲薄从而失去了掌握它们的勇气。
先来了解一下它们各自的含义以及有什么实际的意义:
二进制:
含义:只使用 0 和 1 两个数字来表示数值,满 2 进 1。
意义:-是计算机内部存储和处理数据的基本形式。计算机的硬件电路基于二进制工作,因为只有两种稳定的状态(开/关、高电平/低电平),易于实现和操作。
-对于理解计算机的底层工作原理,如内存存储、位运算等非常重要。
八进制:
含义:使用数字 0 - 7 表示数值,满 8 进 1。
意义:-在某些特定的编程环境或场景中,八进制可以提供一种更简洁的方式来表示二进制数据。
-有时用于特定的文件权限设置等。
十六进制:
含义:使用数字 0 - 9 和字母 A - F 表示数值,满 16 进 1。
意义:-能更紧凑地表示二进制数据,特别在处理内存地址、颜色值、字节编码等方面,比二进制更易读和书写。
-在调试和分析程序时,十六进制常用于查看和操作内存中的数据。
了解了它们的计数规则后,就让我们来一起看个具体例子:
用三种进制分别表示数字15
15的10进制:15
15 的 2 进制: 111115 的 8 进制: 1715 的 16 进制: F
2.2 进制转换
在开介绍各进制的相互转化前,我们需要先明晰一个概念:权重。
拿十进制来说:在10进制中,第一、二、三...位数就是我们常说的个位、十位、百位...各个位数在一个数字中所对应的权重就表示为10^0(十的零次方)、10^1(十的一次方)、10^2(十的二次方)......
具体的权重值就要看各个位数上所对应的数是多少了,如:十位上的数字为6,则十位的权重值就为6*10^1=60。
2.2.1 二进制与十进制的相互转化
二进制转化为十进制:
因为我们现在所看到的任何一个数字都是10进制下的数值表示,因此要实现二进制向十进制的转化,我们就需要算出二进制每一位数所对应的权重值,所有位数的权重值的总和便是我们十进制表示下的二进制数啦~
举个具体例子:
已知一个二进制数:1101,其对应的10进制数是多少?
2进制位数 | 4 | 3 | 2 | 1 |
位数上对应的数值 | 1 | 1 | 0 | 1 |
权重 | 2^3 | 2^2 | 2^1 | 2^0 |
权重值 | 1*2^3 | 1*2^2 | 0*2^1 | 1*2^0 |
总值(对应的10进制数) | 1*2^3+1*2^2+0*2^1+1*2^0=13 |
因此,二进制数1101所对应的10进制数便是13~
十进制转化为二进制:
先说思路:将一个十进制数 n 不断 /2,过程中将每次的余数保留,直到n==0时停止操作,以最后一次倒着向前的顺序,将余数填入对应的二进制位数,即可得到十进制数的二进制表示啦~
可能文字描述还是有些晦涩,废话不多说,直接上图!
当然还可以通过代码进行验证:
#include<stdio.h>
int main()
{
int n = 0;
int i = 0;
int count = 0;
int a[10] = { 0 };
printf("请输入一个小于等于1023 的数\n");
scanf("%d", &n);
while (n)
{
a[i] = n % 2;
i++;
n = n / 2;
printf("%d\n", n);
count++;
}
printf("所对应的二进制数为:");
for (int j = count-1; j >=0; j--)
{
printf("%d ",a[j]);
}
return 0;
}
运行效果~
2.2.2 二进制与八进制、十六进制间的转化
2.2.2.1二进制与八进制间的转换
可以使用“取三合一法”,具体步骤如下:
以小数点为界,将整数部分从右向左每三位分为一组,若最后一组不足三位则在左边补0(或直接有多少位就算多少位的数值然后进行转换);小数部分则从左向右每三位分为一组,若最后一组不足三位则在右边补0(或直接有多少位就算多少位的数值然后进行转换)。
对照二进制与八进制数的对应关系,将每三位二进制数转换为一位八进制数。
二进制与八进制的对应关系为:
二进制:000、001、010、011、100、101、110、111
八进制:0 、1 、 2 、 3 、 4 、 5 、 6 、 7
也许看到这里你和我也有相同的疑惑,“难道这七组我全都要记下来嘛......o(╥﹏╥)o”
当然不是~
仔细看我们不难发现其中的规律,它事实上就是把二进制数从右到左三个为一组,然后在每一组中又从右到左将三位数字分别看做这一组里的2^0、2^1、2^2。就如上面所展示的,每一组无论如何都不会大于7(最大就111),因此我们只需要将每一组的总和算出来,看它等于多少个8^0,得出的结个数就为这一位的八进制数啦~
“听你这么一说好像还真是那么回事儿啊,但感觉距离完全搞懂还是差点意思 ”:
那我们就来个具体例子吧~
比如:将二进制数01101010转化的八进制数是多少?
二进制 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 |
分组后每组的值 | 0*2^1 | 1*2^0 | 1*2^2 | 0*2^1 | 1*2^0 | 0*2^2 | 1*2^1 | 0*2^0 |
=0 | =1 | =4 | =0 | =1 | =0 | =2 | =0 | |
0+1=1 =1*8^0 | 4+0+0=5=5*8^0 | 0+2+0=2=2*8^0 | ||||||
八进制 | 1 | 5 | 2 |
因此,01101010的八进制表示就为0152(0开头的数字会被当做是8进制数)了,这下明白了吧~
“有来就要有回,那要是知道了一个八进制数又如何转换回二进制呢?”
第一种方法:还记得刚才那七对数吧~已知八进制数之后直接将每一位对应的二进制数写出来再按顺序合到一起,完事儿!
第二种方法便是:先将其转化为10进制数,再转化为2进制数。但要注意的是,在八转十的过程中可就不想我们刚刚二转八那样只用到8^0了,这下每一位上的权重数都要按照那一位所对应的原本的权重去计算了嗷!
打个比方~
就拿刚刚算出来的152为例:从右向左,2对应8^0,5对应8^1,1对应8^2,这三位的权重数分别是:2*8^0=2,5*8^1,1*8^2,再将这三个权重数加到一起,在对其进行%2、/2的操作(上面刚刚讲过的方法),这样也可以得出对应的二进制数~
2.2.2.2 二进制与十六进制间的转换
采用“取四合一”的方法:
从低位起从右到左,每四位二进制数对应一位十六进制数,最后一组不足四位时,左边添零凑齐四位(或直接有多少就算多少)。
在开始分享进制间的转换前我们分别对三种进制都有过介绍,16进制内的范围是数字0~9与字母a~f,在数字9之后紧接着的便是字母a,直到f后便进行进一操作。
二进制与十六进制的对应关系如下:
(十六进制)0 = 0000(二进制)
1 = 0001
2 = 0010
3 = 0011
4 = 0100
5 = 0101
6 = 0110
7 = 0111
8 = 1000
9 = 1001
A(或a)= 1010
B(或b)= 1011
C(或c)= 1100
D(或d)= 1101
E(或e)= 1110
F(或f)= 1111
当然,除了记住这些对应的信息,一一比对完成转换以外,还有另外的转换方法:
与二变八相似,每一组中的四位二进制数从右到左分别对应着2^0、2^1、2^2、2^3,也就是说一组数的总和最大不会超过15,因此,要求出二进制数的十六进制表示,我们要做的便是分组后求出每一组数的和,若和等于0~9,那么对应的十六进制直接就直接为得出的结果;若和为10~15,十六进制对应的就为a~f。
再来打个比方~(比方...对不起了!)
算出01011011的十六进制表示:
二进制 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
分组后每组的值 | 0*2^3 | 1*2^2 | 0*2^1 | 1*2^0 | 1*2^3 | 0*2^2 | 1*2^1 | 1*2^0 |
=6 | =11 | |||||||
十六进制 | 5 | b |
因此,01011011对应的十六进制数便是0x5b(0x开头的数会被当做是16进制数)。
注意!!!
若某个组和超过9,千万不可在心里直接按照顺序数是对应的哪个,就像上面的11,若按照顺序数下来应该对应的是a(0~9是十个数,a刚好是第十一个),而所对应的正确答案b是上面那一竖条字符里的第12个,和我们求得的值是对不上的!!!
那应该怎么操作呢?
反正记住一点就够了:10对应着a,后面的11,12......就按照正常的字母顺序b,c......就可以啦~
已知十六进制数如何求二进制数呢?
方法一:一一对应。按照上面那一竖溜下来的16组对应数,将每一位十六进制数按照顺序替换成对应的二进制数
方法二:先化成10进制,再转回2进制。对于十六进制数,从右往左的每一位的权重分别为16^0、16^1、16^2......求出总的权重数的和后再循环%2、/2的操作,直到权重总数为0时停止。
2.2.2.3 八进制、十进制、十六进制间的转换
刚刚我们已经把二进制与其他三种进制间的相互转换都梳理了一遍,可如果要实现另外三种进制之间的转化应该怎么做呢?
相信绝世聪慧的你一定已经想到了:无论是哪一种进制,都先化为2进制,再用2进制转成其他的目标进制表示就搞定啦~
3.原码、反码、补码
3.1 原反补码的本体
原码、反码和补码是计算机中用来表示带符号整数的三种编码方式。
在任意一个带符号的整数的二进制表示中都存在符号位与数值位:
从左向右的第一位便是符号位,之后的都为数值位
若符号位上的数值为0,则表示这是个负数;若为1,则为正数。
3.2 原 、反 、补间的转换过程
正数的原反补码都相同;
负数则三者都不一样:
原码:符号位为1,另外的数字转换为二进制形式变为原码
反码:除了符号位以外,其他的都按位取反,得到反码
补码:在反码的基础上再加1,即为补码
(注意:补码转原码也可以直接取反+1~)
例:
原码 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 1 |
反码 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
补码 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 |
对于整形来说:数据存放内存中其实存放的是补码!
之所以这样,是因为采用补码能够对符号位和数值域进行统一处理;并且,加法与减法也能够统一处理(毕竟 CPU 仅有加法器)。另外,补码和原码相互转换时,其运算过程是一致的,无需额外的硬件电路~
4.移位操作符 << , >>
移位操作符的作用数只能是整数!
4.1 左移位操作符
4.1.1 操作原理
操作准则:“左边抛弃,右边补零”(仅对数值位来说,字符位不变)
整数中,一个int占32个bit位,进行左移位操作就像下面这样:
(正数进行左移位操作)
(负数进行左移位操作)
4.1.2 代码验证
#include<stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
int n = num << 1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
运行结果~
不难发现,进行左移位运算就相当于将原本的数*2,但Num本身的值并不改变
4.2 右移位操作符
右移位操作符和左移位的有一点不一样,它分为两种:逻辑右移与算术右移。
逻辑右移:“右边丢弃,左边(符号位)补零”
算数右移:“右边丢弃,左边(符号位)不变”
注意:无论是左还是右,都不可移动负的位数,如:
n<<-1
n>>-2
这种操作是标准未定义的!
5.位操作符 & 、| 、^、~
& :操作“与”
| :操作“或”
^ : 操作“异或”
~ :操作“取反”
注意:均只能作用在整数上且运算过程在两个数的补码上进行
5.1 运算原理
5.1.1 & “与”
我们将两个二进制的补码上下叠放在一起,让上线爱两个位数之间做“与”运算:
“同时为1才得1,其他情况都得0”
n1补码 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 |
n2补码 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 |
n1&n2 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
5.1.2 | “或”
“同时为0才得0,其他情况都为1”
n1补码 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 |
n2补码 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 |
n1 | n2 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
5.1.3 ^ “异或”
“两数相同则为0,其他情况都得1”
n1补码 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 1 |
n2补码 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
n1 ^ n2 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
5.1.3 ~ “取反”
n补码 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
~n | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
5.2 代码验证
#include <stdio.h>
int main()
{
int num1 = 5;
int num2 = -7;
printf("%d\n", num1 & num2);
printf("%d\n", num1 | num2);
printf("%d\n", num1 ^ num2);
printf("%d\n", ~0);
return 0; }
运行结果~
6. 单目操作符
单目操作符是指只有一个操作数的操作符。常见的单目操作符包括:
(1) `!`:逻辑非操作符,用于对一个布尔值进行取反操作,将“真”变为“假”,将“假”变为“真”;
(2) `+`:正号,通常可以省略不写,对数值没有实际影响;
(3)`-`:负号,用于改变一个数的正负;
(4) `++`:自增操作符,使操作数的值增加1,分为前置++(先自增再使用)和后置++(先使用再自增);
(5)`--`:自减操作符,使操作数的值减1,也有前置--和后置--之分;
(6) `&`:取地址操作符,用于获取变量的地址;
(7)`*`:指针间接寻址操作符,通过指针访问所指向的变量;
(8)`sizeof`:操作数可以是变量或数据类型,用于返回操作数所占用的字节数;
(9)`~`:按位取反操作符,将操作数的每一位(包括符号位)取反,即0变为1,1变为0;在C语言中,对于有符号整数,取反后的结果以补码形式存储,要得到原码,需要先减1,然后除符号位外其余位取反;
(10) 强制类型转换:使用小括号将目标类型括起来,放在操作数前面,用于将操作数强制转换为指定的类型
7. 逗号表达式 “ , ”
exp1,exp2,exp3......expN
7.1 逗号表达式的作用
(1)允许在单个表达式中包含多个子表达式,并依次计算这些子表达式。
(2)逗号表达式的值是其最后一个子表达式的值。
例如:int a = (1 + 2, 3 * 4, 5 - 6);
这里先计算 1 + 2
,再计算 3 * 4
,最后计算 5 - 6
,整个逗号表达式的值是最后计算的 5 - 6
的结果,即 -1
,然后将 -1
赋值给变量 a
。
7.2 逗号表达式的一些使用情况
逗号表达式常用于以下情况:
(1)在 for
循环的初始化或更新部分,可以方便地执行多个初始化或更新操作。
例如:for (int i = 0, j = 10; i < 10; i++, j--)
(2)在函数调用中,传递多个参数时,其中一些参数的计算可能需要通过逗号表达式来组合。
注意:逗号表达式提供了一种在一个表达式中执行多个相关但独立计算的方式,增加了表达式的灵活性和简洁性。但过度使用可能会导致代码的可读性降低,所以应谨慎使用。
8. 下标引用操作符 [ ]
一般都是使用在数组的创建以及数组的访问中:
int arr[10]={0};
int a=arr[3];
乍一眼看去,似乎它的操作数就是它里面的那个数字。
事实上,他的操作数有两个:数组名 + 索引值
int arr[10]={0};
int a=test[5]; //棕色的就是操作数
9. 函数调用操作符 ( )
它接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
void add(int a,int b)
{
int c=a+b;
printf("%d",c);
}
int main()
{
int x=2;
int y=3;
add(x,y);
return 0;
} // 棕色都为()的操作数~
以上便是本次C语言操作符分享的所有内容啦!
非常感激你能看到这里。如果本次分享能对你有所启发,麻烦你为我点赞、收藏、关注,这对我将是莫大的鼓励!(厚脸皮)
再次感谢你的耐心阅读。后续我还会把最后的结构成员访问操作符放在结构体部分一同与你分享,敬请期待!
如果对本次分享有什么建议,劳烦写在评论区~ 每一点意见我都会认真对待,集思广益,日后为你带来更好的内容呈现!
让我们下次再见!