Linux学习之路 -- 进程篇 -- 自定义shell的编写

前面介绍了进程程序替换的相关知识,接下来,我将介绍如何基于前面的知识,编写一个简单的shell,另外本文的所展示的shell可能仅供参考。

目录

<1>获取用户的输入和打印命令行提示符

<2>切割字符串

<3>执行这个命令

<4>判断内建命令

<1>cd命令

<2>export

<3>echo命令

<5>全部代码


<1>获取用户的输入和打印命令行提示符

首先我们打开shell时,一般都会看到一个命令行提示符

目前光标卡在当前位置不动,就是在等待用户输入一段命令,这一段命令会被当成字符串。所以我们首先要做的工作就是获取命令行提示符和用户的输入。

在获取命令行提示符前,我们需要回顾一下命令行提示符的组成

虽然用户名、主机名和路径都能通过系统接口进行获取,但是我们也可以通过环境变量来获取这些数据。而我们可以通过getenv接口,就能获得特定环境变量的内容。

下面演示一下输出命令行提示符

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int main(int argc, char* argv[],char* env[])
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());return 0;
}

 这里需要注意的是,HOSTNAME在一些操作系统中可能没有,这可能是很多原因导致的。所以我们可以在获取HOSTNAME这个环境变量之前手动添加。

运行效果

接下来再解决一下用户输入问题

我们可以用一个字符数组先储存命令行参数。至于输入,我们可以使用fgetc,不能用scanf函数,scanf在读到空串时,会自动停止读取。所以我们使用fgetc从缓冲区里面读取(当然也可以使用其他的接口,这里我以fgets为例)。下面介绍一下fgetc接口

第一个参数表示,存放缓冲区数据的数组,第二个表示数组大小,第三个表示输入流指针(这个暂不做介绍,涉及文件系统内容,这里直接写stdin即可)。

下面演示一下代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#define SIZE 1024const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int main(int argc, char* argv[],char* env[])
{char command[SIZE];printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);printf("command line: %s\n",command);return 0;
}

运行结果

这里我们会发现,中间printf打印完一条语句后,多了一行空白,这行空白是其实是因为我们在输入时也会敲回车键,所以实际在执行printf语句时,会有两个\n,这就会造成中间多了一行空白。这里我们只需要将command数组里面存储的最后一个字符由‘\n’变成‘\0’或0即可。所以只需在fgets语句后添加下面一条语句即可。

    command[strlen(command) - 1] = 0;

无需担心command长度为零的情况,因为无论如何你都要输入一个‘\n’。所以command的长度至少也是1。但是我们需要对空串进行一下判断,如果是空串,后面的代码就不需要执行了(这条下面封装时会用到)。

调整后的结果

这里我们可以对命令行提示符的显示和用户输入功能封装一下。

<2>切割字符串

如果我们要执行一个命令,就必需调用程序替换的接口,而在之前的介绍里面,程序替换接口的参数都是没有空格的字符串,而且都是一个一个分开的。所以我们必需要将字符串切割成一个一个子串,然后传递给这些接口。所以第一步就是先分割子串,那我们该如何切割呢?下面介绍一个函数strtok

str参数表示要切割的串,delim表示以什么为分割符。需要注意的是,第一次调用该函数时,str参数传需要切割的字符串指针,第二次传NULL,delim不变,就是” “(空格)。

下面演示一下代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int interactive(char* command)
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);command[strlen(command) - 1] = 0;return strlen(command);
}
void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
}
int main()
{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);//如果是空串,则下面的代码就不必执行了if(!ret){}else{//2.切割命令行Split(command);for(int i = 0; argv[i];i++){printf("argv[%d]:%s\n",i,argv[i]);}}return 0;
}

Split函数中的while循环条件变成argv[i++] = strtok(NULL, SYM),可以直接把strtok切割的子串放进argv里面,而且在切完后,strtok会返回NULL,我们之前了解过,命令行参数列表的结尾就是NULL。同时我们argv[ i ]设为空后,条件判断也就不成立了,此时也就跳出循环并 i++。

