c语言基础知识+OS+数据结构

c语言:

memory section:

.bss:  uninitialized or zero-initialized global and static variables

.data:   initialized global and static variables

.text:    Read only,  code and const

C语言编译流程:

pre-compiler:  #define, #include   【text substitution】

compiler:  turn source code into machine code(object file), perform syntax error check

link:  link these object files together to create an executable file, resolves references to functions or variables that are defined in other object files【map file里面可以看到】

【C语言关键字 / keyword】

Macro写一个函数:

Macro本质就是pre-processor将内容进行文本替换 【text substitution】

#define SET_BIT(var, position)   var = var | (1<<position)#define MAX(A,B)   (A>B)?A:B

  • short 2bytes
  • long 4 bytes

typedef/ macro哪个好

const的含义:    Read only

volatile的含义:        tell compiler not to use cache to optimize, always to read from address. 

const 和 volatile公用:         

uint8_t const volatile * reg_1 = (uint8_t *) 0x10000000;

对固定地址赋值

*(unsigned int *)0xFC880000 = 1;

Storage class:

  • extern: change visible scope
  • register: tell compiler to save the variable into cpu register instead of RAM

static 修饰

  • 全局变量:  accessible only within the file
  • 局部变量:  variables retain their values between function calls
  • 函数: accessible only within the file

sizeof (sizeof也是个keyword): 返回的是byte数

sizeof(int)

sizeof(Node)            //typedef struct{} Node;


 

【位操作】

&

|

^  (exclusive or)

~:    unsigned int mask = ~(0); 

【数据类型】

数组

字符和字符串

end with \0;  NULL character  

qsort 对数组排序

#include <stdio.h>int compare( const void *a, const void *b)
{return *(const int*)a - *(const int*)b; 
}int main()
{int m[] = {1,3,5,2,6};int num = sizeof(m)/sizeof(m[0]);int size = sizeof(m[0]);qsort( m, num, size, compare);printf("0x%x\n", m[0] );printf("0x%x\n", m[1] );printf("0x%x\n", m[2] );printf("0x%x\n", m[3] );printf("0x%x\n", m[4] );return 0;
}注:
1. compare的返回值永远是int, 但是 如果输入数组是char,那么return那行
就要写成 *(const char*)a 【否则不工作】
2. return 那行的 *(const char*)a,星号不要忘了
3. 三个int 变量,啪啪啪写完朝函数里面放就行了
4. qsort是void类型,没有返回值

Format specifier:

%d: decimal

%x: hex

%s: string

malloc 分配空间

void *malloc(size_t size)    //返回的是void型指针char *p;p = (char*) malloc( sizeof(char) * 15 );       //15个元素的空间

结构体/union

data alignment: data will be padding based on the data element boundary 

虽然sBa[20],但是仍然看做是2byte的长度,所以结构体还是以 4bytes的长度 来 alignment的

typedef struct
{int Num;short sDate;char cha[2];short sBa[20];
}Test;

指针

指针类型:

void: 可以指向任何类型, qsort的函数就有用到。使用的时候要记得 强制类型转换

wild: not been initialized to anything

NULL: a pointer which is pointing to nothing 

dangling: A pointer pointing to a memory location that has been deleted (or freed)

没有权限访问该地址,所以*p 直接就报错了

空指针,不指向任何地址。在新建链表的时候会用到

直接对物理地址赋值:

* (int*) 0xFF00CCFF = 1;

或者: 【 地址前的 (int*)不要忘了 】

int *p = (int*) 0xFF8800FF;*p = 1

指针数组:

从上图代码可以看出:

二维数组parr[0][0],可以拆开看成  ( parr[0] )[0], 也就是(arrl)[0];

函数指针:

使用很广,中断向量表里面存的就是函数指针

用的时候:

void(*p)(void);或者
void(*0xF32D0000)(1,2);

typecasting:

int will be promoted to unsigned int

Enum:

user defined data type, like struct keyword

enum week{Mon, Tues, Wed};enum week func(void);

bit field:

说白了就是 int A 后面带一个 :n 即可

