shell的模拟实现 ─── linux第16课

news2025/7/12 16:24:19

目录

第一版只能维护命令行参数表+创建子进程, 执行非内建命令

第一版的执行结果:

 第二版能维护命令行参数表+执行cd命令 ,判断了是否是自建命令(mysell自己执行自建命令,可以对环境变量发生改变),子进程执行其他命令.

第二版执行结果:

第三版 模拟真实shell从系统文件中获取环境变量,维护命令行参数表+维护环境变量表(execvpe)

第三版运行结果:


在shell的命令行中输入命令,会有两种执行命令的途径

  1.         shell自己执行
  2.         shell创建子进程(fork ,exit ,waitpid,exec) ,子进程去执行
  • shell自己执行的命令是自建命令(bulit command)
  • 子进程执行的是非自建命令

第一版只能维护命令行参数表+创建子进程, 执行非内建命令

        我们先创建了命令行提示符 ,获取了命令行的内容,维护了命令行参数表,创建了子进程进行命令的执行

​
  1 #include<iostream>
  2 #include<cstdio>
  3 #include<stdlib.h>
  4 #include<cstring>
  5 #include<string>
  6 #include<unistd.h>
  7 #include<sys/types.h>
  8 #include<sys/wait.h>
  9 
 10 using namespace std;
 11 const int charsize =1024;//命令行提示符的大小
 12 const int gargvnum =64;//命令行参数列表的大小
 13 
 14 //全局的
 15 char* gargv[gargvnum];
 16 int gargc;
 17 
 18 
 19 int lastcode = 0;
 20 
 21 string GetUsrName()
 22 {
 23     string name =getenv("USER");
 24     return name.empty()?  "None" : name;
 25 }
 26 string GetHostName()
 27 {
 28     string name =getenv("HOSTNAME");
 29     return name.empty()?  "None" : name;
 30 }
 31 string GetPwd()
 32 {
 33     string name =getenv("PWD");
 34     return name.empty()?  "None" : name;
 35 }
 36 string MakeCommandLine()
 37 {  //[root@hcss-ecs-1f3a lesson17]#  
 38     char CommandLine[charsize];
 39     snprintf(CommandLine ,charsize,"[%s@%s %s]# ",\
 40         GetUsrName().c_str(),GetHostName().c_str(),GetPwd().c_str());
 41     return CommandLine;
 42 }
 43 void PrintCommandLine()//1.打印命令行提示符
 44 {
 45     printf("%s",MakeCommandLine().c_str());
 46     fflush(stdout);
 47 }
 48 
 49 
 50 bool GetCommand(char Command_buff[] ,int size)//2.获取命令
 51 {
 52     //将命令输出到字符数组中
 53     //ls -a -l -n
 54     char*result =fgets(Command_buff,size,stdin);
 55     if(!result)
 56     {
 57         return false;
 58     }
 59     Command_buff[strlen(Command_buff)-1]= 0;//fgets会将回车(\n)也输入,将回车置零
 60     if(strlen(Command_buff) == 0) return false;//命令行就输入了回车,strlen遇0(\0)会停下来
 61     return true;
 62 }
 63 
 64  void ParseCommand(char Command_buff[] ,int size)//3.分析命令
 65 {
 66     memset(gargv ,0,sizeof(gargv));
 67     gargc=0;
 68     const char* SEP =" ";
 69     gargv[gargc++] = strtok(Command_buff ,SEP);//strtok 会在字符串中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个
 70     while((bool)(gargv[gargc++] = strtok(nullptr,SEP)));//strtok后续使用要用nullptr代替元字符串
 71     gargc--;
 72 }
 73 
 74 void debug()
 75 {
 76     printf("argc: %d\n", gargc);
 77     for(int i = 0; gargv[i]; i++)
 78     {
 79         printf("argv[%d]: %s\n", i, gargv[i]);
 80     }
 81 }
 85 bool ExecuteCommand()//4.执行命令
 86 {
 87     pid_t id =fork();
 88     if(id ==0)
 89     {//子进程
 90         int ret =execvp(gargv[0],gargv);
 91         if(ret ==-1) cout<<"子进程出错\n"<<endl;
 92         exit(1);
 93     }
 94     int status =0;
 95     pid_t rid =waitpid(id,&status ,0);
 96     if(rid >0)
 97     {
 98         if(WIFEXITED(status))
 99         {
100            lastcode=WEXITSTATUS(status);
101         }
102         else lastcode =100;
103         return true;
104     }
105     return false;
106 
107 }
108 
109 int main()
110 {
111     char Command_buff[charsize];
112     while(true)
113     {
114         PrintCommandLine();//1.打印命令行提示符
115 
116         if(!GetCommand(Command_buff,charsize))//2.获取命令
117         {
118             continue;
119         }
120         ParseCommand(Command_buff ,charsize);//3.分析命令
121         //debug();
122         ExecuteCommand();//4.执行命令
123     }
124     return 0;
125 }