运行结果

从结果上来看,上述的代码逻辑并没有什么问题。

<3>执行这个命令

在切割完命令行后,我们就需要依照argv来执行命令。在这里我们依照程序替换的方式来进行执行命令。不过在这之前,我们需要创建一个子进程。这是因为我们的shell需要关注用户和机器之间的交互,如果我们直接让shell执行命令,那交互性能就会变差。万一程序崩溃了,shell也会无法运行。所以这里我们就需要让子进程替我们执行命令。

在创建完子进程后,我们就可以让子进程执行对应的任务。执行任务的过程其实并不难,就是选择一个合适的程序替换接口即可。这里我们选择execvp这个接口是最好的,因为该接口的参数是最少的,并且中间不用做处理。在执行完后,我们需要等待回收子进程,这里我使用的是阻塞等待。

下面是演示的代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int interactive(char* command)
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);command[strlen(command) - 1] = 0;return strlen(command);
}
void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
}void Execute()
{pid_t id = fork();if(id == 0){execvp(argv[0],argv);}pid_t rid = waitpid(id,NULL,0);// if(rid > 0)// {//     printf("wait success, pid: %d\n",rid);// }
}
int main()
{while(1)//让shell持续运行{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);if(!ret){continue;}//2.切割命令行Split(command);//3.执行命令Execute();}// for(int i = 0; argv[i];i++)// {//     printf("argv[%d]:%s\n",i,argv[i]);// }return 0;
}

运行结果:

此时,我们运行普通的命令已经没有什么问题了,但是一旦我们运行类似于cd .. 命令时,就会出现无法执行的情况。其实这是因为决定当前路径的父进程,这里我们使用子进程执行cd 命令,但是父进程的路径并没有改变,所以当我们执行cd命令后,再执行pwd命令,会发现路径并没有改变。像这样的情况还有很多,这些命令本就不应该交由子进程执行,而是让父进程直接执行。而这些命令叫做内建命令。我们在执行命令前要加一个步骤,那就是判断内建命令,并让父进程去执行这个命令。

<4>判断内建命令

<1>cd命令

由于一些命令是需要父进程自己执行的,所以我们就需要修改执行顺序。首先我们先以cd 命令为例,修改一下原来的shell。在此之前我们需要先了解一下chdir接口,这个接口是修改当前工作路径的。

path就是修改后的路径。使用这个命令的原因是因为cd 命令后可能是没有东西的,这会直接进入该用户的家目录。所以我们必需要通过这个接口来实现工作路径的切换。

在编写判断内建命令的函数时,一共分为两步,一是判断是否为内建命令,二是执行内建命令。如果是内建命令,我们需要执行并且在退出后跳过子进程执行的步骤。如果不是内建命令,那就直接退出函数,继续让子进程执行该命令。

下面演示一下代码(这里只截取部分代码,其实较上面的代码,就只是增加了Built-in-com()函数)

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);}return ret;
}
int main()
{while(1)//让shell持续运行{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);if(!ret){continue;}//2.切割命令行Split(command);//3.处理内建命令ret = Built_in_com();if(ret){continue;}//4.执行命令Execute();}// for(int i = 0; argv[i];i++)// {//     printf("argv[%d]:%s\n",i,argv[i]);// }return 0;
}

执行结果

我们可以发现,虽然命令行提示符的路径没有改变,但是pwd命令和cd命令确实是成功执行了。这里的命令行提示符是从环境变量中获取当前的工作路径的,命令行提示符路径没有改变,说明环境变量没有跟着chdir的改变而改变,所以我们在内建命令改变路径时,我们要手动对环境变量进行更新,以确保环境变量是正确的。

要修改环境变量,我们就不得不提到putenv这个接口了,这个接口常用于添加和修改环境变量。

