自定义类型:结构体类型

在学习完指针相关的知识后将进入到c语言中又一大重点——自定义类型,在之前学习操作符以及指针时我们对自定义类型中的结构体类型有了初步的了解,学习了结构体类型的创建以及如何创建结构体变量,还有结构体成员操作符的使用,现在我们将继续结构体相关知识的学习,希望在在看完本篇后你将会有新的收获,一起加油吧!!!


 1. 结构体类型的声明

1.1结构体回顾

在之前c语言常用操作符(2)中已经对结构体的声明进行了讲解,那么现在我们简单的复习一下

struct tag//tag的名字可是自定义的
{
member-list;//成员列表
}variable-list;//变量列表

例如创建一个描述学生的结构体

struct Stu//学生类型
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢

对结构体进行声明后就可以创建结构体变量并进行初始化了

struct Stu//学生类型
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};int main()
{
//按照结构体成员的顺序初始化
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id  : %s\n", s.id);
//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex =
"⼥" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id  : %s\n", s2.id);
return 0;
}

1.2结构体的特殊声明

在结构体中如果我们在创建结构体类型时不给结构体加上名字,那么这个结构体就是匿名结构体类型,例如以下代码就两个都是是匿名结构体类型

struct 
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1,s2;struct 
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}*p1,*p2;int main()
{p1=&s1;return 0;
}

在以上代码中你认为s1=*p1这种写法是合法的吗?
在以上代码中先是创建了一个匿名结构体类型并创建了两个变量,之后又创建了一个结构体类型的指针,后将结构体变量s1的地址传给指针p1,
这时就会存在问题了原因是但我们创建两个结构体类型,虽然这两个结构体类型的成员都相同,但当创建两个匿名结构体类型时,编译器会默认这两个结构体的类型不相同,所以这时将s1的地址传给指针p1会使得等号两边的变量类型不相同 

注:匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次,
       所以使用匿名结构体类型时基本上是只打算对该结构体类型使用一次,之后不再使用

1.3结构体的自引用

数据在内存当中可能是连续存放的,也可能是像以下这样随机存放的

把1,2,3,4,5叫做节点,在节点中除了要存放数据外还要能有下一个节点的信息,这时我们就可以认为一个节点是一个结构体,那么各结构体能写成以下形式吗?

struct Node
{int data;struct Node next;
};

 这时将结构体里面的一个成员为相同的结构体类型,这时就存在一个问题,当我们用sizeof求struct Node的大小,那么使得大小为无穷大,所以以上这种写法是错误的

每各节点中我们是要存放下一个节点的信息,所以在节点中存放下一个结构体的地址也是符合要求的,因此就可以在结构体里面存放一个结构体指针类型,这时两个结构体类型不同就不会存在计算struct Node结构体时候出现无穷大的问题了

struct Node
{int data;struct Node* next;
};

 我们知道使用typedef可以将变量类型简单化,如果在以下代码中将struct Node重命名为Node,那么结构体内的成员也可以简化为Node* next吗?

typedef struct Node
{int data;Node* next;
}Node;

答案是不行的,原因是在对struct Node重命名前就使用简化后的名称Node会使得Node* next是未定义的,这时程序就会报错 

修改以下形式就是正确的了

typedef struct Node
{int data;struct Node* next;
}Node;

2.结构体的内存对齐

在想要学习计算结构体在内存当中的大小,就需要学习一个重要的知识点:结构体的内存对齐

2.1内存对齐规则

#include<stdio.h>
struct s1
{char c1;char c2;int n;
};struct s2
{char c1;int n;char c2;
};
int main()
{printf("%zd", sizeof(struct s1));printf("%zd", sizeof(struct s2));return 0;
}

以上两个结构体的大小相同吗? 
运行程序发现结果不一样,这是为什么呢?

结构体的对齐规则:
1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值

- VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

要理解以上的对齐规则首先要了解偏移量偏移的是什么
其实偏移量就是相较于起始位置的字节偏移量


如果我们想知道s1中的各个成员变量的偏移量是多少,这时就可以用到一个宏offsetof,是c语言提供给我们的宏,作用是计算结构体成员相较结构体变量起始位置的偏移量

这时我们打开c++官网查看offsetof




 

注:使用offsetof需要引用头文件#include<stddef.h> 

#include<stdio.h>
#include<stddef.h>
struct s1
{char c1;char c2;int n;
};int main()
{printf("%zd\n", offsetof(struct s1,c1));printf("%zd\n", offsetof(struct s1,c2));printf("%zd\n", offsetof(struct s1, n));return 0;
}

​ 

 这时运行程序就可以得到c1,c2,n相较起始位置的偏移量分别为0,1,4

