【linux】进程管理:进程控制块、进程号、fork创建进程、特殊进程及exec函数族解析
一、进程的概述
可执行程序运行起来后(没有结束之前),它就成为了一个进程。程序是存放在存储介质上的一个可执行文件,而进程是程序执行的过程。进程的状态是变化的,其包括进程的创建、调度和消亡。程序是静态的,进程是动态的。
1、程序和进程的区别
程序 静态的 占磁盘空间(存放在存储介质上的一个可执行文件)
进程 动态的 (调度、执行、消亡),占内存空间。(进程是程序执行到结束间的这个过 程)
2、单道和多道程序设计
单道程序设计 所有进程一个一个排队执行。若A阻塞,B只能等待,即使CPU处于空闲状 态。这种模型在系统资源利用上及其不合理,大部分已被淘汰了。
多道程序设计 在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之 下,相互穿插的运行。当下常见CPU为纳秒级,由于人眼的反应速度是毫秒级,所以看似同时在运行。
而多道程序设计必须有硬件基础作为保证。时钟中断(强制让进程让出cpu资源)即为多道程序设计模型的理论基础。
3、并行和并发的区别
并行(微观)和并发(宏观)都是多个任务同时执行(多道程序)。
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行(微观上同时执行)(多核)
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执 行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时 间分成若干段,使多个进程快速交替的执行(单核 )
4、进程控制块(PCB)
进程运行时,内核为每个进程分配一个PCB(进程控制块),维护进程相关的信 息,Linux内核的进程控制块是task_struct结构体。 PCB存在于进程的内核空间里面。系统会为每一个进程分配一个进程ID,其类型为pid_t(非负整数) ,进程的状态,有就绪、运行、挂起、停止等状态。进程状态切换时需要保存和恢复的一些CPU寄存器。进程是系统分配资源的基本单位。
5、进程的状态
进程的三大状态:就绪态、执行态、等待态
- 就绪态:执行条件全部满足,等待CPU的执行调度
- 执行(运行)态:正在被CPU调度执行
- 等待态:不具备CPU调度执行的执行条件,等待条件满足。


ps命令查看进程信息:
| 选项 | 含义 |
| -a | 显示终端上的所有进程,包括其他用户的进 程 |
| -u | 显示进程的详细状态 |
| -x | 显示没有控制终端的进程 |
| -w | 显示加宽,以便显示更多的信息 |
| -r | 只显示正在运行的进程 |
查看进程状态:ps -aux
stat中的参数意义如下:

以树状显示进程:pstree
二、进程号PID
每个进程都由一个进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。 进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用 。
- 进程号(PID): 标识进程的一个非负整型数
- 父进程号(PPID):父进程号
- 进程组号(PGID): 进程组是一个或多个进程的集合。
1、获取进程号的函数
头文件:
#include<sys/type.h>
#include<unistd.h>
函数:
pid_t getpid(void);
功能: 获取本进程号(PID)
参数: 无
返回值: 本进程号
2、获取父进程的ID
#include<sys/type.h>
#include<unistd.h>
pid_t getppid(void);
功能: 获取调用此函数的进程的父进程号(PPID)
参数: 无
返回值: 调用此函数的进程的父进程号(PPID)
3、获取进程组的ID
#include<sys/type.h>
#include<unistd.h>
pid_t getpgid(pid_t pid);
功能: 获取进程组号(PGID)
参数: pid:进程号
返回值: 参数为 0 时返回当前进程组号,否则返回参数指定的进程的进程组号
查看父子进程号:ps -ef

查看所有进程号:ps -ajx
getchar();防止进程结束。
三、 fork创建进程
1、fork函数
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进 程树结构模型。
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
功能: 用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
参数: 无
返回值: 成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为整型。 失败:返回-1。
失败的两个主要原因:
1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
2)系统内存不足,这时 errno 的值被设置为 ENOMEM
2、fork出来的子进程和父进程之间的关系
使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。 地址空间: 包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优 先级、进程组号等。 子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的 。
父子进程 从fork后开始继续执行。父子进程是同时运行,空间独立,子进程复制父进程的所有空间,谁先运行不确定。
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{//创建子进程pid_t pid=fork();if(pid<0){perror("创建失败\n");}else if(pid==0){printf("%d为子进程\n",getpid());}else if(pid>0){printf("%d为父进程\n",getpid());}getchar();return 0;
}

3、子进程 复制 父进程的资源(各自独立)
4、父子进程同时运行
5、父进程 给子进程 足够的准备时间时
四、特殊的进程
孤儿进程、僵尸进程、守护进程。
1、孤儿进程(无危害)
父进程先结束、子进程就是孤儿进程,会被1号进程接管(1号进程负责给子进程回收资 源)

终止子进程:
2、僵尸进程(有害)
子进程结束,父进程没有回收子进程资源(PCB),子进程就是僵尸进程。


