【逐步剖C】第四章-操作符

news2024/12/23 4:02:33

一、算术操作符

即基本的+、-、*、/ 和 %。但也有几个需要注意的地方:

  1. 除了 ‘%’ 取模操作符只能作用整数,其他可以作用于整数和浮点数

  2. 对于除法,只要有操作数为浮点数就执行浮点数除法。如果两个操作数都为整数,执行整数除法,此时当你想创建一个浮点型变量来接收时,编译器会给出警告。(注意:进行相应运算后准备输出时,要注意好输出的格式,不要把本应是用 %f 输出的浮点型习惯写成了 %d)

二、移位操作符

分为左移操作符 ‘<<’ 与 右移操作符 ‘>>’

所谓移指的就是将一个数的二进制补码(即整数在内存中的存储方式)整体向左或向右移动(一定情况下,其实可以相应地视为×2或÷2)

1. 左移操作符

移位规则:移出左边界的抛弃,右边空着的补0

需要注意的是:这个移位的动作仅用来理解,若操作数在没有被赋值的情况下自身的值不会发生变化。即 int n = 2;n<<1; 此时n的值不变,还是2,若想让n为移位后的结果就需写成:n = n<<1;

2. 右移操作符

移位规则:对于右移有两种

(1)逻辑右移:同左移的移位规则,只是移动方向不同

(2)算术右移:移出右边界的抛弃,左边空着的用原该值符号位填充

说明:对二进制原码,符号位是1代表其为负数,为0代表其为正数,因为正数的原、反、补三码相同,所以这个规则主要还是针对负数

其他注意点:

(1)对于移位运算符,不要移动负数位,这个是标准未定义的行为

(2)我在用VS2022时发现:左移负的无符号数时会警告这是未定义的行为,如:int a = -2<<1; 但程序可以运行且能算出正确的值

三、位操作符

共有3个:按位与‘&’按位或 ‘|’按位异或 ‘^’ 。他们的操作数都是整数,运算过程是相应二级制码的对比运算。

即两个操作数相应的二进制数逐位进行对比,满足 “与” 逻辑或 “或” 逻辑或 “异或” 逻辑后,那一位的值就为1。每一位都对比完后所得到的新二进制序列就是计算的结果。

  1. 简单应用:

(1)按位与:可求一个整数存储在内存中二进制中1的个数,如:

int main()
{
	int n;
	scanf("%d", &n);
	int count = 0;
	while(n)
	{
		count++;
		n = n & (n - 1);
	}
	printf("%d", count);
	return 0;
}

解释:核心:语句n = n & (n - 1);能把n二进制序列最右边的1变为0
假设n = 15;则如下为执行过程:
第一次:n二进制为1111;n-1二进制为1110;相与后得 1110
第二次:n二进制为1110;n-1二进制为1101;相与后得 1100
第三次:n二进制为1100;n-1二级制为1011;相与后得 1000
第四次:n二进制为1000;n-1二进制为0111;相与后得 0000
可以发现,n从原来的1111(4个1),先变为了1110(3个1),再变为了1100(2个1),再变为1000(1个1),最后变为0000(0个1)。其变化次数其实就是其二进制序列中含有1的个数。

原理:减1之后,一定会出现0,1。若最低位上已经为0,那么会朝高位借1,一直借到有1的那个高位,借完之后,高位上的1就为0,前面的0由于借位就全为1了。进行相与运算之后,原来(没减1)二进制序列中最右边的1就变为0了。

如:在第二次中,n为1110,其最右边的1在从右往左数第二个位上;那么在n - 1进行借1时,有1的那个高位其实就是上述最右边的1,借完之后得到n-1的结果为1101。此时n和n-1进行相与运算:

1  1  1  0
     &
1  1  0  1
	 =
1  1  0  0

最终效果就是把n二进制序列中最右边的1边为了0,即1110 -> 1100
(2)按位或:可将一个整数的二进制补码的某一位上的0改为1,如:

n = n | (1 << i);

执行完上面这条语句后,n的二进制补码中的第 i+1 位若是0则会被改为1

(3)按位异或:可不创建临时变量而实现两个数的交换

int main()
{
	int a = 1, b = 2;
	a = a ^ b;
	b = a ^ b;
//逻辑层面的理解:此时a为a^b,故a^b^b等价于a^0,也就是a本身
	a = a ^ b;	
//同理,右边是a^b^a等价于b^0,也就是b本身
	printf("%d %d", a, b);
	return 0;
}
//也可通过二进制补码的具体变化来理解

