进制和操作符
二进制介绍
十进制属于一种常见的进制,它满足满十进一并且数字每一位都是0~9数字组成的。 二进制也是同理满二进一并且数字每一位都是0~1数字组成的。
注:二级制、八进制、十六进制,只是数值的不同表现形式。
比如:
数字:15 二级制表示:1111 八进制表示:17 十进制表示:15 十六进制表示:F
进制之间转换
关于这一点,首先知道每个进制的每一位有权重,N进制的数字从右到左是个位、十位、百位…,分为每一位权重是(N)0 ,(N)1 ,(N)2…。
二级制转换为十进制
说明:将2进制的每个位乘于对应的权重值,再全部相加
十进制转换二进制
二进制转换为八进制和十六进制
- 八进制:八进制的数字每一位是0~7的数字,各自写成二进制,最大数字7的二进制是111,所以最多有3个2进制位就足够了
- 十六进制:十六进制的数字每一位是0~9,a ~f各自写成二进制,最大数字f的二进制是1111,所以最多有4个2进制位就足够了
对于2进制转换为8进制和16进制时,是从2进制序列中右边低位开始向左每3(4)个2进制位会换算一个8(16)进制为,剩余不够3(4)个2进制的直接换算。
比如:
二进制转换八进制 二进制:001 101 011 八进制:1 5 3 二进制转换十六进制 二进制:0110 1011 八进制: 6 b(11)
原码、反码、补码
整型的2进制表示方法有三种:原码、反码和补码
三种表示方法均有符号位和数值位两部分组成
- 符号位:是一个二进制数的最高位(最左边的位),如果符号位为0,则表示该数为正数;如果符号位为1,则表示该数为负数
- 数值位:除了符号位,剩余的都是数值位
原码、反码、补码之间的转换
- 正整数的原码、反码、补码**都是相同**
- 负整数的三种表示方式各不同
- 原码:直接将数值按照正负数的形式翻译为二进制得到的就是原码
- 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码
- 补码:反码+1就得到补码
对于整型来说:数据存放在内存中其实存放的是补码
- 在计算机系统中,数值一律用补码来表示和存储。使用补码,可以将符号位和数值位统一处理
- 加法和减法可以统一处理(CPU只有加法器) 此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
移位操作符(操作数只能是整数)
左移操作数 <<
移位规则:左边抛弃,右边补0
右移操作数 >>
移位规则:分为逻辑右移、算术右移
- 逻辑右移:左边用0填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃
注:对于移位运算符,不要移动负数位,这个是标准未定义的。
在位运算中,将一个数向左移动一位相当于将其乘以2;将一个数向右移动一位相当于将其除以2
对于无符号整型
在移位操作中,无符号整数的每一位都是数值位,没有专门的符号位。因此,无符号整数的移位操作都是逻辑移位,不考虑符号位的影响。
具体来说:
逻辑左移(<<):将无符号整数的所有位向左移动指定的位数,右侧用0填充。这种操作相当于对数值乘以2的n次方(其中n是左移的位数)。
逻辑右移(>>):将无符号整数的所有位向右移动指定的位数,左侧用0填充。这种操作相当于对数值除以2的n次方(其中n是右移的位数)。
位操作符(操作符必须是整数)
位操作符:``& | ^ ~`
- 按位与(&):有0为0,全1为1
- 使用规则:当两个对应的二进制位,至少存在一边为0,那么结果为0,若当两边全为1,结果才是1
2.按位或(|):有1为1,全0为0
- 使用规则:当两个对应的二进制位,至少存在有一边为1,那么结果为1,若当两边全为0,结果才是0
3.按位异或(^):相同为零,不同为一
- 使用规则:当两个对应的二进制位,两边数字相同,那么结果为0,若当两边数字不相同,那么结果为1
4.按位取反(~):
- 使用规则:用于操作符的每个二进制位取反,将1转化位0,0转化位1
典型两道题:
一道变态的面试题:
题目:实现两个数的交换(不能创建临时变量)
int main() { int a = 10; int b = 20; a = a ^ b; printf("a = %d,b = %d\n", a, b); //b=a^b^b=a^0=a; b = a ^ b; //a=a^a^b=b; a = a ^ b; printf("a = %d,b = %d", a, b); return 0; }
小总结:
- ^位操作不考虑顺序问题
- a^a==0
- a^0==a
问题:求一个整数存储在内存中的二进制中1的个数
int main() { int num = 15; //00000000 00000000 00000000 00001111 int i = 0; int cout = 0;//计数 for (i = 0; i < 32; i++) { if (num & (1 << i)) //00000000 00000000 00000000 00000001 一开始 cout++; } printf("二进制中1的个数=%d", cout); return 0; }
说明:利用了按位与操作符的特点,有0为0,全是1才是1
缺点:需要循环32次
优化方案:
int main() { int num = 15; int i = 0; int count = 0;//计数 while (num) { count++; num = num & (num - 1); } printf("⼆进制中1的个数 = %d\n", count); return 0; } 达到了优化的效果,但是难以想到
逗号表达式
exp1,exp2,exp3,..expN
逗号表达式:使用逗号隔开的多个表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
使用场景:
常规写法 while (a > 0) { a = get_val(); count_val(a); } 使用逗号表达式 while (a = get_val(), count_val(a), a>0) { }
注:
z=(2,3,4)//4 括号里面是表达式 z=2,3,4//2 3,4是逗号表达式
下标访问[]、函数调用()(简单过一下)
下标引用操作符
操作数:一个数组名+一个索引值
int nums[10]; nums[9]=10; []的两个操作数是nums和9。
函数调用操作符
接收一个或者多个操作数:第一个操作数是函数名;剩余的操作数就是传递给函数的参数
void test() { printf("hehe\n"); } int main() { test();//这里()就是函数调用操作符 }
操作符的属性
C语言的操作符有两个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序
优先级
优先级:如果一个表达式包含多个运算符,根据与运算符的优先级判断哪个运算符先执行,并且各种运算符的优先级是不同的
3+4*5;
说明:这个表达式有加法运算符和乘法运算符。由于乘法运算符的优先级高于加法。导致先计算4*5,而不是3+4;
结合性
如果两个运算符优先级相同,优先级无法判断,这个时候就要看结合性,则根据运算符是左结合,还是右结合,决定执行顺序。
大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符
具体还是看:
参考:https://zh.cppreference.com/w/c/language/operator_precedence
注意:即使有了操作符的优先级和结合性,写出的表达式依然有可能不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在潜在风险的,建议不要写出特别不负责的表达式
比如:
#include <stdio.h> int main() { int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0; } //尝试在linux 环境gcc编译器,VS2013环境下都执⾏,看结果。
gcc编译器运行结果:
vs2022运行结果:
表达式求值
整型提升
C语言中整型算术运算总是以省缺(默认)整型类型的精度来进行。对于表达式中的字符和短整型操作数为了获得这个精度之前被转换为普通整型,这个转换称为整型提升
整型提升的意义:
- 表达式的整型运算:CPU对应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int字节长度,同时也是CPU的通用寄存器的长度
- 关于两个char类型的相加,在CPU执行时实际上也是先转换为CPU内整形操作数的标准长度
- CPU(genenral-purpose CPU)是难以直接实现两个8比特直接相加运算(虽然机器指令中可能有这种字节相指令)。对此表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
如何进行整形提升?
- 有符号整型提升是按照变量的数据类型的符号位来提升的
- 无符号整数提升,高位补0
负数的整型提升
char c1=-1; //1111 1111(补码) char为有符号类型 整型提升的时候,高位补充符号位(1) 提升结果1111 1111 1111 1111 1111 1111 1111 1111
正数的整型提升
char c2=1; //0000 0001 char为有符号类型 整型提升的时候,高位补充符号位(0) 提升结果0000 0000 0000 0000 0000 0000 0000 0000
例子:
int main() { char a = 5; //00000000 00000000 00000000 00000101 //00000101 - a (截断后存储到a中) char b = 127; //00000000 00000000 00000000 01111111 //01111111 - b (截断后存储到b中) char c = a + b; //00000000 00000000 00000000 00000101 //00000000 00000000 00000000 01111111 //00000000000000000000000010000100 - a+b //10000100 - c printf("%d\n", c); //c进行了整型提升 按符号位填充 //11111111111111111111111110000100 //10000000000000000000000001111011 //10000000000000000000000001111100 //-124 //%d 是按照10进制的形式打印有符号的整型 return 0; }
算术转化
当操作符的各个操作数属于不同的类型,那么除非但其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double double float unsigned long int long int unsigned int int
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转化为另外一个操作数的类型后执行运算。
谢谢大家的观看,这里是个人笔记,希望对你学习C有帮助。