​

第一版的执行结果:

  • 第一版在执行cd ..时,是改变了子进程的cwd,子进程执行完又退了,无法影响下面的进程,cd.. 不能让子进程执行.
  • 所以cd .. 或cd / 是自建命令 ,需要shell自己执行,以便可以影响到下面的进程(例如在/ 目录下创建文件)

 第二版能维护命令行参数表+执行cd命令 ,判断了是否是自建命令(mysell自己执行自建命令,可以对环境变量发生改变),子进程执行其他命令.

在执行创建子进程前判断,命令是否是内建命令(是否是cd 命令),是否要创建子进程.

getpwd不再是从环境变量中拿,从pcb中拿(因为pcb中是实时的),拿完更新环境变量(命令行提示符每次运行都会刷新,借此来维护cd 后的环境变量).

    #include<iostream>
  2 #include<cstdio>
  3 #include<stdlib.h>
  4 #include<cstring>
  5 #include<string>
  6 #include<unistd.h>
  7 #include<sys/types.h>
  8 #include<sys/wait.h>
  9 
 10 using namespace std;
 11 const int charsize =1024;
 12 const int gargvnum =64;
 14 //全局的
 15 char* gargv[gargvnum];
 16 int gargc;
 17 
 19 
 20 
 21 //全局的当前shell的工作路径(定义到全局不会被销毁)
 22 char pwd[charsize];
 23 char pwdenv[charsize];
 24 
 25 int lastcode = 0;
 26 
 27 string GetUsrName()
 28 {
 29     string name =getenv("USER");
 30     return name.empty()?  "None" : name;
 31 }
 32 string GetHostName()
 33 {
 34     string name =getenv("HOSTNAME");
 35     return name.empty()?  "None" : name;
 36 }
    string GetPwd()
 38 {
 39     //string name =getenv("PWD");
 40     //return name.empty()?  "None" : name;
 41 
 42 
 43     //从pcb中直接拿pwd
 44     if(nullptr ==  getcwd(pwd,sizeof(pwd))) return "None";
 45     //拿到后还需要更新环境变量中的pwd
 46     snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
 47     putenv(pwdenv);
 48     return pwd;
 49 
 50 
 51 }
 52 string MakeCommandLine()
 53 {  //[root@hcss-ecs-1f3a lesson17]#  
 54     char CommandLine[charsize];
 55     snprintf(CommandLine ,charsize,"[%s@%s %s]# ",\
 56         GetUsrName().c_str(),GetHostName().c_str(),GetPwd().c_str());
 57     return CommandLine;
 58 }
 59 void PrintCommandLine()//1.打印命令行提示符
 60 {
 61     printf("%s",MakeCommandLine().c_str());
 62     fflush(stdout);
 63 }
 64 
 65 
 66 bool GetCommand(char Command_buff[] ,int size)//2.获取命令
 67 {
 68     //将命令输出到字符数组中
 69     //ls -a -l -n
 70     char*result =fgets(Command_buff,size,stdin);
 71     if(!result)
 72     {
 73         return false;
 74     }
 75     Command_buff[strlen(Command_buff)-1]= 0;//fgets会将回车(\n)也输入
 76     if(strlen(Command_buff) == 0) return false;//strlen遇0(\0)会停下来
 77     return true;
 78 }

 80  void ParseCommand(char Command_buff[] ,int size)//3.分析命令
 81 {
 82     memset(gargv ,0,sizeof(gargv));
 83     gargc=0;
 84     const char* SEP =" ";
 85     gargv[gargc++] = strtok(Command_buff ,SEP);//strtok 会在字符串中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个
 86     while((bool)(gargv[gargc++] = strtok(nullptr,SEP)));//strtok后续使用要用nullptr代替元字符串
 87     gargc--;
 88 }
 89 
 90 void debug()
 91 {
 92     printf("argc: %d\n", gargc);
 93     for(int i = 0; gargv[i]; i++)
 94     {
 95         printf("argv[%d]: %s\n", i, gargv[i]);
 96     }
 97 }
 98 
 99 
