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进行了几次自增。

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

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

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

相关推荐
binishuaio3 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE5 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻9 分钟前
WPF中的依赖属性
开发语言·wpf
洋24017 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙19 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点20 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder37 分钟前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
苏三有春38 分钟前
PyQt5实战——UTF-8编码器功能的实现(六)
开发语言·qt
Aniay_ivy1 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python
徐嵌1 小时前
STM32项目---畜牧定位器
c语言·stm32·单片机·物联网·iot