Linux系统编程——进程控制

目录

一,进程创建

1.1 fork回顾

1.2 写时拷贝

1.3 fork用处

1.4 fork调用失败原因

二,进程退出

2.1 进程退出场景

2.2 mainCRTStartup调用

2.3 进程退出码

2.3.1 main函数返回值

2.3.2 strerror 

​编辑 2.3.3 命令的退出码

2.4 进程正常退出

2.5 进程异常退出

三,进程等待

3.1 为什么要有进程等待?

3.2 wait函数和waitpid函数的使用

 3.3 wait函数和waitpid的status参数

3.4 多进程阻塞等待

3.5 非阻塞等待

四,进程程序替换

4.1 什么是进程程序替换?为什么要有这个?

4.2 execl函数

4.3 其它exec函数

​编辑 

4.4 execve

五:一些问题解答

5.1 wait和waitpid如何拿到进程的退出信息的呢?为什么不直接用全局变量拿到退出码?

5.2 僵尸进程中曾经new和malloc的空间会释放码?


一,进程创建

1.1 fork回顾

fork的详细介绍请见上篇博客第四节——这里只做简单回顾Linux系统编程 —— 进程概念,环境变量,虚拟地址空间总结(收藏向)-CSDN博客

fork()函数可以创建子进程,由于fork也是个函数,所以也有返回值,会对父进程返回子进程的pid,对子进程返回0。具体用法如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{printf("我是主进程");pid_t id =fork();if(id<0){printf("创建子进程失败\n");return 1;}else if(id==0){printf("我是子进程:pid:%d,ppid:%d\n",getpid(),getppid());}else {printf("我是父进程:pid:%d,ppid:%d\n",getpid(),getppid());      }return 0;
}

问题:fork创建子进程时,OS做了什么?

解答:创建子进程,系统里就多了一个进程,所以先创建进程的PCB,地址空间和页表,将程序在磁盘上的地址加载到内存中,然后把程序的代码和数据从磁盘加载到物理内存中,然后把PCB放到CPU的运行队列里等待被调度,当该进程开始调度时,就通过虚拟地址的映射找到在物理内存的相关代码,然后就按照顺序语句从上往下在进程内部执行代码

进程 = 内核数据结构 + 进程代码和数据 (前者OS维护,后者一般从磁盘上来,就是C/C++程序加载之后的结果)

1.2 写时拷贝

进程具有独立性,所以创建子进程时分配给它的内核结构是它独有的,理论上进程也要有自己的代码和数据。可是一般而言,子进程只是随着父进程的加载而创建出来的,也就是说子进程没有属于自己的代码和数据,它只能用父进程的代码和数据,但这又和进程的独立性相违背,为啥?

①代码:对进程来说,代码都是已经加载完成的,只能读取不能写入,所以代码数据父子共享没问题

②数据:由于数据可能会被修改,所以必须分离,但是不能直接拷贝分离,因为可能会拷贝子进程根本用不到的数据空间,而且子进程也可能只读数据不做修改,这种情况再进行拷贝就会造成时间和空间的浪费。所以采用写时拷贝的技术,写时拷贝请参照上篇博客第十节:

Linux系统编程 —— 进程概念,环境变量,虚拟地址空间总结(收藏向)-CSDN博客

 如下代码:

#include<stdio.h>
int main()
{const char *str1 = "aaa";const char *str2 = "aaa";printf("%p\n%p\n",str1,str2); return 0;
}

 

 第一个指针创建常量字符,第二个指针也指向这个常量字符时,就不再额外开空间了,所以连编译器编译时都知道节省空间来优化,OS没有理由不优化,所以创建子进程的时候OS采用了写时拷贝的技术,就是字面意思:“你要数据的候再给你拷贝”。

写时拷贝使得父子进程得以彻底分离,保证了进程的独立性,同时也是高效使用内存的一种表现

1.3 fork用处

①一个进程希望复制自己,使执行父进程代码的同时子进程执行不同的代码,例如父进程等待客户端请求数据,子进程处理请求数据

②一个进程要执行一个不同程序,例如子进程创建后来后,调用exec函数,这个后面的进程程序替换再讲

1.4 fork调用失败原因

系统进程过多,或者是用户的进程超过了限制

如下代码,由于下列代码执行后进程满了fork报错,系统可能会出问题,为了各位主机的安全考虑 就不贴代码啦

