当前位置: 首页 > news >正文

Linux基础——进程控制

1. 进程创建

在这之前我们曾了解过进程创建(详见进程初识(二)),我们在这里对fork函数做一些补充

其实对于父子进程来说,若是有一方试图修改数据时,会向物理内存中申请一份新空间,并将数据拷贝到其中,拷贝完成后将自己对应页表中的只读属性去掉。

2. 进程终止

我们之前都知道,在main函数的最后我们一般都有return 0;这个语句,那么为什么要返回0呢?返回1,2怎么样?这个值返回给了谁?以及为什么要返回这个值呢?

在这里,返回的这个0其实是程序的退出码,来表征进程的运行结果是否正常(0表示success)。对于一个进程来说,当它终止时无外乎三种情况:

1. 代码运行完,运行结果正确

2. 代码运行完,运行结果不正确

3. 代码异常终止

对于前两种情况来说,我们怎么知道运行结果是否正确呢?——可以使用return 返回不同数字,来表示不同的出错原因,这就是进程的退出码。因此,对于代码运行完后判断运行结果是否正确,统一采用进程的退出码来判定。

那么除了代码运行完以外,代码也有可能异常终止,此时代码可能没有跑完,因此在这里进程的退出码毫无意义,即不关心退出码,我们需要关注的是为什么异常?发生了什么异常? 在这个过程中,先确定是否出现异常,未出现就返回退出码,而出现了异常操作系统会发出信号,然后退出。

我们可以用如下方式来验证,

对于上面这段代码我们运行可以发现

在kill指令中我们可以找到与之相对应的信号

我们对一个正常运行的进程使用kill -8 PID有

可以得到相同的结果。

main函数的返回值,本质表示进程运行完成时是否是正确结果,若不是可以用不同数字来表示不同的出错原因,而对于进程来说谁最关心当前进程的情况呢?——父进程,因此main函数的返回值其实是返回给了父进程。而对于退出码,我们可以使用

echo $?

来获取最近一次的退出码,如

而在C语言的库里面有一个将错误码转换为错误信息的函数,即strerror,其手册如下

我们可以使用如下代码,将所有错误信息打印出来

运行有

在之前我们ls一个不存在文件时,有

可以看到,这里返回的是错误码为2的错误信息,我们获取错误码也有

即系统提供的错误码和错误码描述是有对应关系的,当然我们也可以自己设计一份,举个例子

而除了使用main函数以外,我们还可以使用exit函数和_exit函数来退出进程,如exit(0);,它们与return的区别在于

exit函数在任意地方被调用,都表示调用进程直接退出

return 只表示当前函数返回

而exit与_exit之间亦有差距,_exit是系统调用,而exit是用户函数,他会在函数实现的过程中调用_exit,对于下面这个代码

在使用exit时结果为

在使用_exit时结果为

在这里printf函数其实是先把数据写入到缓存区,在合适的时候进程刷新,exit属于用户函数它在实现时,内部应该会调用一些函数进行冲刷缓存区的操作,这之后再调用_exit,而_exit则是系统层面直接将这个进程关闭,因此也不会有冲刷其缓存区的情况。

3. 进程等待

①进程等待是什么?

进程等待就是通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能。

②为什么需要进程等待?

在之前我们曾经提到过僵尸进程的存在,由于进程在变成僵尸进程后无法被杀死,我们需要使用进程等待来解决内存泄漏问题(这个问题必须解决)。此外,一个父进程需要通过进程等待,进而获得子进程的退出情况,这样做是为了知道父进程给子进程布置的任务完成地怎么样(也有可能不关心)。

③进程等待是怎么做的?

在代码方面,父进程通过调用wait/waitpid来解决僵尸进程问题,我们可以查看手册,有

在目前看来,进程等待是必须的,对于wait函数,它是等待任意一个子进程退出,如果子进程一直不退出,父进程默认在wait,调用这个系统调用的时候也就不返回,此时就处于阻塞状态。对于waitpid函数,它共有三个参数,对于第一个参数如果传入的pid>0时,表示等待特定的子进程,如果传入的pid=-1时,表示等待任意一个子进程,第二个参数需要解释一下,它是一个输出型参数(即为了把值带出来),这里的int是被当做几部分来使用的,图解如下