这时就会发现与以上计算出的struct s1的大小8字节相同
 

#include<stdio.h>
#include<stddef.h>
struct s1
{char c1;char c2;int n;
};
struct s2
{char c1;int n;char c2;};int main()
{printf("%zd\n", offsetof(struct s2,c1));printf("%zd\n", offsetof(struct s2,c2));printf("%zd\n", offsetof(struct s2, n));return 0;
}


 

这时就会发现这样计算出的struct s2大小为9个字节,这就与以上运行程序计算出的12不相同了,这是为什么呢? 

这时我们就要来了解结构体内存对齐的规则:

结构体的对齐规则:
1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值

- VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

 所以在以上struct s2中一开始第一个成员c1的大小为1,比vs默认偏移数8小,所以偏移数就是1,c1大小为1

第二个成员n的大小为4,比vs默认偏移数8小,所以偏移数就是4,所以n从第五个字节开始,n大小为4字节

 第三个成员c2的大小为1,比vs默认偏移数8小,所以偏移数就是1,所以n从第九个字节开始,c2大小为1字节

 在此还未结束,因为结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍,该结构体struct s2最大对齐数为4,所以结构体大小必须是4的整数倍,以上的9字节不是4的整数倍,所以struct s2大小为12字节

 练习1

#include<stdio.h>
struct S3
{double x;char c;int n;
};
int main()
{printf("%zd\n", sizeof(struct s3));return 0;
}

以上代码的输出结果是什么呢?
 

x的大小为8个字节,偏移量为0,c的大小为1个字节,偏移量为9,n大小为4字节,偏移量为12,以上结构体大小为16字节满足结构体总大小为最大对齐数的整数倍

程序输出结果与分析结果相同

  练习2

#include<stdio.h>
struct S3
{double x;char c;int n;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{printf("%zd\n", sizeof(struct S4));return 0;
}

 以上代码的输出结果是什么呢?

在以上代码就涉及到了结构体嵌套问题,在struct S4中c1的大小为1个字节,偏移量为0,因为在以上已经求出了struct S3的大小为16字节,我们需要再了解一个规律如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍,所以 s3偏移量为8,大小为16字节,d的大小为8个字节,偏移量为24,最终结构体struct S4大小就为32个字节,符合结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

 

2.2 为什么存在内存对齐

 1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

例如在以上结构体struct S中如果机器每次内存读取4个字节,在未对齐情况下就要读取两次,而在对齐的情况下就只要读取一次 


总体来说:结构体的内存对齐是拿空间来换取时间的做法

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起

struct s1
{char c1;char c2;int n;
};struct s2
{char c1;int n;char c2;
};

例如在以上两个结构体中成员都一样,s1将占空间小的成员集中在了一起就使得结构体所占空间小了一些

2.3 修改默认对齐数

在以上的学习中我们知道了VS的默认对齐数是8,那么有什么办法可以修改默认对齐数呢?
其实是有办法的,利用#pragma 这个预处理指令,可以改变编译器的默认对齐数。

#include<stdio.h>
#pragma pake(1)//修改默认对齐数为2
struct s1
{char c1;char c2;int n;
};
#pragma pake()//恢复默认对齐数
int main()
{printf("%zd", sizeof(struct s1));return 0;
}

 使用了#pragma后默认对齐数就被修改为了2,这时以上结构体struct s1的大小就变成6字节

3.结构体传参

#include<stdio.h>
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{for (int i = 0; i < 4; i++){printf("%d ", s.data[i]);}printf("\n");printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{for (int i = 0; i < 4; i++){printf("%d ", ps->data[i]);}printf("\n");printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}

我们知道在函数传参存在传值调用与传址调用,在结构体传参当中也是存在这两种方式的,以上代码中print1就是使用了传值调用,而print是使用了传址调用 ,两种方式都能找到结构体变量s以及其成员,但在结构体传参时一般是使用传址调用,原因是使用传址调用不用再开辟一块内存空间,新创建的指针就指向了原来的那块内存空间,而使用传值调用时,需要再创建一块新的内存空间,如果这个结构体像以上struct S一样所占内存空间很大的话,再创建新的内存就很浪费内存,还有函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

结论:
结构体传参的时候,要传结构体的地址

  

4. 结构体实现位段 

 4.1 什么是位段

位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int、unsigned int signed int ,在C99中位段成员的类型也可以
选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。

例如以下结构体就是使用了位段

struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};

在此结构体中如果成员a知道自存0~3的整数,就可以在a后加上位段,2,这也就表示a成员只占2比特位,而用来a变量位int类型是占32个比特位,位段后就节省了许多空间,同理b,c,d也分别加上位段5,10,30

 

那么这和不使用位段的结构体大小有什么差别吗?

struct A
{
int _a;
int _b;
int _c;
int _d;
};

我们通过使用sizeof计算看看

 

这时就可以发现使用位段可以节省空间

那么为什么使用位段后以上结构体大小为8字节呢?这时我们就要学习位段的内存分配
 

4.2位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

例如以下结构体

struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};

 这时我们知道成员a所占大小为3比特位,类型为char,大小为8比特位,这时就存在一个问题在给了成员变量后在空间内部是从左向右使用还是从右向左使用?这时假设是从右向左使用,这时又存在一个问题了当剩下空间不满足存放下一个成员时候,空间是浪费还是使用呢?这里假设是浪费

 

