【Linux】进程等待
文章目录
- 进程等待
- 进程等待必要性
- 实验(见见猪跑)
- 进程等待的方法
- wait方法
- waitpid**方法**
- 宏的使用方法
- 获取子进程status
- 阻塞VS非阻塞
- 概念对比
- 非阻塞有什么好处
- 具体代码实现
- 进程的阻塞等待方式:
- 进程的非阻塞等待方式:
- 让父进程做其他任务
进程等待
进程等待必要性
- 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
- 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
因此,进程等待是为了:
- 避免内存泄漏
- 获取子进程执行的结果
- 代码跑完,结果对->退出码
- 代码跑完,结果不对->退出码
- 代码运行异常->信号
等待就是通过系统调用,获取子进程退出码或者退出信号的方式,顺便释放内存
实验(见见猪跑)
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id = fork();if(id == 0){int cnt = 10;while(cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n",getpid(),getppid(),cnt--);}exit(10);//此时都是S状态}//此时子进程变成Z状态sleep(15);pid_t ret = wait(NULL);//回收子进程if(id > 0){printf("wait success:%d\n",ret);}sleep(5);//父进程退出
}
waitpid用法;
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id = fork();if(id == 0)//子进程返回0,父进程返回子进程id{int cnt = 10;while(cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n",getpid(),getppid(),cnt--);}exit(10);//退出的三种结果://1.代码跑完,结果是对的//2.代码跑完,结果是错的//3.代码没有跑完,出异常了//此时都是S状态}//此时子进程变成Z状态sleep(15);int status = 0;pid_t ret = waitpid(id,&status,0);//回收子进程//错误观念:此时status的值变成了10//因为status不是被整体使用的,有自己的位图结构if(id > 0){printf("wait success:%d,sig number:%d,child exit code:%d\n",ret,(status & 0x7F),(status>>8)&0xFF);}//sig number:0 child exit code:10sleep(5);//父进程退出
}
监控脚本:
ps ajx | head -1 && ps axj | grep mytest | grep -v grep
第一句话是把标题拿出来
第二句话是把matest的进程信息拿出来
第三句话是把grep本身的进程信息去掉
之后写一个循环语句
while :; do ps ajx | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; done
(ctrl + z 退出)
进程等待的方法
wait方法
:!man 2 wait //可以查看到相关wait的用法信息
需要包含两个头文件:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数: pid: Pid=-1,等待任一个子进程。与wait等效。 Pid>0.等待其进程ID与pid相等的子进程。 status: WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) options: WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
这里有一个问题:
status按理来说是会返回信号+退出码
的,但是一个整数怎么能返回两个整数呢?
所以我们不应该吧status当作一个完整的整数,而是应该看作位图结构(后面会详解)
宏的使用方法
int ret = waitpid(id,&status,0)
if(ret > 0)
{//是否正常退出if(WIFEXITED(status)){//判断子进程运行结果是否OKprintf("exit code :%d\n",WEXITSTATUS(status));}else//出异常了(比如kill -9){//TODOprintf("child exit not normal\n");}
}
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(本身是32位,但是这里我们只研究status低16比特位):
1.代码跑完,结果是对的
2.代码跑完,结果是错的
3.代码没有跑完,出异常了
终止信号(即退出码)用来表示是否正常结束(为0则表示正常退出)
用退出状态表示结果是否正确(通过kill -l进行对应数字的查看(比如发生段错误,除0操作等)),此时退出码是否为0或者是其他的,都无意义,我们不讨论它.
第七位是信号编号
获取退出状态:
(status>>8)&0xFF )
获取终止信号:
``(status & 0x7F))
#include<sys/types.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{pid_t id = fork();if (id == 0){int cnt = 10;while (cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);}}int status = 0;//1.让OS释放子进程的僵尸状态//2.获取子进程的退出结果//3.在等待期间,子进程没有退出的时候,父进程只能阻塞等待.pid_t ret = waitpid(id, &status, 0);if (id > 0){printf("wait success: %d,sig number : %d ,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF );}}
测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{ pid_t pid; if ( (pid=fork()) == -1 ) perror("fork"),exit(1); if ( pid == 0 ){ sleep(20); exit(10); } else { int st; int ret = wait(&st); if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出 printf("child exit code:%d\n", (st>>8)&0XFF); } else if( ret > 0 ) { // 异常退出 printf("sig code : %d\n", st&0X7F ); } }
}
测试结果: [root@localhost linux]# ./a.out #等20秒退出 child exit code:10 [root@localhost linux]# ./a.out #在其他终端kill掉 sig code : 9
等待的本质:
监测子进程退出的信息
并且将子进程退出信息通过status拿回来
这个信息(exit code ,signal)在子进程的PCB中
再谈进程退出:
1.进程退出会变成僵尸进程->会把自己的退出结果写到自己的task_struct
2.wait/waitpid是一个系统调用->OS->OS有资格也有能力去读取子进程的task_struct
阻塞VS非阻塞
概念对比
例子:
1.不挂电话,监测李四的状态->阻塞
2.张三->李四,本质是状态检测,如果没有就绪,直接返回->每一次都是一次非阻塞等待->多次非阻塞等待我们称为:轮询
打电话->系统调用wait/waitpid
张三->父进程
李四->子进程
我们上面写的代码都是阻塞等待
轮询等待:
#include<sys/types.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{pid_t id = fork();if (id == 0){int cnt = 10;while (cnt){printf("我是子进程:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);}}int status = 0;while(1)//这是一个死循环,作为轮询等待的一个条件!{pid_t ret = waitpid(id, &status, WNOHANG);//wnohang:非阻塞:子进程没有退出时,父进程监测之后立即返回//如果没有设置WNOHANG,则会卡在这一步,除非子进程退出,或者你的id值设置得有问题.否则不会执行下面的代码if(ret == 0)//设置了WNOHANG才会可能出现返回值为0的情况{//waitpid调用成功&&子进程没有退出,我的waitpid没有等待失败//仅仅是监测到了子进程没有退出printf("wait done,but child is running\n",);}else if(ret > 0){//1.waitpid调用成功(等待成功)&&子进程退出了 printf("wait success: %d,sig number : %d ,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF );break;}else{//waitpid调用失败,返回值为-1//eg.传入的id参数传错了printf("waitpid call failed\n");break;}sleep(1);}
}
非阻塞有什么好处
不会占用父进程的所有精力,可以在轮询期间干干其他的
[[指针进阶]]
[[typedef函数进阶]]
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <stdlib.h>#define TASK_NUM 10typedef void (*func_t)();
func_t other_task[TASK_NUM] = {NULL};void sync_disk()
{printf("这是一个刷新磁盘的任务\n");
}
void sync_log()
{printf("这是一个同步日志的任务\n");
}
void net_send()
{printf("这是一个网络发送的任务\n");
}void LoadTask(func_t func)
{for (int i = 0; i < TASK_NUM; i++){if (other_task[i] == NULL){other_task[i] = func;break;}}
}void InitTask()
{for (int i = 0; i < TASK_NUM; i++)other_task[i] = NULL;LoadTask(sync_disk);LoadTask(sync_log);LoadTask(net_send);
}void RunTask()
{for (int i = 0; i < TASK_NUM; i++){if (other_task[i] == NULL)continue;other_task[i]();}
}int main()
{pid_t id = fork();assert(id >= 0);if (id == 0){int cnt = 5;while (cnt){printf("我是子进程,我的pid是:%d,我的ppid是%d,我还有%dS存活\n", getpid(), getppid(), cnt--);sleep(1);}printf("我已经退出\n");exit(11);}InitTask();while (1){sleep(1);int status;pid_t ret_id = waitpid(id, &status, WNOHANG);if (ret_id > 0){printf("我是父进程,我已经回收子进程,ret_id是%d,退出码为%d,退出信号为%d\n", ret_id, (status >> 8) & 0xFF, status & 0x7F);exit(0);}else if (ret_id == 0){RunTask();continue;}else{printf("调用出错\n");}}
}
具体代码实现
进程的阻塞等待方式:
#define _CRT_SECURE_NO_WARNINGS 1
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(257);}else {int status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5Sprintf("this is test for wait\n");if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}
运行结果:
[root@localhost linux] # . / a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is : 1.
进程的非阻塞等待方式:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);}else {int status = 0;pid_t ret = 0;do{ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待->子进程没有退出,父进程检测时候,立即返回if (ret == 0) {//子进程没有退出,我的waitpid没有等待失败//仅仅是监测到了子进程没有退出printf("child is running\n");}sleep(1);} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}
让父进程做其他任务
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define TASK_NUM 10// 要保存的任务相关的
typedef void (*func_t)(); //?
func_t other_task[TASK_NUM] = {NULL}; //函数指针数组// 预设一批任务
void sync_disk()
{printf("这是一个刷新数据的任务!\n");
}
void sync_log()
{printf("这是一个同步日志的任务!\n");
}
void net_send()
{printf("这是一个进行网络发送的任务!\n");
}//将任务加载进任务列表
int LoadTask(func_t func)
{int i = 0;for(; i < TASK_NUM; i++){if(other_task[i] == NULL) break;}if(i == TASK_NUM) return -1;else other_task[i] = func;return 0;
}void InitTask()
{for(int i = 0; i < TASK_NUM; i++) other_task[i] = NULL;LoadTask(sync_disk);LoadTask(sync_log);LoadTask(net_send);
}void RunTask()
{for(int i = 0; i < TASK_NUM; i++){if(other_task[i] == NULL) continue;other_task[i]();}
}int main()
{pid_t id = fork();if(id == 0){//子进程int cnt = 50;while(cnt){printf("我是子进程,我还活着呢,我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());sleep(1);//int *p = NULL;//*p = 100;}exit(111);}InitTask();// 父进程//pid_t ret_id = wait(NULL);while(1){int status = 0;pid_t ret_id = waitpid(id, &status, WNOHANG); // 夯住了if(ret_id < 0){printf("waitpid error!\n");exit(1);}else if(ret_id == 0){RunTask();sleep(1);continue;}else{if(WIFEXITED(status)) // 是否收到信号{printf("wait success, child exit code: %d\n", WEXITSTATUS(status));}else{printf("wait success, child exit signal: %d\n", status & 0x7F);}// printf("我是父进程,等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n",\// getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);break;}}
相关文章:

【Linux】进程等待
文章目录 进程等待进程等待必要性实验(见见猪跑)进程等待的方法wait方法waitpid**方法**宏的使用方法获取子进程status 阻塞VS非阻塞概念对比非阻塞有什么好处 具体代码实现进程的阻塞等待方式:进程的非阻塞等待方式:让父进程做其他任务 进程等待 进程等待必要性 之前讲过&am…...

电视「沉浮录」:跌出家电“三大件”?
【潮汐商业评论/原创】 “这年头谁还看电视,家里电视近一年都没打开过了,我明天就打算把它二手卖掉。”想到已落灰许久的电视机,Andy打开了二手平台。 “要不是这几年孩子网课多,我是真没考虑换新电视,家里用了8年的…...

前端实现调用打印机和小票打印(TSPL )功能
Ⅰ- 壹 - 使用需求 前端 的方式 点击这个按钮,直接让打印机打印我想要的东西 Ⅱ - 贰 - 小票打印 目前比较好的方式就是直接用 TSPL 标签打印指令集, 基础环境就不多说了,这个功能的实现就是利用usb发送指令,现在缺少个来让我们能够和usb沟通的工具,下面这就是推…...
串口通信(6)应用定时器中断+串口中断实现接收一串数据
本文为博主 日月同辉,与我共生,csdn原创首发。希望看完后能对你有所帮助,不足之处请指正!一起交流学习,共同进步! > 发布人:日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…...

【WinForm详细教程六】WinForm中的GroupBox和Panel 、TabControl 、SplitContainer控件
文章目录 1.GroupBox和Panel2.TabControl3.SplitContainer 1.GroupBox和Panel GroupBox:是一个分组容器,提供一个框架将相关的控件组织在一起,它有标题、边框,但没有滚动条。 Panel:也是一个容器控件,用来…...
gradle与maven
Gradle 和 Maven 都是流行的构建工具,通常用于构建和管理 Java 和 Android 项目。它们都可以自动下载依赖库、编译代码、运行测试、打包和发布等。 以下是对 Gradle 和 Maven 的介绍: Gradle: Gradle 是一个基于 Groovy 和 Kotlin 的构建自…...

2.Docker基本架构简介与安装实战
1.认识Docker的基本架构 下面这张图是docker官网上的,介绍了整个Docker的基础架构,我们根据这张图来学习一下docker的涉及到的一些相关概念。 1.1 Docker的架构组成 Docker架构是由Client(客户端)、Docker Host(服务端)、Registry(远程仓库)组成。 …...

拓世法宝 | 数字经济崛起,美业如何抓住流量风口?
爱美之心,人皆有之。无论男女,都会很自然地对美好事物燃起兴致,跟高颜值相关的事物总能聚集注意力。例如直播平台里的美女网红收割流量赚得盆满钵满,面庞俊俏的年轻偶像吸引万千粉丝,还有“央视最美记者”王冰冰、“最…...

Scala 泛型编程
1. 泛型 Scala 支持类型参数化,使得我们能够编写泛型程序。 1.1 泛型类 Java 中使用 <> 符号来包含定义的类型参数,Scala 则使用 []。 class Pair[T, S](val first: T, val second: S) {override def toString: String first ":" sec…...
索引失效的场景有哪些?
虽然你这列上建了索引,查询条件也是索引列,但最终执行计划没有走它的索引。下面是引起这种问题的几个关键点。 列与列对比 某个表中,有两列(id和c_id)都建了单独索引,下面这种查询条件不会走索引 select…...
Java进阶04 final关键字、abstract抽象、interface接口、JDK8与JDK9中接口的区别、内部类和匿名类
文章目录 一、final关键字二、abstract关键字三、接口interface四、JDK8和JDK9中接口的区别五、内部类 一、final关键字 final可以修饰类、方法、变量 用final修饰类 表示此类不能被继承 用final修饰方法 表示方法不可以被重写 用final修饰变量 既可以修饰成员变量也可以修饰…...

Python的web自动化学习(五)Selenium的隐式等待(元素定位)
引言: WebDriver隐式等待是一种全局性的等待方式,它会在查找元素时设置一个固定的等待时间。当使用隐式等待时,WebDriver会在查找元素时等待一段时间,如果在等待时间内找到了元素,则立即执行下一步操作;如果…...

20231102从头开始配置cv180zb的编译环境(欢迎入坑,肯定还有很多问题等着你)
20231102从头开始配置cv180zb的编译环境(欢迎入坑,肯定还有很多问题等着你) 2023/11/2 11:31 (欢迎入坑,本篇只是针对官方的文档整理的!只装这些东西你肯定编译不过的,还有很多问题等着你呢&…...
CentOS 安装HTTP代理服务器 Squid
参考:大部分摘自此文,做了少部分修改 Squid 是一个功能全面的缓存代理服务器,它支持著名的网络协议像 HTTP,HTTPS,FTP 等等。将 Squid 放在网页服务器的前端,通过缓存重复请求,过滤网络流量等&…...
ubuntu下开发提效的小tips
一、常用操作使用简写的别名,写进bashrc文件中 背景:经常需要cd至某个文件夹中,然后再执行对应的操作;写进bashrc文件中后,可以直接用缩略命令替代这一连串的命令; 用到的工具: 设置命令别名a…...

Java反射详解:入门+使用+原理+应用场景
反射非常强大和有用,现在市面上绝大部分框架(spring、mybatis、rocketmq等等)中都有反射的影子,反射机制在框架设计中占有举足轻重的作用。 所以,在你Java进阶的道路上,你需要掌握好反射。 怎么才能学好反射,我们需要…...

PostgreSQL 工具的相关介绍
1.1 psql工具 psql是PostgreSQL中的一个命令行交互式客户端工具,类似 Oracle中的命令行工具sqlplus,它允许用户交互地键入SQL语句或命 令,然后将其发送给PostgreSQL服务器,再显示SQL语句或命令的结 果。 1.2 psql的简单使用 使用…...

结合组件库实现table组件树状数据的增删改
如图所示,可以实现树状数据的新增子项,新增平级,删除。主要用到了递归 代码: <template><el-table :data"tableData" style"width: 100%; margin-bottom: 20px" row-key"id" border def…...

Microsoft 365 管理自动化
Microsoft 365 服务被大多数组织广泛使用,每天生成的数据量巨大。解决 Microsoft 365 中的问题可能非常困难,并且使用多个管理中心来保护组织变得复杂。本机控制台还缺少某些批量管理任务、全面的审计报告和基于角色的精细访问控制。 Microsoft 360 管理…...

unraid 安装并设置 zerotier 内网穿透安装 unraid 局域网内其他设备
Read Original 最近看了以下两个文章,感谢发布的各种精彩文章,让我受益匪浅。OPENWRT 的固件在设置了,【自动允许客户端 NAT】后,可以直接访问局域网其他设备,而我 unraid 部署 zerotier 后,只能访问 unra…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...

二叉树-144.二叉树的前序遍历-力扣(LeetCode)
一、题目解析 对于递归方法的前序遍历十分简单,但对于一位合格的程序猿而言,需要掌握将递归转化为非递归的能力,毕竟递归调用的时候会调用大量的栈帧,存在栈溢出风险。 二、算法原理 递归调用本质是系统建立栈帧,而非…...