目录
一、操作符的分类
二、二进制和进制转换
(一)概念
(二)二进制
(三)进制转换
1、2进制与10进制的互换
(1)2进制转化10进制
(2)10进制转化2进制
2、2进制与8进制的转换
3、2进制与16进制的转换
三、原码、反码、补码
四、位移操作符
(一)左移操作符
(二)右移操作符
五、位操作符
六、单目操作符
七、逗号表达式
八、下标访问符、函数调用符
(一)下标访问符 [ ]
(二)函数调用符 ( )
九、结构成员访问操作符
(一)结构体
1、概念
2、结构的声明、定义、初始化
(1)声明
(2)定义
(3)初始化
(二)结构体成员访问操作符
1、结构体成员的直接访问
2、结构体成员的间接访问
十、操作符的属性
(一)优先级
(二)结合性
十一、表达式求值细节
(一)整型提升
1、概念
2、如何整型提升
(二)算术转换
一、操作符的分类
- 算术操作符: 【+ 、- 、* 、/ 、%】
- 移位操作符: 【<< 、>>】
- 位操作符: 【&、 | 、^】
- 赋值操作符: 【= 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=】
- 单⽬操作符:【 !、++、--、&、*、+、-、~ 、sizeof、(类型)】
- 关系操作符: 【> 、>= 、< 、<= 、 == 、 !=】
- 逻辑操作符: 【&& 、||】
- 条件操作符:【 ? : 】
- 逗号表达式: 【 , 】
- 下标引⽤:【 [ 下标 ] 】
- 函数调⽤:【 () 】
- 结构成员访问:【 . 、->】
其中,移位操作符,移动的是二进制位;位操作符,也是使用二进制位进行计算
二、二进制和进制转换
(一)概念
我们经常能听到【2进制、8进制、10进制、16进制】这样的讲法,其实【2进制、8进制、10进制、16进制】是数值的不同表示形式而已,如:
14的2进制:1110
14的8进制:16
14的10进制:14
14的16进制:E
注意:
开头为【0】的数字,才会被认为是八进制的数字;
开头为【0x】的数字,才会被认为是十六进制
(二)二进制
重点介绍一下二进制:
首先我们还是得从10进制讲起,其实10进制是我们⽣活中经常使用的,我们已经形成了很多尝试:
① 10进制中满10进1
② 10进制的每一位数字都是由0~9的数字组成的
二进制也一样:
① 2进制中满2进1
② 2进制的数字每一位数字都是由0~1的数字组成
(三)进制转换
1、2进制与10进制的互换
(1)2进制转化10进制
其实10进制的123表示的值是⼀百⼆⼗三,为什么是这个值呢?其实10进制的每⼀位是有权重的,10进制的数字从右向左是个位、⼗位、百位....,分别每⼀位的权重是10的0次方 , 10的1次方 , 10的2次方...
如下图:
2进制和10进制是类似的,只不过2进制的每⼀位的权重,从右向左是: 2的0次方 , 2的1次方 , 2的2次方...2进制数值乘以每一位的权重再相加的值为10进制的数值表示
以1101为例子,如图所示:
(2)10进制转化2进制
2、2进制与8进制的转换
8进制的数字每一位是0~7,0~7的数字,各⾃写成2进制,最多有3个2进制位就足够了,比如7的⼆进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算⼀个8进制位,剩余不够3个2进制位的直接换算
如:2进制的 01101011,换成8进制:0153【0开头的数字,会被当做8进制】
2进制转8进制:2进制从后向前数,每三位2进制换成8进制的一位
8进制转2进制:8进制从后向前数,每一位8进制换成三位2进制
3、2进制与16进制的转换
16进制的数字每⼀位是0~9, a~f ,0~9, a~f的数字,各自写成2进制,最多有4个2进制位就足够了,比如 f 的⼆进制是1111,所以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算
如:2进制的01101011,换成16进制:0x6b【0x开头的数字,才会被当做16进制】
2进制转16进制:2进制从后向前数,每四位2进制换成16进制的一位
16进制转2进制:16进制从后向前数,每一位16进制换成四位2进制
三、原码、反码、补码
【整数】的二进制有三种表现形式:原码,反码,补码;
【有符号整型 int 】的三种表示方法均有【符号位】和【数值位】,二进制最高一位是【符号位】,其余都是【数值位】;【符号位】:0表示“正”,1表示“负”;
【无符号整型 unsight int 】只表示大于等于0的数,所以没有【符号位】,32个bit位全是【数值位】;
【正整数】的原、反、补码都相同;
【负整数】的三种表示方法各不相同
【原码】:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码
【反码】:将原码的符号位不变,其他位依次按位取反就可以得到反码
注:对于整形来说,数据存放内存中其实存放的是补码
因为:
在计算机系统中,数值⼀律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
四、位移操作符
位移操作符,移动的是【存储在内存中的二进制位(补码)】
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是【整数】
(一)左移操作符
移位规则:左边抛弃、右边补0
#include <stdio.h>
int main()
{
int n = num<<1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
注:
num的值是放到寄存器里面计算的,计算过后num的值不变
左移【一位】有【×2】的效果
(二)右移操作符
移位规则:⾸先右移运算分两种:
- 逻辑右移:左边用0填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃
右移用的是逻辑右移还是算术右移,取决于编译器,通常采用的是算数右移;
右移【一位】有【÷ 2】的效果
如下代码演示:
#include <stdio.h>
int main()
{
int num = -1;
int n = num>>1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
逻辑右移如下:
算数右移如下:
注意:对于移位运算符,不要移动负数位,这个是标准未定义的,如下:
int num = 10;
num >> -1;
五、位操作符
& 按位与
| 按位或
^ 按位异或
~ 按位取反
运算规则:
按位与 &:有0则0;同时为1则1
按位或 | :有1则1;同时为0则0
按位异或 ^ :同0异1
按位取反 ~ :01互换
按位异或的特点:
a ^ a = 0;
0 ^ a = a;
a ^ a ^ b = b;
a ^ b ^ a = b;
异或支持交换律
一道面试题:不能创建临时变量(第三个变量),实现两个整数的交换
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
练习1:编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数
方法一:
#include <stdio.h> int main() { int num = 10; int count= 0;//计数 while(num) { if(num%2 == 1) count++; num = num/2; } printf("⼆进制中1的个数 = %d\n", count); return 0; }
进行之前【num模10,num除10】,算一位就消除一位的方法,二进制就【num模2,num除2】
缺点:负数无法计算
解决:写成函数的形式,形参为num,设置为无符号整型
方法二:
#include <stdio.h> int main() { int num = -1; int i = 0; int count = 0;//计数 for(i=0; i<32; i++) { if( num & 1 ) count++; num = num >> 1; } printf("二进制中1的个数 = %d\n",count); return 0; }
按位与的运算规则为:有0则0,同1才为1;
数字1在内存中的二进制只有最后一位是1,其他为0;和1按位与运算只会检测最后一位是否是1:【num & 1】就可以知道【num】最后一位是不是1;【>> num】就可以让num向右位移一位,检测第二位是否是1,如此循环32次进行检测;
缺点:无论什么数都要循环32次
方法三:
#include <stdio.h> int main() { int num = -1; int count = 0;//计数 while(num) { count++; num = num&(num-1); } printf("⼆进制中1的个数 = %d\n",count); return 0; }
把n的二进制序列中的最右边的1去掉: num = num &(num -1)
因为【num - 1】会让【num】的最后一位数字1之后的数与【num-1】的不同,按位与后总会去掉n的一个1;简而言之,num &(num -1)操作总会让num在内存中的二进制少一个1,循环到没有1就停止,每次循环必会少一个1,设置count++即可统计;举一反三:写一个代码,判断n是否是2的次方数?
n的2次方的特点:二进制只有一个1;
n &(n -1)== 0,则为2的次方数
练习2:二进制位置0或者置1
13的2进制序列: 00000000000000000000000000001101
将第5位置为1后:00000000000000000000000000011101
将第5位再置为0:00000000000000000000000000001101
解:
设置为1:把1的二进制进行【左移<<】后,与目标数进行【按位或 | 】操作(有1则1,同0才为0);
设置为0:把1的二进制进行【左移<<】后,与目标数进行【按位异或 ^ 】操作(同0异1)
#include <stdio.h>
int main()
{
int a = 13;
int n = 5;//第五位改变
a = a | (1<<(n - 1));
printf("a = %d\n", a);
a = a ^ (1<<(n - 1));
printf("a = %d\n", a);
return 0;
}
六、单目操作符
取反 !
自增 ++、自减 --
取地址 &、解引用 *
正 +、负 -
按位取反 ~
计算类型大小 sizeof
强制类型转换 (类型)
七、逗号表达式
逗号表达式,从左向右依次执行,整个表达式的结果是最后⼀个表达式的结果
注:
逗号表达式不能只看最后一个表达式,因为是从左到右依次运算,可能会涉及到最后一个表达式的变量的计算
八、下标访问符、函数调用符
(一)下标访问符 [ ]
操作数:数组名 + 下标,是双目操作符
int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
[ ]的两个操作数是arr和9。
(二)函数调用符 ( )
操作数:函数名 + 括号中的参数,所以()至少有一个操作数
九、结构成员访问操作符
(一)结构体
C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学生,描述⼀本书,这时单⼀的内置类型是不行的
描述⼀个学生需要名字、年龄、学号、身高、体重等;
描述⼀本书需要作者、出版社、定价等
C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型
1、概念
在C语言中,结构体(struct)是一种构造类型,它可以将不同的数据类型组合在一起,形成一个新的数据类型。这种新的数据类型就是结构体;
结构是⼀些值的集合,这些值称为【成员变量】,结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚至是其他结构体
2、结构的声明、定义、初始化
(1)声明
这是声明
struct 结构名
{
成员;
};
例如:
struct Student
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
(2)定义
可以在声明时进行定义
struct 结构名
{
成员;
}结构变量名;
例如:
struct Point
{
int x;
int y;
}p1; 声明类型的同时定义变量p1
struct Point p2; 定义结构体变量p2
定义的类比:
(3)初始化
struct Student s1 = {"zhangsan", 20};//初始化
struct Student s2 = {.age=20, .name="lisi"};//指定顺序初始化
struct Point
{
int x;
int y;
}p1 = {10, 20};
注:
结构体里面有结构体,在初始化的时候就要多加一个大括号给结构体里面的那个结构体
(二)结构体成员访问操作符
1、结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的,点操作符接受两个操作数,如下演示:
#include <stdio.h>
struct Point
{
int x;
int y;
}p = {1,2};
int main()
{
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 = {3, 4};
struct Point *ptr = &p;
ptr -> x = 10;
ptr -> y = 20;
printf("x = %d y = %d\n", ptr->x, ptr->y);
return 0;
}
十、操作符的属性
(一)优先级
优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的,例如:
3 + 4 * 5;
上⾯示例中,表达式 3 + 4 * 5 ⾥⾯既有加法运算符( + ),⼜有乘法运算符( * )。由于乘法的优先级⾼于加法,所以会先计算 4 * 5 ,⽽不是先计算 3 + 4
(二)结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执行),比如赋值运算符(=),如:
5 * 6 / 2;
上面示例中, * 和 / 的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算 5 * 6 ,再计算 / 2;
运算符的优先级顺序很多,下面是部分运算符的优先级顺序(按照优先级从高到低排列),建议大概记住这些操作符的优先级就行,其他操作符在使用的时候查看下面表格就可以了
• 圆括号( () )
• ⾃增运算符( ++ ),⾃减运算符( -- )
• 单⽬运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )
由于圆括号的优先级最高,可以使用它改变其他运算符的优先级
十一、表达式求值细节
(一)整型提升
1、概念
C语⾔中【整型算术运算】总是至少以【默认整型类型 int 】的精度来进⾏的;为了获得这个精度,表达式中的【 字符char 和 短整型short 】操作数在使用之前被转换为【普通整型 int】,这种转换称为整型提升,如下:
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执⾏加法运算
加法运算完成之后,结果将被截断,然后再存储于a中(32个比特位的整型硬是要塞进char里面,就要发生截断,截出后面8个bit位)
2、如何整型提升
①有符号整数提升是按照变量的数据类型的【符号位】来提升的
②无符号整数提升,高位补0
如下:
负数的整形提升(有符号整型):
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
正数的整形提升(无符号整型):
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
(二)算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行,下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
从下到上转换
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算
总结:
整型提升讨论的是:【char 和 short】;
算数转换讨论的是:大于或等于【整型 int 】的其他类型
以上博客内容仅供分享,若有错误,请指正!