补充:当然也可以通过两变量间的加减法实现,但相比于只在二进制位上进行0 1互换的异或运算来说,两变量的加减法含有可能因为进位问题而造成溢出的风险。这里作为一个知识点学会就行,实际在交换两个数最常使用的还是创建一个临时变量。因为异或交换两个数其实有以下三个缺点:
可读性较差执行效率不如创建临时变量的方法只能支持整数的交换

四、赋值操作符

顾名思义,就是给一个变量进行赋值操作。此部分比较需要注意的地方就是其使用的代码风格问题。例:

a=x=y+1;a=x;x=y+1;

二者所表达的一样,但前者的连续赋值相对后者就没这么清晰与便于调试。此外,就是能使语句更整洁的符合运算符,如 +=、-=等等。

五、单目操作符

即操作数只有一个的操作符,共有如下这些:

‘ ! ’ 逻辑反操作

‘ - ’ 负值

‘ + ’ 正值

‘ & ’ 取地址

‘ sizeof ’ 操作数的类型长度(以字节为单位)

‘ ~ ’ 对一个数的二进制按位取反

‘ - - ’ 前置、后置- -

‘ ++ ’ 前置、后置++

‘ * ’ 间接访问操作符(解引用操作符)

‘ (类型) ’ 强制类型转换

其实大部分都是经常使用的,都比较好理解。其中比较需要注意的就是 sizeof~

  1. 按位取反:和上面的位操作符相似,把一个数的二进制的每一位,包括符号位,按位取反(把0和1置换)每一位都置换完后所得到的新二进制序列就是计算的结果。

简单应用:可将二进制数的某一位上的1改为0:

n = n & ~(1 << i);

如上代码,n的二进制补码中的第 i+1 位会被改为1

  1. sizeof:sizeof的作用是以字节为单位来计算操作数的类型长度。但sizeof括号中的表达式是不参与运算的,具体例子如下面这段代码:
int main()
{
	short s = 5;
	int a = 2;
	printf("%d\n",sizeof(s=a+2));
	printf("%d\n", s);

	return 0;
}

运算结果:
在这里插入图片描述

可以这么理解:s=a+2在编译期间就已处理过了,而括号中的表达式只是说把a+2这个结果放到s中,让s说了算,即sizeof的目的只是找到最终要计算的类型的大小。所以s中最终结果还是5。

(1)sizeof与数组的关系

如下面这段代码所示

void print_(int arr[])
{
	printf("%d", sizeof(arr));
}
int main()
{
	int arr[5] = { 0 };
	printf("%d\n",sizeof(arr));
	print_(arr);

	return 0;
}

输出结果:
在这里插入图片描述
解释:主函数中的sizeof(数组名)计算的是整个数组的长度,故与数组本身的类型有关,由于该数组为整型数组,共有5个元素,故结果为10

而函数调用中的sizeof(数组名)计算的其实是该数组首元素地址的大小(数组传参,传递的是首元素的地址),既然是地址的大小,那此时就与数组本身的类型无关了,地址的大小在32位机上为4,在64位机上为8.

(2)sizeof计算字符串与strlen计算字符串

如上述,sizeof的作用是计算操作数的类型长度,故若操作数为字符串时,它就会去计算字符串中每个字符的类型大小(1字节)并相加后返回,当然也就包括了字符串的结束标志 ‘ \0 ’。而strlen计算字符串的大小是以 ‘ \0 ’为结束标志,看其之前有几个字符,统计数量并返回。

如下代码:

printf("%d\n", sizeof("abc"));
printf("%d\n", strlen("abc"));

输出结果:
在这里插入图片描述

六、关系操作符

就是两操作数间的关系运算,满足关系则运算结果为1,反之为0。
总共有下面六种关系:
’ > ’
’ >= ’
’ < ’
’ <= ’
’ != ’
’ == ’

也是属于常见和常用的,没有太多需要深入理解的地方,唯一需要注意的就是其中的 “==” 与赋值操作符 “=” 的区别。

七、逻辑操作符

共有两种:逻辑与 ‘&&’ 和 逻辑或 ‘ || ’

  1. 注意二者与单目操作符按位与 “&” 和 按位或 “|” 的区别

如:1&2结果为0,而1&&2结果为1。前者是在操作数的二进制补码上的操作运算,而后者相当于是对操作数数本身进行逻辑判断运算。

  1. 特有的运算规则(短路运算):
    (1)表达式1 && 表达式2 只要表达式1的值为假,后面不再算
    (2)表达式1 || 表达式2 只要表达式1的值为真,后面不再算
    如以下这段代码:
#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

输出结果:
在这里插入图片描述

解释:a++先使用后自增,即&&的左操作数为假,后面不在算

八、条件操作符

格式为:exp1?exp2:exp3

也称为三目操作符,有三个操作数,可以理解为是 if else 语句的“简化”版

即:if (a>b) max = a; else max = b; 等价于 max = (a>b) ? a : b

九、逗号表达式

格式为:(表达式1,表达式2,…表达式N)

形式就是用逗号隔开的多个表达式

运算过程:从左向右依次执行,整个表达式的结果即为最后一个表达式的结果。

如下面这段代码:

int a = 1;
int b = 2;
int c = (a = b + 10, a++,b = a + 1);

c的结果为14,但a与b的值也相应发生改变,为13和14

应用:可以让代码更简洁,如下代码:

a = get_();
count_(a);
while (a > 0)
{
     ……
}
//用逗号表达式改写如下

while (a = get_(),count_(a),a > 0)
{
     ……
}

十、下标引用、函数调用和结构体访问

也是属于常见常用的操作符

  1. 下标引用 “[ ]” 操作数:数组名+索引值

  2. 函数调用 “()” 操作数:一个或多个。第一个操作数就是函数名,其余的就是传给函数的参数

  3. 结构体访问 “.” 和 “->” 操作数:前者为 “结构体 . 成员名”; 后者为 “结构体指针 -> 成员名”

十一、使用操作符进行表达式求值时的注意事项

表达式求值的顺序一部分由操作符的优先级结合性决定

1. 隐式类型转换(整型提升)

C的整型算术运算总是至少以(缺省)整型的精度来进行的。即若操作数在运算的过程中自身的大小达不到一个整型的大小时,也就是类型的精度不够时,就会在使用前被转化为整型,这种转化就称作整型提升。所以实质上就两种类型会发生整型提升:短整型 short(大小为2个字节)和 字符型 char(大小为1个字节)

整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

提升的方法:按照变量的数据类型的符号位来提升

例:对于有符号数:
负数:char c = -1 变量c的二进制补码为11111111(8个比特位),整型提升时,高位补符号位,也就是1,故提升之后的结果就为:11111111111111111111111111111111(32个比特位)
正数:char c = 1 ,提升前二进制补码为:00000001
提升后为:00000000000000000000000000000001

对于无符号数的整型提升,高位补0

通过下面这段代码加深理解:

#include <stdio.h>
int main()
{
    char a = 127;
    char b = 3;
    char c = a+b; 
    printf("%d", c);
    return 0;
}

输出结果:
在这里插入图片描述
其中发生了两次整型提升

第一次:c = a + b
变量a和b发生整型提升来进行加法运算,运算的结果为:00000000000000000000000100000010(二进制补码)
运算结束后,需把这个结果赋值给变量c,故提升后的结果会发生截断而恢复原来的类型,则运算结束后c的补码为10000010。

第二次:printf(“%d”, c);
即需要将c以整型的形式输出。此时在第一次整型提升基础上进行提升,故提升之后的二级制补码就为11111111111111111111111110000010,转换为原码就是屏幕上输出的-126。(其实在第一次整型提升结束后,即c的补码为10000010时,c的值就已经是-126了。)

再看一段代码:

int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0;
}

输出结果:
在这里插入图片描述

解释:这里先补充一个知识点:表达式可以归结两个属性,值属性和类型属性,sizeof想获取的就是类型属性,因而会发生整型提升而不是上面在介绍运算符sizeof时所说的sizeof括号中的内容不参与运算

2. 算术转换

如果某个操作符的各操作数属于不同类型,那么只有当其中一个操作数向另一个操作数的类型转换时,操作才能进行。操作数精度较低的类型转为精度较高的类型后再执行运算的转换体系称为寻常算术转换,也是合理的算术转换。

注意不合理的算术转换,如:float f = 3.14;int n = f

此时会发生隐式转换(如 1 中的代码例,精度高向精度低转换会发生截断),会有精度的丢失

3. 操作符的属性