前面我们知道,父进程关心子进程,那么父进程期望获得子进程退出后的哪些信息呢?

1. 首先是子进程代码是否异常?——对于status的0-7位来说,当操作系统没有信号发出的时候默认都是0,因此只有0-7位都是0就认为没有收到信号。

2. 没有异常发生,那么结果对吗?不对是因为什么?——对于status的8-15位来说,程序正常退出时默认为0,即0->success,若是结果不对则从其中读取不同的错误码。

举个例子,status值为256时,低16位为0000 0001 0000 0000此时程序正常退出,退出码为1。我们可以使用以下代码来验证

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main()
{pid_t id = fork();if (id < 0){perror("fork\n");exit(1);}else if (id == 0){// childint cnt = 5;while (cnt--){printf("this is child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);sleep(1);}}else{// fatherint cnt = 10;while (cnt--){printf("this is father, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);sleep(1);}// pid_t ret = wait(NULL);int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret == id){// 0x7f:0111 1111printf("wait success, ret:%d, exit sig:%d, exit code:%d\n", ret, status & 0x7F, (status >> 8) & 0xFF);}}return 0;
}

运行有

既然如此,那么进程等待原理是怎么样的呢?

因为操作系统不会相信任何用户,因此他会提供一个接口来让用户访问数据。此外,操作提供提供了两个宏来供我们查看信息

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)

之前我们的代码可以修改一部分,即

而对于第三个参数options来说,其一般默认为0,即以阻塞方式等待,除了0以外还可以传入一个参数——WNOHANG(HANG意思是夯住(指的是系统或进程在执行某个任务时变得非常慢或停滞,导致系统或应用程序不再响应),整体代表wait no HANG),即等待的时候不要夯住,举个例子来理解,在一个男生等女友出门的时候,WNOHANG表示的是男生每隔几分钟向女生打一次电话确认女生出门了没有,而阻塞表示的是男生给女生打电话并且说不要挂电话,等你出门了再挂。

④非阻塞轮询

在上面举的例子中,男生打电话询问之后,如果女友未出门(未准备好),也不进入阻塞状态,再加上打电话的间隔时间,就形成了非阻塞+循环的形式,我们将其称为非阻塞轮询。对于非阻塞轮询来说,相对于阻塞最大的优势就是可以在这个等待的期间做一些自己的事情,此时对于返回的ret来说,当等待事件未就绪时就返回0。但是对于一个父进程来说,当它处于非阻塞轮询的状态时,等待子进程退出才是它的主要工作,因此在此时,能做的事只能是一些轻量化工作(如打印日志等)。我们可以定义如下的一些工作列表

#define TASK_NUM 10typedef void(*task_t)();
task_t tasks[TASK_NUM];void task1()
{printf("这是一个执行打印日志的任务, pid: %d\n", getpid());
}void task2()
{printf("这是一个执行检测网络健康状态的一个任务, pid: %d\n", getpid());
}void task3()
{printf("这是一个进行绘制图形界面的任务, pid: %d\n", getpid());
}int AddTask(task_t t);// 任务的管理代码
void InitTask()
{for(int i = 0; i < TASK_NUM; i++) tasks[i] = NULL;AddTask(task1);AddTask(task2);AddTask(task3);
}int AddTask(task_t t)
{int pos = 0;for(; pos < TASK_NUM; pos++) {if(!tasks[pos]) break;}if(pos == TASK_NUM) return -1;tasks[pos] = t;return 0;
}void DelTask()
{}void CheckTask()
{}void UpdateTask()
{}void ExecuteTask()
{for(int i = 0; i < TASK_NUM; i++){if(!tasks[i]) continue;tasks[i]();}
}

而我们可以在主函数代码中这样调用

int status = 0;
InitTask();
while (1) // 轮询
{pid_t ret = waitpid(id, &status, WNOHANG); // 非阻塞if (ret > 0){if (WIFEXITED(status)){printf("进程是正常跑完的, 退出码:%d\n", WEXITSTATUS(status));}else{printf("进程出异常了\n");}break;}else if (ret < 0){printf("wait failed!\n");break;}else{ExecuteTask();usleep(500000);}
}

 这样封装也带来了非阻塞轮询和执行任务之间的解耦。

