操作符详解

在上一篇文章中,讲述了简易版扫雷游戏。今天就讲解新的知识---操作符。其实在之前我们就已经使用了一部分操作符,例如:+、-、*、/、&&、||等等。今天对于操作符进行更加充分的介绍。

1. 操作符的分类

算术操作符:+、-、*、/、%

移位操作符:<<、>>

位操作符:&、|、^

赋值操作符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=

关系操作符:<、>、=、<=、>=、!=

单目操作符:++、--、&、+、-、*、!、~、sizeof(类型)

逻辑操作符:&&、||

条件操作符:?:

逗号表达式:

下标引用操作符:[ ]

函数调用操作符:( )

结构体成员访问:. 、->、、

在前面的文章中,算数操作符、赋值操作符、关系操作符等等大部分都是讲解了。今天就对剩下的一些操作符进行讲解,余下的操作符大多都与二进制有关。所以我们先来了解一下二进制!

2. 二进制和进制转换

我们日常生活中,频繁使用的是10进制,例如153就是十进制数字。即十进制:满10进1

那么相同的道理:

二进制:满2进1(即组成的数字只有0~1)

八进制:满8进1(组成的数字为0~9)

十六进制:满16进1(组成的数字为0~15)

为了便于理解,拿一个数举例:15

2进制1111
8进制17
10进制15
16进制F

注意:在使用8进制和16进制时,8进制数值前要加:0,16进制数值前要加:0x

15---017(8进制)---0xF(16进制)

2.1 二进制转10进制

在上述不同进制,同一个值得结果不一样,这是因为不同进制分配的权重不一样。

对于二进制而言:2进制的每一位的权重,从右向左是: 2^0、2^1、2^2、2^3……

同理对于10进制:

2.2 10进制转2进制

2.3 2进制转8进制和16进制

2.3.1 2进制转8进制

由于8进制是由0~7的数字组成,由于2^3=8,那么3位二进制就能表示8进制了。

2.3.2 2进制转16进制

由于16进制是由0~15的数字组成,又2^4=16,即用4位二进制即可表示16进制。

3. 原码、反码和补码

整数的2进制表达方式有三种,原码、反码和补码

对于有符号整数来说,原码、反码和补码是由符号位和数值位所构成,其中符号位是最高位。

在C语言中的符号位:0---表示正(+);1---表示负(-)

整形是占4个字节==32位bit位,即原码、反码和补码都是32位,最高1位是符号位

无符号整数:原码、反码和补码是一样的

有符号整数:原码、反码和补码的表达结果不一样

有符号整数:

原码:将数值按照正负形式转化为2进制序列(32位bit位)。

反码:在原码的基础上,符号位不便,其他位按位取反。

补码:反码+1得到补码。

这样说可能有点难以理解,举个简单例子:

#include <stdio.h>
int main()
{
	int num = -5;//0--表示正,1--表示负
	//101--这是5,其他位补0
	//10000000000000000000000000000101---最高位为符号位1--表示是负数
	//10000000000000000000000000000101 -- (-5)的原码
	//11111111111111111111111111111010 -- (-5)的反码(符号位不变,其他位按位取反)
	//11111111111111111111111111111011 -- (-5)的补码(补码+1)
	return 0;
}

注:原码得到补码:取反,+1;

补码得到原码也可以:取反,+1。

同时这里需要补充一点:在C语言中,内存中存储的是2进制,并且在内存中存储的是补码。

在计算机系统中,数值一律采用补码来存储。这样做的原因是:方便在内存中将符号位和数值位进行统一的处理。这样在内存中可以统一处理加减法运算(cpu只有加法器)。同时,原码和补码的转换过程是一样的,不需要额外的硬件电路。

举个简单例子:1+(-1)=0;如果直接使用原码进行运算,