这时我们创建一个struct S类型的变量并给各成员初始化为0,再给其赋值调试程序观察内存

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}


 

存放入成员变量a的数是10二进制表示形式是1010,但成员a空间大小只有3比特位,所以存入是010, 存放入成员变量b的数是12二进制表示形式是1100,所以存入是1100, 存放入成员变量c的数是3二进制表示形式是11,所以存入是00011, 存放入成员变量d的数是4二进制表示形式是100,所以存入是0100

这时用16进制表示内存当中就为62 03 04

通过调试,查看内存与以上分析结果相同

 4.3 位段的跨平台问题

1. int 位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

 总结:
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

 

4.4 位段使用的注意事项 

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。

 

正如以上的结构体中的a成员就没有相应的地址


所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。

struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
//正确的⽰范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}

4.5位段的应用 

下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这⾥使用位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报大小也会较小⼀些,对⽹络的畅通是有帮助的。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

建WordPress主题官网模板

蓝色的中文WordPress企业模板 https://www.zhanyes.com/qiye/6305.html 暗红色WordPress律师事务所网站模板 https://www.zhanyes.com/qiye/23.html 红色大banner图WordPress外贸网站模板 https://www.zhanyes.com/waimao/27.html

【WEB前端2024】开源智体世界:乔布斯3D纪念馆-第32课-旋转动画

【WEB前端2024】开源智体世界&#xff1a;乔布斯3D纪念馆-第32课-旋转动画 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引擎&am…

某直聘zp_stoken纯算法还原

郑重声明&#xff1a;本项目的所有代码和相关文章&#xff0c; 仅用于经验技术交流分享&#xff0c;禁止将相关技术应用到不正当途径&#xff0c;因为滥用技术产生的风险与本人无关。 难点概要&#xff1a; 每日切换一次算法&#xff0c;其中包括环境&#xff0c;运行方式等。…

24.6.2(动态开点线段树)

星期一&#xff1a; cf edu round 36 E cf传送门 题意&#xff1a;1到n天初始全为工作日&#xff0c;有两种操作&#xff0c;将 l-r 区间变为 工作日/休息日&#xff0c;每次操作后询问剩余总工作日有多少 思路&…

前端调用接口有参数正常显示返回值,但是打印是undefined

前端调用接口有参数正常显示返回值&#xff0c;但是打印是undefined 这种有几种情况&#xff0c;但总的来说是因为我们做了接口拦截器的处理 一、后端返回code值有误 比如新来的后端忘记传code了。&#xff08;按照公司规范&#xff0c;一般都是200成功码&#xff09; 或者网上…

Oracle 序列-SEQUENCE

文章目录 序列-SEQUENCE创建序列访问序列序列的修改和删除查询序列信息 序列-SEQUENCE 创建序列 访问序列 序列的修改和删除 DROP SEQUENCE SEQ_EKPO;查询序列信息 可以通过视图 dba/all/user_sequences 查询序列的相关信息 SELECT SEQUENCE_NAME FROM DBA_SEQUENCES WHERE …

洪师傅代驾系统开发 支持公众号H5小程序APP 后端Java源码

代驾流程图 业务流程图 管理端设置 1、首页装修 2、师傅奖励配置 师傅注册后,可享受后台设置的新师傅可得的额外奖励; 例:A注册了师傅,新人奖励可享受3天,第一天的第一笔订单完成后可得正常佣金佣金*奖励比例 完成第二笔/第三笔后依次可得正常佣金佣金*奖励比例 完成的第四…

媳妇面试了一家公司,期望月薪20K,对方没多问就答应了,只要求3天内到岗,可我总觉得哪里不对劲。

“20k&#xff01;明天就来上班吧&#xff01;” 听到这句话&#xff0c;你会不会两眼放光&#xff0c;激动得差点跳起来&#xff1f; 朋友媳妇小丽&#xff0c;最近就经历了这样一场“梦幻面试”。然而&#xff0c;事情的发展却远没有想象中那么美好…… “这公司也太好了吧…

