C语言——操作符详解

目录

1.操作符的分类

2.原码、反码和补码

3.移位操作符

[3.1 左移操作符](#3.1 左移操作符)

[3.2 右移操作符](#3.2 右移操作符)

4.位操作符

[4.1 按位与&](#4.1 按位与&)

[4.2 按位或|](#4.2 按位或|)

[4.3 按位异或^](#4.3 按位异或^)

[​编辑 4.4 按位取反~](#编辑 4.4 按位取反~)

[4.5 应用题](#4.5 应用题)

[4.5.1 题目:不能创建临时变量,实现两个整数的交换](#4.5.1 题目:不能创建临时变量,实现两个整数的交换)

[4.5.2 题目:求一个整数存储在内存中的二进制中1的个数](#4.5.2 题目:求一个整数存储在内存中的二进制中1的个数)

[4.5.3 题目:写一个代码,判断n是否为2的次方数](#4.5.3 题目:写一个代码,判断n是否为2的次方数)

[4.5.4 二进制位置0或者置1](#4.5.4 二进制位置0或者置1)

5.单目操作符

6.逗号表达式

[7.下标访问[ ]、函数调用( )](#7.下标访问[ ]、函数调用( ))

[7.1 [ ] 下标引用操作符](#7.1 [ ] 下标引用操作符)

[7.2 函数调用操作符](#7.2 函数调用操作符)

8.结构成员访问操作符

[8.1 结构体](#8.1 结构体)

[8.1.1 结构体的声明](#8.1.1 结构体的声明)

[8.1.2 结构体的定义和初始化](#8.1.2 结构体的定义和初始化)

9.操作符的属性:优先级和结合性

[9.1 优先级](#9.1 优先级)

[9.2 结合性](#9.2 结合性)

10.表达式求值

[10.1 整型提升](#10.1 整型提升)

[10.2 算数转换](#10.2 算数转换)

[10.3 问题表达式解析](#10.3 问题表达式解析)

[10.3.1 表达式1](#10.3.1 表达式1)

[10.3.2 表达式2](#10.3.2 表达式2)

[10.3.3 表达式3](#10.3.3 表达式3)

[10.3.4 表达式4](#10.3.4 表达式4)

[10.3.5 表达式5](#10.3.5 表达式5)


1.操作符的分类

操作符可以分为很多类

2.原码、反码和补码

整数的二进制表示方法有三种:原码、反码和补码

有符号整数的三种表示方法均有符号位和数值位两部分,二进制序列中最高位的一位是被当做符号位,剩余的都是数值位;符号位都是用0表示"正",用1来表示"负"。

正整数的三码均相同;

负整数的三种表示方法不同。

原码:直接翻译成二进制的就是原码

反码:符号位不变,其他位次依次取反

补码:反码加1就得到补码

补码得到原码可以用------取反,+1的操作

对于整数来说,数据存放内存中其实存放的是补码

3.移位操作符

移动的是存储在内存中二进制位(即补码

<<为左移操作符

>>为右移操作符

3.1 左移操作符

规则:左边遗弃,右边补0

例如:

cpp 复制代码
#include<stdio.h>
int main()
{
	int a = 10;
	int b = a << 1;
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	return 0;
}

运行结果为:

分析:根据规则,a不变,其二进制表示为1010,而b为a的基础上在最后加了一个0,则b为10100

3.2 右移操作符

运算分为两种:

1.逻辑右移:左边用0填充,右边丢弃

2.算数右移:左边用原该值的符号位填充,右边丢弃

右移到底采用哪一种方法,取决于编译器,通常是采用算数右移

4.位操作符

位操作符有四种:

  1. & 按位与

2.| 按位或

3.^ 按位异或

4.~ 按位取反

注意:他们的操作数必须为整数

4.1 按位与&

运算规则:有0为0,全1为1

例如:

cpp 复制代码
int main()
{
	int a = 6;
	//00000000000000000000000000000110   ->6的补码
	int b = -7;
	//10000000000000000000000000000111   ->-7的原码
	//11111111111111111111111111111000   ->-7的反码
	//11111111111111111111111111111001   ->-7的补码
	//00000000000000000000000000000110   ->6的补码
	//00000000000000000000000000000000   ->&之后,因此为0
	int c = a & b;
	printf("c = %d\n", c);
	return 0;
}

根据推算,我们可以得到c为0,则运行结果为:

则可以知道推算结果正确。

4.2 按位或|

运算规则:有1为1,全0为0

例如:

cpp 复制代码
int main()
{
	int a = 6;
	//00000000000000000000000000000110   ->6的补码
	int b = -7;
	//10000000000000000000000000000111   ->-7的原码
	//11111111111111111111111111111000   ->-7的反码
	//11111111111111111111111111111001   ->-7的补码
	//00000000000000000000000000000110   ->6的补码
	//11111111111111111111111111111111   ->|之后
	//10000000000000000000000000000000
	//10000000000000000000000000000001   ->因此为-1
	int c = a | b;
	printf("c = %d\n", c);
	return 0;
}

运行结果为:

因此,我们可以知道,推算结果是正确的。

4.3 按位异或^

运算规则:相同为0,相异为1

例如:

cpp 复制代码
int main()
{
	int a = 6;
	//00000000000000000000000000000110   ->6的补码
	int b = -7;
	//10000000000000000000000000000111   ->-7的原码
	//11111111111111111111111111111000   ->-7的反码
	//11111111111111111111111111111001   ->-7的补码
	//00000000000000000000000000000110   ->6的补码
	//11111111111111111111111111111111   ->^之后
	//10000000000000000000000000000000
	//10000000000000000000000000000001   ->因此为-1
	int c = a ^ b;
	printf("c = %d\n", c);
	return 0;
}

运行结果为:

4.4 按位取反~

与前三个不同的是,~操作符是单目操作符

例如:

cpp 复制代码
int main()
{
	int a = 0;
	printf("%d\n", ~a);
	//00000000000000000000000000000000
	//11111111111111111111111111111111
	//10000000000000000000000000000000
	//10000000000000000000000000000001
	//所以最终结果为-1
	return 0;
}

运行结果为:

4.5 应用题

4.5.1 题目:不能创建临时变量,实现两个整数的交换

方法一:

cpp 复制代码
int main()
{
	int a = 3;
	int b = 5;
	//实现过程
	printf("交换前:a = %d, b = %d", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:a = %d, b = %d", a, b);
	return 0;
}

运行结果为:

这样看似是正确的,但是,我们需要注意的是int 类型是有范围的,当a和b所代表的数都很大的时候,就可能会出现问题,所以,我们还要对代码进行改进。

方法二:

运用上述的操作符来解决

cpp 复制代码
int main()
{
	int a = 3;
	int b = 5;
	//实现过程
	printf("交换前:a = %d, b = %d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后:a = %d, b = %d\n", a, b);
	return 0;
}

运行结果为:

我们需要记住一个结论为:a = b ^ b ^ a,即b ^ b = 0 ,0 ^ a = a

4.5.2 题目:求一个整数存储在内存中的二进制中1的个数

方法一:结合原先分别列出一个数据的方法来进行求解

cpp 复制代码
int count_bit_one(unsigned int n)
{
	int count = 0;
	while (n)
	{
		if ((n % 2) == 1)
			count++;
		n /= 2;
	}
	return count;
}

int main()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_bit_one(num);
	printf("%d\n", ret);
	return 0;
}

需要注意的是:我们要注意负数的情况,因此在函数定义的时候,应该为unsigned int

方法二:运用操作符来进行解决

分析:当n & 1 == 1时, 我们可以得出32位二进位中最后一个为1

cpp 复制代码
int count_bit_one(unsigned int n)
{
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		if ((n >> i) & 1 == 1)
			count++;
	}
	return count;
}

int main()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_bit_one(num);
	printf("%d\n", ret);
	return 0;
}

运行结果为:

所以,我们学习了操作符的知识后,要学会把它们运用到常见的问题中

方法三:

对第二种方法进行改进------

用**n = n & (n - 1)**来进行循环,循环了多少次,那么n中就有几个1(此处省略证明过程,有兴趣的可以举例进行验证)

cpp 复制代码
int count_bit_one(int n)
{
	int i = 0;
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}

	return count;
}

int main()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_bit_one(num);
	printf("%d\n", ret);
	return 0;
}

运行结果为:

4.5.3 题目:写一个代码,判断n是否为2的次方数

分析:2的次方数中,只含有一个1

因为 n & (n - 1) 是去掉一个1,因此,当 n & (n - 1) == 0 时,此时满足题意。

该题省略代码过程。

4.5.4 二进制位置0或者置1

例题:编写代码将13二进制序列的第五位修改为1,然后再改回0

cpp 复制代码
int main()
{
	int a = 13;
	//00000000000000000000000000001101
	//00000000000000000000000000010000  ->借助这个数来得到改为1的数
	//00000000000000000000000000011101  ->改为1
	int n = 5;
	a = a | (1 << (n - 1));
	printf("%d\n", a);
	//00000000000000000000000000011101
	//11111111111111111111111111101111  ->&之后就可以得到原来的数
	//00000000000000000000000000001101  ->改为0
	a = a & ~(a << (n - 1));
	printf("%d\n", a);
	return 0;
}

运行结果为:

5.单目操作符

单目操作符常见的有:| ++ -- & * + - sizeof (类型)

单目操作符的特点是只有一个操作数。

其中单目操作符时&为取地址操作符,在指针中运用广泛。

6.逗号表达式

逗号表达式,从左到右依次执行,整个表达式的结果是最后一个表达式的结果

为了比较好理解我们给出一个例子:

cpp 复制代码
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;
}

算c的值的时候,首先执行a > b 的语句,这里没有任何值的改变,接着执行a = b + 10 ,此时a的值发生改变,a 的值变为12 ,然后执行a 的语句,最后执行b = a + 1 的语句,此时b = 13 ,因此,最终c 的值为13,我们运行一下来进行检验------

7.下标访问[ ]、函数调用( )

7.1 [ ] 下标引用操作符

操作数 :一个数组名 + 一个索引值(下标)

cpp 复制代码
int arr[10];//创建数组
arr[9] = 10;//使用下标引用操作符
[ ]的两个操作数是arr和9

7.2 函数调用操作符

例如:

cpp 复制代码
int main()
{
    printf("hello\n");//()就是函数调用操作符
    printf("%d \n", 1000);
    return 0;
}

操作数就是函数名和()中的内容,那么我们可以得出,函数调用操作符最少有一个操作数(函数名)。

8.结构成员访问操作符

8.1 结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。

8.1.1 结构体的声明

cpp 复制代码
struct 名字
{
    结构体内容
};

例如:描述一个学生可以为

cpp 复制代码
struct student
{
	char name[20];//名字
	int age;//年龄
	double score;//成绩
};

8.1.2 结构体的定义和初始化

通过以下的代码示例,我们来了解一下结构体的定义和初始化

cpp 复制代码
struct student
{
	char name[20];//名字
	int age;//年龄
	double score;//成绩
}s4, s5, s6;//第一种定义方式

struct student s3;//第二种定义方式

int main()
{
	int a;
	struct student s1 = { "zhangsan", 20, 98.50 };//第三种定义方式
	struct student s2 = { "lisi", 35, 91.98 };//初始化方式
	return 0;
}

其中,第一种和第二种方法定义的变量为全局变量。

结构体方面的知识点在后续会有专门的补充,今天我们先简单了解一下~

9.操作符的属性:优先级和结合性

这两个属性决定了表达式求值的计算顺序。

9.1 优先级

各种运算符的优先级是不一样的,相邻操作符中,优先级高的先进行计算。

例如:

cpp 复制代码
int main()
{
	int r = 3 + 4 * 5;
	printf("%d\n", r);
	return 0;
}

在上述的操作符中**'*'** 的优先级大于**'+'** ,所以应该先计算4 * 5 这一部分。

关于优先级,我们可以在网上搜索相应的规则和知识,这里就不再赘述。

9.2 结合性

相邻的操作符的优先级相同的情况下,由结合性来决定。

10.表达式求值

10.1 整型提升

C语言中,整型算数运算总是至少以默认整型类型的精度进行的,为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

例如:

cpp 复制代码
int main()
{
	char a = 20;
	char b = 130;
	char c = a + b;
	printf("%d\n", c);
	return 0;
}

此时,就把a和b转换为了普通整型来进行运算。

意义CPU 内的整型运算器**(ALU)** 的操作数的字节长度和int相同,也是CPU 的通用寄存器长度。表达式中各种长度可能小于Int 长度的整型值,都必须先转换为unsigned int 或者Int ,才能送入CPU进行运算。

如何进行整型提升呢

1.有符号的整数提升是按照变量的数据类型的符号位来提升的

2.无符号整数提升,高位补0

10.2 算数转换

算数转换讨论的就是类型大于等于整型类型的类型,如long double、double、float、long int、int等类型的数据。

下面的层次为寻常算数转换:

cpp 复制代码
1    long double
2    double
3    float
4    unsigned long int 
5    long int 
6    unsigned int
7    int

为从下到上进行转换。

10.3 问题表达式解析

10.3.1 表达式1

cpp 复制代码
a * b + c * d + e * f

在这个表达式中,只能确定 * 的计算比 + 的早,但是优先级并不能决定第三个 * 比第一个 + 早执行。

10.3.2 表达式2

cpp 复制代码
c + --c;

这时,前一个c中存的数不确定是原来的,还是--之后的。

10.3.3 表达式3

表达式过于复杂,影响可读性。

例如:

cpp 复制代码
i = i-- - --i * (i = -3) * i++ + ++i

经过运行,我们可以发现在不同的编译器中,所得到的结果不同,这样代码本身就存在问题。

10.3.4 表达式4

cpp 复制代码
int fun()
{
	static int count = 1;
	return ++count;
}

int main()
{
	int answer;
	answer = fun() - fun() * fun();
	printf("%d\n", answer);
	return 0;
}

这时,存在的问题是,我们不知道**fun( )**所调用出来的数赋值在哪里。

10.3.5 表达式5

cpp 复制代码
int main()
{
	int i = 1;
	int ret = (++i) + (++i) + (++i);
	printf("%d\n", ret);
	printf("%d\n", i);
	return 0;
}

这时代码也存在问题,我们不知道 reti进行了几次自增。

在不同的编译器中,所得到的结果也是不一样的。所以,这个代码是存在问题的。

所以------即使我们知道了操作符的优先级和结合性,我们写出的代码也是存在风险的,因此,我们最好不要写过于复杂的表达式,既影响了可读性,也容易出错。

今天就到这里,我们下个知识点见~

相关推荐
shinelord明4 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
Monly2111 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu12 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202112 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
7yewh14 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
waicsdn_haha26 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
嵌入式科普27 分钟前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
_WndProc28 分钟前
C++ 日志输出
开发语言·c++·算法
qq_4335545437 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
数据小爬虫@1 小时前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python