int main()
{
    //(-1)+1 = 0;
	//-1
	//10000000000000000000000000000001 --- (-1)的原码
	//11111111111111111111111111111110 --- (-1)的反码
	//11111111111111111111111111111111 --- (-1)的补码
	
	//1
	//00000000000000000000000000000001 --- 1的原码、反码和补码

	//如果用原码进行运算
	//10000000000000000000000000000001 --- (-1)的原码
	//00000000000000000000000000000001 --- 1的原码
	//10000000000000000000000000000010 --- 结果(-2)//err

	//如果使用补码进行运算
	//11111111111111111111111111111111 --- (-1)的补码
	//00000000000000000000000000000001 ---   1的补码
	//00000000000000000000000000000000 ---  0//ok

	//存储在内存中是补码,它的结果需要将其转换为原码
	//补码转原码:取反,+1
	//00000000000000000000000000000000 --- (-1)+1得到的补码
	//11111111111111111111111111111111 --- 反码
	//00000000000000000000000000000000 --- 原码//0--结果正确
	return 0;
}

4. 移位操作符

<<左移操作符

>>右移操作符

注意:这里移动的是二进制序列

4.1 左移操作符

用法:左边抛弃,右边补0

举个简单例子:5<<1的结果

int main()
{
    int a = 5;
    //00000000000000000000000000000101  5的补码
    //5<<1
    //00000000000000000000000000001010  5<<1得到的补码,也是原码

    printf("%d ", 5 << 1);//10
    return 0;
}

4.2 右移操作符

1)逻辑右移:右边抛弃,左边补0;

2)算术右移:右边抛弃,左边补该原值的符号位

在C语言中采用什么右移方式是由编译器决定的,但是大多数编译器都采用的是算术右移

举个简单例子:(-1)<< 2

注意:在使用移位操作符时,不能移动负数位,例如1>>-3这种写法是错误的。 

5. 位操作符

& ----- 按位与
| ----- 按位或
^ ----- 按位异或
~ ----- 按位取反

对于位操作符,操作数必须是整数!!

5.1 & -- 按位与

用法:对应二进制为同时为1才为1,有0则为0。(二进制位)

举个简单例子:3 & 5,求它的结果

int main()
{
	int a = 3;
	int b = 5;
	//a & b
	//00000000000000000000000000000011	3的补码
	//00000000000000000000000000000101	5的补码
	// 
	//&---同时为1才为1,有0则为0;
	//00000000000000000000000000000001   按位与之后得到的补码
	//00000000000000000000000000000001	 原码//1
	printf("%d\n", a & b);//1

	return 0;
}

5.2 | -- 按位或

用法:有1则为1,同时为0才为0。

举个简单例子:-10 | 5,求其结果

int main()
{
	int a = -10;
	int b = 5;
	//a | b
	// //-3
	//10000000000000000000000000001010 原码
	//11111111111111111111111111110101 反码
	//11111111111111111111111111110110 补码

	//5
	//00000000000000000000000000000101 补码
	//有1则为1,同时为0才为0
	//11111111111111111111111111110110 (-10)补码
	//00000000000000000000000000000101 ( 5)补码
	//11111111111111111111111111110111	补码
	//10000000000000000000000000001000
	//10000000000000000000000000001001  原码

	printf("%d\n", a | b);//-9
	return 0;
}

5.3 ^ -- 按位异或 

用法:相同为0,相异为1。

举个简单例子:-2 ^ 5,求其结果。

int main() 
{ 
	int a = -2;
	int b = 5;

	//-2
	//10000000000000000000000000000010  原码
	//11111111111111111111111111111101  反码
	//11111111111111111111111111111110  补码

	//5
	//00000000000000000000000000000101  补码

	//-2 ^ 5
	// //相同为0,相异为1
	//11111111111111111111111111111110  补码
	//00000000000000000000000000000101  补码
	//11111111111111111111111111111011  补码
	//10000000000000000000000000000100
	//10000000000000000000000000000101  补码//-5

	printf("%d\n", a ^ b);//-5
	return 0;
}

5.4 ~ -- 按位取反

用法:对每一位进行取反

举个例子:~0,求结果。

int main()
{
	int a = 0;
	//00000000000000000000000000000000
	// ~a  
	//11111111111111111111111111111111	补码
	//10000000000000000000000000000000
	//10000000000000000000000000000001	原码

	printf("%d\n", ~a);//-1
}

