1.进制相关知识
1.1进制转换及表示
1.1.1 进制一般包括二进制,八进制,十进制,十六进制
二进制:以0,1数字表示
八进制:以0-7的数字表示
十进制:以0-9的数字表示
十六进制:以0-F的表示
如果出现10则表示进制进位了,所以在十六进制下不以10表示数字十,而是用A,B,C,D,E,F表示10–15
1.1.2 进制转换
- 十进制整数转为二进制:
整数除2取余,从下向上取余数,如下:105的二进制数为1101001
- 十进制小数转二进制:
小数部分乘2,取整数部分,直到小数消尽为0,如下:5.625的二进制表示为101.101
- 二进制转化十进制计算:
- 整数:第n位(0或1)乘2n-1 之和其中n为当前位从右向左数的次序
1101001=1 * 26 + 1 * 25 + 0 * 24 + 1 * 23 + 0 * 22 + 0 * 21 + 1 * 20=105 - 小数:整数部分按正常计算,小数部分为第n位依次乘2-n 之和,n为当前位从左到右数的次序
101.101=1 * 22 + 0 * 21 + 1 * 20 + 1 * 2-1 + 0 * 2-2 + 1 * 2-3=5.625 - 其余进制之间转化类似 (整数除进制数取余,小数乘进制数取整),通常都先转化为10进制后再相互转化
- 二进制和其他进制间快速转换方式
- 二进制和八进制:二进制每三位确定一个八进制数,二进制 000-111 分别对应了八进制 0-7 ,如八进制数 314 写成二进制数为:011 001 100。通过此方法可实现快速转换八进制和二进制
- 二进制和十六进制:二进制每四位确定一个十六进制数,二进制 0000-1111 分别对应了十六进制的 0-F,如十六进制数 4AB2 对应二进制为 0100 1010 1011 0010。通过此种方法可实现快速转换十六进制和二进制
1.1.3 进制的表示
二进制:(前缀:0b/0B)(后缀:b/B)
八进制:(前缀:0或0O)(后缀:o/O)
十进制:(前缀:无,可加+/-)(后缀d/D)
十六进制:(前缀:0x/0X)(后缀:h/H)
1.2 计算机数据存储
1.2.1 源码、反码、补码
- 正数源码、反码、补码相同;
- 负数源码、反码、补码有区别:
–源码:最高位作为符号位,后面存入相应数据绝对值的二进制位
–反码:源码除符号位其他各位取反
–补码:反码加1 - 计算机内数据均以补码形式存储
1.2.2 关于补码的思考
- 整数定义源、反、补码是为了方便正负数计算。二进制以最高位为0表示正数,最高位为1表示负数,但仅有符号区别不易计算,从而定义源、反、补码。
- 正数三码一致,负数取反加1。当正负数绝对值一致时:
整数 | 源码 | 反码 | 补码 |
---|---|---|---|
10 | 0000 1010 | 0000 1010 | 0000 1010 |
-10 | 1000 1010 | 1111 0101 | 1111 0110 |
-1 | 1000 0001 | 1111 1110 | 1111 1111 |
-127 | 1111 1111 | 1000 0000 | 1000 0001 |
由此可知:
10与-10反码之和为1111 1111
10与-10补码之和为1 0000 0000
-1与-127补码之和为1 1000 0000
故规定,当二进制数全为0时,取数为0;当符号位为1,其余全为0时,取数为当前二进制下能取得的最小数
以8位二进制举例,能容纳数共有256个,正数0—127(此处默认0为正数),负数 -1— -128
- 负数补码为反码加1的原因:
二进制源码+反码的值为当前整数二进制形式下位数的最大值,最大值加1即为该整数所在区间所有数的模(计量系统的计数范围),及可容纳量。
如下图:10的二进制共有4位,说明其最大值为1111–>15,其模为10000–>16
模的意义在于不引入负数概念而能将减法当作加法计算。
设已知数A为7,数B为10,求数B-A的值
A二进制 = 0000 0111 , B二进制 = 0000 1010
8位二进制最大值1111 1111,其模为1 0000 0000
模 - A = C(1111 1001) 与A互补,C为A取反+1 ---》 -A = C
B + C = 1 0000 0011 = 3 第8位为0故结果为正数,第九位不做处理
B-A = B+(-A) = B+C
2.移位操作符
2.1 操作符号
>> 右移操作符
<< 左移操作符
右移操作符将二进制位全部向右移一位,空余位用符号位填充。右移一位后二进制对应十进制数缩小两倍。
左移操作符将二进制数除符号位全向左移一位,空余位用0填充。左移一位后二进制数对应十进制结果扩大两倍。
需要注意,右移操作时,因为负数会在反码减一,所以正负数右移时会有一位的差距
正数:a = 55 = 0000 0000 0011 0111(源码)
计算机中负数存储形式 a = 0000 0000 0011 0111(补码)
移位数 | 补码 | 反码 | 源码 | 对应十进制数 |
---|---|---|---|---|
a>>3 | 0000 0000 0000 0110 | 0000 0000 0000 0110 | 0000 0000 0000 0110 | 6 |
a<<3 | 0000 0001 1011 1000 | 0000 0001 1011 1000 | 0000 0001 1011 1000 | 440 |
负数:b = -55 = 1000 0000 0011 0111(源码)
计算机中负数存储形式 b = 1111 1111 1100 1001(补码)
移位数 | 补码 | 反码 | 源码 | 对应十进制数 |
---|---|---|---|---|
b>>3 | 1111 1111 1111 1001 | 1111 1111 1111 1000 | 1000 0000 0000 0111 | -7 |
b<<3 | 1111 1110 0100 1000 | 1111 1110 0100 0111 | 1000 0001 1011 1000 | -440 |
2.2 移位操作符注意事项
2.2.1 移动范围
- 当左移或右移32位时,会恢复为原来的数据
- 数据左移至一定程度可能改变二进制符号位,进而影响最终结果
- 正数右移至一定程度可能导致结果为 0 ,负数右移至一定程度可能导致结果为 -1。
int a = 44;
int a1,a2,a3;
int b = -44;
int b1,b2,b3;
a1 = a >> 32;//a1 = 44 = a << 32
a2 = a << 26;//a2 = -1342177280
a3 = a >> 6;//a3 = 0
b1 = b >> 32;//b1 = -44 = b << 32
b2 = b << 26;//b2 = 1342177280
b3 = b >> 6;//b3 = -1
a = 44 = 0000 0000 0000 0000 0000 0000 0010 1100(补码)
a << 26 = 1011 0000 0000 0000 0000 0000 0000 0000(这是移位后的补码)
反码 = 1010 1111 1111 1111 1111 1111 1111 1111
源码 = 1101 0000 0000 0000 0000 0000 0000 0000 = -1342177280
b = -44 = 1111 1111 1111 1111 1111 1111 1101 0100(补码)
a << 26 = 0101 0000 0000 0000 0000 0000 0000 0000(移位后的补码)
为正数补码源码均相同结果最终为 1342177280
2.2.2 移动位数的取值及右移操作符
- 移位操作符不能移动浮点数,也不能移动浮点数位
。 浮点数存储方式与正数存储方式有区别(使用科学计数法存储),若用移位符对结果影响很大
。移动浮点数无法明确具体需要移动多少位,故不用 - 移动位数不能用负数
移动负数为等于放方向移动其绝对值位,写法无意义
eg : a >> -1 == a << 1 - 右移操作符包括算数右移和逻辑右移
。算术右移:左边用符号位填充
。逻辑右移:左边用0填充(若为负数,则变为正数),编译器基本都是使用算数右移
3.位操作符
3.1 操作符号
& 按位与
| 按位或
^ 按位异或
符号 | 作用 | 举例 |
---|---|---|
&(按位与) | 0 & 0=0;1 & 0=0;1 & 1=1 | 3 & 5 = 0011 & 0101 = 0001 = 1 |
|(按位或) | 0 | 0=0;1 | 0=1 ;1 | 1=1 | 3 | 5 = 0011 | 0101 = 0111 = 7 |
^(按位异或) | 0 ^ 0=0;1 ^ 0=1;1 ^ 1=0 | 3 | 5 = 0011 ^ 0101 = 0110 = 6 |
3.2 按位异或的使用
- 当两个二进制数对应位值相同则为0,不同则为1;
- 任何数与0抑或其结果都为这个数本身;
3 ^ 3 = 0;0 ^ 3 = 3
由此可知:3 ^ 3 ^ 5 = 5
int a = 3;
int b = 5;
a = a ^ b;//a = 3 ^ 5
b = b ^ a;//b =5 ^ 3 ^ 5 = 3
a = a ^ b;//a = 3 ^ 5 ^ 3 = 5
4.逻辑操作符
&& 逻辑与操作符
|| 逻辑或操作符
- 逻辑操作符不关注二进制位,只关注事件真假
- 逻辑与为全部运行,全部正确则继续运行,有一个错误即跳过
- 逻辑或为包含运行,只要有一个正确即可向后运行
- 逻辑或至少有一个正确则结果为1,否则结果为0
- 逻辑与结果全部正确则结果为1,否则结果为0
int i = 0,a = 0,b = 1,c = 2;
i = a++ && b++ && c++
printf("%d %d %d %d ",i,a,b,c)//i = 0,a = 1,b = 1,c = 2
//a = 0,故第一步就为假,i = 0,跳过后面步骤,只运行了a++
int i = 0,a = 1,b = 1,c = 2;
i = a++ && b++ && c++
printf("%d %d %d %d ",i,a,b,c)//i = 1,a = 2,b = 2,c = 3
int i = 0,a = 0,b = 1,c = 2;
i = a++ || b++ || c++
printf("%d %d %d %d ",i,a,b,c)//i = 1,a = 1,b = 2,c = 2
int i = 0,a = 1,b = 1,c = 2;
i = a++ || b++ || c++
printf("%d %d %d %d ",i,a,b,c)//i = 1,a = 2,b = 1,c = 2
5.赋值操作符
分类 | 符号 | 意义 |
---|---|---|
直接赋值 | = | 将后面内容赋予符号之前,不是等号, a = 10 |
复合赋值符号 | +=,-=,/=,*=,>>=,<<=,%=,&=,|=,^= | 表达式简写,a %= 3 ==> a = a % 3,其余类同 |
6.单目操作符
6.1 单目操作符整理
符号名 | 内容 | 使用 |
---|---|---|
逻辑反 | ! | 否定表达式,if(!(a=0))意为a = 0时为假,不执行 |
正,负 | +,- | 标记一个数是正数还是负数,一般正数符号不带 |
字节数运算符 | sizeof | 计算类型创建的变量所占内存空间的大小(以字节计) |
按位取反 | ~ | 将一个数的二进制取反(0变为1,1变为0) |
前置、后置加加 | ++ | 前置为运行前加1,后置为运行后加1 |
前置、后置减减 | – | 具体格式同加加,只是将加1变为减1 |
解引用操作符 | * | 找到地址指向的值 |
强制类型转换 | () | 实现不同类型值之间的转换 |
6.1.1 sizeof
sizeof后面的括号若是放的一个变量则可以省略,若是一个类型则不能省略;此也佐证了sizeof是一个操作符而并非函数(函数后的括号是函数调用操作符,不可省略)
int a = 0;
sizeof a;
//sizeof int是错误的
6.1.2 按位取反的用法
注意按位取反后会影响符号位
int a = 10;
a = ~a;//a = -11
int b = -10;
b = ~b;//b = 9
a = 10 = 0000 0000 0000 0000 0000 0000 0000 1010(补码)
~a = 1111 1111 1111 1111 1111 1111 1111 0101(补码)
反码:1111 1111 1111 1111 1111 1111 1111 0100
源码:1000 0000 0000 0000 0000 0000 0000 1011 = -11
b = -10 =1111 1111 1111 1111 1111 1111 1111 0110(补码)
~b = 0000 0000 0000 0000 0000 0000 0000 1001(补码)
整数反、补码一致:~b = 9
6.1.3 前置和后置加加(减减)
前置先加减,后置先运行
int a = 0;
int b = a++;//b = a,a = a+1(在此处值未+1,此后值+1;先b = a,再b=a+1)
int c = 0;
int d = ++c;//c = c + 1,d = c(在此处先+1;先c=c+1,再d=c)
6.1.4 解引用操作符
可以通过解引用操作符对地址中的值进行改变
int a = 0;
int* p = &a;//此处需要注意使用的类型
*p = 10;
printf("a = %d",a)//结果为a = 10
6.1.5 强制转换操作符
一般用于大容量类型转为小容量类型,小转大可以自动类型转换
double a = 20.0;
int b = (int)a;
但需注意使用强制类型转换将小转为大可能造成精度损失
7.其它操作符
7.1 关系操作符
>,>= 大于和大于等于
<,<= 小于和小于等于
==,!= 等于和不等于
多用与循环或分支语句,判断是否结束或运行
7.2 条件操作符(三目操作符号)
exp1 ? exp2 : exp3
。表达式1为真时表达式2运行
。表达式1为假时表达式3运行
。此操作符可以与if…else相互转换,但需注意所有条件操作符都可转换为if…else,但if…else不一定能转换为条件操作符
int a = 0,b = 0;
scanf("%d %d",&a,&b);
int c = (a + b == 0! a : b);
printf("%d\n",c);
//a = 1,b = -1时结果为真c = a = 1
//a = 2,b = 4时结果为假c = b = 4
7.3 逗号
逗号表达式:用逗号隔开的多个表达式的总称
执行方式:从左向右依次执行,整个表达式的结果为最后一个表达式的结果
int a = 1,b = 2;
int c = (b++,a = b + 10,b = a + 1);//必须加括号
//int c = b++,a = b + 10,b = a + 1如果不加括号相当于对a,b多次重新定义,会报错
printf("%d",c);//结果为14
7.4 下标引用
用于数组表示,确定数组下标
- arr[7] = *(arr + 7),注意初始化时arr[7]并非表示第七位数的地址,而是数组可容纳七位数
- arr即是数组首位的地址,加7后为第8个数的地址
- arr[7]也等价于7[arr],但是不推荐此种写法
7.5 函数调用操作符
录入实参处的 () ,不可省略,其操作数包括函数名和各个参数,至少有1个(即函数名)
Add(x , y) //此处操作数包括Add、x、y
int Add(int x,int y)
{
return x + y;
}
int main()
{
int a = 2,b = 4;
int c = Add(a,b);//函数调用
printf("%d", c);
return 0;
}
7.6 结构成员选择操作符
符号 | 成员选择表达式 |
---|---|
. | 结构体 . 成员名 |
-> | 结构体指针->成员名 |
- “成员选择表达式”是指结构、并集和类的成员(意为表达式即为某一个成员)。这样的表达式具有所选成员的值和类型
- 此列表描述了成员选择表达式的两种形式:
。 第一种形式中,后缀表达式表示结构、并集或类的值,标识符命名指定结构、联合或类的成员。运算的值是标识符的值,如果后缀表达式是l-value,则运算的值为l-value
。 第二种形式中,后缀表达式表示指向结构、并集或类的指针,标识符命名指定结构、并合或类的成员。该值是标识符的值,并且是一个l-value
。 l-value(左值,可以在赋值号左边出现的表达式或者变量,表示可以被写入)
。 两种形式的成员选择表达式具有相似的效果。- 事实上,如果句点之前的表达式由应用的间接运算符(*)组成,则涉及成员选择运算符(->)的表达式是使用句点(.)的表达式的简写版本
#include<string.h>
#include<stdio.h>
struct student//相当于新建了一个类型
{
char name[20]
int age;
double score;
}
void SetStu(struct student* ps)//struct student相当于一个类型
//因为想要改变数据内容,所以传入了地址
{
//第一种写法
strcopy((*ps).name,"zhangsan");
(*ps).age = 20;
(*ps).score = 100.0;
//第二种写法
strcopy(ps->name,"zhangsan");
ps->age = 20;
ps->score = 100.0;
}
void PrintStu(struct student s)
{
printf("%s %d %lf\n",s.name,s.age,s.score);
}
int main()
{
struct student stu = {0};
SetStu(&stu);
PrintStu(stu);
return 0;
}
综上:ps->age等价于(*ps) . age
8.运算符优先级
8.1 C和C++运算符及其优先级和关联性值:
1)从上至下优先级依次递减
2)结合性即相同优先级下操作符运行顺序
运算符 | 描述 | 结合性 |
---|---|---|
++ | 后置(Post)-increment | 左至右(left to right) |
- - | 后置-decrement | 左至右 |
() | 函数调用(Function call) | 左至右 |
[] | 数组元素(Array element) | 左至右 |
-> | 指向结构成员指针(Pointer to structure member) | 左至右 |
. | 结构或并集成员 | 左至右 |
++ | 前置(Pre)-increment | 右至左(right to left) |
- - | 前置-decrement | 右至左 |
! | 逻辑非(Logical NOT) | 右至左 |
~ | 按位取反 | 右至左 |
- | 减(Unary minus) | 右至左 |
+ | 加(Unary plus) | 右至左 |
& | 取地址(Address) | 右至左 |
* | 间接引用(Indirection) | 右至左 |
sizeof | 变量大小计算(字节) | 右至左 |
new | 分配程序内存 | 右至左 |
delete | 取消分配程序内存 | 右至左 |
() | 强制类型转换(Type cast) | 右至左 |
.* | 指向成员(对象)的指针 | 左至右(left to right) |
->* | 指向成员指针的指针 | 左至右 |
* | 乘(Multiply) | 左至右 |
/ | 除(Divide) | 左至右 |
% | 取余(Remainder) | 左至右 |
+ | 加(Add) | 左至右 |
- | 减(Subtract) | 左至右 |
<< | 左移(Left shift) | 左至右 |
>> | 右移(Right shift) | 左至右 |
< | 小于(Less than) | 左至右 |
<= | 小于等于(Less than or equal to) | 左至右 |
> | 大于(Greater than) | 左至右 |
>= | 大于等于(Greater than or equal to) | 左至右 |
== | 等于(Equal) | 左至右 |
!= | 不等于(Not equal) | 左至右 |
& | 按位与(Bitwise AND) | 左至右 |
^ | 按位异或(Bitwise exclusive OR) | 左至右 |
| | 按位与(Bitwise OR) | 左至右 |
&& | 逻辑与(Logical AND) | 左至右 |
|| | 逻辑或(Logical OR) | 左至右 |
?: | 条件运算符(Conditional) | 右至左(right to left) |
= | 赋值 | 右至左 |
*=,/=,+=,-=,%=,>>=,<<=,&=,^=,|= | 复合赋值(Compound assignment) | 右至左 |
, | 逗号(Comma) | 左至右(left to right) |
8.2 问题表达式
8.2.1 运算顺序不具有唯一性
int a,b,c,d,e,f,g;
g = a*b + c*d + e*f;
* 解析:
运行顺序1:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
运行顺序2:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
这种算法在每个值相互独立时不会有影响,但若是多个表达式相加,且表达式间会相互影响则可能会影响最终结果
8.2.2 运算结果不确定
int c = 10;
int b = 0;
b = c + --c;
* 解析:
--优先级高于+,所以要先运行c--,但运行后c的值便无法确定是否减1
8.2.3 运算顺序无法确定
int a = 1;
int b = 0;
b = (++a) + (++a) + (++a);
printf("%d",b);
* 解析:
此问题为8.2.1和8.2.2的结合
如果先运行 ++a 在运算 + 则结果为
b = 4 + 4 + 4
//++a = 2,++a = 3,++a = 4
//a最终值确定为4
如果先运行前两个 ++a 和第一个 + 则结果为
b = 3 + 3 + 4
//++a = 2,++a = 3,++a = 4
//前两个运行后 a 结果为3,运行第一个+
8.2.4 函数调用混乱
int fun()
{
static int count = 1;
//使变量值生命周期延长
return count++;
}
int main()
{
int ret = 0;
ret = fun() + fun() * fun();
printf("%d",ret);
return 0;
}
* 解析:
函数调用顺序无法确定,进而使结果不具有唯一性
。若函数调用顺序为1–>3–>2则运行结果为:
ret = 2 + 3 * 4 = 14
。若函数调用顺序为2–>1–>3则运行结果为:
ret = 3 + 2 * 4 = 11
。若函数调用顺序为3–>1–>2则运行结果为:
ret = 4 + 2 * 3 = 10
8.2.5 总结
- 我们写出的表达式如果不能通过操作符属性确定唯一计算路径则这个表达式有问题
- 在写表达式时尽量简化,不要将表达式写的过于冗杂,也要习惯使用 () 将表达式优先级进行划分