可以看见创建将近800个进程后再创建就失败了 

二,进程退出

2.1 进程退出场景

进程退出只能有三种情况:

①代码运行完成,结果正确

②代码运行完成,结果不对

③代码未运行完成,异常退出(OS通过发送信号的方式强制终止进程)

2.2 mainCRTStartup调用

为什么main函数也有返回值呢?有人会说main函数也是函数所以有返回值,那么既然main是函数,那么是谁调用的main函数呢?下面是VS2022编译器的main函数层层调用的示例图:(下面代码需要在release调试环境下用逐语句依次执行才可显示)

所以其实main函数最开始是被一个叫做mainCRTStartup的函数调用的,而这个函数又是通过加载器被操作系统所调用的,也就是说main函数是间接被操作系统调用的。

main函数只是用户级别的代码的入口,而程序的入口还是操作系统 

2.3 进程退出码

2.3.1 main函数返回值

通过2.2,我们知道main函数既然是被操作系统调用的,那么main函数作为被调用者就应该给调用者返回退出信息,而这个退出信息就是以退出码的形式作为main函数的返回值返回,一般以返回0代表代码执行完毕,非0代表代码执行过程中出现错误,但是我们前面写的程序都是小程序,代码数不多,所以一般以0返回。

main函数的返回值就是进程的退出码,我们可以用" echo $? "来查看最后一次进程退出时的退出码

问题:为什么0代表执行成功,非0代表错误?

解答: 代码执行完毕只有两种情况,成功和错误,成功就是我们想要的只有一种情况,但是错误往往是有很多的,这个大家都懂,所以我们就用这些非0的数字代表代码执行错误的原因

2.3.2 strerror 

 但是退出码是方便计算机返回设定的,而我们并不清楚像“ 1234 ”这样的退出码到底是什么意思,所以我们需要一个能将错误码转化成字符串显示给用户的方案 —— ”strerror“,打印退出码代表的字符信息,如下代码:

#include<stdio.h>
#include<string.h>
int main()
{int i = 0;for(i = 0; i < 10; i++){printf("[%d]: %s\n", i, strerror(i));}return 0;
}

 2.3.3 命令的退出码

Linux中的各种命令也是C语言写的程序,所以也会有退出码,我们使用命令后照样也可以通过“ echo $? ”查看退出码

2.4 进程正常退出

return

main函数使用的return退出就是我们最常用的方法来退出进程

exit

exit函数也是常用的退出进程的方法,但是exit在进程退出前多做了一系列工作:

1,执行用户定义的atexit或on_exit定义的清理函数

2,关闭所有打开的流,所有缓存数据均被写入

3,调用_exit系统调用终止进程

#include<stdio.h>
#include<stdlib.h>void Hello()
{printf("hello world"); //未加\n,不会刷新缓冲区exit(1);
}int main()
{Hello();return 0;
}

 

_exit

_exit是系统调用的进程退出函数,但是我们一般不用_exit来退出进程,因为_exit推出前不会有exit中的推出前工作,它只是退出,比如上面的代码如果把exit换成_exit就不会刷新缓冲区了,hello world不会打印

而这三个函数的关系呢,就是下面这个图了:

_exit就是进程直接退出,什么也不干,exit就是在推出前做了些善后工作再退出,然后return,在C语言语法实现中就是调用的exit() 

2.5 进程异常退出

前面说过:所有的进程异常终止都是操作系统干的,具体有下面两种方式:

①操作系统向进程发送特定信号导致进程异常退出,例如kill -9和Ctrl+C

②代码错误导致进程运行时异常退出,例如野指针,越界访问和除0等(其实代码的逻辑错误异常退出本质也是操作系统检测到了代码的非法操作然后向进程发送信号的方式终止进程的)

三,进程等待

3.1 为什么要有进程等待?

①子进程退出父进程如果不读取子进程的退出信息,子进程就会变成僵尸进程,造成内存泄漏

②子进程被创建出来是要它去做事的,它的事做得咋样父进程需要关心,也就是进程退出的三个结果

③然后就是子进程把结果返回父进程了,那么父进程咋收到子进程的退出信息呢?那么包括回收子进程申请的相关内存资源,还有父进程获得子进程退出结果都需要进程等待来完成

3.2 wait函数和waitpid函数的使用

