浮点数
- 除号两边至少要有一个数是浮点数
- 单精度浮点数:6.0f/5.0f 防止double警告
- 取模:两端必须是整数
左移右移操作符
- 不改变原来变量的值。
- 左移:一个数的二进制位,向左移动一位。
- 左边丢弃,右边补0。根据二进制位数移动。
- 有×2的效果。
- 右移
- 算术右移
- 右边丢弃,左边补原符号位
- 逻辑右移
- 右边丢弃,左边补0
- 算术右移
- 左移:一个数的二进制位,向左移动一位。
负数的补码
计算机中存储的是数据的补码。
- 原码:直接根据数值写出的二进制序列就是原码。
- 反码:符号位不变,其他位按位取反
- 补码:反码加一
- 当前的右移操作符使用的是算数右移。测试验证:-1右移一位。
- 正整数的原反补相等
位操作符
- 操作数只能是正数。
- 按位与 &
- 对应的二进制位,两个都是1(真),结果与出来才是1.
- 按位或 |
- 对应的二进制位,有一个为1(真),结果就是1.
- 两个为0,结果也是0.
- 按位异或 ^
- 对应的二进制位,相同为0(假),相异为 1.(真)
练习题
交换两个整型变量的值,不能创建第三个临时变量
- 前边的二进制位 0 进行位操作都不影响结果。所以写简化的二进制位。因为两个为假,进行什么位操作,都是假。
- a 011
- b 101
- 异或结果: 110,想象成密码,和b异或就能翻译出来原来的a。
- 再和 b异或:101,结果就是:a 011
- 0和任何数异或,都是它本身。两个相同数异或结果是 0.
这个题等于是 a ^b^b = a.
#include <stdio.h>
int main()
{
int a = 10; 01010
int b = 20; 10100
a = a^b; a保存的是两个数异或结果
b = a^b; 则:b = a^b^b =a 相同的两个数异或,结果为0.
a = a^b; a=a^a^b =b
printf("a = %d b = %d\n", a, b);
return 0;
}
求一个数的二进制位的1的个数
- 和1按位与。然后每次向右移一位和1,按位与。
#include <stdio.h>
int main()
{
int num = -1;
int i = 0;
int count = 0; 计数
for(i=0; i<32; i++)
{
if( ((num>>i)&1) == 1 )
count++;
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
- 采用相邻的两个数据进行按位与运算
- 把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.
- 那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
int Count(int n)
{
int count=0;
while(n)
{
n=n&(n-1)
count++;
}
return count;
}
解析
- 举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。
- 减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.
- 我们发现减1的结果是把最右边的一个1开始的所有位都取反了。
- 这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。
- 如:1100&1011=1000.。就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.
- 那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
将某一位的0变成1
- 和1左移n位后按位或。
- 要变回去的话:和1左移n位后,取反,然后按位与。
单目操作符
- 只有一个操作数。
- sizeof 是一个操作符 sizeof a 可以不加()。内部放的表达式不参与运算。在编译期间已经算过了。
整型提升和截断
原因:
- CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
- 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
- 任何一个表达式都有两个属性:值属性,类型属性
- a+b值属性----类型属性int
- short(s=a+3),s=5;推导出来a=2.
类型属性决定了以下几个方面:
- 内存占用: 不同的数据类型占用的内存空间不同。例如,int 通常占用 4 字节,而 char 通常占用 1 字节。
- 可进行的操作: 不同的数据类型支持不同的操作。比如,整数类型可以进行数学运算,而字符类型支持字符操作。
- 合法值范围: 每种数据类型都有其合法值的范围。例如,unsigned int 可以存储的值范围是从 0 到最大值(通常为 2^32 - 1,取决于具体的系统架构)。
#include <stdio.h>
int i; //未初始化,默认为0
int main()
{
i--; //-1
if (i > sizeof(i))
//但是sizeof的返回值类型实际为无符号整形,因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形
//一个非常大的数字,超过4或者8
{
printf(">\n"); //所以输出(>)
}
else
{
printf("<\n");
}
return 0;
}
- 整型提升:补符号位
- 整形截断:如int ->char 只保留最低位的8个比特位。最右边的是二进制是低位,最左边的是高位。
寻常算术转换
- 向上转换
- int ->unsigned int->long int
- 向精度更高的转换
- int ->folat
&&
- 只有``a&&b:只有a和b同时为真,才会做什么。。。
- 结合性:从左到右
- 左边为假,后面的就不用算了,
- 左边为真,后面也不用算了。
||
- 有一个为真后面的就不用算了。
三目操作符
- max=a>b?a:b
- a大于b吗?如果大于,max取a的值,否则就取b的值。
逗号表达式()
- 要从左往右以此计算,但其判断作用的是最后一个表达式的结果。
++、–
#include <stdio.h>
int main()
{
int a, b, c;
a = 5;
c = ++a; ++a://加给a+1,结果为6,用加完之后的结果给c赋值,因此:a = 6 c = 6
b = ++c, c++, ++a, a++;
//++c:c为7 c++:c值不变 ++a:a的值为7,a++:a值不变,b取a的值:7
//表达式结束时,c++和a++会给a和c分别加1,此时c:8,a:8,b:7
b += a++ + c; //a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
return 0;
}
复杂表达式的求值
有三个影响的因素
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
- 调试,反汇编,可看到对于相同优先级下:复杂表达式是如何运算的。
16进制数字与内存
在计算机中,16进制(十六进制)数字是一种表示方式,用于简化二进制(0和1)数字的书写。十六进制使用0-9表示数字0到9,使用A-F(或a-f)表示数字10到15。
内存模型:
-
字节(Byte): 计算机内存的基本单位是字节,每个字节由8个二进制位(bit)组成。一个字节可以表示从0到255的数值,即在16进制中,从00到FF。
-
内存地址: 内存中的每个字节都有一个唯一的地址,通常以16进制表示,这样更紧凑易读。
-
数据类型: 在内存中,数据的存储形式和大小根据数据类型的不同而异:
- 整数: 通常以4个字节(32位)或8个字节(64位)存储。比如,整数
255
在内存中以十六进制表示为FF
。 - 字符: 字符串通常以ASCII或Unicode编码存储。ASCII字符在内存中每个字符占1个字节。
- 整数: 通常以4个字节(32位)或8个字节(64位)存储。比如,整数
-
十六进制与二进制有直接的对应关系,每一位十六进制数字可以用 4 位二进制数字表示。例如,十六进制数字 0 - F 分别对应二进制的 0000 - 1111。
示例:
-
数字
255
:- 在十六进制表示为
FF
。 - 在内存中以字节表示为
0xFF
。
- 在十六进制表示为
-
字符
'A'
:- 在 ASCII 编码中,其十六进制值为
41
。 - 在内存中以字节表示为
0x41
。
- 在 ASCII 编码中,其十六进制值为
内存中的表示方式:
- 假设一个整数变量在内存中占用 4 个字节(32 位),如果这个整数的值用十六进制表示为 0x12345678。
- 从低位到高位,在内存中(小端序)的存储顺序是:78(二进制为 01111000)存储在最低地址处,然后是 56(二进制为 01010110),接着是 34(二进制为 00110100),最后是 12(二进制为 00010010)存储在最高地址处。
- 在大端序的计算机系统中,存储顺序则是从高位到低位,即 12 存储在最低地址,78 存储在最高地址。
读取与写入:
在读取内存内容时,计算机根据内存地址从存储中提取数据,并可能转换为十六进制输出进行显示。
- 总结:十六进制是一种方便的数字表示方式,有助于我们理解和操作内存中的数据,特别是在调试和系统编程时,它提供了一种更直观的查看方式。
大小端字节序
以下是大小端字节序的介绍:
定义
- 在计算机系统中,对于多字节数据(如整数)在内存中的存储方式有两种不同的字节序,分别称为大端字节序(Big - Endian)和小端字节序(Little - Endian)。
大端字节序(Big - Endian)
- 概念:大端字节序是指将数据的高位字节(Most Significant Byte,MSB)存储在内存的低地址处,而数据的低位字节(Least Significant Byte,LSB)存储在内存的高地址处。
- 示例:例如,对于一个 4 字节的整数 0x12345678,在大端字节序的系统中,其在内存中的存储顺序是(假设从地址 0x0000 开始存储):0x0000 存储 0x12,0x0001 存储 0x34,0x0002 存储 0x56,0x0003 存储 0x78。
- 常见系统:PowerPC、IBM 大型机等系统通常采用大端字节序。
小端字节序(Little - Endian)
- 概念:小端字节序与大端字节序相反,它将数据的低位字节存储在内存的低地址处,而高位字节存储在内存的高地址处。
- 示例:对于同样的 4 字节整数 0x12345678,在小端字节序的系统中,其在内存中的存储顺序是(假设从地址 0x0000 开始存储):0x0000 存储 0x78,0x0001 存储 0x56,0x0002 存储 0x34,0x0003 存储 0x12。
- 常见系统:x86、x64 等常见的 PC 架构系统采用小端字节序。
字节序的影响
- 数据传输和存储:在不同字节序的系统之间进行数据传输(如网络通信)或读取存储在不同系统上的数据时,需要注意字节序的转换问题,否则可能会导致数据解析错误。
- 程序编写和调试:在处理多字节数据时,程序员需要清楚所使用的系统的字节序,以确保程序能够正确地处理数据。例如,在进行位操作、指针操作以及与硬件设备交互时,字节序可能会对结果产生影响。