这里的string参数就是要修改的环境变量参数,具体的参数形式:“USER=root”(例)。不过要获得修改后的环境变量参数,又要使用别的字符串函数。

要获得修改后的环境变量参数,就得先获取环境变量形式的字符串,而要获得这个字符串,我们可以通过很多种方式,可以使用strcat,strcpy等等,这里我使用snprintf。

printf就是把特定格式的内容写到显示器上,而sprintf就是把特定格式的内容写到一个str字符串里面,而snprintf就是把指定长度的内容写到一个str字符串里面。

我们可以定义一个全局变量数组pwd,里面存放修改后的环境变量字符串。通过snprintf我们就可以把对应的字符串写入pwd中,再putenv(pwd)即可。

下面演示一下代码(这里只对Built_in_com这个函数进行修改,其他的一律不做修改)

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);snprintf(pwd,SIZE,"PWD=%s",home);putenv(pwd);}return ret;
}

运行结果

除了cd .. 外,其他的命令都还正常,说明之前的代码总体逻辑是没有啥问题的,只不过cd .. 这个需要处理一下。这里出现 .. 路径是因为没有获得 “ .. ”代表的绝对路径。所以我们就需要通过特定的接口获得..路径的绝对路径 。我们可以通过getcwd函数获取“..”的绝对路径。

getcwd函数就是获取当前的工作路径,具体的参数含义如下:

  1. buf:这是一个指向字符数组的指针,用于存储获取到的当前工作目录的路径。getcwd函数会将路径字符串写入到这个数组中。

  2. size:这个参数指定了buf数组的大小,也就是它能够存储的字符数量。这个大小应该至少能够容纳当前工作目录的路径加上一个终止的空字符('\0')。                                           

既然可以通过此时,我们就可以直接把buf里面内容写到pwd中,再由pwd写入到环境变量表中。

下面演示一下代码

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);putenv(pwd);}return ret;
}

 运行结果

其他的内建命令

内建命令当然不止只有cd,还有很多,下面列举一些供大家参考

  • cd - 改变当前工作目录
  • echo - 显示消息或变量的值
  • exit - 退出当前shell
  • export - 设置环境变量
  • history - 显示或操作命令历史
  • kill - 发送信号到特定进程
  • pwd - 显示当前工作目录的路径
  • set - 设置或显示shell特性或位置参数
  • source - 在当前shell执行脚本
  • unset - 删除变量或函数
  • wait - 等待后台进程结束

下面在原来shell基础上,再添加几个内建命令

<2>export

export命令导入环境变量也是内建命令,所以我们需要添加进Built_in_com 这个函数中。这个命令会相对简单一点,我们可以直接写代码

下面演示一下代码

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界putenv(pwd);}else if(strcmp(argv[0],"export") == 0) // {ret = 1;if(argv[1]) putenv(argv[1]);}return ret;
}

运行结果

这个结果看似是非常正确的,但其实我们只要运行几次其他命令,再次查看环境变量。我们就会发现,我们新增的环境变量消失了。所以上述的代码其实是不完善的。这里的argv[ 1 ]是一个指针,指向command里面的一段内容,而我们每次输入新的命令,command就会被覆盖,而argv[ 1 ]指向的内容也会随之改变。 所以我们需要通过一个数组来存储特定环境变量,令其固定不变。这里为了方便演示,这里只用一个一维字符数组来存储一个新增的环境变量。这里严格意义上来说,是要通过一个环境变量表来存储环境变量的。

下面演示一下代码

    else if(strcmp(argv[0],"export") == 0){ret = 1;if(argv[1]) {strcpy(env,argv[1]);putenv(env);}}

由于运行结果过长,这里不变展示,读者可以自行测试。

<3>echo命令