4. 进程程序替换

1. 单进程的进程程序替换

我们以下面这段代码为例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{printf("before: this is a process pid: %d, ppid: %d\n", getpid(), getppid());// 标准进程程序替换接口execl("usr/bin/ls", "ls", "-a", "-l", NULL);printf("after: this is a process pid: %d, ppid: %d\n", getpid(), getppid());return 0;
}

我们编译运行可以看到

在结果中我们可以看到,before的打印成功了,而after的打印未成功,那么这究竟是怎么回事呢?

2. 进程程序替换的原理

在一个正常运行的进程中,各部分对应关系如下,而当遇到exec*函数时,会将exec函数中的一个参数中文件的代码与数据替换当前进程的代码与数据,即

从这个基本原理我们可以看到,整个过程没有创建新的进程,同时也没有修改页表中的对应关系。

3. 多进程的进程程序替换

接下来我们使用多进程版来进行测试,代码如下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id == 0) // child{ printf("before: I am a process,pid: %d, ppid:%d\n",getpid(), getppid());//这类方法的标准写法//execl"/usr/bin/ls",“ls","=a","-l",NULL);execl( "/usr/bin/top", "top", NULL);printf("after: I am a process,pid: %d, ppid:%d\n", getpid(), getppid());exit(0);}// fatherpid_t ret = waitpid(id, NULL, 0);if (ret > 0) printf("wait success, father pid: %d,ret id: %d\n", getpid(), ret);return 0;
}

 在这里我们让子进程退出后,可以看到

在子进程退出后,父进程仍能对子进程进行进程等待,由此我们可以得出结论——子进程的程序替换不会影响到父进程,那么在这个过程中代码肯定发生了写实拷贝。在程序替换成功后,exec*函数后的代码不会执行,如果替换失败才可能执行后面的代码,所以exec*函数只有失败的返回值而没有成功的返回值。

4. 多进程中验证exec*接口

我们使用man手册查看execl有

这6个程序替换的接口都提供加载器的效果,即在shell中我们输入一条指令,shell会创建一个新的进程并在其中调用exce*函数来加载指令。

①execl

首先,先解释一下我们已经使用过的execl函数,这里的l意为list

execl("/usr/bin/ls", "ls", "-a", "-l", NULL);

我们要想执行一个程序,第一件事应该是什么呢?——先找到程序在哪,因此在这个函数中传入的第一个参数就决定了如何找到这个程序,后面的参数是命令行如何写就如何传参。在找到了程序后,又干什么呢?——根据传入的参数选项,具体执行对应程序。

②execlp

然后,我们介绍一下execlp函数,l我们已经解释过,这里的p意为PATH,execlp会在默认的PATH环境变量中查找指令,我们可以用下面的代码验证

int main()
{printf("before: I am a process,pid: %d, ppid:%d\n",getpid(), getppid());execlp( "ls", "ls", "-a", "-l", NULL);printf("after: I am a process,pid: %d, ppid:%d\n", getpid(), getppid());return 0;
}

③execle

再然后,我们介绍一下execle函数,l不多说,这里的e意为env,即环境变量,使用方式如下

extern char** environ;execle("/usr/bin/ls", "ls", "-a", "-l", NULL, environ);

需要注意的是,在这里传入自己所定义的环境变量采取的措施是覆盖而非追加。那么我们如何追加环境变量呢?—— 我们可以调用脚本来将一个程序的环境变量导入到另一个程序中,举个简单的例子

我们可以使用下面的代码来测试,即

int main()
{printf("before: I am a process,pid: %d, ppid:%d\n",getpid(), getppid());execl( "/usr/bin/bash", "bash", "shell.sh", NULL);printf("after: I am a process,pid: %d, ppid:%d\n", getpid(), getppid());return 0;
}

看到这里我们能提出一个问题——为什么无论是可执行程序还是脚本,都能跨语言调用呢?其实所有语言运行的程序本质上都是进程。回归正题,在了解了execle函数后,我们若是想给子进程传递环境变量如何传呢?