当父进程结束后,僵尸进程的资源被回收。
3、守护进程
守护进程 是脱离终端的 孤儿进程。在后台运行。为特殊服务存在的。(一般用于服务器)
五、父进程回收子进程的资源
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但 是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退 出状态、运行时间等)
父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。 wait()和 waitpid()函数的功能一样,区别在于,wait()函数会阻塞,waitpid()可以设置不阻塞。注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。 wait、waitpid基本上都是在父进程调用。
1、wait函数
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
功能: 等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数: status : 进程退出时的状态信息。
返回值: 成功:已经结束子进程的进程号 失败: -1
注意:
- 调用wait的进程会阻塞(挂起)、直到该函数返回才被唤醒。
- 若调用进程没有子进程,该函数立即返回 子进程已经结束,该函数同样会立即返回,并且会回收那个早已结束进程的资源。
- 如果参数status 的值不是NULL,wait()就会把子进程退出时的状态取出并存入其中。这是一个整数值( int),指出了子进程是正常退出还是被非正常结束的。
状态值: int中包含了多个字段,直接使用这个值是没有意义的,WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零。 WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在status变 量的8~16位,在用此宏前应先用宏WIFEXITED判断子进程是否正常退出,正常退出才可以使用此宏。
2、waitpid函数
wiatpid常用于等待多个子进程结束。
#include<sys/type.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能: 等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
参数:
pid : 参数 pid 的值有以下几种类型:
- pid > 0 等待进程 ID 等于 pid 的子进程。
- pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组, waitpid 不会等待它。
- pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
- pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。
status : 进程退出时的状态信息。和 wait() 用法一样。
options : options 提供了一些额外的选项来控制 waitpid()。
- 0:同 wait(),阻塞父进程,等待子进程退出。
- WNOHANG:没有任何已经结束的子进程,则立即返回。
- WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,极少用到)
返回值: waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:
- 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
- 如果设置了选项 WNOHANG,而调用中 waitpid() 还有子进程在运行,且没有子进程退出,返回0; 父进程的所有子进程都已经退出了 返回-1; 返回>0表示等到一个子进 程退出
- 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在, 如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD
waitpid等价于wait的案例:
六、创建多个子进程
1、创建2个子进程出现的问题


2、防止子进程 创建孙进程

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#define N 3
int main(int argc,char *argv[])
{//创建3个子进程int i=0;for(;i<N;i++){pid_t pid=fork();if(pid==0)//防止子进程创建孙进程{break;}}//判断具体的子进程if(i==0)//子进程1{//完成任务Aint i=3;for (; i > 0; i--){printf("子进程%d工作剩余时间%d\n",getpid(),i);sleep(1);}_exit(-1);}else if(i==1)//子进程2{//完成任务Bint i=5;for (; i > 0; i--){printf("子进程%d工作剩余时间%d\n",getpid(),i);sleep(1);}_exit(-1);}else if(i==2)//子进程3{//完成任务Cint i=7;for (; i > 0; i--){printf("子进程%d工作剩余时间%d\n",getpid(),i);sleep(1);}_exit(-1);}else if(i==N)//父进程{//回收子进程资源while(1){pid_t pid=waitpid(-1,NULL,WNOHANG);//不阻塞if(pid>0){printf("子进程%d退出\n",pid);}else if(pid==0)//还有子进程在运行{continue;}else if(pid==-1){printf("所有子进程已结束\n");break;}}}return 0;
}

七、 进程相关
1、终端
用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal),进程中,控制终端是保存在PCB中的信息,而 fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。


2、进程组
一个或多个进程的集合,也称之为作业。当父进程创建子进程的时候,默认子进程与父进程属于同一进程组。当bash创建进程时,该进程自己创建与自己ID相同的进程组,不与父进程同属于一个进程组。
进程组ID为第一个进程ID(组长进程): 进程ID和进程组ID相同的进程就是 组长进程。
可以使用kill -SIGKILL -进程组ID(负的)(正的为组长进程)来将整个进程组内的进程全部杀死 只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。 进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。