echo命令也是一个内建命令,通常用于打印一些变量值,常见的就是“echo XXX”,向显示器打印XXX;”echo $环境变量名“,向显示器打印环境变量;“echo $?” 打印退出码。除此之外,echo还可以结合重定向进行操作,不过这里不做演示,这部分内容涉及文件系统的内容。这里主要演示上面所述的三个与echo有关的命令。

1.直接echo

echo后面不接内容,就是直接换行。

2.echo $? 

该命令会显示上一个进程的退出码,这个退出码最好就用全局变量来保存。

3.echo $环境变量

该命令会显示对应环境变量的内容,不过需要判断这个环境变量是否存在。

4.echo XXX

该命令会直接打印XXX到显示器上

下面演示一下代码(只展示修改被修改部分的代码)

void Execute()
{pid_t id = fork();if(id == 0){execvp(argv[0],argv);}int status = 0;pid_t rid = waitpid(id,&status,0);if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}// if(rid > 0)// {//     printf("wait success, pid: %d\n",rid);// }
}
int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界putenv(pwd);}else if(strcmp(argv[0],"export") == 0){ret = 1;if(argv[1]) {strcpy(env,argv[1]);putenv(env);}}else if(strcmp(argv[0],"echo") == 0){ret = 1;  if(argv[1] == NULL){printf("\n");}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n",lastcode);lastcode = 0;}else // echo $环境变量名{char* n = getenv(argv[1]+1);if(n){printf("%s\n",n);}else{printf("The environment variable does not exist\n");}}}else//echo XXXX 这里暂不考虑其他情况,例如echo和重定向符号结合{printf("%s\n",argv[1]);}}}return ret;
}

 这里lastcode变量定义为全局变量。echo的其他搭配暂时不考虑。

运行结果

额外的配置:我们可以看见当我们使用ls命令时,打印出来的文件名是没有颜色的。如果我们想让文件名具有颜色,需要再Split函数里面进行修改,我们需要再命令行参数列表中加上一个“--color”字符即可。

下面演示一下代码

void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏if(strcmp(argv[0],"ls") == 0){argv[i - 1] = "--color";argv[i] = NULL;}
}

运行结果

<5>全部代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];
char pwd[SIZE];
char env[SIZE];
int lastcode;const char* HostName()
{putenv("HOSTNAME=iZuf6at4ih6u7gbg2vxumnZ");char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
char* Home()
{char* home = getenv("HOME");if(home){return home;}else{return NULL;}
}
int interactive(char* command)
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);command[strlen(command) - 1] = 0;return strlen(command);
}
void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏if(strcmp(argv[0],"ls") == 0){argv[i - 1] = "--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){execvp(argv[0],argv);}int status = 0;pid_t rid = waitpid(id,&status,0);if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}// if(rid > 0)// {//     printf("wait success, pid: %d\n",rid);// }
}
int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界putenv(pwd);}else if(strcmp(argv[0],"export") == 0){ret = 1;if(argv[1]) {strcpy(env,argv[1]);putenv(env);}}else if(strcmp(argv[0],"echo") == 0){ret = 1;  if(argv[1] == NULL){printf("\n");}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n",lastcode);lastcode = 0;}else // echo $环境变量名{char* n = getenv(argv[1]+1);if(n){printf("%s\n",n);}else{printf("The environment variable does not exist\n");}}}else//echo XXXX 这里暂不考虑其他情况,例如echo和重定向符号结合{printf("%s\n",argv[1]);}}}return ret;
}
int main()
{while(1)//让shell持续运行{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);if(!ret){continue;}//2.切割命令行Split(command);//3.处理内建命令ret = Built_in_com();if(ret){continue;}//4.执行命令Execute();}// for(int i = 0; argv[i];i++)// {//     printf("argv[%d]:%s\n",i,argv[i]);// }return 0;
}

 如有需要,可自行拓展,另外上述代码仅供参考,不是唯一的写法。

以上就是全部内容,文中如有不对之处,还望各位大佬指正,谢谢!!!

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

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