此属性主要服务于复杂表达式的求值。两个相邻的操作符的执行取决于他们的优先级,若两者优先级相同,则取决于他们的结合性。还有一点需要注意的其是否控制了求值的顺序,如“&&” “||” 和 “ ? : ” 及逗号表达式等。
下面举几个问题表达式的例子加深一下印象:
(1)a*b + c*d + e*f;
解释: 对以上表达式,由于乘法运算符比加法运算符的优先级高,只能保证,乘法的计算是比加法早,但是优先级并不能决定第三个乘号比第一个加号早执行。即表达式计算的顺序就可能是:
a * b 然后 c * d 然后 a * b + c * d 然后 e * f 最后 a * b + c * d + e * f
或者是:
a * b 然后 c * d 然后 e * f 然后 a * b + c * d 最后 a * b + c * d + e * f
那么这里我们如果把a b c d e f看作是会互相影响的表达式而不只是简单的变量就会出现一些问题了。
(2)c + --c;
解释: 同(1),操作符的优先级只能决定自减 - - 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
如:若c的值为3,那么表达式可能是3 + 2 = 5;也有可能是2 + 2 = 4
(3)answer = fun() - fun() * fun();
解释: 上述代码中我们只能通过操作符的优先级得知:先算乘法,
再算减法。但函数的调用先后顺序无法通过操作符的优先级确定。
(4)int i = 1; int ret = (++i) + (++i) + (++i);
解释: 这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。

所以,我们在写表达式的时候要严格按照操作符的属性来确定唯一的计算路径,若计算路径不唯一确定,那么该表达式就会存在问题,它的最终结果在不同编译环境下的值可能就不同了。所以如果所需计算路径确实复杂而自己又没有把握的情况下,不妨多分几步来写。

以上就是我对操作符这一部分知识的总结啦。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们🌹🌹🌹!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196232.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python多进程、多线程(详细)

多任务概念同一时间执行多个任务多任务优势最大的好处是充分利用CPU资源&#xff0c;提高程序的执行效率GIL锁&#xff08;全局解释锁&#xff09;让一个进程中同一个时刻只有一个线程可以被CPU调用&#xff0c;可以解决线程安全问题&#xff0c;有线程锁也有进程锁Rlock&#…

「自控元件及线路」1.3 直流电动机的特性与控制方法

本节介绍电机的基本物理量和基本关系 本节介绍直流电机的静态特性以及动态特性 本节介绍直流电机的控制方法、启动方法、稳定运行条件 本节介绍直流电动机的四种工作状态 本节介绍控制系统中应用的直流电动机的类型 文章目录基础知识基本物理量&#xff1a;电磁转矩与电枢反电势…

将TensorFlow模型快速迁移到昇腾平台

当前业界很多训练脚本是基于TensorFlow的Python API进行开发的&#xff0c;默认运行在CPU/GPU/TPU上&#xff0c;为了使这些脚本能够利用昇腾AI处理器的强大算力执行训练&#xff0c;需要对TensorFlow的训练脚本进行迁移。首先&#xff0c;我们了解下模型迁移的全流程&#xff…

.net6Api返回统一结果+Vue3前端访问

目录 第一种 第二种 第三种 Vue3前端访问 在我们开发api的时候&#xff0c;需要让接口返回统一的接口&#xff0c;这样容易理解&#xff0c;也容易管理。所以封装返回的统一结果是非常必要的。 下面介绍3种方案。 第一种 建立一个控制器&#xff0c;让所有控制器都继承…

电脑休眠唤醒后会出现屏幕闪烁问题怎么彻底解决?

电脑休眠唤醒后会出现屏幕闪烁问题怎么彻底解决&#xff1f;有的用户在电脑待机休眠之后&#xff0c;重新去唤醒电脑使用&#xff0c;这个时候电脑屏幕就会出现验证的屏幕闪烁&#xff0c;导致无法进行正常的使用。这个情况是电脑系统不兼容导致的。如果想要彻底解决问题&#…

NoClassDefFoundError错误解决

NoClassDefFoundError 类型报错 NoClassDefFoundError与ClassNotFoundException略有区别&#xff0c;从两者的异常类型可以发现&#xff0c;前者属于Error&#xff0c;后者属于Exception&#xff0c;发生了Error往往会导致程序直接崩溃或者无法启动运行。 NoClassDefFoundErro…

ecchart关系图展示(知识图谱)

<!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>ECharts 关系图</title><script type"text/javascript" src"http://code.jquery.com/jquery-3.5.1.min.js"></script><script s…

蓝桥杯-迷宫

没有白走的路&#xff0c;每一步都算数&#x1f388;&#x1f388;&#x1f388; 题目描述&#xff1a; 已知一个30行50列的方格&#xff0c;方格由0和1组成&#xff0c;1 表示障碍物&#xff0c;0表示可行的方块。人从最上边开始行走&#xff0c;逃出这个迷宫&#xff0c;走到…

Git 之reflog回滚操作失误

