操作符详解(下)

embedded/2024/9/24 7:38:10/

6、单目操作符

!、++、--、&、*、+、-、~、sizeof、(类型)

单目操作符只有一个操作数,除了&、*,剩下的我们之前讲过了,这两个我们再之后的指针我们再讲。

7、逗号表达式

表达式,表达式,表达式,表达式

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

逗号表达式依次从左往右执行,最后的表达式就是整个表达式的值;

比如下面代码,看c的逗号表达式,从左往右执行,a>b为假是0,a,这两个表达式没有任何影响,接着a+10,赋予b,b=11,最后b>a,为真,返回1就是整个表达式的结果;

int main()
{int a = 1;int b = 3;int c = (a > b, a, b = a + 10, b > a);return 0;
}

有时候,a<b,是这个判断的条件,那从左往右计算,是会影响到最后的表达式的结果的,所以逗号表达式前面计算也是重要的;

 if(a = b + 10,b = c / 5,a < b)

举个例子,假设有个APP函数的值赋予a,a又是CPP函数的参数,接着while循环,如果a>0,执行代码,执行完后,再继续之前的代码,那这样写就比较冗余,我们可以用逗号表达式写出来;

int main()
{a = APP();CPP(a);while (a > 0){//执行代码a = APP();CPP(a);}return 0;
}

用逗号表达式写的效果是一样的,先执行APP函数,CPP函数,再判断a是否大于0,再执行代码,循环完一次后再进行判断,再执行APP,CPP函数; 


int main()
{while (a = APP(),CPP(a), a > 0){//执行代码}return 0;
}

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

8.1 [  ]下标引用操作符

比如我们有一个数组,我们想打印数组的第7个元素,那就是arr[6],访问数组的元素下标6,

那[ ]就是下标引用操作符,[  ]下标引用操作符的操作数有两个,一个是数组名,一个是元素下标,这两个操作数,少一个都不行,是找不到对应的元素的;

int main()
{int arr[10] = { 1,2,3,4,5,6,7 };printf("%d\n", arr[6]);return 0;
}

 8.2函数调用操作符

比如我们最简单的printf函数后面的括号( )就是函数调用操作符,add( )这个也是函数调用操作符,那函数调用操作符的操作数有几个?

这是不固定的,比如下面第一行代码操作数是函数名printf和字符串“hahaha”,操作数有两个;第二行代码操作数就是函数名add,参数2,参数3,操作数有3个;第三行代码,操作数只有一个函数名,因为它没有参数,只有函数名,那操作数就是1个;所以是不固定的;

int main()
{printf("hahaha\n");//( )操作数就是printf和“hahaha”int r = add(2, 3);//( )操作数就是add,2,3text();//( )操作数就是textreturn 0;
}

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

 9.1结构体

C语言已经提供了内置类型,比如char、short、int、long、float、double,但是只有这些类型是不够的,比如要描述一个学生,学生的名字用char,但是学生的成绩、学生的体重、年龄呢?不能只用一种数据类型来描述学生,学生是一个复杂对象,那我们自定义一个合适的数据类型,结构体就是自定义的数据类型,

结构是一些值得结合,那这些值就是成员变量;

首先先声明结构体,那里面的变量就是成员变量,比如学生的名字,年龄,成绩;

成员变量可以有一个或是多个;

struct Stu
{char name[20];  //名字int  age;       //年龄float score;   //成绩
};

 9.1.1结构体的声明

先是结构体的关键字struct,tag就是结构体的名字,这个可以随便起,括号里面的就是成员变量列表,下面就是变量列表,可写可不写,但是最后的分号;一定要加上;

struct tag{member-list;}variable-list;

 9.1.2结构体变量的定义和初始化

我们定义了结构体类型,那类型就相当于一张图纸,有了图纸,我们就可以按照图纸去盖房子,我们有了学生的数据类型,我们就可以去创建一个“学生”;

那我们创建了数据类型,那就是数据类型+变量名;

int main()
{struct Stu s1;struct Stu s2;return 0;
}

那我们定义变量,那我们就可以初始化; 

那我们说数组给它多个元素,我们用括号,那结构体里面有这么多变量,我们也用括号括起来;

比如s1学生,名字是张三,年龄是18,成绩是95.5;

我们这是按着顺序给它赋值的,那能不能不按顺序给它数值呢?

struct Stu
{char name[20];  //名字int  age;       //年龄float score;   //成绩
};int main()
{struct Stu s1 = { "张三",18,95.5f };struct Stu s2 = { "李四",19,90.5f };return 0;
}

那我们可以通过结果成员访问操作符,来实现这个操作;

  .  是结构成员访问操作符,.name访问结构里的name...........

int main()
{struct Stu s1 = { .age = 18,.score = 95.5f,.name = "张三" };struct Stu s2 = { .score = 90.5f,.name = "李四",.age = 19 };return 0;
}

 那我们再复杂一点,说结构体里面还有一个结构体呢?

结构体Stu里面还有一个结构体BB,结构体BB里面有两个成员变量char a、int b;

那我们再初始化的时候,既然它还是一个结构体,那我们就还是用{ }括号给它赋值,就像s1那样;

也可以向s2那样,.bb.a、.bb.b、bb是个结构体也可以用结构成员访问操作符;

struct AA
{char a;int b;
};struct Stu
{char name[20];  //名字int  age;       //年龄struct AA  BB;float score;  //成绩
};int main()
{struct Stu s1 = { .age = 18,.score = 95.5f,.name = "张三" ,.BB = {"A",1}};struct Stu s2 = { .score = 90.5f,.name = "李四",.age = 19,.BB.a = "2",.BB.b = 1 };return 0;
}

9.2结构成员访问操作符

9.2.1结构体成员的直接访问

那创建好变量,赋值了,我们可以打印出来,比如我们打印s1学生的名字,用结构成员访问操作符,就是结构体变量.成员名;

int main()
{struct Stu s1 = { .age = 18,.score = 95.5f,.name = "张三" ,.BB = {"A",1}};struct Stu s2 = { .score = 90.5f,.name = "李四",.age = 19,.BB.a = "2",.BB.b = 1 };printf("%s\n",s1.name);printf("%d\n",s1.age);return 0;
}

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

 操作符有两个重要的属性就是优先级和结合性,决定了表达式求值的计算顺序。

10.1优先级

 比如在一个表达式里面有多个操作符,哪一个操作符先算呢?那不同的操作符的优先级是不一样的。

*的优先级比+的优先级高,所以先算5*6,乘法,再算3+30,加法;

3+5*6

10.2结合性

 如果两个操作符的优先级是一样的,那就是看结合性了,看它是左结合,从左往右计算,还是右结合,从右往左计算;大部分是操作符是左结合,左往右计算,但是有少部分是右结合,从右往左计算,比如赋值运算符 = 

* 和 / 的优先级是相同的,它们的结合性都是左结合运算符,那就是先算5*6,再 / 2;

5 * 6 / 2

 运算符的优先级顺序很多,我们只要大概记记了解一些常用的运算符优先级就行

下列按照优先级从高到低排列;

1、()圆括号;比如5 * 6 / 2,我就先算6/2,那就那就可以5 * (6 / 2);

2、自增运算符(++)、自减运算符(--)

3、单目运算符+、-

 4、乘法 *、除法  /

 5、加法+、减法-

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

7、赋值运算符 =

我们也有一个表格可以查看

https://zh.cppreference.com/w/c/language/operator_precedence

11、表达式求值

11.1整型提升

C语言中整型算术运算都是以整型类型的精度来计算的;

如果表达式中有char和short,那再使用之前先将它们转换为普通整型,那这种转换就叫做整型提升,char的底层是 ASCII码,它和short也被归为int类型里面;

那a+b就是整型提升;

int main()
{char a = 5;char b = 126;char c = a + b;return 0;
}

 整型提升的意义

表达式的整型运算是在CPU相应的运算器上运行的,CPU的整型运算器的操作数的字节长度一般是int的字节长度,因此,两个char类型相加,在CPU上实际也要先转换为CPU整型运算器操作数的字节长度,CPU是难以实现两个8比特位字节直接相加的,所以,表达式中各种长度小于int长度的整型值,都必须先转换位int后者unsigned int,再给CPU计算。

 如何整型提升?

1、有符号整数的整数提升是按照数据的符号位来提升的

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

5的原码放在char里面,5的二进制位是32个比特位,char是8个比特位,放在char里面我们就要截断成8个比特位,126也是同样的道理;

a+b整型运算,对a,b整型提升,就要提升到32个比特位,在当前编译器char是有符号整型, 所以剩下的24个比特位,用这个数据的符号位来提升;

a和b整型提升后的结果是32个比特位,放在char里面也是要截断的;

当我们打印c,用%d打印,%d打印的是有符号的整型,%u打印的是无符号的整型,c还要整型提升,提升完后,这是c在内存的补码,但是打印的是原码,还要取反+1,得到原码;

这就i是char和short在整型运算的过程;

int main()
{char a = 5;//00000000 00000000 00000000 00000101//00000101 --截断char b = 126;//00000000 00000000 00000000 01111110//01111110 --截断char c = a + b;//00000000 00000000 00000000 000000101 --a整型提升//00000000 00000000 00000000 011111110 --b整型提升//00000000 00000000 00000000 100000011//截断//10000011 --c//用%d打印是有符号整型//对c整型提升//11111111 11111111 11111111 100000011 --补码//打印的是原码//10000000 00000000 00000000 011111101 c原码printf("%d\n",c);return 0;
}

11.2算术转换

 如果操作符的两个操作数的类型是不一样的,那就需要算术转化,另一个操作数的类型转换位另一个操作数的类型,是类型相同,可以运算;

算术转换是按照下面表格从下往上转换的;

假设一个操作数类型是float,另一个操作数是int类型,那就i是int操作数向float操作数转换变成float类型,其他的同理;

long double

double

float

unsigned long int

long int

unsigned int

int

 11.3问题表达式解析

11.3.1 表达式1

这个表达式是存在问题的,它的计算顺序不是唯一的;

a*b + c*d + e*f

 它的顺序可能是两种,都是符合表达式运算的优先级,结合性,但这不是唯一的计算路径,是会有两个结果的,比如我们写一个计算银行利息的代码,一个计算是50,另一个计算是50000,这些很可怕的;

11.3.2表达式2

这个表达式是有问题的,在优先级来说,先计算--,再计算+,但是这个表达式右边的--是知道了,那左边的c是--c之后的c,还是--c之前的c,是有争议的,这也不是唯一的计算路径。

 c + --c;

 11.3.3表达式3

这个代码是有问题的,这段代码放在不同的编译器结果是不一样的,里面的i的运算,编译器也会凌乱的,这种代码是不能这么写的,你要么拆分,存在变量里,再计算。


int main(){int i = 10;i = i-- - --i * ( i = -3 ) * i++ + ++i;printf("i = %d\n", i);return 0;}

11.3.4表达式4

这段代码的fun函数里的变量用了static,变量的值是会累计的,fun函数第一次返回的是2,第二次返回的是3,第三次返回的是4,那再main函数里面谁是第一次返回,谁是第二次返回,谁是第三次返回,这是不确定的。

#include <sdtio.h>int fun(){static int count = 1;return ++count;}int main(){int answer;answer = fun() - fun() * fun();}printf( "%d\n", answer);return 0

11.3.5表达式5

这段代码也是有问题的,这段代码放在不同编译器的结果也是不一样的。

#include <stdio.h>int main(){int i = 1;int ret = (++i) + (++i) + (++i);printf("%d\n", ret);printf("%d\n", i);return 0;}

11.4总结

我们学了运算表达式的优先级,结合性,我们写出来的表达式不是万无一失的,通常这种表达式是有问题的,它计算的路径不是唯一的,对于以上的表达式,我们不要写成这样,我们可以根据需要,把这些表达拆分,或者用括号确定它的优先级,拆分用变量存起来,再去计算。

感谢观看,感谢指正!


http://www.ppmy.cn/embedded/104392.html

相关文章

Java后端 - 常见BUG及其处理策略(持续更新中~)

Bug 收集与总结 本文记录的是 本人SpringBoot 后端项目使用和运行代码时所遇到的各种问题&#xff0c;全部都已解决&#xff0c;欢迎在评论区补充你遇到的 Bug 哦&#xff01;仅以本文记录学习社区项目时&#xff0c;所遇到的奇奇怪怪的 bug&#xff0c;以及一些很愚蠢的错误&…

【从问题中去学习k8s】k8s中的常见面试题(夯实理论基础)(十九)

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

Linux tee

tee : 显示程序的输出并将程序输出复制到一个文件中。 Usage: tee [OPTION]... [FILE]... Copy standard input to each FILE, and also to standard output. -a, --append append to the given FILEs, do not overwrite -i, --ignore-interrupts ignore inte…

【GD32】RT-Thread实时操作系统移植(GD32F470ZGT6)

1. 简介 最近几年可以发现国产的实时操作系统越来越受欢迎了&#xff0c;本篇要移植的就是当中的翘楚——RT-Thread。 RT-Thread诞生于2006年&#xff0c;是国内以开源中立、社区化发展起来的一款高可靠实时操作系统 &#xff0c;由睿赛德科技负责开发维护和运营 。并且在上一年…

RabbitMQ~架构、能力、AMQP、工作模式、高可用、死信队列了、事务机制了解

RabbitMQ RabbitMQ是使用Erlang编写的一个开源的消息中间件。它实现了AMQP(高级消息队列协议)&#xff0c;并支持其他消息传递协议&#xff1a;例如STOMP(简单文本定向消息协议)和MQTT(物联网协议)。 支持多种客户端如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、…

基于卷积神经网络的磨削平板类零件擦伤检测

基于卷积神经网络的磨削平板类零件擦伤检测 前言正文 前言 还记得读研那会儿刚学习完了卷积神经网络&#xff0c;初步学会了最基础的分类问题&#xff0c;当时也有点python基础&#xff0c;同时对TensorFlow也有点入门了。正好我的课题中有一类缺陷比较难以用传统方法识别判断&…

基于BP神经网络的项目风险识别,BP神经网络训练窗口详解,BP神经网络详细原理

目录 摘要 BP神经网络参数设置及各种函数选择 参数设置 训练函数 传递函数 学习函数 性能函数 显示函数 前向网络创建函数 BP神经网络训练窗口详解 训练窗口例样 训练窗口四部详解 基于BP神经网络的项目风险识别 效果图 结果分析 摘要 本文总结BP神经网络的参数设置,训练函…

Java中的API网关:Spring Cloud Gateway与Zuul

在微服务架构中&#xff0c;API网关扮演着至关重要的角色。它作为系统的入口&#xff0c;负责请求的路由、负载均衡、认证授权、限流熔断等功能。本文将深入探讨两个流行的Java API网关解决方案&#xff1a;Spring Cloud Gateway和Netflix Zuul&#xff0c;并通过详细的解释和代…