相关文章

第 4 篇 : Netty客户端互发图片和音/视频

说明 因为图片和音/视频不能确定其具体大小, 故引入MinIO。客户端之间只发送消息, 通过上传/下载来获取额外信息 1. MinIO搭建(参考前面文章), 并启动 2. 登录MinIO创建3个Bucket: image、voice、video 3. 客户端改造 3.1 修改 pom.xml <?xml version"1.0" …

苍穹外卖绕过微信支付

经过以下改动可实现&#xff1a; 1、不用微信支付端口 2、弹出支付成功的界面 3、数据库修改支付成功后的数据 #在OrderServiceImpl.java里加入Autowiredprivate OrderService orderService; #在OrderServiceImpl.java里的payment函数做以下改动 #图片里有&#xff0c;红色为原…

2024李卜常识王小晨申论类比刷题课

2024年&#xff0c;李卜常识与王小晨申论类比刷题课成为备考公务员考试的热门选择。李卜老师以其深厚的学识&#xff0c;为学员们剖析常识的精髓&#xff1b;而王小晨老师则通过类比刷题的方式&#xff0c;帮助学员们掌握申论的技巧。这两门课程相互补充&#xff0c;让学员们在…

03-JAVA设计模式-观察者模式

观察者模式 什么是观察者模式 Java中的观察者模式是一种常见的设计模式&#xff0c;它允许对象&#xff08;观察者&#xff09;订阅另一个对象&#xff08;被观察者&#xff09;的状态变化&#xff0c;并在状态变化时自动得到通知。 核心&#xff1a; 观察者模式主要用于1&a…

手搓带头双向循环链表(C语言)

目录 List.h List.c ListTest.c 测试示例 带头双向循环链表优劣分析 List.h #pragma once#include <stdio.h> #include <stdlib.h> #include <assert.h>typedef int LTDataType;typedef struct ListNode {struct ListNode* prev;struct ListNode* next…

如何提升WordPress网站安全

上周遇到Hostease的客户反馈他想要提升wordpress网站的安全性。提升WordPress网站安全是网站所有者必须重视的事项。以下是一些有效的安全措施&#xff0c;可帮助您保护WordPress网站免受潜在威胁&#xff1a; 1.选择可靠的WordPress主机 选择一个可靠的WordPress主机提供商至…

关于文档中心的英文快捷替换方案

背景&#xff1a;文档中心需要接入国际化&#xff0c;想节省时间做统一英文方案处理&#xff1b; 文档中心是基于vuepress框架编写的&#xff1b; 1、利用百度翻译 API 的接口去做底层翻译处理&#xff0c;https://api.fanyi.baidu.com/需要在该平台上注册账号&#xff0c;个人…

unittest自动化测试框架详解

一、单元测试的定义 1. 什么是单元测试&#xff1f; ​ 单元测试是指&#xff0c;对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作&#xff0c;这里的最小可测试单元通常是指函数或者类&#xff0c;一般是开发来做的&#xff0c;按照测试阶段来…

day5 c++

#include <iostream> using namespace std; class Person { public:string name;int *age;//Person():name(name),age(new int(100)){cout<<"无参构造"<<endl;}Person(string name,int age):name(name),age(new int(100)){cout <<"P的有…

创新科技赋能旅游服务:智慧文旅引领旅游发展新篇章,智能体验助力产业转型升级

随着科技的飞速发展和人们生活水平的提高&#xff0c;旅游业正迎来前所未有的发展机遇。创新科技在旅游服务领域的广泛应用&#xff0c;不仅提升了旅游体验的品质&#xff0c;也为旅游产业的转型升级注入了新的动力。智慧文旅作为旅游业与信息技术深度融合的产物&#xff0c;正…

Linux内核广泛采用的侵入式数据结构设计