1. 新增:我们可以使用putenv函数来给自己(父进程)添加环境变量

2. 彻底替换:即使用execle函数来直接替换所有的环境变量

既然在这里谈到了我们之前讲过的环境变量,那么我们可以思考一下:环境变量是什么时候传入给进程的呢?——我们要知道,环境变量也是数据,创建子进程的时候,环境变量已经被子进程继承下去了。因此,在程序替换中,环境变量不会被替换的。

④execv

execv函数中的v意为vector,它其实是一个指针数组,我们以如下代码测试

int main()
{char* const myargv[] = {"ls","-a","-l",NULL};printf("before: I am a process,pid: %d, ppid:%d\n",getpid(), getppid());execv( "/usr/bin/ls", myargv);printf("after: I am a process,pid: %d, ppid:%d\n", getpid(), getppid());return 0;
}

运行有

在这个例子中,myargv是一个命令行参数,而ls内部含有main函数,这个main函数会去调用这个命令行参数去执行,这样就能完成指定任务。后面的execvp, execvpe大同小异,这里就不再赘述。

⑤execve

除了上面几个接口外,还有一个execve函数,其文档如下

它与前面六个函数的区别在于execve是系统调用,前面六个函数都是库函数,它们都要调用execve接口。

相关文章:

Linux基础——进程控制

1. 进程创建 在这之前我们曾了解过进程创建&#xff08;详见进程初识&#xff08;二&#xff09;&#xff09;&#xff0c;我们在这里对fork函数做一些补充 其实对于父子进程来说&#xff0c;若是有一方试图修改数据时&#xff0c;会向物理内存中申请一份新空间&#xff0c;并…...

网络工程师笔记8

华为VRP系统 设备管理方式 web管理方式 命令行管理方式 修改命令&#xff1a;undo 基础配置命令...

从零学算法128

128.给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#xff1a; 输入&#xff1a;nums [100,4,200,1,3,2] 输出&#xff1a;4…...

2024免费mac苹果电脑的清理和维护软件CleanMyMac X

对于 Mac 用户来说&#xff0c;电脑的清理和维护是一件让人头疼的事情。但是&#xff0c;有了 CleanMyMac X&#xff0c;这一切都将变得轻松愉快。CleanMyMac X 是一款专为 Mac 设计的电脑清理软件&#xff0c;它以其强大的功能和简单的操作&#xff0c;让无数用户为之倾倒。 C…...

Python反射机制在实际场景中的应用

Python 的反射机制是指在运行时动态地访问、检测和修改类和对象的属性和方法。反射为开发者提供了一种灵活的方式来处理对象和类&#xff0c;可以在实际场景中提供一些有用的功能和应用&#xff0c;下面是 Python 反射在实际场景中的一些常见应用&#xff1a; 插件系统&#xf…...

网络原理初识

一、IP地址 概念 IP 地址主要用于标识网络主机、其他网络设备&#xff08;如路由器&#xff09;的网络地址。简单说&#xff0c; IP 地址用于定位主机 的网络地址 。 就像我们发送快递一样&#xff0c;需要知道对方的收货地址&#xff0c;快递员才能将包裹送到目的地。 二、…...

关于uniapp小程序的分包问题

开发uniapp小程序时&#xff0c;在打包上传代码时会出现超出2M的打包限制不能上传&#xff0c;那么我们该怎么做呢&#xff1f; 1.对于图片&#xff0c;将图片从后端服务取&#xff0c;尽量不要放在静态资源&#xff0c;图片体积会影响打包大小。 2.使用分包&#xff0c;tabb…...

MySQL:索引的优化方法

索引是帮助存储引擎快速获取数据的一种数据结构&#xff0c;形象的说就是索引是数据的目录。 索引创建的时机&#xff1a; 索引并不是越多越好的&#xff0c;虽然他再查询时会提高效率&#xff0c;但是保存索引和维护索引也需要一定的空间和时间成本的。 不创建索引&#xff1a…...

前后端分离vue+nodejs+mysql高校学生社团管理系统xgp16

