1. 操作符的分类
2. 二进制和进制转换
15 的 2 进制: 111115 的 8 进制: 1715 的 10 进制: 1515 的 16 进制: F//16 进制的数值之前写: 0x//8 进制的数值之前写 :0
就比如十进制的123, 个位数的权重是十的零次方,十位数的权重是十的一次方,百位数的权重是十的二次方,依此类推,123 = 3 * 1 +2 * 10 + 3 * 100。
2.1 2进制转10进制
10进制123每一位权重的理解
2.1.1 10进制转2进制数字(除2取余的方式来计算)
2.2 2进制转8进制和16进制
2.2.1 2进制转8进制
2.2.2 2进制转16进制
3. 原码、反码、补码
为什么呢?在计算机系统中,数值⼀律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
4. 移位操作符
4.1 左移操作符
4.2 右移操作符
1. 逻辑右移:左边用0填充,右边丢弃2. 算术右移:左边用原该值的符号位填充,右边丢弃
右移到底是采用算术右移还是逻辑右移是取决于编译器的!通常采用的都是算术右移。
逻辑右移1位演示
算术右移1位演示
警告⚠️:对于移位运算符,不要移动负数位,这个是标准未定义的。
5. 位操作符:&、|、^、~
& // 按位与| // 按位或^ // 按位异或~ // 按位取反
注:他们的操作数必须是整数。
1.对于按位与来说,要用两个操作数的补码的二进制位进行运算。
运算规则:对应的二进制位,有0则为0,两个同时为1,才为1。
写法:
2.对于按位或来说,要用两个操作数的补码的二进制位进行运算。
运算规则:对应的二进制位,只要有1就是1,两个同时为0,才为0。
写法:
3.对于按位异或来说,要用两个操作数的补码的二进制位进行运算。
运算规则:对应的二进制位,相同为0,相异为1。
对于按位与、按位或、按位异或,都是双目操作符,有两个操作数。
写法:
4.对于按位取反来说,要用一个操作数的补码的二进制位进行运算。
运算规则:按二进制位取反,把0变成1,把1变成0。
写法:
注:这几个操作符都能影响到符号位。
⼀道变态的面试题:
这种算法好像解决了问题又好像有点问题,但这两个变量小的时候,这种算法还可以,这两个变量是整型,有他们的上限,如果a、b都特别大,但是又没超过一个整型大小,但是它们两个相加得到的数字就超过了整型大小,a + b的值放到a里面就放不下这个值了,这个时候有些值就丢了,这个时候就放不下原来的值了,这个时候可能就会导致问题。
这个代码交换两个整型变量是没有任何问题的,这个操作符可以作用于两个操作数运算的。
^操作符的特点:
3 ^ 3 = 0;
a ^ a = 0;
3 ^ 3 ^ 5 = 5;异或支持交换律
3 ^ 5 ^ 3 = v
假如我们就要交换整型变量,我们是用方法1还是用方法2呢?
建议还是用方法1。1:虽然多创建了空间,但这种代码可读性高,容易理解。2:这种方法效率很高。第二种方法,1:虽然也能解决问题,但是它的可读性差。2:效率没有第一种方法高。第二种写法是面试官逼着我们写出来的,没有面试官,我们想不到这种方法。
int count_bit_one(int n)
{
int count = 0;
while (n)
{
if (n % 2 == 1)
count++;
n = n / 2;
}
return count;
}
int main()
{
int num = 0;
scanf("%d", &num);
int ret = count_bit_one(num);
printf("%d\n", ret);
return 0;
}对于上面的这个代码,当我们输入-1的时候,得到的值为0。我们知道,运算的时候都是拿补码来运算的,-1的补码全是1,而结果是0。其实是因为当我们运算的时候,-1%2的余数是-1,-1/2的值是0,count的值没有改变,这个时候得到的值就是0。如果想把-1的补码全都变成有效位,就要让它传的是无符号整型。无符号整形站在1的补码这个角度就会认为是一个非常大的正数。
另一种写法:
如果我们想知道一个补码的最后一位是0还是1,就让这个数按位与1就可以了,前面的都是0,得到最后的结果就是1或者0。
下面这个效率更高,这种算法是有几个1算几次,前两种算法是不管有几个1,都要算32次。
写一个代码判断是不是2的次方数。
if (a & (a - 1) == 0)
printf("yes!");2的次方数只有这样的:
00000000000000000000000000000010
00000000000000000000000000000100
00000000000000000000000000001000
…………
13的2进制序列: 00000000000000000000000000001101
将第5位置为1后:00000000000000000000000000011101
将第5位再置为0:00000000000000000000000000001101
这里我们可以想象将操作数的第5位按位或1(按位或的数第n位为1,其它全为0,这里把这个数称为x)就可以了,但是如果要求的是第n位修改为1的话怎么办?这时我们可以让该操作数按位与或1的左移n-1位就可以了。
得到的结果是29,说明操作成功。
要将第n位再改回去,可以让得到的值按位与第n位的0(按位与的数第n位为0,其它的全为1),但如何得到这样一个值呢?可以让1向左移动n-1位,再取反就可以了。
当然,下面这样写也是可以的。
二进制位的操作其实在嵌入式、单片机编程中非常常见。
6. 单目操作符
7. 逗号表达式
if语句也可以写逗号表达式int main()
{
int a = 0;
int b = 0;
int c = 0;
int d = 0;
if (a = b + 1, c = a / 2, d > 0)
return 0;
}要看最后一个表达式的结果来决定if要不要进去。
下面这段代码是一段伪代码,所谓伪代码就是不是真实的代码,是一段假的代码,这段代码没有实际的意义。
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
//...
a = get_val();
count_val(a);
}这种代码看起来比较冗余,可以采用逗号表达式的形式
while (a = get_val(), count_val(a), a > 0)
{
//业务处理
}
8. 下标访问[]、函数调用()
8.1 [ ] 下标引用操作符
int arr[10] = { 1,2,3,4,5 };这个arr[10]的[ ]不叫下标引用操作符,只是在数组定义时的一种语法,是指定数组元素大小的,这个地方不是在访问数组的某个元素。
arr[4];//数组中下标是4的元素
//[ ]这个方块就是下标引用操作符,通过这样的操作符来访问arr数组中下标为4的元素
//[ ]的两个操作数是arr和4。4既叫下标又叫索引。
8.2 函数调用操作符
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("haha\n");//()就是函数调用操作符printf("%d\n", 100)//这里的操作数是printf和"%d\n"(包括这个双引号,这是一个格式字符串)和100
int ret = Add(3, 5);//这里的()也是函数调用操作符,操作数是:Add和3和5
return 0;
}函数调用操作符至少有1个操作数。因为当一个函数不传参的时候,直接去使用,这个时候就没有参数的概念了,一个函数调用的时候可以没有参数,但是不能没有函数名。至少有一个操作数就是函数名。
9. 结构成员访问操作符
9.1 结构体
9.1.1 结构的声明
结构体的关键字叫struct,关键字的后面 tag 是一个标签名,这个标签名自定义,下面是一个大括号,里面是成员列表,大括号里面可以有一个成员,有多个成员,但不能没有成员,至少有一个。variable-list是变量列表,下面再给大家说。
struct student
{
//下面这些就称为成员变量
char name[20];
int age;
float score;
};结构体里面,这些叫做成员变量,这些变量放在一起叫做成员列表。那要如何创建一个变量呢?类型创建变量,struct student构成一个类型名struct student s1;
9.1.2 结构体变量的定义和初始化
struct student
{
//下面这些就称为成员变量
char name[20];
int age;
float score;
}s4,s5,s6;//这里可以写一个,也可以写多个,也可以一个不写,不写的时候就没有创建变量。我们用struct student类型创建了变量,这里可以写多个变量,所以叫变量列表。在这创建s4,s5,s6和s3是一样的,也是全局变量。
//s4,s5,s6也是用结构体创建的变量,
struct student s3;//全局变量
int main()
{
struct student s1;//局部变量
struct student s2;
return 0;
}当然在变量创建的同时也可以给它一些值。结构体变量在初始化的时候用{}。struct student
{
//下面这些就称为成员变量
char name[20];
int age;
float score;
}s4,s5,s6;//这里也可以初始化,例如:struct student
{
//下面这些就称为成员变量
char name[20];
int age;
float score;
}s4={······},s5,s6;
struct student s3 = { "王五",20,88.8 };//全局变量
int main()
{
struct student s1 = { "翠花",20,98.0 };//局部变量
struct student s2 = { "旺财",16,68.8 };
return 0;
}对结构体嵌套的初始化:
9.2 结构成员访问操作符
9.2.1 结构体成员的直接访问
9.2.2 结构体成员的间接访问
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = { 3, 4 };
struct Point* ptr = &p;
ptr->x = 10;
ptr->y = 20;
printf("x = %d y = %d\n", ptr->x, ptr->y);
return 0;
}
10. 操作符的属性:优先级、结合性
10.1 优先级
10.2 结合性
5 * 6 / 2 ;
11. 表达式求值
11.1 整型提升
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度⼀般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
// 实例 1char a,b,c;...a = b + c;
如何进行整体提升呢?
1. 有符号整数提升是按照变量的数据类型的符号位来提升的也就是说原来是有符号数,提升时在高位补的就是符号位,2. 无符号整数提升,高位补0int main()
{
char a = 20;
char b = 130;
char c = a + b;
printf("%d\n", c);
return 0;
}a的原、反、补码是:00000000000000000000000000001010b的原、反、补码是:00000000000000000000000010000010要把a放到char类型里面,char类型的大小是一个字节,占八个比特位,而a里面放的是00001010,b里面放的是10000010,a和b不到一个整型的大小,要发生整形提升,那要怎么发生整形提升呢?有符号整数提升是按照变量的数据类型的符号位来提升的,a的符号位是0,所以前面要补0,把它补成32位(00000000000000000000000000001010)这就是一个整形了。b也是一样,b的符号位是1,前面要补1才行(11111111111111111111111110000010),整型提升完之后才进行相加,(11111111111111111111111110010110)加完之后是这个补码放到c中。c是八个比特位,存到c里面的就是(10010110),当是4个字节的整型值的时候,是32个比特位,但是非要把它放到char类型的时候,放不下,就要发生截断了。打印c的时候,对c进行整型提升,得到的是(11111111111111111111111110010110),也就是-106。
11.2 算术转换