// 定义typedef struct
{int a_bit0 :1 ;int a_bit1 :1 ;int a_bit2 :1 ;int a_bit3 :1 ;int a_reserved :28 ;
}A_bitfield;typedef union
{int a;A_bitfield a_bitfield;
}A;//使用A m;m.a;
m.a_bitfield.a_bit0;

一个数组,再加一个栈顶指针

队列

链表

 【库函数】

strlen(const char* str);void* malloc(size_t size);
void free(void *ptr);void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));

【恶心的】

auto:  default storage class, for local variable 【storage duration is automatic,be created when the program execution enters the block in which it is defined and destroyed when the block exits】

register:  only for local variables, store in cpu register instead of RAM 【& cannot be used on register variable】

switch case:

Exp1 ? Exp2 : Exp3;

i++ / ++i

0xFF

0b011

char  1bit

short 2bit

int:  4 bytes

long: 4bytes

double: 

float: 

runtime error:  X is local variable

OS + Embedded System:

【核心】

basic/extended task:

  • basic task: 在running状态下,只有terminate和被更高优先级任务抢占,进入ready状态两种 释放cpu的方式 【不涉及等待共享资源的简单任务】【反正我看到的AUTOSAR project里面都是extended task】
  • extended task:在running状态下,有主动释放cpu,进入wait状态的能力【等待共享资源的释放】
  • basic: suspend, ready, running
  • extend: suspend, ready, running + wait

deadlock:

【单核的多线程之间】

前提: 有两个共享的resource A和 B,task 1和2 都需要 A和B 两个资源

task1使用了resource A,然后被task2抢占,task使用了resource B,然后发现resource A不可用,于是进入wait 模式【此时B没有被释放】。 task1拿回cpu控制权继续执行,然后发现 resource B不可用,互相wait

解决: timeout,如果等待时间timeout以后,task需要释放自己的resource

优先级反转:  

Priority Inversion

【单核的多线程之间】

前提:有一个共享的resource  【优先级3>2>1】

优先级1的task 被 优先级3的task抢占,但task3在执行过程中发现share resource不可用【被task1用着呢】,于是释放了cpu【进入wait,任务就绪表置0,触发scheduler】,scheduler根据任务就绪表让task1继续执行,但此时task2又抢占了task1并执行完成,

解决:临时提升低优先级任务的优先级。 task3在因为resource释放cpu的时候,把持有resource的task1优先级临时提升到3,这样task2 就不能抢占他了

【因为basic task中没法处理share resource,所以只有extended task需要考虑deadlock 和优先级反转】

ECU多核之间通讯:

多核间通讯:  IOC, 原理是spinlock

就一个core,task发现resource 不能用的时候,就只应该立刻释放cpu 【mutex + semaphore

多核的时候,才有一个core 来while(1),等待另一个core的task运行完release resource的道理【spinlock】

因为spinlock用于多核cpu,其中一个core一直while(1)等待另一个core释放资源,也无所谓 【Spin locks are a low-level synchronization mechanism suitable primarily for use on shared memory multiprocessors. When the calling thread requests a spin lock that is already held by another thread, the calling thread spins in a loop to test if the lock has become available】

进程间通讯:

mutex VS semaphore

都是用来做进程间同步的(synchronization mechanism between tasks),区别是mutex必须是进程自己释放,semaphore可以是别的进程释放,而且可以大于1

  • mutex: 主要的purpose是protect shared resources
  • semaphore: 主要的purpose是notice一个event已经发生了,比如taskA 在等一个semaphore,然后一个传感器触发了ISR,ISR去把semaphore置1了,然后taskA就可以继续执行了,这就是为什么说semaphore可以是别的进程释放

semaphore: 【由计数器和 任务等待表 两部分组成,也就是说每个信号量都有自己的任务等待表】

  1. 如果信号量的值为0,任务进入wait状态,并在任务等待表上面被标记,然后触发scheduler
  2. 当其他任务释放了信号量后,会在该信号量的任务等待表中找到最高优先级的任务,并将其从wait转为ready状态,然后触发scheduler