SpringBoot+百度地图+Mysql实现中国地图可视化

通过SpringBoot百度地图Mysql实现中国地图可视化 一、申请百度地图的ak值 进入百度开发者平台 编辑以下内容 然后申请成功 二、Springboot写一个接口 确保数据库里有数据 文件目录如下 1、配置application.properties文件 #访问端口号 server.port9090 # 数据库连接信息 spr…

移远通信携手高通,共启智能出行新时代

5月30-31日&#xff0c;2024高通汽车技术与合作峰会在无锡国际会议中心举行。作为高通“汽车朋友圈”的重要一员&#xff0c;移远通信应邀参会&#xff0c;展示了数十款基于高通平台打造的车载蜂窝通信模组、C-V2X模组、智能座舱模组、Wi-Fi/蓝牙模组&#xff0c;适配高通多个平…

揭秘:Java字符串对象的内存分布原理(二)

接上篇揭秘&#xff1a;Java字符串对象的内存分布原理 再看看下面几道关于String的真实面试题&#xff0c;看看你废不废&#xff1f; ② String str1 "Hello"; String str2 "He" "llo"; String str3 "He"; String str4 "l…

Github上一款开源、简洁、强大的任务管理工具:Condution

Condution 是一款开源任务管理工具&#xff0c;它以简洁易用、功能强大著称。它旨在为用户提供一个简单高效的平台&#xff0c;帮助他们管理日常任务、提高工作效率。 1. Condution 的诞生背景 现如今&#xff0c;市面上存在着许多任务管理软件&#xff0c;但它们往往价格昂贵…

C语言:学生成绩管理系统(含源代码)

一.功能 二.源代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_NUM 100 typedef struct {char no[30];char name[10];char sex[10];char phone[20];float cyuyan;float computer;float datastruct; } *student, student1;typ…

SQL Server定期收缩日志文件详细步骤——基于SQL Server 2012

SQL Server定期收缩日志文件详细步骤 一、环境配置1、查看数据库的属性2、文件设置3、备份模式4、查看收缩配置5、查看收缩选项 二、编写作业计划1、选择新建作业2、常规配置3、步骤4、输入内容5、脚本详解6、新建计划7、输入名称、选择执行时间8、查看测试9、查看测试结果 一、…

如何学习ai agent?

如何学习Agent&#xff0c;推荐阅读《动手做AI Agent》这本书。 推荐理由&#xff1a; 1&#xff1a;一本书能够全方位了解并探索Agent的奥秘&#xff01; &#xff08;1&#xff09;Agent的发展进程。 &#xff08;2&#xff09;可以帮我们做哪些事&#xff1a;自动办公&am…

COZE工作流超详细教程(胎教版)

前言 不知道有没有人和我一样喜欢经常收集一些好看的壁纸&#xff0c;但是有时候寻找的壁纸却总是差强人意。最近在学习COZE的时候搭建了一个自己的智能体通过工作流设计感觉还不错今天和大家分享分享。COZE的工作流界面友好&#xff0c;操作直观&#xff0c;即便是没有设计经…

6月2号训练(Codeforces Round 306 (Div. 2))(待补)

前言&#xff1a; 昨天晚上的训练&#xff0c;昨天下午刚刚打了百度的编程之星大赛&#xff0c;已经被题目橄榄了&#xff0c;榜上清一色的都是搞oi的中学生大佬&#xff0c;而我想了一下午也才只写了一道题&#xff0c;只能说路阻且长啊。晚上去洗了个澡&#xff0c;就没怎么认…

jmeter发送webserver请求和上传请求

有时候在项目中会遇到webserver接口和上传接口的请求&#xff0c;大致参考如下 一、发送webserver请求 先获取登录接口的token&#xff0c;再使用cookie管理器进行关联获取商品(webserver接口)&#xff0c;注意参数一般是写在消息体数据中&#xff0c;消息体有点像HTML格式 执…

算法工程师需要学习C++的哪些知识?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;以下是算法工程师需要学习的一些…

输入a和b两个整数,按先大后小的顺序输出a和b(用指针变量处理)

解题思路&#xff1a; 定义两个&#xff08;int*&#xff09;型指针变量p1和p2&#xff0c;使它们分别指向a和b。使p1指向a和b中的大者&#xff0c;p2指向小者&#xff0c;顺序输出*p1,*p2就实现了按先大后小的顺序输出a和b。 编写程序&#xff1a; 运行结果&#xff1a; 程序…