100 
101 bool ExecuteCommand()//4.执行命令
102 {
103     pid_t id =fork();
104     if(id ==0)
105     {//子进程
106         int ret =execvp(gargv[0],gargv);
107         if(ret ==-1) cout<<"子进程出错\n"<<endl;
108         exit(1);
109     }
110     int status =0;
111     pid_t rid =waitpid(id,&status ,0);
112     if(rid >0)
113     {
114         if(WIFEXITED(status))
115         {
116            lastcode=WEXITSTATUS(status);
117         }
118         else lastcode =100;
119         return true;
120     }
         return false;
122 
123 }
124 
125 //内建命令的执行(调用函数,改变状态)
126 bool CheckandExecBuiltCommand()
127 {   //使用穷举法找内建命令
128     if(0 == strcmp(gargv[0],"cd"))
129     {
130         if(gargc == 2)
131         {
132             chdir(gargv[1]);
133         }
134         return true;
135     }
136     return false;
137 
138 }
139 
140 int main()
141 {
142     char Command_buff[charsize];
143     while(true)
144     {
145         PrintCommandLine();//1.打印命令行提示符
146 
147         if(!GetCommand(Command_buff,charsize))//2.获取命令
148         {
149             continue;
150         }
151         ParseCommand(Command_buff ,charsize);//3.分析命令
152         //debug();
153         //判断是否是内建命令,shell自己执行
154         if(CheckandExecBuiltCommand())//是内建命令并执行
155         {
156             continue;
157         }
158         //不是内建命令,创建子进程执行
159         ExecuteCommand();//4.执行命令
160     }
161     return 0;
162 }

第二版执行结果:

第三版 模拟真实shell从系统文件中获取环境变量,维护命令行参数表+维护环境变量表(execvpe)

  • myshell前面的两版都是从系统shell中获取的环境变量表
  • 实际上,系统shell开启时,是从系统文件中获取环境变量表,但是这个过程涉及shell脚本(比较难搞,意义不大),
  • 所以我们用将系统shell的环境变量表手动拷贝到myshell中的过程来模拟系统shell开启时,是从系统文件中获取环境变量表

  • 要让myshell执行的子进程的环境变量与myshell一致(不与系统shell一致)使用execvpe
  • 系统的shell维护了两张表(命令行参数表+环境变量表)

第三版拷贝了环境变量表,维护了环境变量表(execvpe),增加了内置命令(export  echo env)

设置了退出码(lastcode)

第三版运行结果:

#include<iostream>
  2 #include<cstdio>
  3 #include<stdlib.h>
  4 #include<cstring>
  5 #include<string>
  6 #include<unistd.h>
  7 #include<sys/types.h>
  8 #include<sys/wait.h>
  9 
 10 using namespace std;
 11 const int charsize =1024;
 12 const int gargvnum =64;
 13 const int envnum =64;
 14 //全局的
 15 char* gargv[gargvnum];
 16 int gargc;
 17 
 18 char* genv[envnum];
 19 
 20 
 21 //全局的当前shell的工作路径(定义到全局不会被销毁)
 22 char pwd[charsize];
 23 char pwdenv[charsize];
 24 
 25 //全局的进程返回码
 26 int lastcode = 0;
 27 
 28 string GetUsrName()
 29 {
 30     string name =getenv("USER");
 31     return name.empty()?  "None" : name;
 32 }
 33 string GetHostName()
 34 {
 35     string name =getenv("HOSTNAME");
 36     return name.empty()?  "None" : name;
 37 }
string GetPwd()
 39 {
 40     //string name =getenv("PWD");
 41     //return name.empty()?  "None" : name;
 42 
 43 
 44     //从pcb中直接拿pwd
 45     if(nullptr ==  getcwd(pwd,sizeof(pwd))) return "None";
 46     //拿到后还需要更新环境变量中的pwd
 47     snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
 48     putenv(pwdenv);
 49     return pwd;
 50 
 51 
 52 }
 53 string MakeCommandLine()
 54 {  //[root@hcss-ecs-1f3a lesson17]#  
 55     char CommandLine[charsize];
 56     snprintf(CommandLine ,charsize,"[%s@%s %s]# ",\
 57         GetUsrName().c_str(),GetHostName().c_str(),GetPwd().c_str());
 58     return CommandLine;
 59 }
 60 void PrintCommandLine()//1.打印命令行提示符
 61 {
 62     printf("%s",MakeCommandLine().c_str());
 63     fflush(stdout);
 64 }