练习题:

1、不能创建临时变量(第三个变量),实现两个整数的交换。

//^ 相同为0,相异为1
//3^3 = 0;
//011
//011
//000---0
//0^3 = 3;
//000
//011
//011---3
//3^3^5=5;

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a = %d b = %d\n", a, b);

	a = a ^ b;
	b = a ^ b;//a^b^b = a
	a = a ^ b;//a^a^b = b;

	printf("交换后:a = %d b = %d\n", a, b);
	return 0;
}

分析:根据按位异或的用法,相同为0,相异为1;可以知道a ^ a = 0,0 ^ b = b,即a ^ a ^ b = b

2.编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数

int main()
{
	int a = 0;
	scanf("%d", &a);
	int i = 0;
	int count = 0;
	while (i < 32)
	{
		//a&(1<<i)//统计第i位是否是1
		if (a & (1 << i))//如果没有1了,这个按位与的结果则为0
		{
			count++;
		}
		i++;
	}
	printf("%d\n", count);
	return 0;
}

这里,我们可以看另外一种方法: 

int main()
{
	int n = 0;
	scanf("%d", &n);
	int count = 0;
	while (n)
	{
		n = n & (n - 1);//每次减少1个1
		count++;
	}
	printf("%d ", count);
	return 0;
}

 

6. 单目操作符

在上述分类中,我们已经了解了大部分操作符,在单目操作符只有 &和 *没有做详细的解释,这个部分我们将会和指针练习起来,所以在介绍指针的时候再介绍。

7. 逗号表达式

其实这个逗号就是我们日常使用的逗号,它的作用主要是分隔代码

基本格式:

exp 1, exp 2, exp 3, .....

逗号表达式从左向右执行,逗号表达式的结果取决于最后一个表达式的结果 

举个简单例子:

int main()
{
	int a = 5;
	int b = 3;
	if (a > b, b = a + 1, a++, a > 0)//这里的结果a>0,只要满足就可进行下一语句
		//这里逗号将表达式隔开,但最终的计算结果取决于最后一个表达式
	{
		a = a + b;
	}
	printf("%d\n", a);//那么a>0满足,这里的a=12
	return 0;
}

这样是不是更加容易理解了呢?相信聪明的你们肯定理解了!

8. 下标访问[ ]和函数调用( )

8.1 下标访问操作符[ ]

在数组中,我们经常通过使用数组名+[ ]来访问数组中的每一个元素

所以[ ]的操作数为:数组名和元素下标

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int num = arr[3];//[]就是下标引用操作符,访问数组中下表为3的元素
	printf("%d\n", num);
	return 0;
}

8.2 函数调用操作符

我们在函数篇章中,通常使用函数名+( )+实参,去调用函数 

函数的操作数可以是一个也可以是多个:第一个操作数是函数名,第二个操作数是实参变量1 等等

int Add(int x, int y)
{
	return x + y;
}

int main() 
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int ret = Add(a, b);//这里( )就是函数调用操作符,调用函数
	//三个操作数:函数名Add	实参变量a 实参变量b
	printf("%d\n", ret);
	return 0;
}

9. 结构体成员访问操作符

9.1 结构体

对于结构体,简单来说就是元素的集合体。

举个通俗点的例子:把结构体看作学生,那么其中的元素可以是学生的年龄、身高、体重、成绩等

同理:把结构体看作商品,那么其中的元素可以是商品的价格,商品的质量等

这就是结构体,相当于描述一个物理具体的细节。

9.1.1 结构体的基本结构
struct s
{

    menber-list;

}variable-list;

这既是结构体的基本结构,也是结构体的声明 

描述一个学生:

struct student
{
    char name[];//姓名
    int age;//年龄
    double high;//身高
    int arr[20];//学号
};//分号不能丢
9.1.2 结构体的定义和初始化
//结构体声明
struct s
{
	int x;
	int y;

}p1;//p1为结构体变量,这里的定义p1是全局变量

//定义结构体变量
struct s p2;

