目录
6.3.3 sizeof计算计算一维数组元素个数和二维数组的行数和列数
6.4.1 练习1 :把整型数字13的二进制数,从低到高的第四位由1改成0
学习C语言中的各种操作符的使用方法及使用场景,可以让我们对于底层编译器的运算逻辑和过程更加清楚,同时能够减少开发中出现的小错误。本篇博客详细总结C语言中的操作符的使用方法、适用场景、及注意事项,学完本篇博客,可以让我们对于各种运算更加清晰,这也是开发人员的基本功。
操作符分类:
一、数据的原码、反码、补码和常见进制及进制转换(铺垫知识)
进位制/位置计数法是一种记数方式,故亦称进位记数法/位值计数法,可以用有限的数字符号代表所有的数值。可使用数字符号的数目称为基数或底数,基数为n,即可称n进位制,简称n进制。现在最常用的是十进制,通常使用10个阿拉伯数字0-9进行记数。 对于任何一个数,我们可以用不同的进位制来表示。常用的进制有:二进制、八进制、十进制、十六进制,在计算机中,数据的存储其实存储的是数据的二进制!整数在内存中存放的是数据的补码,因此在内存中对于整数的运算也是以补码进行运算的。下期博客总结数据在内存中的存储,相信会有更加深刻的理解!加油!
1.1 数据的原码、反码、补码(重点)
数据在内存中都是以二进制的形式存储的,对于整数来说,整数二进制有3种表示形式:原码、反码、补码;有符号整数的三种表示方法均有符号位和数值位两部分,二进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。符号位都是用0表示“正”,用1表示“负”。对于无符号的整数来说,三种表示方法所有位均是数值位。
(1)正整数:原码、反码、补码相同;
(2)负整数:原码、反码、补码必须要进行计算,按照如下规则进行计算:
- 按照数据的数值直接写出的二进制序列就是原码;
- 原码的符号位不变,其他位按位取反,得到的就是反码;
- 反码+1,得到的就是补码;
- 补码得到原码也是可以使用:取反,+1的操作。
对于整数来说,整数中在内存中存储的是补码,这是因为:在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一计算处理; 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
//举例理解为什么存放的是补码? #include <stdio.h> int main() { // 1-1=0如何在内存中计算?CPU只有加法器,因此执行以下运算: 1-1相当于1+(-)1 //方式1:假设存储的是原码 //1的原码:00000000 00000000 00000000 00000001 //-1的原码:10000000 00000000 00000000 00000001 //结果为:10000000 00000000 00000000 00000010 (-2) 不正确 //方式2:假设存储的是反码 //1的反码:00000000 00000000 00000000 00000001 //-1的反码:11111111 11111111 11111111 11111110 //结果为:11111111 11111111 11111111 11111111 (一个非常大的负数) 不正确 //方式3:假设存储的是补码 //1的补码:00000000 00000000 00000000 00000001 //-1的补码:11111111 11111111 11111111 11111111 //结果为:100000000 00000000 00000000 00000000 上述为33位,最高位会被舍弃,因此最终结果为: 00000000 00000000 00000000 00000000 (0) 结果正确 //因此,如果使用补码,计算更加合适! return 0; }
因此,在计算机内部,数据是以二进制的补码进行存储的,在进行计算的过程当然也是以二进制的补码进行计算的,这是由于数据写入内存与IO设备连接是通过总线通断电来传输数据的,内存条是一个非常精密的部件,包含了上亿个电子元器件,它们很小,达到了纳米级别。这些元器件,实际上就是电路;电路的电压会变化,要么是 0V,要么是 5V,只有这两种电压。5V 是通电,用1来表示,0V 是断电,用0来表示。所以,一个元器件有2种状态,0 或者 1。前面指针初识的时候学过,内存条划分的基本单位为一个字节byte(8个比特位),因此才会有各种数据类型,一个原因是:描述生活中的各种数据,另一个原因是:节省内存使用,更加高效的使用内存。
内存的换算单位如下:
为方便快捷进行二进制和十进制的转换,请记住以下数据:
2^0=1; 2^1=2; 2^2=4; 2^3=8; 2^4=16; 2^5 =32;
2^6=64; 2^7=128; 2^8=256; 2^9=512; 2^10=1024;
//举例理解正数和负数在内存中存放
#include <stdio.h>
int main()
{
int a = 200; //正数的原码反码补码均相同
int b =-200; //负数便需要进行计算
//a和b在内存中的存储如下:4个字节,一共32个比特位:
// a的补码: 00000000 00000000 00000000 11001000 (2^7+2^6+2^3=128+64+8=200)
// b的原码:10000000 00000000 00000000 11001000 (与正数相比,只是符号位变成1了 )
// b的反码:11111111 11111111 11111111 00110111
// b的补码:11111111 11111111 11111111 00111000 (内存中存放的便是这个二进制序列)
//注意:补码往回推原码方法相同!
return 0;
}
1.2 常见进制
生活中一个数字默认就是十进制的,表示一个十进制数字不需要任何特殊的格式。但是,表示一个二进制、八进制或者十六进制数字就不一样了,为了和十进制数字区分开来,必须采用某种特殊的写法,具体来说,就是在数字前面加上特定的字符,也就是加前缀。进制也就是进位制。进行加法运算时逢X进一(满X进一),进行减法运算时借一当X,这就是X进制,这种进制也就包含X个数字,基数为X。十进制有 0~9 共10个数字,基数为10,在加减法运算中,逢十进一,借一当十。
1.2.1 二进制(重点)
二进制由 0 和 1 两个数字组成,使用时必须以0b
或0B
(不区分大小写)开头,二进制数运算规律是逢二进一。
在计算机内部,数据都是以二进制的形式存储的,二进制是学习编程必须掌握的基础。
二进制加减法和十进制加减法的思想是类似的:
- 对于十进制,进行加法运算时逢十进一,进行减法运算时借一当十;
- 对于二进制,进行加法运算时逢二进一,进行减法运算时借一当二。
1.2.2 八进制
八进制由 0~7 八个数字组成,使用时必须以0
开头(注意是数字 0,不是字母 o),八进制数运算规律是逢八进一。
八进制有 0~7 共8个数字,基数为8,加法运算时逢八进一,减法运算时借一当八。
1.2.3 十进制
十进制由 0~9 十个数字组成,没有任何前缀,和我们平时的书写格式一样,十进制数运算规律是逢十进一,不再赘述。
在printf函数中%d打印的便是有符号的十进制数(因此要把补码转换成原码,然后计算对应的十进制数才是结果),%u打印的是无符号的十进制数(因此,它的所有位都是数值位,直接按位加权求和即可!)
1.2.4十六进制(重点)
十六进制由数字 0~9、字母 A~F 或 a~f(不区分大小写,它们分别表示十进制数10~15),组成,使用时必须以0x
或0X
(不区分大小写)开头 。十六进制数运算规律是逢十六进一。
十六进制中,用A来表示10,B表示11,C表示12,D表示13,E表示14,F表示15,因此有 0~F 共16个数字,基数为16,加法运算时逢16进1,减法运算时借1当16。
在C语言中,地址占4个字节,一共32个比特位,为了展示方便通常用十六进制,因此8位十六进制数便对应着一个地址编号!
1.3 进制之间的转换(在内存中仍然是以补码进行运算的)
1.3.1 将二进制、八进制、十六进制转换为十进制
二进制、八进制和十六进制向十进制转换都非常容易,就是“按权相加”。所谓“权”,也即“位权”。
假设当前数字是 N 进制,那么:
- 对于整数部分,从右往左看,第 i 位的位权等于N^i-1
- 对于小数部分,恰好相反,要从左往右看,第 j 位的位权为N^-j。
更加通俗的理解是,假设一个多位数(由多个数字组成的数)某位上的数字是 1,那么它所表示的数值大小就是该位的位权。
1.3.2 将十进制转换为二进制、八进制、十六进制
将十进制转换为其它进制时比较复杂,整数部分和小数部分的算法不一样,下面我们分别讲解。
下表列出了前 17 个十进制整数与二进制、八进制、十六进制的对应关系:
1.3.3 二进制和八进制、十六进制的转换
其实,任何进制之间的转换都可以使用上面讲到的方法,只不过有时比较麻烦,所以一般针对不同的进制采取不同的方法。将二进制转换为八进制和十六进制时就有非常简洁的方法,反之亦然。
//举例理解对于负数的转换(内存中不论是正数还是负数,都是以补码进行计算,不过整数的原码反码补码均相同)
//十进制数-100转二进制:
-100的原码:10000000 00000000 00000000 01100100
-100的反码:11111111 11111111 11111111 10011011
-100的补码:11111111 11111111 11111111 10011100
//因此-100的二进制为:11111 11111111 11111111 10011100
//二进制数(有符号) 11011100转十进制数
//最高位为符号位代表这是负数,然后推回原码,计算对应的十进制数
//按位取反: 10100011
//加1得到原码: 10100100进而计算十进制数 -(2^5+2^2=32+4)=-36
二、算术操作符
同数学运算一样,C语言中也有 +、- 、*、/、%算术操作符,/ 结果为商的部分,%为余数。
注意事项:
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
#include<stdio.h>
int main()
{
/*第一种:结果为:1
int a = 6 / 5; //1.2
printf("%d\n",a);
第二种:结果为:1.000000
float a=6/5;
printf("%f\n",a);
第三种:结果为:1.200000
float a = 6.0/5;
printf("%f\n",a);
第四种:结果为:1.200000
float a = 6/5.0;
printf("%f\n", a);
第五种:结果为:1.200000
float a = 6.0/5.0;
printf("%f\n", a);*/
return 0;
}
在C语言中, C 语言提供一些位运算符,用来操作二进制位。
1.位运算符只能用于整数类型的数据,不能用于浮点型。
2. 位运算符的运算过程都是基于二进制的补码运算。
三、移位操作符
- >>:右移操作符
- <<:左移操作符
注意事项:
移位操作符针对的是该数在内存中的补码,移位操作符的操作数只能是整数。并且对于位移运算不要移动负数位,这个是标准未定义的!
int n