bool GetCommand(char Command_buff[] ,int size)//2.获取命令
 68 {
 69     //将命令输出到字符数组中
 70     //ls -a -l -n
 71     char*result =fgets(Command_buff,size,stdin);
 72     if(!result)
 73     {
 74         return false;
 75     }
 76     Command_buff[strlen(Command_buff)-1]= 0;//fgets会将回车(\n)也输入
 77     if(strlen(Command_buff) == 0) return false;//strlen遇0(\0)会停下来
 78     return true;
 79 }
 80 
 81  void ParseCommand(char Command_buff[] ,int size)//3.分析命令
 82 {
 83     memset(gargv ,0,sizeof(gargv));
 84     gargc=0;
 85     const char* SEP =" ";
 86     gargv[gargc++] = strtok(Command_buff ,SEP);//strtok 会在字符串中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个
 87     while((bool)(gargv[gargc++] = strtok(nullptr,SEP)));//strtok后续使用要用nullptr代替元字符串
 88     gargc--;
 89 }
 90 
 91 void debug()
 92 {
 93     printf("argc: %d\n", gargc);
 94     for(int i = 0; gargv[i]; i++)
 95     {
 96         printf("argv[%d]: %s\n", i, gargv[i]);
 97     }
 98 }
bool ExecuteCommand()//4.执行命令
103 {
104     pid_t id =fork();
105     if(id ==0)
106     {//子进程
107         int ret =execvpe(gargv[0],gargv,genv);
108         if(ret ==-1) cout<<"子进程出错\n"<<endl;
109         exit(1);
110     }
111     int status =0;
112     pid_t rid =waitpid(id,&status ,0);
113     if(rid >0)
114     {
115         if(WIFEXITED(status))//子进程正常的返回
116         {
117            lastcode=WEXITSTATUS(status);
118         }
119         else lastcode = 100;//子进程异常
120         return true;
121     }
122     return false;
123 
124 }
125 
126 void AddEnv(char* item)
127 {
128     int index =0;
129     while(genv[index])
130     {
131         index++;
132     }
133     genv[index]=(char*)malloc(strlen(item)+1);
134     strncpy(genv[index] ,item,strlen(item)+1);
135     genv[++index]= nullptr;
136 }
//内建命令的执行(调用函数,改变状态)
138 bool CheckandExecBuiltCommand()
139 {   //使用穷举法找内建命令
140     if(0 == strcmp(gargv[0],"cd"))
141     {
142         if(gargc == 2)
143         {
144             chdir(gargv[1]);
145             lastcode= 0;
146         }
147         else
148         {
149             lastcode=1;
150         }
151         return true;
152     }
153     else if(0 ==strcmp(gargv[0] ,"export"))
154     {
155         if(gargc ==2)
156         {
157              AddEnv(gargv[1]);
158             lastcode = 0;
159         }
160         else
161         {
162             lastcode= 1;
163         }
164         return true;
165     }
166     else if(0 == strcmp(gargv[0],"env"))
167     {
168         for(int i=0 ;genv[i];i++)
169         {
170             printf("%s\n",genv[i]);
171 
172         }
173         lastcode =0;
174         return true;
175     }
176     else if(0 ==strcmp(gargv[0] ,"echo"))
177     {
178         if(gargc ==2)
179         {
180             //echo $?
181             //echo $PATH
182             //echo hello
183             if(gargv[1][0] == '$')
184             {
185                  if(gargv[1][1] == '?')
186                  {
187                     printf("%d\n",lastcode);
188                     lastcode =0;
189                  }
190             }
191             else
192             {
193                 printf("%s\n",gargv[1]);
194                 lastcode =0;
195             }
196         }
197         else
198         {
199             lastcode=1;
200         }
201         return true;
202 
203     }
204     return false;
205 
206 }
207 
208 // 作为一个shell,获取环境变量应该从系统的配置来
209 //  我们今天就直接从父shell中获取环境变量   
210 void IniEnv()
211 {
212     extern char** environ;
213     int index=0;
214     while(environ[index])
215     {
216         genv[index]=(char*)malloc(strlen(environ[index])+1);//strlen不含\0
217         strncpy(genv[index],environ[index],strlen(environ[index])+1);
218         index++;
219     }
220     genv[index]= nullptr;
221 
222 }
223 
224 int main()
225 {
226     IniEnv();
227     char Command_buff[charsize];
228     while(true)
229     {
230         PrintCommandLine();//1.打印命令行提示符
231 
232         if(!GetCommand(Command_buff,charsize))//2.获取命令
233         {
234             continue;
235         }
236         ParseCommand(Command_buff ,charsize);//3.分析命令
237         //debug();
238         //判断是否是内建命令,shell自己执行
239         if(CheckandExecBuiltCommand())//是内建命令并执行
240         {
241             continue;
242         }
243         //不是内建命令,创建子进程执行
244         ExecuteCommand();//4.执行命令
245     }
246     return 0;
247 }

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

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