#include<unistd.h>
pid_t getpgrp(void);
功能:获取当前进程的进程组ID
参数:无
返回值:总是返回调用者的进程组ID
pid_t getpgid(pid_t pid);
功能:获取指定进程的进程组ID
参数: pid:进程号,如果pid = 0,那么该函数作用和getpgrp一样
返回值: 成功:进程组ID 失败:-1
int setpgid(pid_t pid, pid_t pgid)
功能: 改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。
参数: 将参1对应的进程,加入参2对应的进程组中
返回值: 成功:0 失败:-1
3、会话
会话是一个或多个进程组的集合。 一个会话可以有一个控制终端。 一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组;如果一个会话有一个控制终端,则它有一个前台进程组,其它进程组为后台进程组;如果终端接口检测到断开连接,则将挂断信号发送至控制进程(会话首进程)。
如果进程ID==进程组ID==会话ID 那么该进程为会话首进程。
创建新会话的步骤:
- 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
- 该调用进程是组长进程,则出错返回 。
- 该进程成为一个新进程组的组长进程
- 需有root权限(ubuntu不需要)
- 新会话丢弃原有的控制终端,该会话没有控制终端
- 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
#includ<unistd.h>
pid_t getsid(pid_t pid);
功能:获取进程所属的会话ID
参数: pid:进程号,pid为0表示查看当前进程session ID
返回值: 成功:返回调用进程的会话ID 失败:-1
#include<unistd.h>
pid_t setsid(void);
功能: 创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。调用了setsid函数的进程,既是新 的会长,也是新的组长。
参数:无
返回值: 成功:返回调用进程的会话ID 失败:-1
4、创建守护进程
- 创建子进程,父进程退出(必须) 所有工作在子进程中进行形式上脱离了控制终端
- 在子进程中创建新会话(必须) setsid()函数 使子进程完全独立出来,脱离控制。
- 改变当前目录为根目录(不是必须) chdir()函数 防止占用可卸载的文件系统 也可以换成其它路径
- 重设文件权限掩码(不是必须) umask()函数 防止继承的文件创建屏蔽字拒绝某些权限,增 加守护进程灵活性。
- 关闭文件描述符(不是必须) 继承的打开文件不会用到,浪费系统资源,无法卸载
- 开始执行守护进程核心工作(必须) 守护进程退出处理程序模型


八、 vfork创建子进程
1、vfork函数说明
vfork函数:创建一个新进程
pid_t vfork(void);
功能: vfork函数和fork函数一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的。
返回值: 创建子进程成功,则在子进程中返回0,父进程中返回子进程ID。出错则返回-1。
2、vfork函数和fork函数的区别
区别1:vfork创建的子进程 会保证子进程先运行,只有当子进程退出(调用 exec)的时候,父进程才运行。
区别2:vfork创建的子进程 和父进程 共用一个空间。
九、exec函数族
exec函数族功能:在进程中 启动另一个进程。
#include<unistd.h>
- int execl(const char *path, const char *arg, .../* (char *) NULL */);
- int execlp(const char *file,cconst char *arg, ... /* (char *) NULL */);
- int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execvpe(const char *file, char *const argv[], char *const envp[]);
- int execve(const char *filename, char *const argv[], char *const envp[]);
函数中有l(list)表明使用列表方式传参,函数中有v(vector)表明使用指针数组传参。 函数中有p(path)表明 到系统环境中 找可执行性文件 函数中有e(evn) 表明exec可以使用环境变量值
案例1:在代码中使用execl执行ls命令
查看ls命令的路径:

一个进程调用exec后不会返回,exec函数族取代调用进程的数据段、代码段和堆栈段。除了进程ID,进程还保留了下列特征不变: 父进程号 进 程组号 控制终端 根目录 当前工作目录 进程信号屏蔽集 未处理信号 ...

案例2:在代码中使用execlp执行ls命令

案例3:在代码中使用execvp执行ls命令
案例4:vfork和exec配合使用,会为子进程开辟新的空间