如下图,是man文档的2号文档对wait接口的说明:

wait函数

如下代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id = fork();if(id<0){perror("fork");exit(1);//标识进程运行完毕结果不对}else if(id==0){//子进程int cnt =3;while(cnt){printf("cnt:%d,我是子进程,pid:%d,ppid:%d\n",cnt,getpid(),getppid());sleep(1);cnt--;}exit(0);}else{//父进程printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());sleep(6); //子进程跑3秒,父进程等6秒,所以可以看到子进程有3秒的Z状态,然后wait后Z状态就没了pid_t ret = wait(NULL); //阻塞式等待,简单来说就是卡在这,等子进程完成//pid_t ret = waitpid(id, NULL, 0); //waitpid的效果和wait一样,wiatpid的第一个参数表示要等待的进程的pid,第三个参数为0表示阻塞等待if(ret > 0){printf("等待子进程成功,ret:%d\n", ret);}}
}

可以看到完美地消除了Z进程,有效解决了僵尸进程问题 

 3.3 wait函数和waitpid的status参数

那么只解决了僵尸进程问题还不够,因为父进程还需要知道子进程的退出码滴,当然也是通过wait和waitpid函数来获取,这时候就要着重讲讲它们的参数了

该参数是一个输出型参数,是一个指针,为NULL时不会获取到子进程退出码,不为NULL时会传入子进程的退出码。参数这里是一个指针,那么指针指向的是一个16比特位的整型。但是status不能当作一个简单的整型来对待,一共16个比特位,而操作系统对这16个比特位做了划分,如下图:

 在status一个int的16个比特位中高8位代表进程的退出状态也就是退出码,后7位表示终止信号

进程最终的结果有两种,一种是正常退出,一种是被信号所杀,而两种退出方式对status做的修改也不同

先看下正常退出对status做的修改:

 信号所杀做的修改:

 所以获取退出码和退出信号时,我们需要做一些额外的处理,如下:

int status;
wait(&status);
waitpid(pid, &status, 0);exitCode = (status >> 8) & 0xFF;  //退出码
exitSignal = status & 0x7F;       //退出信号//当然上面的方式用起来肯定是比较别扭的而且不美观,所以系统中提供了两个宏来方便用户操作
IfexitSignal = WIFEXITED(status); //返回值为bool类型,用于查看进程是否正常退出,检查是否收到终止信号
exitCode = WEXITSTATUS(status);   //获取进程退出码

 注意,凡是在C语言中,像这种全大写的特殊字符,很大可能就是宏定义

3.4 多进程阻塞等待

上面的都是父进程创建一个进程,然后单独等待一个子进程退出的场景,但是我们也可以同时创建多个子进程,然后让父进程依次等待子进程退出,如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t ids[10]; //该数组存储子进程的pidint i = 0;for (i = 0; i < 10; i++){pid_t id = fork(); //创建10个子进程if (id == 0){printf("child[%d] created success pid:%d, \n",i, getpid());sleep(3);exit(i); //使每个子进程的退出码设置为创建它的编号}ids[i] = id; //这一条是父进程执行}for (i = 0; i < 10; i++){int status = 0;pid_t ret = waitpid(ids[i], &status, 0);if (ret >= 0){printf("wiat child success..PID:%d\n", ids[i]);if (WIFEXITED(status)){//正常退出printf("exit code: %d\n", WEXITSTATUS(status));}else{//信号终止printf("signal kill: %d\n", status & 0x7F);}}}return 0;
}

 

3.5 非阻塞等待

前面说到过waitpid的第三个参数option,为0时就是代表阻塞等待,那么不为0的时候呢?

阻塞等待:一般都是在内核中阻塞,等待被唤醒 --> 伴随被唤醒 --> scanf,cin --> 必定封装系统调用

非阻塞等待:我们父进程通过调用waitpid来进行等待,如果子进程没有退出,那么waitpid立即返回,waipid的伪代码实现如下:

waitpid(chile_id, status, flag);
if(status == 进程已经退出) {return child_pid;
}
else if(status == 进程还没退出) {if(flag == 0) {} //如果falg为0,那么就阻塞else if(flag == WNOHANG) return 0; //直接返回,不阻塞进程return 0;
}
else {//出错了或者其他原因return -1;
}

 当waitpid第三个参数为WNOHANG时,就代表父进程非阻塞等待,这个其实也是宏定义,我们可以查看,如下图:

所以我们可以用这个特性, 让子进程忙的时候父进程做点其它的事,但是又能让父进程获取到子进程的退出信息,如下代码:

#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<vector>typedef void (*handler_t) (); //函数指针类型
std::vector<handler_t> handlers ; //函数指针数组void fun_one() { printf("这是一个临时任务1\n"); }
void fun_two() { printf("这是一个临时任务2\n"); }
//设置对应的方法回调
//以后想让父进程闲的时候执行其他方法时,只要向Load里面注册,就可以让父进程执行对应的方法
void Load()
{handlers.push_back(fun_one);handlers.push_back(fun_two);
}int main()
{pid_t id = fork();if(id==0){//子进程int cnt = 5;while(cnt){printf("我是子进程:%d\n",cnt--);sleep(1);}exit(11);}else{int quit=0;while(!quit){int status =0;pid_t res = waitpid(-1,&status,WNOHANG); //非阻塞等待if(res>0){//等待成功,子进程退出printf("等待子进程退出成功,退出码:%d\n",WEXITSTATUS(status));quit = 1;}else if(res==0){//等待成功,但子进程未退出printf("子进程还在运行还未退出,父进程再等一等或者做其他事情\n");if(handlers.empty()) Load();for(auto e : handlers){//执行处理其他任务e();}}else{//等待失败printf("wait失败");quit=1;}sleep(1);}}
}

四,进程程序替换

4.1 什么是进程程序替换?为什么要有这个?

fork之后,父子进程各自执行代码的一部分,代码数据只读,父子进程共享;变量数据写时拷贝

那么如果子进程我不执行原来的代码了,我想执行一份全新的程序呢?就是父子代码不共享了,子进程执行自己的代码。

程序替换是通过特定的接口,加载磁盘上一个全新的程序(代码和数据),加载到子进程的地址空间中

问题:进程替换的时候有没有创建新的子进程?

解答:没有创建进程。进程为内核数据结构+代码和数据,把一个全新的程序加载到内存里,仅仅是重新建立了映射关系,进程的内核数据结构,状态和优先级等未发生变化,所以没有创建进程

4.2 execl函数

先看man execl的查询:

可以看到exec系列函数一共有6个,我们先搞第一个execl。 

第一个参数是一个const char指针代表其它可执行程序的绝对路程或相对路径,第二个参数是一个可变参数列表,表示你要替换的可执行程序后面要带的参数,以NULL结尾。比如我要执行ls函数,如下代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>int main(int argc,char* argv[],char *env[])
{pid_t id = fork();if(id == 0){//子进程printf("子进程开始运行,pid:%d\n",getpid());sleep(3);execl("/usr/bin/ls","ls","--color=auto", "-a","-l",NULL);exit(1);}else{//父进程printf("父进程开始运行,pid:%d\n",getpid());int status =0;pid_t id =waitpid(-1,&status,0);//阻塞等待if(id>0){printf("wait success,exit code:%d\n",WEXITSTATUS(status));}}
}

 

刚开始父子进程都开始运行,但是3秒后才有ls -a -l的内容,虽然不知道父子进程谁先开始的,但是肯定是父进程最后结束

问题:为什么要创建子进程让子进程来执行execl函数?

解答:如果不创建子进程,那么替换的就是父进程,我们需要让父进程聚焦在读取数据解析数据,指派子进程执行代码的功能(类似工头和工人的关系),所以我们要保证子进程在使用execl替换的时候不影响父进程。

(加载程序之前,父子进程代码共享,数据写时拷贝,当子进程替换的时候,其实也是一种写入,这时候代码也要写时拷贝,并且要将父子进程的代码分离) 

4.3 其它exec函数

 

 ①execlp

这个函数就是在execl后面加了个“ p ” ,p通常表示环境变量,这个函数作用和execl一样,只是第一个参数可以不传路径了,我们输入可执行程序名,它会直接在环境变量当中找,比如我要替换ls:

execlp("ls","ls", "--color=auto", "-l", NULL);

②execle

这个函数和execlp相比多了一点东西又少了一点东西。第一个参数和execl仍然是路径,第二个参数为可变模板列表,第三个参数是新增的,该参数可以由用户自己定义,并在替换后被另一个进程使用

char* myenvp = {"MYVAL=2024", NULL};
execle("./mytest", "mytest", NULL, myenvp);

 ③execv

execl最后的l我们可以看成list的意思,那么execv的v我们就可以看成vector。

第一个参数是路径,后面跟的是一个指针数组了,数组里面每一个指针都指向一个字符串,每个字符串都是替换过后的进程的参数,比如我要用execv将当前进程替换成ls,如下代码:

char* myargv[] = {"ls", "--color=auto", "-l"};
execv("/usr/bin/ls", myargv);

④execvp

和execlp一样,第一个参数不再是路径,可以直接传可执行程序名,然后系统回去环境变量当中找,比如我要执行ls,如下代码

char* myargv[] = {"ls", "--color=auto", "-l"};
execv("ls", myargv);

⑤execvpe

算是这几个函数里最难用的一个吧,也是把自定义环境变量传给替换后的进程。如下代码:

char* myargv[] = {"mytest", NULL}
char* myenvp[] = {"MYVAL=2024", NULL};
execvpe("./mytest", myargv, myenvp);

4.4 execve

上面的要想在man文档查,发现只能“man 3 execl”,而不能“man 2 execl”,这说明上面的6个函数都不是系统调用,那么真正实现替换的是哪个系统调用呢?

man 2 execve后可以看到下面内容:

可以发现execve的参数和execvpe一模一样,上面的6个函数都是都是对系统调用的execve做了封装,因为这么做是为了满足上层用户的不同调用场景

五,一些问题解答

5.1 wait和waitpid如何拿到进程的退出信息的呢?为什么不直接用全局变量拿到退出码?

僵尸进程是已经死掉的进程,代码和数据可以释放,但是进程的内核数据结构也就是PCB不能释放,而PCB内部也保留了进程退出时的各种对出结果,所以wait和waitpid本质就是去读取子进程的task_struct里的退出信息来获取子进程的退出信息。

不能用全局变量是因为进程具有独立性,而且对全局变量做修改时会发生写时拷贝

5.2 僵尸进程中曾经new和malloc的空间会释放码?

前面讲过,僵尸进程是已经死掉的进程,代码和数据会被释放,进程new和malloc申请的空间也会随着进程死掉被释放,因为new和malloc是用户申请的空间,僵尸进程的PCB是操作系统开辟的空间,前者属于用户层的,后者是属于内核层的,所以僵尸进程的内存泄漏是属于操作系统内部的内存泄漏,与用户层无关。

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

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

相关文章

【第19章】spring-mvc之全局异常处理

文章目录 前言一、全局异常处理1. 前端2. 后端 二、常见错误页1.增加界面2.web.xml3.异常处理4.效果 总结 前言 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基…

CSS-伪类选择器

结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素 分类&#xff1a; 选择器说明元素名:first-child查找第一个元素元素名:last-child查找最后一个元素元素名:nth-child(N)查找第N名元素 <!DOCTYPE html> <html lang"en"> <head><me…

【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第3章 Ubuntu20.04系统设置

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

【论文阅读笔记】jTrans(ISSTA 22)

个人博客地址 [ISSTA 22] jTrans&#xff08;个人阅读笔记&#xff09; 论文&#xff1a;《jTrans: Jump-Aware Transformer for Binary Code Similarity》 仓库&#xff1a;https://github.com/vul337/jTrans 提出的问题 二进制代码相似性检测&#xff08;BCSD&#xff0…

2024数维杯数学建模B题完整论文讲解(含每一问python代码+结果+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024数维杯数学建模挑战赛生物质和煤共热解问题的研究完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 B题论…

数学学习笔记1——二次函数中的数形结合

二次函数中的数形结合 一、解一元二次不等式 基本方法&#xff1a;配方。 x 2 − 4 x 3 < 0 → ( x − 2 ) 2 < 1 → ∣ x − 2 ∣ < 1 → 1 < x < 3 x^2-4x3<0\to(x-2)^2<1\to\lvert x-2\rvert<1\to1<x<3 x2−4x3<0→(x−2)2<1→∣x−…

VBA_MF系列技术资料1-605

MF系列VBA技术资料1-605 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-0…

具备教学意义的实操(用队列实现栈)

225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/implement-stack-using-queues/description/ 实现逻辑 一个是先进先出&#xff08;队列&#xff09;&#xff0c;一个是后进先出&#xff08;栈&#xff09; 这里用两个队列导入一下数据…

项目管理 | 如何做好项目管理?

大部分人在做项目管理时会遇到以下问题&#xff1a; 团队沟通不畅&#xff0c;对于项目的各个环节和配合方无法掌控项目的任务分配和跟踪困难&#xff0c;项目进度不透明项目上线进度慢&#xff0c;没有威信难以服众 那项目管理怎么做&#xff1f;这篇就结合简道云团队的经验…

爬虫学习:XPath提取网页数据

目录 一、安装XPath 二、XPath的基础语法 1.选取节点 三、使用XPath匹配数据 1.浏览器审查元素 2.具体实例 四、总结 一、安装XPath 控制台输入指令&#xff1a;pip install lxml 二、XPath的基础语法 XPath是一种在XML文档中查找信息的语言&#xff0c;可以使用它在HTM…

数据结构----二叉树

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1. 树型结构 1.1 概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上…

【软考】模拟考卷错题本2024-05-11

1 设计模式- 适配器模式 基本上上述的图解已经涵盖了绝大多数主流的设计模式和其特点。理解记忆下即可&#xff0c;这里对下午的考题也有帮助的。 2 计算机组成原理 cpu 访问速度 这个真的是憨憨咯~看到内存就选内存&#xff0c;题目都没审好。这里的速度比cpu内部的要比外部的…

c++ STL 之栈—— stack 详解

vector 是 stl 的一个关联容器,名叫“栈”&#xff0c;何为“栈”&#xff1f;其实就是一个数组&#xff0c;但有了数组何必还需栈&#xff0c;这是一个高深的问题。 一、简介 1. 定义 栈&#xff0c;是一个柔性数组&#xff08;可变长数组&#xff09;&#xff0c;可以变大变小…

Centos7安装图形化界面

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; Centos7安装图形化界面https://myweb.myskillstree.cn/43.html 目录 一、安装GNOME桌面 二、开机自启动修改为命令行模式 三、卸载图形化界面 一、安装GN…

【C++】string类的使用③(修改器Modifiers || 非成员函数重载Non-member function overloads)

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 前言&#x1f525;修改器&#xff08;Modifiers&#xff09;**operator**appendpush_back和pop_backassigninserterasereplaceswap &#x1f525;非成员函数重载&#xff…

Java入门基础学习笔记4——开发Helloworld入门程序

Java程序开发的三个步骤&#xff1a; 1&#xff09;编写代码 2&#xff09;编译代码 3&#xff09;运行代码 注意事项&#xff1a; 第一个java程序建议使用记事本来编写。 建议代码文件名全英文、首字母大写、满足驼峰模式&#xff0c;源代码文件的后缀必须是.java 注意&a…

栈实现队列

一、分析 栈的特点是先出再入&#xff0c;而队列的特点为先入先出&#xff0c;所以我们创造两个栈&#xff0c;一个用来存放数据&#xff0c;一个用来实现其它功能此时栈顶为队尾&#xff1b;当要找队头数据时将前n-1个数据移入到另一个栈中&#xff0c;此时剩余那个数据为队头…

Sqlite在Mybatis Plus中关于时间字段的处理

我的个人项目中&#xff0c;使用Mybatis-Plus 和 Sqlite数据库&#xff0c; 但是在存储和查询时间字段的时候&#xff0c;总是出现问题&#xff0c;记录下我解决问题的过程。 Sqlite会默认把时间字段转成时间戳存储到数据库的字段中&#xff0c;看起来不直观&#xff0c;所以我…

在Linux上安装并运行RabbitMQ

目录 准备CentOS服务器 下载rabbit-server和erlang文件 启动RabbitMQ服务 准备CentOS服务器 两个命令&#xff0c;选一个能用的&#xff0c;查看CentOS服务器的版本 lsb_release -a下载rabbit-server和erlang文件 参考文章&#xff1a;http://t.csdnimg.cn/t8BbM 1、创建新…

Python专题:八、列表(3)

列表的场景应用 统计和肺考试成绩 >不支持字符串和整数之间的比较 sort()函数从小到大排列 reverse&#xff08;&#xff09;函数从大到小排列 列表推导式 字符串列表的转化&#xff0c;join&#xff08;&#xff09;函数列表生成字符串 split&#xff08;&#xff09;函…