相关文章

游戏引擎学习第153天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾 目前正在进行的是一个比较大的系统调整&#xff0c;原本预计今天会继续深入这个改动&#xff0c;但实际上在昨天的开发中&#xff0c;我们已经完成了大部分的代码编写&#xff0c;并且运行之后几乎一切都能正常工作&#x…

Java EE 进阶:SpringBoot 配置⽂件

什么是配置文件 “配置文件”是一个用来保护程序或者系统设置信息的文件&#xff0c;它的作用是让程序在启动或者运行中&#xff0c;能够读取这些设置并按预期进行工作&#xff0c;而不需要手动的设置。 Spring Boot 配置文件 设置服务器端口、编码格式配置数据库连接控制日…

【redis】五种数据类型和编码方式

文章目录 五种数据类型编码方式stringhashlistsetzset查询内部编码 五种数据类型 字符串&#xff1a;Java 中的 String哈希&#xff1a;Java 中的 HashMap列表&#xff1a;Java 中的 List集合&#xff1a;Java 中的 Set有序集合&#xff1a;除了存 member 之外&#xff0c;还有…

色板在数据可视化中的创新应用

色板在数据可视化中的创新应用&#xff1a;基于色彩感知理论的优化实践 引言 在数据可视化领域&#xff0c;色彩编码系统的设计已成为决定信息传递效能的核心要素。根据《Nature》期刊2024年发布的视觉认知研究&#xff0c;人类大脑对色彩的识别速度比形状快40%&#xff0c;色…

【无人机路径规划】基于麻雀搜索算法(SSA)的无人机路径规划(Matlab)

效果一览 代码获取私信博主基于麻雀搜索算法&#xff08;SSA&#xff09;的无人机路径规划&#xff08;Matlab&#xff09; 一、算法背景与核心思想 麻雀搜索算法&#xff08;Sparrow Search Algorithm, SSA&#xff09;是一种受麻雀群体觅食行为启发的元启发式算法&#xff0…

STM32_GPIO系统外设学习

按照STM32MCUWIKI、参考手册的外设介绍----->CubeF4的软件包中相关的Exmple代码----->CubeMX设置截图加深理解记忆 资料链接&#xff1a;嵌入式开发_硬软件的环境搭建 我的飞书文档-GPIO篇 如果觉得内容不错&#xff0c;欢迎给我的飞书文档点赞。同时如果有什么意见或…

【操作系统安全】任务1:操作系统部署

目录 一、VMware Workstation Pro 17 部署 二、VMware Workstation 联网方式 三、VMware 虚拟机安装流程 四、操作系统介绍 五、Kali 操作系统安装 六、Windows 系统安装 七、Windows 系统网络配置 八、Linux 网络配置 CSDN 原创主页&#xff1a;不羁https://blog.csd…

下载安装启动 VMware 个人免费版本