所以scheduler的工作很简单,在任务就绪表里面找最高优先级的任务【有更高的就上下文切换】

  • 想让任务wait,并交出cpu控制权很简单:把它在任务就绪表自己的格子中置0,在任务等待表中置1,然后触发scheduler即可
  • 想让任务从wait到ready,更简单:把它在任务就绪表自己的格子中置1,在任务等待表中置0,然后触发scheduler即可

说白了extended task,就是task可以【因为共享资源不可用】主动放弃cpu控制权,进入wait状态,让优先级低的先去执行【任务就绪表置0,任务等待表置1】

basic task,task没法自己主动放弃cpu,只能是被动的被其他优先级更高的抢占

任务调度的原理:

scheduler被调用的场景:

  1. timer ISR调用scheduler【周期性的任务切换】
  2. task结束,调用scheduler
  3. extended task因为share resource不可用,放弃cpu进入wait状态,然后触发scheduler

任务就绪表【1张】  + 任务等待表【多张,每个信号量都有自己的1张任务等待表,信号量在被task释放的时候,该task会去该信号量的任务等待表唤醒最高优先级的任务进入任务就绪表,然后触发一次scheduler】

scheduler做的事情:

查找任务就绪表优先级最高的任务,如果需要切换,就context switch切换。否则就继续执行当前任务,开销很小

【杂项】

process/thread

  • process has its own dedicated memory space, consume more resource【硬件MPU保护】
  • Threads within the same process share the same memory and resources

big/little endian

针对超过一个byte的数据而言。most significant byte在低地址的为big endian

检测方法:

int a = 0x12345678;char *p = (char*)&a;    //(char*) 告诉编译器用char类型来解析a地址的数据注:
通过char型指针p变量,获取第一个byte,既可判断

inline 函数

a suggestion to the compiler that it should generate code for the function at the call site, instead of generating a separate function call. for better performance

only a suggestion, compiler will make the final decision 

include <> 和 “” 的区别

<>: 寻找 system path(编译器的安装目录文件夹)

"":  寻找当前project path(当前工程文件夹),找不到再找system path

上电流程/startup phase 

【以下针对嵌入式MCU而言】

单核:

  • reset vector
  • startup code
    • init clock
    • memory setup: copy from FLASH to RAM + init stack pointer(stack信息在linker里面定义的 .lsl文件) (.bss .data 会在 RAM运行, .text还是在FLASH运行)
    • init peripheral: init GPIO, CAN (如果需要bootloader功能的话)
    • system init: 中断向量表初始化,enable 中断
  • main()

【linker里面配置了stack的大小,startup phase把stack pointer指向了该位置, 之后main函数入的就是这个栈。只是说操作系统在任务切换的时候,会把cpu register内该task的信息copy到那个task自己的任务控制块里面零时存起来,保护现场。之后再copy回来,就等于恢复现场,可以继续运行了】

【对于操作系统,每个task都要有自己的任务栈,运行的时候,cpu的stack pointer要指过去,是为了方便任务切换。如果所有task公用一个栈,假如task1先入栈运行,然后被优先级更高的task2抢占,入栈到它上面,然后task2运行一半休眠了,那么就没法access到task1的栈内容了。就算你把task2的栈pop出来去存储,也太麻烦了,消耗时间太多】

多核:

  • 硬件启动master core0, core0去唤醒slave core1,2,3 。【唤醒的含义是硬件初始化+startOS】 在所以核心的OS start以后,会进行第一次同步。 
  • 第一次同步后,各个core会去call application startup hook,然后进行第二次同步。从而确保所有core的OS kernel一起开始运行。
  • 【第一次同步是OS 初始化完成(OS的stack什么的)【EcuM startupOne】,第二次同步是进程初始化完成(OS-Application,Task的堆栈,控制块初始化等等)【EcuM startupTwo】,然后同时开启时间片进行Task调用】

中断向量表:

中断向量表网站链接:(array of function pointers)

  • 一个用来存储各个中断服务函数地址的内存区域
  • 中断向量表区间默认是空着的
  • 用户在c文件里面创建了ISR函数,那么MCU上电的时候,startup code就要根据用户创建的各个ISR的地址,把这些函数地址都注册到 中断向量表 里面
  • 中断向量表在code/FLASH section