系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方法对高校社团的现状进行系统调查。采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计中采用“自下而上”的思想&a…...

HCIA-Datacom实验指导手册:7 构建简单 IPv6 网络

HCIA-Datacom实验指导手册&#xff1a;7 构建简单 IPv6 网络 一、实验介绍&#xff1a;二、实验拓扑&#xff1a;三、实验目的&#xff1a;四、配置步骤&#xff1a;步骤 1 设备基础配置设备命名 步骤 2 配置设备及接口 IPv6 功能步骤 3 配置接口的 link-local 地址&#xff0c…...

ElasticSearch搜索引擎使用指南

一、ES数据基础类型 1、数据类型 字符串 主要包括: text和keyword两种类型&#xff0c;keyword代表精确值不会参与分词&#xff0c;text类型的字符串会参与分词处理 数值 包括: long, integer, short, byte, double, float 布尔值 boolean 时间 date 数组 数组类型不…...

mysql与oracle的区别

一、并发性并发性是oltp数据库最重要的特性&#xff0c;但并发涉及到资源的获取、共享与锁定。mysql:mysql以表级锁为主&#xff0c;对资源锁定的粒度很大&#xff0c;如果一个session对一个表加锁时间过长&#xff0c;会让其他session无法更新此表中的数据。虽然InnoDB引擎的表…...

JVM相关面试题及常用命令参数

JVM常用命令和参数 常用命令&#xff1a; jps&#xff1a;查看进程及其相关信息 jmap&#xff1a;用来生成dump文件和查看堆相关的各类信息的命令 jstat&#xff1a;查看jvm运行时的状态信息 jstack&#xff1a;查看jvm线程快照的命令 jinfo&#xff1a;查看jvm参数和动态修改…...

Material UI 5 学习01-按钮组件

Material UI 5 学习01-按钮组件 一、安装Material UI二、 组件1、Button组件1、基础按钮2、variant属性3、禁用按钮4、可跳转的按钮5、disableElevation属性6、按钮的点击事件onClick 2、Button按钮的颜色和尺寸1、Button按钮的颜色2、按钮自定义颜色3、Button按钮的尺寸 3、图…...

解决移除数字问题的两种方法:暴力法和使用栈

题目 给你一个以字符串表示的非负整数 num 和一个整数 k &#xff0c;移除这个数中的 k 位数字&#xff0c;使得剩下的数字最小。请你以字符串形式返回这个最小的数字 示例 1 &#xff1a; 输入&#xff1a;num "1432219", k 3 输出&#xff1a;"1219"…...

高校宣讲会管理系统|基于Springboot的高校宣讲会管理系统设计与实现(源码+数据库+文档)

高校宣讲会管理系统目录 目录 基于Springboot的高校宣讲会管理系统设计与实现 一、前言 二、系统功能设计 1、学生信息管理 2、企业信息管理 3、宣讲会管理 4、公告信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 …...

6_怎么看原理图之协议类接口之LCD笔记

首先想一想再前几篇文章讲的协议类的前提 1、双方约定好通信的协议 2、双方满足一定的时序要求 以上第二点又有一些要求&#xff1a; 1&#xff09;弄清2440在这个通信协议中&#xff0c;能设置哪些时序的值&#xff0c;这些值的含义是什么——2440手册 2&#xff09;弄清楚这…...

SpringCloud Alibaba 学习

一&#xff1a;SpringCloud Alibaba介绍 Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服 务的必需组件&#xff0c;方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 依托 Spring Cloud Alibaba&…...

【ros2 control 机器人驱动开发】双关节多控制器机器人学习-example 4

【ros2 control 机器人驱动开发】双关节多控制器机器人学习-example 4 文章目录 前言一、创建controller相关二、测试运行测试forward_position_controller总结前言 本篇文章在上篇文章的基础上主要讲解双轴机器人驱动怎么编写机器人外部/内部(扭矩、压力)传感器数据反馈1,…...

Leetcode 3071. Minimum Operations to Write the Letter Y on a Grid