相关文章:
【linux】进程管理:进程控制块、进程号、fork创建进程、特殊进程及exec函数族解析
一、进程的概述 可执行程序运行起来后(没有结束之前),它就成为了一个进程。程序是存放在存储介质上的一个可执行文件,而进程是程序执行的过程。进程的状态是变化的,其包括进程的创建、调度和消亡。程序是静态的,进程是…...
【DL经典回顾】激活函数大汇总(八)(Maxout Softmin附代码和详细公式)
激活函数大汇总(八)(Maxout & Softmin附代码和详细公式) 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里,激活函数扮演着不…...
Docker进阶:深入了解 Dockerfile
Docker进阶:深入了解 Dockerfile 一、Dockerfile 概述二、Dockerfile 优点三、Dockerfile 编写规则四、Dockerfile 中常用的指令1、FROM2、LABEL3、RUN4、CMD5、ENTRYPOINT6、COPY7、ADD8、WORKDIR9、 ENV10、EXPOSE11、VOLUME12、USER13、注释14、ONBUILD 命令15、…...
【LeetCode热题100】206. 反转链表(链表)
一.题目要求 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 二.题目难度 简单 三.输入样例 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2: 输入:head [1,2…...
电玩城游戏大厅计时软件怎么用,佳易王计时计费管理系统软件定时语音提醒操作教程
电玩城游戏大厅计时软件怎么用,佳易王计时计费管理系统软件定时语音提醒操作教程 一、前言 以下软件操作教程以 佳易王电玩计时计费软件V18.0为例 说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、软件计时计费,只需点击开…...
selenium也能过某数、5s盾..
文章转载于:selenium也能过某数、5s盾… 直接安装: pip install undetected_chromedriver运行代码: import undetected_chromedriver as uc import timedriver uc.Chrome(executable_pathrC:\Users\chromedriver.exe,version_main111) driver.get(网…...
mysql笔记:8. 视图
文章目录 创建视图修改视图删除视图通过视图更新数据1. 插入数据2. 更新数据3. 删除数据 查看视图信息1. DESCRIBE2. SHOW TABLE STATUS3. SHOW CREATE VIEW4. 在views表中查看 数据库中的视图是一个虚拟表。同真实的表一样,视图包含一系列带有名称的列和行数据。行…...
指针的基本概念和用法
指针的基本概念 每个变量都被存放在从某个内存地址(以字节为单位)开始的若干字节中 “指针”也被称作“指针变量”,大小为4个字节(在64位编译器中,也优肯为8个字节)的变量,其内容代表一个内存地…...
工作随记:oracle重建一张1T数据量的大表
文章目录 一、删除测试表二、重命名旧表:三、验证:四、检查alert日志和昨天到今天的统计信息任务收集是否正常 一、删除测试表 #xshell登录用户hthis用户连接登录处理: sqlplus ht/"123456" sqlplus ht/"123456"10.8.5.…...
使用timm库的一些知识点
timm(Torch Image Models)是一个在PyTorch上构建的图像模型库,它提供了一系列预训练的深度学习模型,使得研究人员和开发者可以方便地进行图像分类、目标检测等任务。 使用timm库创建模型时,如何确定模型的名字 使用…...
一种基于宏和serde_json实现的rust web中统一返回类
本人rust萌新,写web碰到了这个,基于ChatGPT和文心一言学了宏,强行把这玩意实现出来了,做个学习记录,如果有更好的方法,勿喷。 先看效果,注意不支持嵌套,且kv映射要用>(因为它这个…...
每周一算法:A*(A Star)算法
八数码难题 题目描述 在 3 3 3\times 3 33 的棋盘上,摆有八个棋子,每个棋子上标有 1 1 1 至 8 8 8 的某一数字。棋盘中留有一个空格,空格用 0 0 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局…...
爬虫练习:获取某网站的房价信息
一、相关网站 二、相关代码 import requests from lxml import etree import csv with open(房天下数据.csv, w, newline, encodingutf-8) as csvfile:fieldnames [名称, 地点,价格,总价,联系电话]writer csv.DictWriter(csvfile, fieldnamesfieldnames)writer.writeheader…...
第一个C语言hello world
#include <stdio.h> int main() {printf("hello world ! \n");//打印函数return 0; } "#" : 预处理标志 include <> : 表示预处理的文件在<>内 stdio.h : 标准的io头文件 // io : 输入输出 // printf()…...
【Python】新手入门学习:详细介绍依赖倒置原则(DIP)及其作用、代码示例
【Python】新手入门学习:详细介绍依赖倒置原则(DIP)及其作用、代码示例 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、Py…...
嵌入式驱动学习目录索引(更新中)
前言 这是一篇索引博客,用来作为索引记录学习嵌入式Linux的过程,可以用来给自己以及需要的读者作为一个目录索引,每次更新完博客都会添加进该目录中。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度…...
ruoyi-vue插件集成websocket
链接:插件集成 | RuoYi WebSocketServer.java:补充代码 /*** 此为广播消息* param message 消息内容*/public void sendAllMessage(String message) {LOGGER.info("【websocket.sendAllMessage】广播消息:"message);try {for(String sessionI…...
华为ce12800交换机m-lag(V-STP模式)配置举例
配置## 标题思路 采用如下的思路配置M-LAG双归接入IP网络: 1.在Switch上配置上行接口绑定在一个Eth-Trunk中。 2.分别在SwitchA和SwitchB上配置V-STP、DFS Group、peer-link和M-LAG接口。 3.分别在SwitchA和SwitchB上配置LACP M-LAG的系统优先级、系统ID。 4.分别在…...
STM32第九节(中级篇):RCC——时钟树讲解(第一节)
目录 前言 STM32第九节(中级篇):RCC——时钟树讲解 时钟树主系统时钟讲解 HSE时钟 HSI时钟 锁相环时钟 系统时钟 SW位控制 HCLK时钟 PCLKI时钟 PCLK2时钟 RTC时钟 MCO时钟输出 6.2.7时钟安全系统(CSS) 小结 前言 从…...
c/c++字符串处理标准库 string 介绍
c语言中string.h介绍 C语言的标准库中包含了一个头文件 <string.h>,该头文件提供了一系列字符串处理函数的声明和定义。以下是一些常用的函数: 字符串复制:strcpy(dest, src)。将源字符串 src 复制到目标字符串 dest,包括…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

