Hello,大家好今天让我们来一起来了解一下C语言中的操作符和进制转换吧。
一. 操作符的分类
- 算术操作符: + 、- 、* 、/ 、%
- 移位操作符: > • 位操作符: & | ^
- 赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、>= 、&= 、|= 、^=
- 单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)
- 关系操作符: > 、>= 、< 、<= 、 == 、 !=
- 逻辑操作符: && 、||
- 条件操作符: ? :
- 逗号表达式: ,
- 下标引用: []
- 函数调用: ()
- 结构成员访问: . 、->
由于操作符中有⼀些操作符和⼆进制有关系,所以我们今天也要了解二进制和机制转换。
二. 二进制和进制转换
我们经常能听到 2进制、8进制、10进制、16进制 这样的讲法,其实在生活中我们的身边也能发现很多的进制,例如
- 一分钟六十秒,逢六十进一,就是六十进制
- 一年十二个月,逢十二进一,就是十二进制
- 一年四个季度,逢四进一,就是四进制。
但其实进制只是数值的不同表示形式而已。
比如数值12的各种进制的表示形式:
- 12的2进制:1100
- 12的8进制:14
- 12的10进制:12
- 12的16进制:c
1.2进制转10进制
二进制数为1100,转换为10进制
我们可以看到如果我们想要求值,就只需要将2进制的每一位从后往前依次乘以2^0,2^1……
就能得到我们想要的值,8进制和16进制同理,一个是8的几次幂,一个是16的几次幂。
2.10进制转2进制数字
125的2进制数
只要让每个数字都余上2求出的余数在从后往前写就是他的2进制数 。
3.2进制转8进制
8进制的数字每⼀位是0~7的数字,以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算一个8进制位,剩余不够3个2进制位的直接换算。
例如:2进制的 01101011,换成8进制:0153,0开头的数字,会被当做8进制。
4. 2进制转16进制
16进制的数字每⼀位是0~9, a~f的数字(0 1 2 3 4 5 6 7 8 9 a b c d e f)由a~f代表10~15的数字,以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算。
例如:2进制的01101011,换成16进制:0x6b,16进制表示的时候前面加0x。
三.原码、反码、补码
整数的2进制表示方法有三种,即原码、反码和补码
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。
符号位都是用0表示“正”,用1表示“负”。
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
对于整形来说:数据存放内存中其实存放的是补码。
四. 移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
1.左移操作符
移位规则:左边抛弃、右边补0
本质就是将补码整体向左移超出原本框架的数抛弃,空出来的位置补0;
2. 右移操作符
移位规则:首先右移运算分两种:
1. 逻辑右移:左边用0填充,右边丢弃
2. 算术右移:左边用原该值的符号位填充,右边丢弃
五.位操作符:&、|、^、~
- & //按位与
- | //按位或
- ^ //按位异或
- ~ //按位取反
注:他们的操作数必须是整数。
1.&(按位与)
有0为0,全1为1。,
#include<stdio.h>
int main()
{
int a = -2;
int b = 5;
int c = a & b;
printf("%d\n", c);
}
a的原码:10000000 00000000 00000000 00000010
b的原码:00000000 00000000 00000000 00000101
a的原码:10000000 00000000 00000000 00000010
a的反码:11111111 11111111 11111111 11111101 //1和0取反
a的补码:11111111 11111111 11111111 11111110 //反码+1
b的补码:00000000 00000000 00000000 00000101
//正数的原码,反码,补码都相同
对a的补码和b的补码,进行按位比较(有0为0,全1为1),就得到了a&b。
11111111 11111111 11111111 11111110 //a的补码
00000000 00000000 00000000 00000101 //b的补码
00000000 00000000 00000000 00000100 //a&b
00000000 00000000 00000000 00000100根据转换应为4 与结果相符。
2.| (按位或)
有1为1,全0为0。
#include<stdio.h>
int main()
{
int a = -2;
int b = 5;
int c = a | b;
printf("%d\n", c);
}
a的原码:10000000 00000000 00000000 00000010
b的原码:00000000 00000000 00000000 00000101
a的原码:10000000 00000000 00000000 00000010
a的反码:11111111 11111111 11111111 11111101 //1和0取反
a的补码:11111111 11111111 11111111 11111110 //反码+1
b的补码:00000000 00000000 00000000 00000101
//正数的原码,反码,补码都相同
打印应由原码转换,由于符号位为1,为负数,原码,反码,补码,并不相同,所以要进行取反+1。
11111111 11111111 11111111 11111110 //a的补码
00000000 00000000 00000000 00000101 //b的补码
11111111 11111111 11111111 11111111 //由于符号位为1,为负数,所以要转换为原码
10000000 00000000 00000000 00000000 //取反
10000000 00000000 00000000 00000001 //+1,变成原码
10000000 00000000 00000000 00000001根据转换应为-1,与结果相符。
3. ^(按位异或)
相同为0,不同为1。
#include<stdio.h>
int main()
{
int a = -2;
int b = 5;
int c = a ^ b;
printf("%d\n", c);
}
a的原码:10000000 00000000 00000000 00000010
b的原码:00000000 00000000 00000000 00000101
a的原码:10000000 00000000 00000000 00000010
a的反码:11111111 11111111 11111111 11111101 //1和0取反
a的补码:11111111 11111111 11111111 11111110 //反码+1
b的补码:00000000 00000000 00000000 00000101
//正数的原码,反码,补码都相同
11111111 11111111 11111111 11111110 //a的补码
00000000 00000000 00000000 00000101 //b的补码
11111111 11111111 11111111 11111010 //由于符号位为1,为负数,所以要转换为原码
10000000 00000000 00000000 00000101 //取反
10000000 00000000 00000000 00000110 //+1,变成原码
10000000 00000000 00000000 00000110根据转换应为-5,与结果相符。
4.~(按位取反 )
按位取反(0变1,1变0)
#include<stdio.h>
int main()
{
int a = -2;
printf("%d\n", ~a);
}
a的原码:10000000 00000000 00000000 00000010
a的反码:11111111 11111111 11111111 11111101 //1和0取反
a的补码:11111111 11111111 11111111 11111110 //反码+1
00000000 00000000 00000000 00000001 //a按位取反,符号位为0,为正数,补码=原码
00000000 00000000 00000000 00000001根据转换应为1,与结果相符。
六.逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d\n", c);
return 0;
}
根据逗号表达式,结果应为b=a+1的结果,但前面的表达式也至关重要,第一个表达式对整体并没有影响,到第二个表达式a=b+10,他改变了a的值让a=12,这同样也影响了第三表达式,让b=13;
最后使得此13;
七.[ ] (下标引用操作符)
操作数:⼀个数组名 + ⼀个索引值(下标)
int arr[10];//创建数组
arr[9] = 10;//使用下标引用操作符。
[ ]的两个操作数是arr和9。
八.函数调用操作符
接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
int c = Add(a, b);//这里的()作为函数调用操作符,Add,a和b为操作数
printf("%d\n", c);
return 0;
}
九.结构成员访问操作符
1.结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。
#include <stdio.h>
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = { 1,2 };
printf("x: %d y: %d\n", p.x, p.y);
return 0;
}
使用方式:结构体变量 . 成员名
2.结构体成员的间接访问
有时候我们得到的不是⼀个结构体变量,而是得到了⼀个指向结构体的指针。
#include <stdio.h>
struct Point
{
int x;
int y;
};
int main()
{
struct Point p = { 1,2 };
struct Point* prt = &p;
prt->x = 10;
prt->y = 20;
printf("x: %d y: %d\n", prt->x, prt->y);
return 0;
}
使用方式:结构体指针->成员名
十. 操作符的属性:优先级、结合性
1.优先级
优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。
例如
3+4*5
由于乘法的优先级高于加法,所以会先计算 4 * 5 ,而不是先计算 3 + 4 。
2.结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执行顺序。
下⾯是部分运算符的优先级顺序(按照优先级从高到低排列)
圆括号( () )
- 自增运算符( ++ ),自减运算符( -- )
- 单目运算符( + 和 - )
- 乘法( * ),除法( / )
- 加法( + ),减法( - )
- 关系运算符( < 、 > 等)
- 赋值运算符( = )
十一.表达式求值
1.整型提升
C语言中整型算术运算总是值少以缺省(默认)整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
如何进行整体提升呢?
1. 有符号整数提升是按照变量的数据类型的符号位来提升的
2. 无符号整数提升,高位补0
例如
11111111
有符号,整型提升为
11111111 11111111 11111111 11111111
无符号
00000000 00000000 00000000 11111111
红色部分皆是整形提升所补的。
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下⾯的层次体系称为寻常算术转换(由低向高)。
- long double
- double
- float
- unsigned long int
- long int
- unsigned int
- int
好了,今天就分享到这里吧,还请大家多多关注,下一篇见。