02 FreeRTOS 任务

1、创建任务函数

1.1 动态内存的使用

        在之前我们如果要创建一个与学生有关的任务,我们会定义:

//打印50个学生的信息
char name[50][100];
int age[50];
int sex[50];	//1表示男,0表示女
int score[50];

         如果之后要对其进行修改会非常麻烦,因此我们要引入面对对象的编程思想,这个对象就是student,那么我们就定义一个相关的结构体来表示一个学生:

struct Student{char name[100];int age;int sex;int score;struct Student *next;    //这里定义一个指针,可以使用链表把学生管理起来
};

        通过这种编程思想,我们在FressRTOS中对任务也要构造出一个结构体,之前我们是通过xTaskCreate函数动态创建了任务,xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1),同时在创建时指定了栈的大小为100*4个字节,这个TaskHandle_t * const则是指向了typedef struct tskTaskControlBlock * TaskHandle_t,也就是TCB_t *这个结构体,所以说我们创建任务时所返回的handle,就只是这个TCB_t 的指针,另外取了一个名字。

        在xTaskCreate中有一个TCB_t *结构体,同时可以看出这里用了malloc从动态内存,也就是从堆里面来做分配

1.2 静态创建任务

        使用xTaskCreateStatic函数静态创建任务,要事先分配好TCB结构体,栈。

        如果要使用这个函数,还要在FreeRTOSconfig.h中定义"configSUPPORT_STATIC_ALLOCATION""为1,并且实现vApplicationGetldleTaskMemory函数。

//关键代码
void Task1Function( void * param)
{while(1){printf("1");}
}void Task2Function( void * param)
{while(1){printf("2");}
}void Task3Function( void * param)
{while(1){printf("3");}
}/*-----------------------------------------------------------*/
StackType_t xTask3Stack[100];StaticTask_t xTask3TCB;StackType_t xIdleTaskStack[100];	//定义空闲任务栈
StaticTask_t xIdleTask3TCB;			//定义空闲任务TCB//申请获得空闲任务内存
//要提供空闲任务的TCBBuffer,空闲任务的栈Buffer,空闲任务的栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t ** ppxIdleTaskTCBBuffer, StackType_t ** ppxIdleTaskStackBuffer,uint32_t * pulIdleTaskStackSize)
{*ppxIdleTaskTCBBuffer = &xIdleTask3TCB;*ppxIdleTaskStackBuffer = xIdleTaskStack;*pulIdleTaskStackSize = 100;
}//main.c
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);    //动态创建
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);    //动态创建
xTaskCreateStatic(Task3Function, "Task3", 100, NULL, 1, xTask3Stack, &xTask3TCB);    //静态创建

1.3 进一步实验

1.3.1 优先级实验

        在FreeRTOS中,优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。

        在之前的代码中添加修改:

static int task1flagrun = 0;
static int task2flagrun = 0;
static int task3flagrun = 0;void Task1Function( void * param)
{while(1){task1flagrun = 1; task2flagrun = 0;task3flagrun = 0;printf("1");}
}void Task2Function( void * param)
{while(1){task1flagrun = 0; task2flagrun = 1;task3flagrun = 0;printf("2");}
}void Task3Function( void * param)
{while(1){task1flagrun = 0; task2flagrun = 0;task3flagrun = 1;printf("3");}
}

        然后再main函数中打断点,打开调试,运行到断点,然后将task1flagrun、task2flagrun、task3flagrun这三个变量添加到逻辑分析仪中去运行一段时间,我们会发现,同优先级的3个任务,不会同时进行。

        如果我们将Task1Function的优先级设置为2,其它仍为1,运行时候我们就会发现这时候,只打印1,没有执行另外两个任务。由此可以看出,对于FreeRTOS来说,在默认的调度下面,高优先级的任务先执行,如果高优先级的任务没有主动放弃运行,其它低优先级的任务无法执行。