//结构体初始化
struct s p3 = { 10,20 };

struct stu	//声明类型---结构体
{
	char name[20];//姓名
	int age;//年龄
	double high;//身高
};

struct p
{
	struct s s5;//结构体的嵌套
	struct stu stu5;
};
int main()
{
	//结构体创建学生s1(结构体变量)
	struct stu s1 = { "lisi",16,1.78 };//初始化
	//不按顺序初始化 即 . + 元素名称
	struct stu s2 = { .high = 1.75,.age = 25,.name = "zhangsan" };
	//结构体嵌套初始化
	struct p p1 = { {10,20},{"wangwu",16,1.89} };//按顺序初始化
	struct p p2 = {.s5.y=10,.stu5.age=22,.stu5.name ="lisi"};//不按顺序初始化};
	return 0;
}
9.2 结构体成员访问操作符
9.2.1 结构体直接访问
结构体成员的访问是由 (.)操作符进行访问的
struct s
{
	int x;
	int y;
}s2 = {10,20};

int main()
{
	struct s s1 = { .x = 15,.y = 20 };//结构体直接访问
	return 0;
}
9.2.2 结构体间接访问

间接访问在后面的篇章中讲解。

10.操作符的优先级和结合性

10.1 优先级

我们在学小学数学的时候,在加减乘除运算中,老师会告诉我们先算乘除再算加减。例如:3+4*5我们首先会算4*5在计算加法,这就是优先级的体现。在C语言中,也是存在优先级的。

这里我们可以通过连接去查看:

C 运算符优先级 - cppreference.comicon-default.png?t=N7T8https://zh.cppreference.com/w/c/language/operator_precedence

10.2 结合性

举个简单例子:3+4*5先算的时4*5,如果我把这个表达式变一变(3+4)*5,这个时候就先算3+4,这就体现的结合性。结合性决定了执行的顺序,左结合(左边先算),右结合(右边先算)。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符( = )。下面就说一下经常使用的操作符的结合性高低吧~

1)圆括号 ()

2)自增(++)自减(--)

3)单目运算符 (+、-)

4)乘法(*) 除法(/)

5)加法(+) 减法(-)

6)关系运算符(<、>等)

7)赋值运算符(=)

11. 表达式求值

11.1 整型提升

在C语言中,整形算术运算总是至少以默认的整型类型的精度来进行计算。

为了获得这个精度,在进行运算时,字符型和短整型操作数在使用之前会被转化为普通整形,这就是整型提升。

int main()
{
	char a = 67;
	char b = 46;
	char c = a + b;
	//这里a和b会先发生整形提升
	//由于接收的类型为字符型
	//a和b就会发生截断再存储
	return 0;
}

如何进行整体提升呢?

1)有符号整数:按照变量的类型的符号位进行提升

2)无符号整数:高位补0

举个例子:

int main()
{
	char a = 5;
	//5 -- 整形
	//00000000000000000000000000000101
	//00000101 //发生截断,char类型占1个字节==8个bit位
	char b = 126;
	//126
	//00000000000000000000000001111110
	//01111110 //发生截断
	char c = a + b;
	//00000101 -- a
	//01111110 -- b
	//a+b要发生整形提升再进行运算
	//char是有符号,那么补位补变量类型的符号位
	//a为正数,最高位为0补0
	//00000000000000000000000000000101  -- a发生整形提升
	//b为正数,最高位补0
	//00000000000000000000000001111110  -- b发生整形提升
	//a+b
	//00000000000000000000000010000011
	//发生截断
	//10000011
	//%d是打印有符号整形,即再次发生整形提升
	//11111111111111111111111110000011 -- 补码
	//10000000000000000000000001111100
	//10000000000000000000000001111101 -- 原码

	printf("%d\n", c);//-125

	return 0;
}

11.2 算数转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。

如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外一个操作数的类型后执行运算。 

那么操作数部分到这里就差不多了,由于操作符的内容比较多,所以时间晚了点,求谅解一下啦~

下一节就是指针部分了, 内容也是比较多,所以可能要晚一点啦~ 我们下期再见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值