Leetcode 3071. Minimum Operations to Write the Letter Y on a Grid 1. 解题思路2. 代码实现 题目链接&#xff1a;3071. Minimum Operations to Write the Letter Y on a Grid 1. 解题思路 这一题思路上也是比较直接的&#xff0c;就是首先找到这个Y字符&#xff0c;然后…...

随想录算法训练营第五十一天|309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

309.最佳买卖股票时机含冷冻期 public class Solution {public int MaxProfit(int[] prices) {if(prices.Length<2){return 0;}int [,]dpnew int[prices.Length,4];dp[0,0]-prices[0];for(int i1;i<prices.Length;i){dp[i,0]Math.Max(dp[i-1,0],Math.Max(dp[i-1,3]-pric…...

【语言学习】std::transform函数

阅读llvm的这个提交时&#xff0c;发现了其中使用了一个函数std::transform&#xff08;原文对其进行了一层封装&#xff09; 如果不理解std::transform的三个参数的关系&#xff0c;就会对第三个参数的lambda表达式理解不了。其实&#xff0c;第三个参数的作用是提供给了一种…...

Java开发面试准备,轻松搞定SpringBoot数据校验

程序员&#xff1a;给多少工资&#xff0c;干多少事 我们不是经常会看到一个关于西游记的“悖论”吗&#xff1a; 为什么孙悟空初期大闹天宫的时候那么厉害&#xff1f;因为他自己当老板&#xff0c;打一群天庭的打工仔。 为什么取经路上又变得不行了&#xff1f;作为一个打工…...

信呼OA普通用户权限getshell方法

0x01 前言 信呼OA是一款开源的OA系统&#xff0c;面向社会免费提供学习研究使用&#xff0c;采用PHP语言编写&#xff0c;搭建简单方便&#xff0c;在中小企业中具有较大的客户使用量。从公开的资产治理平台中匹配到目前互联中有超过1W的客户使用案例。 信呼OA目前最新的版本是…...

MySQL进阶之(四)InnoDB数据存储结构之行格式

四、InnoDB数据存储结构之行格式 4.1 行格式的语法4.2 COMPACT 行格式4.2.1 记录的额外信息01、变长字段长度列表02、NULL 值列表03、记录头信息 4.2.2 记录的真实数据 4.3 Dynamic 和 Compressed 行格式4.3.1 字段的长度限制4.3.2 行溢出4.3.3 Dynamic 和 Compressed 行格式 4…...

【Qt学习笔记】(四)Qt窗口

Qt窗口 1 菜单栏1.1 创建菜单栏1.2 在菜单栏中添加菜单1.3 创建菜单项1.4 在菜单项之间添加分割线1.5 给菜单项添加槽函数1.6 给菜单项添加快捷键 2 工具栏2.1 创建工具栏2.2 设置停靠位置2.3 设置浮动属性2.4 设置移动属性2.5 添加 Action 3 状态栏3.1 状态栏的创建3.2 在状态…...

入侵和攻击模拟 (BAS) 技术应用实践

文章目录 前言一、实施BAS的必要性二、实施BAS的关键步骤1、识别网络中的脆弱区域2、创建基线安全模型3、选择合适的BAS工具4、进行模拟攻击测试5、分析结果并改进三、BAS实施中的挑战1、组织的专业知识和能力有限2、改变传统工作流程3、安全预算不足4、难以与现有安全基础设施…...

数据结构(七)——线性表的基本操作

&#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f609; 在csdn获奖荣誉: &#x1f3c6;csdn城市之星2名 ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ …...

Python 系统学习总结(基础语法+函数+数据容器+文件+异常+包+面向对象)

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 六天时间系统学习Python基础总结&#xff0c;目前不包括可视化部分&#xff0c;其他部分基本齐全&#xff0c;总结记录&#xff0…...

汽车碰撞与刮伤的实用维修技术,汽车的车身修复与涂装修补教学

一、教程描述 本套汽车维修技术教程&#xff0c;大小7.44G&#xff0c;共有60个文件。 二、教程目录 01-汽车车身修复教程01-安全规则&#xff08;共3课时&#xff09; 02-汽车车身修复教程02-汽车结构&#xff08;共3课时&#xff09; 03-汽车车身修复教程03-汽车修复所使…...