前言 以前只知道有git log命令&#xff0c;并不知道有git reflog。今天一个偶然的机会&#xff0c;我不小心把自己前两天写的代码给整丢了&#xff0c;如果时几个小时的代码&#xff0c;我重新写一遍就算了&#xff0c;但是这次不一样&#xff0c;这次是非常重大的修改&#x…

openfeign集成sentinel实现服务降级

openfeign集成sentinel实现服务降级使用openfeign调用服务&#xff08;不含sentinel&#xff09;代码测试openfeign集成sentinel实现服务降级引入sentinel相关环境编写FeignClient注解接口的实现类在服务提供者中&#xff0c;认为添加异常代码&#xff0c;以供测试 / 或者不启动…

SpringBean的生命周期

下文要讲的均是spring的默认作用域singleton的bean的生命周期&#xff0c;对spring作用域不了解的可以 https://blog.csdn.net/hlzdbk/article/details/128811271?spm1001.2014.3001.5502 什么是SpringBean的生命周期 springBean的生命周期&#xff0c;指的是spring里一个be…

Python爬虫以及数据可视化分析之某站热搜排行榜信息爬取分析

目录前言一&#xff0c;确定目标二&#xff0c;发送请求三, 解析数据四, 保存数据pyecharts进行可视化“某站”数据排名前10视频类型“某站”标题标签可视化“某站”喜欢视频分类概况总结前言 本项目将会对“某站”热搜排行的数据进行网页信息爬取以及数据可视化分析 本教程仅…

数据结构:栈的学习

作者&#xff1a;爱塔居 专栏&#xff1a;数据结构 作者简介&#xff1a;大三学生&#xff0c;希望跟大家一起进步 目录 一、栈 1.1 概念 1.2 栈的使用 1.3 示例 二、栈的应用场景 2.1 改变元素的序列 2.2 逆波兰表达式求值 2.3 括号匹配 2.4 栈的压入、弹出序列 一、栈…

upstream sent duplicate header line: “Transfer-Encoding: chunked“

实际情景&#xff1a; 公司项目有一个下载文件的功能&#xff0c;没有经过Nginx代理之前&#xff0c;好好的&#xff0c;正常下载&#xff1b; 加入了Nginx代理之后&#xff0c;过Nginx访问就会有 err_empty_response 这个错误&#xff1b; 搞了半天&#xff0c;nginx.conf加入…

第一章 linux概述

第一章 Linux概述 1、为什么要使用Linux Linux内核最初只是由芬兰人林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在赫尔辛基大学上学时出于个人爱好而编写的。 Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和UNIX的多用户、多任务、支…

【蓝桥杯_学习_51单片机】矩阵键盘 状态机法

矩阵键盘 一.基础知识 在键盘中按键数量较多时&#xff0c;为了减少I/O口的占用&#xff0c;通常将按键排列成矩阵形式采用逐行或逐列的“扫描”&#xff0c;就可以读出任何位置按键的状态 矩阵键盘和独立按键一样&#xff0c;也需要进行消抖处理&#xff01; 于此补充一下抖…

c++之基础入门一

一、c的初始化typedef struct student {int age;char name[10];int num; }student;int main() {//在c中可以利用花括号进行初始化struct student student1{12,"zs",123456 };int a 10, b 20;int b{ 20 }, a{ 10 };double c{ 20 };int* p{ nullptr };int arr[10]{ 1…

Day877.数据空洞 -MySQL实战

数据空洞 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于数据空洞的内容。 数据库占用空间太大&#xff0c;把一个最大的表删掉了一半的数据&#xff0c;怎么表文件的大小还是没变&#xff1f; 一个 InnoDB 表包含两部分&#xff0c;即&#xff1a; 表结构定义数…

我通过 tensorflow 预测了博客的粉丝数

前言&#xff1a;由于最近接触了 tensorflow.js&#xff0c;出于试一下的心态&#xff0c;想通过线性回归预测一下博客的粉丝走向和数量&#xff0c;结果翻车了。虽然场景用错地方&#xff0c;但是整个实战方法用在身高体重等方面的预测还是有可行性&#xff0c;所以就记录下来…

亚马逊云科技助力游戏上云学习心得-增长篇

云服务已经是大势所趋了&#xff0c;通过购置传统服务器来进行应用开发&#xff0c;无法与现代化敏捷的开发方法相结合&#xff0c;对于系统运维的难度也大大增加&#xff0c;而云服务的弹性伸缩、动态计费可以很好地帮助中小企业实现快速应用开发&#xff0c;使得产品的价值最…