1.3.2 删除任务

        我们在创建任务的时候传入了一个handle,以后想要引用这个任务就必须要通过这个handle执行。如果

void Task2Function( void * param)
{int i = 0;while(1){task1flagrun = 0; task2flagrun = 1;task3flagrun = 0;printf("2");//删除任务一if(i++ == 100){vTaskDelete(xHandleTask1);}//自杀if(i == 200){vTaskDelete(NULL);}}
}

        通过观察实验结果可以发现,经过修改的程序,在运行之初会打印1、2、3,运行一会儿后只打印2、3,再之后就只打印3了。

        使用vTaskDelete既可以删除动态创建的任务,也可以删掉静态创建的任务,但是要想删除任务,必须要记录创建任务时的返回值handle。

        FreeRTOS在xTaskCreate中分配TCB和栈,但是并不是在vTaskDelete中释放TCB和栈,而是在空闲任务中进行这些清理工作,如果连续不断的调用xTaskCreate和vTaskDelete,最终会导致内存耗光。 

1.3.3 使用同一个任务函数创建多个任务

void TaskGenericFunction( void * param)
{int val = (int)param;while(1){		printf("%d", val);}
}xTaskCreate(TaskGenericFunction, "Task4", 100, (void *)4, 1, NULL);
xTaskCreate(TaskGenericFunction, "Task5", 100, (void *)5, 1, NULL);

        通过结果发现4和5都成功打印出来了,证明可以在同一个任务函数中创建多个任务。

        使用同一个任务会产生不同的效果,是因为它们的栈是不一样的,传入的参数都保存在不同的栈里面,运行的时候互不影响。

1.3.4 栈大小实验

void Task1Function( void * param)
{//在创建任务时,申请了100*4的空间,这里故意定义500,耗尽栈的空间,从高地址向下增长,破坏下面的空间//这样程序运行的时候是完全不可控的,程序会崩溃volatile char buf[500];	//使用关键字,不允许没有使用的空间被优化掉int i;while(1){task1flagrun = 1; task2flagrun = 0;task3flagrun = 0;printf("1");for(i = 0; i < 500; i++){buf[i] = 0;}}
}

        程序按上面修改之后运行,直接崩溃了,在申请的栈空间以及TCB空间的前面会有一个头来存储有关的信息,便于程序返回等操作,一旦栈的空间使用不当,冲破了空间限制,把前面头部的信息更改了,程序就会发生不可控的问题。

2、任务状态

2.1 任务切换的基础:tick中断

        在FreeRTOS系统中有个定时器,这个定时器每隔一段时间会产生一个中断,这个间隔就称为tick,发生中断时,它会将发生中断的次数记录下来,初始值为0,每发生一次中断就累加1,这个中断值就成为tick count,这将是RTOS的时钟基准。

        在发生中断时,tick中断处理函数被调用,在这个函数中,它会判断是否要切换任务,如果要切换任务就会去切换任务。而运行任务的基准时间(周期),可以在源码的FreeRTOSConfig.h中去配置,我们也可以指定每个任务每次执行几个tick:

#define configTICK_RATE_HZ        ((TickType_t)1000)

        在使用keil模拟器中的逻辑分析仪时,如果时间不准确,要确认代码中设置时钟时用的频率和Options中Xtal的频率相同。

        在main函数中创建多个任务时,为什么后面创建的任务反而先运行?因为后面的任务插入链表时,pxCurrentTCB先执行它。创建任务时代码如下,后创建的最高优先级任务先执行:

2.2 有哪些任务状态?状态切换图

        正在运行的任务:Running状态

        可以随时运行但是现在还没轮到:Ready状态

        阻塞,等待某些事情发生才能继续执行:Blocked状态

        暂停,主动/被动休息:Suspended状态

        状态转换图:

2.3 怎么管理不同状态的任务:放在不同的链表里

        将不同状态的任务放在不同的链表里,当有需要执行时,就从对应链表中挑出一个来执行。

2.4 阻塞状态(Blocked)举例:vTaskDelay函数

        使用vTaskDelay时,如果想要延时若干毫秒,那么可以自己把毫秒换算成Tick数,或者使用宏把毫秒换算成Tick数。有一个宏:pdMS_TO_TICKS(ms),可以把毫秒转换成Tick数。

void Task2Function( void * param)
{int i = 0;while(1){task1flagrun = 0; task2flagrun = 1;task3flagrun = 0;printf("2");//让任务2进入阻塞状态vTaskDelay(10);}
}

2.5 暂停状态(Suspended)举例:vTaskSuspend/vTaskResume

void Task1Function( void * param)
{//获得TickCount的值TickType_t tStart = xTaskGetTickCount();TickType_t t;int flag = 0;while(1){t = xTaskGetTickCount();	//在运行的时候获取当前时间task1flagrun = 1; task2flagrun = 0;task3flagrun = 0;printf("1");//命令任务3进入暂停状态,如果想让自己主动休息,可以传入NULL//对于进入暂停状态的任务必须由别人来唤醒if(!flag && t > tStart + 10){vTaskSuspend(xHandleTask3);flag = 1;	//设置一个标志位,不要让重复让任务3进入暂停状态}if(t > tStart + 30){//命令任务3恢复运行状态vTaskResume(xHandleTask3);}}
}

3、实现周期性的任务

3.1 vTaskDelay

        至少等待指定个数的Tick Interrupt才能变成就绪状态。

static int rands[] = {2, 18, 64, 121, 9};void Task1Function( void * param)
{//获得TickCount的值TickType_t tStart = xTaskGetTickCount();int i =0;int j =0;while(1){task1flagrun = 1; task2flagrun = 0;task3flagrun = 0;for(i = 0; i < rands[j]; i++){printf("1");		}j++;if(j == 5){j = 0;}vTaskDelay(20);	//延时20个tick}
}

        通过结果图可以看出,延迟的时间的一致的。

3.2 vTaskDelayUntil

        等待到指定的绝对时刻,才能变为就绪状态。

        vTaskDelayUntil是老版本,它没有返回值,在新版本中可以用xTaskDelayUntil函数,二者传入的参数是一样的,只是新的有返回值。

static int rands[] = {2, 18, 64, 121, 9};void Task1Function( void * param)
{//获得TickCount的值TickType_t tStart = xTaskGetTickCount();int i =0;int j =0;while(1){task1flagrun = 1; task2flagrun = 0;task3flagrun = 0;for(i = 0; i < rands[j]; i++){printf("1");		}j++;if(j == 5){j = 0;}//设置个开关
#if 0vTaskDelay(20);	//延时20个tick
#elsevTaskDelayUntil(&tStart, 20);	//延时到(tStart+20tick)时刻,同时更新tStart=tStart+20tick
#endif}
}

        通过结果图可以看出,每两次开始执行之间的时间是一致的。

4、空闲任务及其钩子函数

4.1 空闲任务        

        在xTaskCreate中分配TCB和栈,但是并不一定是在vTaskDelete中释放TCB和栈,对于自杀的任务,由空闲任务来清理内存,对于他杀的任务,由凶手来清理。

        我们在任务1中创建任务2,并且将任务一的优先级设置为1,任务二的优先级设置为2,在任务二中打印语句之后自杀。这样我们在程序中会有3个任务,任务一、任务二和空闲任务,空闲任务的优先级最低,为0。这样执行之后,程序很快就崩溃了,因为可分配的内存不够了,堆不够了。

void Task2Function( void * param);/*-----------------------------------------------------------*/void Task1Function( void * param)
{TaskHandle_t xHandleTask2;BaseType_t xReturn;while(1){printf("1");xReturn = xTaskCreate(Task2Function, "Task2", 1024, NULL, 2, &xHandleTask2);if(xReturn != pdPASS){printf("xTaskCreate err\r\n");}}
}void Task2Function( void * param)
{while(1){printf("2");//vTaskDelay(2);vTaskDelete(NULL);}
}

        如果是在任务一中杀死任务二,那么程序现象会一直执行。

void Task2Function( void * param);/*-----------------------------------------------------------*/void Task1Function( void * param)
{TaskHandle_t xHandleTask2;BaseType_t xReturn;while(1){printf("1");xReturn = xTaskCreate(Task2Function, "Task2", 1024, NULL, 2, &xHandleTask2);if(xReturn != pdPASS){printf("xTaskCreate err\r\n");}vTaskDelete(xHandleTask2);}
}void Task2Function( void * param)
{while(1){printf("2");vTaskDelay(2);}
}

4.2 钩子函数         

        使用空闲任务不仅可以帮我们清理自杀的任务,还可以执行一些低优先级、后台的、需要连续执行的函数、测量系统的空闲时间、让系统进入省电模式等。如果要做到这些,我们可以通过修改空闲任务的函数来实现,但是如果直接修改会破坏FreeRTOS的核心文件,因此提供了一个钩子函数,可以先配置规定的宏,来告诉程序你要使用这个钩子函数。

        对钩子函数,空闲任务对其也会有一些限制:不能导致空闲任务进入阻塞状态、暂停状态;如果你会使用vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务一直卡在钩子函数里的话,它就无法释放内存。

        使用钩子函数的前提:

                在FreeRTOSConfig.h中把这个宏定义为1:configUSE_IDLE_HOOK

                实现vApplicationIdleHook函数

void Task2Function( void * param);/*-----------------------------------------------------------*/
static int task1flagrun = 0;
static int task2flagrun = 0;
static int taskidleflagrun = 0;void Task1Function( void * param)
{TaskHandle_t xHandleTask2;BaseType_t xReturn;while(1){task1flagrun = 1; task2flagrun = 0;taskidleflagrun = 0;printf("1");xReturn = xTaskCreate(Task2Function, "Task2", 1024, NULL, 2, &xHandleTask2);if(xReturn != pdPASS){printf("xTaskCreate err\r\n");}vTaskDelete(xHandleTask2);}
}void Task2Function( void * param)
{while(1){task1flagrun = 0; task2flagrun = 1;taskidleflagrun = 0;printf("2");vTaskDelay(2);}
}void vApplicationIdleHook(void)
{task1flagrun = 0; task2flagrun = 0;taskidleflagrun = 1;printf("0");
}//main函数里
xTaskCreate(Task1Function, "Task1", 100, NULL, 0, &xHandleTask1);

        在这个程序中,任务一先执行,在任务一中创建任务二,此时任务二的优先级最高,因此任务二先执行,之后杀掉任务二,任务一和空闲任务的优先级相同,二者交替执行,在执行空闲任务时会用到钩子函数。

5、任务调度算法

5.1 状态与事件

        正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理器系统中,任何时间里只能有一个任务处于运行状态。

        非运行状态的任务,它处于这3种状态之一:

                阻塞(Blocked)

                暂停(Suspended)

                就绪(Ready)

        就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。

        阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。

        事件分为两类:

                时间相关的事件:就是设置超时时间,在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。

                同步事件:同步事件就是某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么"发送信息"的方法有很多,比如任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。这些方法用来发送同步信息,比如表示某个外设得到了数据。

5.2 调度策略

5.2.1 可否抢占?

        高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)

        如果可以:被称作"可抢占调度"(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。

        如果不可以:不能抢就只能协商了,被称作"合作调度模式"(Co-operative Scheduling)。当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出CPU资源。其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点

2.2.2 可抢占的前提下,同优先级的任务是否轮流执行(配置项: configUSE_TIME_SLICING)?

        轮流执行:被称为"时间片轮转"(Time Slicing),同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片

        不轮流执行:英文为"without Time Slicing",当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占。

5.2.3 允许抢占、允许时间片轮转时,空闲任务是否让步?

        在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项:configlDLE_SHOULD_YIELD)。如果让步,则空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务。如果不让步,空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊。

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

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

相关文章

使用稀疏约束水平集算法对MR图像中的脑肿瘤进行分割| 文献速递-深度学习肿瘤自动分割

Title 题目 Brain tumor segmentation in MR images using a sparse constrained level set algorithm 使用稀疏约束水平集算法对MR图像中的脑肿瘤进行分割" 01 文献速递介绍 脑磁共振&#xff08;MR&#xff09;成像是成像患者脑结构的主要方法&#xff0c;从MR图像…

最后7天,高考翻盘秘籍等你开启!

高考&#xff0c;这场关乎未来的考试&#xff0c;对于每一个学生来说都是一次严峻的挑战。随着倒计时的进行&#xff0c;无数考生和家长的焦虑和期待达到了顶点。在这个最后7天的关键时期&#xff0c;我们为即将参加高考的学生及其家长提供一份复习秘籍&#xff0c;帮助你们抓住…

rfid资产管理系统如何帮助医院管理耗材的

RFID资产管理系统可以帮助医院管理耗材&#xff0c;提高耗材管理的效率和准确性。以下是它可以发挥作用的几个方面&#xff1a; 1. 实时跟踪和定位&#xff1a;使用RFID标签附加在耗材上&#xff0c;可以实时跟踪和定位耗材的位置。医院可以通过系统查询耗材的实时位置&#xf…

微服务八股-分布式事务-注册中心-服务保护

一、分布式事务 1.CAP和BASE 三者不能同时存在。 CP&#xff1a;由于网络分片的存在&#xff0c;如果要保证强一致性就不能写&#xff0c;此时不满足可用性 AP&#xff1a;由于网络分片的存在&#xff0c;如果要保证可用性&#xff0c;能读也能写&#xff0c;就不能保证强一致…

【讲解下Web前端三大主流的框架】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Java项目:基于SSM框架实现的学生就业管理系统分前后台(ssm+B/S架构+源码+数据库+毕业论文+开题报告)

一、项目简介 本项目是一套基于SSM框架实现的学生就业管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…

pytorch文本分类(四)模型框架(模型训练与验证)

关卡四 模型训练与验证 本文是在原本闯关训练的基础上总结得来&#xff0c;加入了自己的理解以及疑问解答&#xff08;by GPT4&#xff09; 原任务链接 目录 关卡四 模型训练与验证1. 训练1.1 构建模型结构1.2 模型编译1.3 模型训练1.4模型超参数调试 2. 推理2.1 模型准确性…

束测后台实操文档1-PVE、PBS

合肥先进光源束测系统后台基础架构初步设计报告 合肥先进光源束测系统后台搭建进展2024.4.29 关于后台基础架构&#xff0c;写了上面两篇文档&#xff0c;只是框架的印象&#xff0c;没涉及到具体的实操&#xff0c;后面针对具体的搭建慢慢的完善操作的细节&#xff0c;从今年…

基于STM32实现智能园艺系统

目录 引言环境准备智能园艺系统基础代码示例&#xff1a;实现智能园艺系统 土壤湿度传感器数据读取水泵控制温湿度传感器数据读取显示系统用户输入和设置应用场景&#xff1a;智能农业与家庭园艺问题解决方案与优化收尾与总结 1. 引言 本教程将详细介绍如何在STM32嵌入式系统…

四川省税务局CDH国产化替代实践

“传统数据仓库对于数据处理时效较低&#xff0c;且无法处理实时增量数据及数据变更&#xff0c;同时&#xff0c;在面对海量税务数据大规模进行查询分析等方面存在一些挑战。我们希望尽快寻找到一款能够替代CDH&#xff0c;并且具备灵活扩展能力的大数据解决方案&#xff0c;以…

有限元法之有限元空间的构造

目录 一、区域Ω的剖分 二、三角形一次元 三、一次元的基函数与面积坐标 四、三角形二次元及其基函数 前两节我们介绍了有限元基本概念和变分理论的推导&#xff0c;本节我们继续探讨有限元空间的构造。 一、区域Ω的剖分 对矩形区域进行三角剖分&#xff0c;其中x方向剖…

类 和 对象(二)

构造方法 接上篇&#xff0c;若每次都想下面的setDate方法给对象初始化&#xff0c;未免比较麻烦&#xff0c;那有什么方法可以让初始化更加简便呢&#xff1f; public void setDate(int year, int month, int day){this.year year;this.month month;this.day day;}答&#…

文献分享《Microbiome and cancer》

人类微生物群构成了一个复杂的多王国群落&#xff0c;与宿主在多个身体部位共生相互作用。宿主-微生物群的相互作用影响多 种生理过程和各种多因素的疾病条件。在过去的十年中&#xff0c;微生物群落被认为会影响多种癌症类型的发展、进展、转移 形成和治疗反应。虽然微生物对癌…

2024年短视频评论区批量爬取采集软件

一、背景说明 前言 评论区引流&#xff0c;顾名思义&#xff0c;是通过在视频下方进行留言评论、回复评论&#xff0c;吸引用户的注意&#xff0c;从而和你的账号产生互动、交易。比如&#xff0c;在一个关于健身的视频下方&#xff0c;留言分享自己的健身经验或者提出问题。…

使用 SwanLab 进行可视化 MNIST 手写体识别训练

使用 SwanLab 进行可视化 MNIST 手写体识别训练 在线演示demo 本案例主要&#xff1a; 使用pytorch进行CNN&#xff08;卷积神经网络&#xff09;的构建、模型训练与评估使用swanlab跟踪超参数、记录指标和可视化监控整个训练周期 一、相关简介 SwanLab SwanLab是一款开源…

Linux中ftp配置

一、ftp协议 1、端口 ftp默认使用20、21端口 20端口用于建立数据连接 21端口用于建立控制连接 2、ftp数据连接模式 主动模式&#xff1a;服务器主动发起数据连接 被动模式&#xff1a;服务器被动等待数据连接 二、ftp安装 yum install -y vsftpd #---下…

使用httpx异步获取高校招生信息:一步到位的代理配置教程

概述 随着2024年中国高考的临近&#xff0c;考生和家长对高校招生信息的需求日益增加。了解各高校的专业、课程设置和录取标准对于高考志愿填报至关重要。通过爬虫技术&#xff0c;可以高效地从各高校官网获取这些关键信息。然而&#xff0c;面对大量的请求和反爬机制的挑战&a…

蓝桥杯物联网竞赛_STM32L071KBU6_字符串处理

前言&#xff1a; 个人感觉国赛相较于省赛难度上升的点在于对于接收的字符串的处理&#xff0c;例如串口发送的字符串一般包含字母字符串 数字字符串&#xff0c;亦或者更复杂&#xff0c;对于LORA也是如此&#xff0c;传递的字符串如#9#1亦或者#1a#90,#1#12&#xff0c;如何…

剖析【C++】——类与对象(上)超详解——小白篇

目录 1.面向过程和面向对象的初步认识 1.面向过程&#xff08;Procedural Programming&#xff09; 2.面向对象&#xff08;Object-Oriented Programming&#xff09; 概念&#xff1a; 特点&#xff1a; 总结 2.C 类的引入 1.从 C 语言的结构体到 C 的类 2.C 中的结构…

调用萨姆索诺夫函数:深入探索函数的参数与返回值

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、萨姆索诺夫函数的引入与调用 二、如何获取函数的返回值 三、无参数与无返回值的函数调…