Interrupts and exceptions:

  • interrupt: trigger by external event, save context and jump to the ISR

  • system exceptions: trigger by cpu (divided by zero, invalid memory access), save context and jumps to an exception handler routine

下图可以看到,Interrupts and exceptions 都在 中断向量表里面

Interrupt latency:

time that elapses between the occurrence of an interrupt and the execution of the first instruction of the interrupt service routine (ISR)

Factors Affecting Interrupt Latency

There are several factors that can affect interrupt latency, including:

  • Hardware: The architecture of the processor, as well as the design of the memory and I/O subsystems, can have a significant impact on interrupt latency.
  • Operating System: The operating system plays a big role in interrupt latency, with some OSes having fewer interrupt latencies due to advanced scheduler and interrupt handling mechanism.
  • Interrupt Priority: The priority level of the interrupt can also affect interrupt latency, with higher priority interrupts being serviced before lower priority interrupts.

抢占式: (Preemptive)

  1. Preemptive OSes are often used in real-time systems where tasks must meet strict deadlines. The OS can guarantee that high-priority tasks will run when required.

  2. some preemptive OSes use time slicing, where each task is given a fixed time quantum to run. When the time quantum expires, the task is preempted even if it hasn't finished.

函数入栈流程:

函数入栈流程网站链接:

函数栈从高地址到低地址增长

return address, actual parameters and local variable are pushed into stack【返回地址就是下一行该执行的代码的地址】

ebp, esp  stands for base pointer and stack pointer

一道很好的题目

pass by value/reference:

  • by value: a copy of the actual argument's value is passed to the function, any changes made to the parameter (in the function) do not affect the original argument
  • by reference: pointer to the actual argument is passed to the function, It is useful when you want a function to modify the original data

actual / formal parameters:

  • formal: variables or placeholders that are declared as part of a function's definition

  • actual: the values  passed to a function when it is called

context switch:

save the current status of task into control blocks 【register, task stacks】

trigger by interrupt, pre-emptive multi-task, 

reentrant function 

  • can be safely called simultaneously by multiple threads, 
  • Reentrant functions use only local data (variables or memory) and do not rely on global or shared data, do not use static variables

#pragma

memory mapping 的时候,用到过

  • variable placement:
  • code section placement: 

原码,反码,补码

正数的原码,反码,补码都一样

负数的原码:和正数一样,除了符号位置1 【问题,正负数原码相加不为0】