Linux内核广泛采用的侵入式数据结构设计恐怕很难应用到一般程序开发中。基本上是个高维十字链表&#xff0c;一个节点(struct)可以同时位于多个hash/list/tree中。我分享下我的经历&#xff0c;我刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;一年时间从3k薪…

如何安全高效地进行网点文件下发?

随着IT技术的飞速发展&#xff0c;以银行为代表的企业数字化技术转型带来了大量的电子化文档传输需求。文件传输数量呈几何级数增长&#xff0c;传统集中式文件传输模式在爆炸式的增长需求下&#xff0c;银行网点文件下发的效率、可靠性、安全性等方面&#xff0c;都需要重点关…

工具:如何在国内高速下载SRA

如何在国内高速下载SRA 下载公共测序数据&#xff0c;一般是通过在NCBI上搜索到study的Run SRA号&#xff0c;然后使用NCBI提供的prefetch下载数据&#xff0c;但因NCBI国内访问较为缓慢导致下载速度巨慢&#xff0c;因此推荐使用EBI提供的enaBrowserToolsAspera的方式从ENA下…

艾体宝案例 | 使用Redis和Spring Ai构建rag应用程序

随着AI技术的不断进步&#xff0c;开发者面临着如何有效利用现有工具和技术来加速开发过程的挑战。Redis与Spring AI的结合为Java开发者提供了一个强大的平台&#xff0c;以便快速构建并部署响应式AI应用。探索这一整合如何通过简化的开发流程&#xff0c;让开发者能够更专注于…

大核注意力 LKA | Visual Attention Network

论文名称&#xff1a;《Visual Attention Network》 论文地址&#xff1a;2202.09741 (arxiv.org) 尽管最初是为自然语言处理任务而设计的&#xff0c;但自注意力机制最近在各个计算机视觉领域迅速崭露头角。然而&#xff0c;图像的二维特性给计算机视觉中的自注意力应用带来了…

【Vue】组件化编程

定义 实现应用中局部功能代码和资源的集合 为什么要用组件化编程? 传统方式编写:依赖关系混乱,不好维护,且代码复用率不高 模块化编写:只关注解决js,复用js,简化js的编写与效率 组件方式编写:好维护、复用率更高、提高运行效率 在组件出现之前,我们开发基本都是用htm…

GD32E103C8T6 封装LQFP-48 GigaDevice(兆易创新) 单片机

GD32E103C8T6 是由GigaDevice&#xff08;兆易创新&#xff09;公司生产的一款基于ARM Cortex-M4内核的32位MCU&#xff08;微控制器&#xff09;。以下是GD32E103C8T6的一些主要功能和参数介绍&#xff1a; 主要功能&#xff1a; 高性能ARM Cortex-M4内核: 采用120MHz的ARM …

修复所有 bug 并不能解决所有问题

原文&#xff1a;jeffpsherman - 2024.04.08 在软件领域&#xff0c;如同在制造业&#xff0c;有些问题是由于 bug 或“特殊原因”引发的&#xff0c;而有些则是“常见原因”&#xff0c;这是由于系统设计和实现的性质所导致的。修复 bug 就是移除特殊原因&#xff0c;消除 bu…

基于SpringBoot的合家云社区物业管理平台 - 项目介绍

合家云社区物业管理平台 2.合家云需求&设计 2.1 项目概述 2.1.1 项目介绍 合家云社区物业管理平台是一个全新的 ”智慧物业解决方案“&#xff0c;是一款互联网的专业社区物业管理系统。平台通过社区资产管理、小区管理、访客管理、在线报修、意见投诉等多种功能模块&a…

指针笔试题模拟

题目一 int main() {int a[5] { 1, 2, 3, 4, 5 };int *ptr (int *)(&a 1);printf( "%d,%d", *(a 1), *(ptr - 1));return 0; } 1*&#xff08;a1&#xff09;可以等价于a[1],即第一个打印的是数组第二个下标的元素&#xff1a;2 2 而ptr拿到的是整个数组的地…