一、进入官网并登录账号下载软件 进入官网 [ https://www.vmware.com ]&#xff0c;点击Products&#xff0c;将页面划到最底下&#xff0c;点击 “SEE DESKTOP HYPERVISORS”按钮。 然后点击 Desktop hypevisor &#xff0c;会出现如下界面&#xff0c;可以根据自己的操作系…

C#+AForge 实现视频录制

C#AForge 实现视频录制 ​ 在C#中&#xff0c;使用AForge 库实现视频录制功能是一个比较直接的过程。AForge 是一个开源的.NET框架&#xff0c;提供了许多用于处理图像和视频的类库。 开发步骤 安装AForge库 ​ 首先&#xff0c;确保你的项目中已经安装了 AForge.Video和AFo…

SAP SD学习笔记31 - 销售BOM

上一篇讲 前受金处理(预付款处理)。 SAP SD学习笔记29 - 前受金处理(预收款处理)_fplt 付款申请与sd 数据表的关联关系-CSDN博客 本章继续讲SAP SD模块的其他知识&#xff1a;销售BOM。 销售BOM在现场还是会用到的。 目录 1&#xff0c;销售BOM概要 2&#xff0c;受注BOM的…

大数据学习(63)- Zookeeper详解

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91e; &#x1f…

嵌入式八股C语言---面向对象篇

面向对象与面向过程 面向过程 就是把整个业务逻辑分成多个步骤,每步或每一个功能都可以使用一个函数来实现面向对象 对象是类的实例化,此时一个类就内部有属性和相应的方法 封装 在C语言里实现封装就是实现一个结构体,里面包括的成员变量和函数指针,然后在构造函数中,为结构体…

C# ListView设置标题头背景颜色和字体颜色

一、向ListView 添加数据 for (int i 1; i < 5; i) {ListViewItem litem new ListViewItem("data:"i);lv_WarnList.Items.Add(litem); }如果需要在ListView中绑定实体类对象的话&#xff0c;需要将数据放在Tag属性里 for (int i 1; i < 5; i) {AngleData …

嵌入式 ARM Linux 系统构成(6):应用层(Application Layer)

目录 一、应用层概述 二、应用层的核心组成 2.1 主应用程序&#xff08;Main Applications&#xff09; 2.2 系统服务&#xff08;System Services&#xff09; 2.3 用户界面&#xff08;User Interface&#xff09; 2.4 脚本与自动化工具 2.5 第三方库与框架 2.6 通信…

【HTML】一、基础标签

文章目录 1、开发环境准备2、html介绍3、html基本骨架4、标签的关系5、常用标签5.1 标题5.2 段落5.3 换行与水平线5.4 文本格式化标签5.5 图像标签5.6 超链接标签5.7 音频标签5.8 视频标签 6、路径7、网页制作 1、开发环境准备 在编辑器中写代码&#xff0c;在浏览器中看效果 …

centos7通过yum安装redis

centos7通过yum安装redis 1.安装redis数据库 yum install -y redis2.启动redis服务 systemctl start redis3.查看redis状态 systemctl status redis4、停止服务 systemctl stop redis5、重启服务 systemctl restart redis6、查看redis进程 ps -ef | grep redis7、开放端…

AutoMQ x OSS 的 Iceberg 数据入湖的最佳实践

背景 在数字化转型进程中&#xff0c;用户交互行为产生的多维度数据已成为企业的重要战略资产。以短视频平台为例&#xff0c;基于用户点赞事件的实时推荐算法能显著提升用户活跃度和平台粘性。这类实时数据主要通过 Apache Kafka 流处理平台进行传输&#xff0c;通过其扇出&a…

【Help Manual】导出PDF中英文不在一行解决方案

在使用Help Manual 的时候&#xff0c;会出现导出PDF时&#xff0c;中英文在同一行出现水平不对齐的问题。如下&#xff1a; 解决方案&#xff1a; 结果如下&#xff1a;

Scala编程_实现Rational的基本操作

在Scala中实现一个简单的有理数&#xff08;Rational&#xff09;类&#xff0c;并对其进行加法、比较等基本操作. 有理数的定义 有理数是可以表示为两个整数的比值的数&#xff0c;通常形式为 n / d&#xff0c;其中 n 是分子&#xff0c;d 是分母。为了确保我们的有理数始终…

用python和Pygame库实现“跳过障碍”游戏

用python和Pygame库实现“跳过障碍”游戏 游戏开发 跳过障碍游戏流程说明&#xff1a; 启动游戏后显示开始界面&#xff08;包含游戏说明&#xff09; 按空格键进入游戏 游戏过程中躲避障碍物获取分数 碰撞后显示结束界面&#xff08;包含最终得分&#xff09; 按空格键…