反码(ones' complement):直接把正数的原码反过来【问题,正负数反码相加为0xFFFF】

补码(twos' complement):反码+1 【正负数补码相加不为0】

虚拟内存: paged memory

process can only access to their own virtual memory, MMU 

数据结构:

常见错误

的正确用法:

int a[10] = {0};     //记得将所有元素初始化为0 【有些在线编译器很傻逼】sizeof(a);     // 除了declare,没有a[]这种用法,不要写 sizeof( a[] );num = num <<4;  // 记得写num =, 单独的num<<4 不会改变num本身的值unsigned int mask = ~0;   //如果要位运算,一定记得是unsigned,要么就直接 (~0)<<8-----
int a = 0x12345678;char *p = (char*)&a;    //(char*)不要忘了, 告诉编译器用char类型来解析a地址的数据


 

常用:

int a[] = “abcdefg”;

sizeof(a);

strlen(a);

printf(“%s\n”,a);

基本上:

  • 数组题,就可以考虑先qsort一下,可能会简单很多

  • 数操作,就是从2进制的角度去看, XOR, 一下什么的。 &1111 就是截取, |0000就是删除

  • 不知道循环次数的时候, while(n) 就很好用

位操作: 

数字题的工具:

  1. 截取: &1111 

  2. 删除: |0000

  3. 半加法: XOR 

  4. 取某一个bit:  n&1         n = n>>1

  5. 1的个数:   a&1 == 1   ,   for循环  a = a>>1 【>>1 记得写在for里面,而不是if里面】

  6. 最高位的1: a&1 ==1,    for循环  a = a>>1

  7. char型指针可以很方便的操作int型变量的每一个byte 

  8. 二进制字符串转数字: 找到1的位置, 然后n = n| (1<<i) 

  9. 找到的元素,可以用一个数组把下标都存起来,然后再操作

  10. 不知道循环长度,可以 while() 循环

  11. 将某一段bit翻转:  unsigned int mask = (~0)>>n,  然后XOR

  12. 找不同: XOR 的结果为1

第5条:   0x5A5A,5A5A   想变成 0xA5A5,A5A5

第8条:

数组操作:

数组题的工具:

  1. qsort

  2. 删除重复的数: XOR

  3. 有的时候,搞个int型指针,就可以一次操作4个char数组元素

指针:

qsort

链表:

malloc

free

stack:

queue:

递归: 

XOR实现加法

#include <stdio.h>int add( int a, int b)
{int sum = a^b;int new = a&b;if( new==0 ){return sum;}else{return add(sum, new<<1);}
}int main()
{printf("%d\n", add(3,21));return 0;
}

冒泡算法:

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

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

相关文章

在创业公司,我靠它续命 ...

不知不觉就在新公司工作了一周&#xff0c;没有想象中那么难受。创业公司里没有复杂的人际关系&#xff0c;也没有无聊的会议&#xff0c;更没有复杂的流程。每天上班第一件事就是开个小会&#xff0c;可能是站着开&#xff0c;也可能是连麦开。大家简单过一下前一天的进度&…

vue-cli创建项目、vue项目目录结(运行vue项目)、ES6导入导出语法、vue项目编写规范

vue-cli创建项目、vue项目目录结构、 ES6导入导出语法、vue项目编写规范 1 vue-cli创建项目 1.1 vue-cli 命令行创建项目 1.2 使用vue-cli-ui创建 2 vue项目目录结构 2.1 运行vue项目 2.2 vue项目的目录结构 3 es6导入导出语法 4 vue项目编写规范 4.1 修改项目 4.2 以后…

LabVIEW崩溃后所产生的错误日志文件的位置

LabVIEW崩溃后所产生的错误日志文件的位置 LabVIEW开发环境刚刚崩溃&#xff0c;请问我如何访问崩溃后自动生成的日志文件&#xff1f; LabVIEW崩溃后产生的转储文件位于何处&#xff1f; 代码导致了LabVIEW崩溃&#xff0c;请问哪些文件可以帮助NI技术支持了解具体原因&…

腾讯mini项目-【指标监控服务重构】2023-08-24

今日已办 Jeager 功能 监控分布式工作流程并排除故障识别性能瓶颈追踪根本原因分析服务依赖关系 部署 部署 Deployment — Jaeger documentation (jaegertracing.io) 支持 clickhouse jaegertracing/jaeger-clickhouse: Jaeger ClickHouse storage plugin implementation …

Python之列表

标题 列表什么是列表列表的创建列表的删除列表的访问 列表的常用方法append()、insert()、extend()pop()、remove()、clear()count()、index()list()、 filter()、 reduce()、lambda() 列表支持的运算加法运算符乘法运算符*成员测试运算符in 内置函数对列表的操作列表推导式列表…

Python 逢七拍手小游戏2.0

"""逢七拍手游戏介绍&#xff1a;逢七拍手游戏的规则是&#xff1a;从1开始顺序数数&#xff0c;数到有7&#xff0c;或者是7的倍数时&#xff0c;就拍一手。例如&#xff1a;7、14、17......70......知识点&#xff1a;1、循环语句for2、嵌套条件语句if/elif/e…

tensorrt获取输入输出

利用Netron打开onnx&#xff0c;右边名字&#xff1a; int input_index engine->getBindingIndex("inout1.1");int output_index engine->getBindingIndex("191");

010_第一代软件开发(二)

第一代软件开发(二) 文章目录 第一代软件开发(二)项目介绍界面布局功能完善快照功能获取可用串口播放按键提示音 关键字&#xff1a; Qt、 Qml、 QSerialPort、 QPixmap、 QSoundEffect 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff…

Java基础(一)——Hello World,8种数据类型,键盘录入

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

C++项目:仿muduo库实现高性能高并发服务器

文章目录 一、实现目标二、前置知识&#xff08;一&#xff09;HTTP服务器1.概念 &#xff08;二&#xff09;Reactor模型&#xff1a;1.概念2.分类&#xff08;1&#xff09;单Reactor单线程&#xff1a;单I/O多路复用业务处理。&#xff08;2&#xff09;单Reactor多线程&…

ElementUI之登陆+注册

一.什么是ElementUI 二.ElementUI完成用户注册登录界面搭建 使用命令npm install element-ui -S&#xff0c;添加Element-UI模块 导依赖 建立登录和注册页面 ​编辑 配置样式 编写登录页面&#xff08;Login&#xff09; 编写注册页面&#xff08;reginter&#xff09; …

Elasticsearch(Es搜索(简单使用、全文查询、复合查询)、地理位置查询、特殊查询、聚合操作、桶聚合、管道聚合)

Elasticsearch&#xff08;三&#xff09;——Es搜索&#xff08;简单使用、全文查询、复合查询&#xff09;、地理位置查询、特殊查询、聚合操作、桶聚合、管道聚合 一、Es搜索 这里的 Es 数据博主自己上网找的&#xff0c;为了练习 Es 搜索。 1、Elasticsearch 搜索入门 …

三子棋小游戏(简单详细)

设计总体思路 实现游戏可以一直玩&#xff0c;先打印棋盘&#xff0c;玩家和电脑下棋&#xff0c;最后分出胜负。 如果编写较大的程序&#xff0c;我们可以分不同模块 例如这个三子棋&#xff0c;我们可以创建三个文件 分别为&#xff1a; game.h 函数的声明game.c 函数…

MySQL简介以及安装和部署(Linux)

MySQL简介 MySQL是一个小型关系数据库管理系统&#xff0c;开发者为瑞典MySQL AB公司。在2008年1月16号被sun公司10亿美金收购。2009年&#xff0c;SUN又被Oracle以74亿美金收购。 目前MySQL被广泛地应用在Internet上的中小型网站中。由于体积小、速度快、总体拥有成本低&…

java微服务项目整合skywalking链路追踪框架

skywalking官网网址&#xff1a;Apache SkyWalking 目录 1、安装skywalking 2、微服务接入skywalking 3、skywalking数据持久化 1、安装skywalking 下载skywalking&#xff0c;本篇文章使用的skywalking版本是8.5.0 Index of /dist/skywalkinghttps://archive.apache.org/…

网站整站优化-网站整站优化工具

您是否曾为您的网站在搜索引擎中的排名而感到焦虑&#xff1f;是否苦苦思考如何提高流量、吸引更多用户&#xff1f; 什么是整站优化。简而言之&#xff0c;它是一项用于提升网站在搜索引擎中排名的策略和技巧。通过对网站的内容、结构、速度等方面进行优化&#xff0c;可以使…

腾讯mini项目-【指标监控服务重构】2023-08-27

今日已办 Docker Monitoring with cAdvisor, Prometheus and Grafana Docker Monitoring with cAdvisor, Prometheus and Grafana | by Mertcan Simsek | MediumMonitoring Docker container metrics using cAdvisor | Prometheus prometheus.yml global:scrape_interval: …

电子信息工程专业课复习知识点总结:(五)通信原理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第一章通信系统概述——通信系统的构成、各部分性质、性能指标1.通信系统的组成&#xff1f;2.通信系统的分类&#xff1f;3.调制、解调是什么&#xff1f;有什么用…

牛客java训练题 day1

9.24 day1 Q 1. this 指针是用来干什么的&#xff1f; 2.基类和派生类分别是指什么&#xff1f; 3.为什么方法中不能写静态变量 4. 解释一下ASCII码和ANSI码和两者的区别 5.简述j ava.io java.sql java.awt java.rmi 分别是什么类型的包 6. 看下面一段代码&#xff1a;…

自定义数据类型

前言&#xff1a;小伙伴们又见面啦&#xff0c;今天这篇文章&#xff0c;我们来谈谈几种自定义数据类型。 目录 一.都有哪些自定义数据类型 二.结构体 结构体内存对齐 1.如何对齐 2.为什么要对齐 3.节省空间和提升效率的方法 &#xff08;1&#xff09;让占用空间小的成员…