【Linux】自定义shell(讲解原理)

server/2024/12/14 15:38:44/

在这里插入图片描述

目录

  • 一、打印命令提示符和获取命令行命令字符串
    • 1.1 设计
    • 1.2 封装
  • 二、分割字符串
    • 2.1 设计
    • 2.2 封装
  • 三、执行指令
    • 3.1 设计
    • 3.2 封装
  • 四、处理內键命令的执行
  • 五、重定向(本文章所有代码)
  • 结尾

一、打印命令提示符和获取命令行命令字符串

1.1 设计

我们首先使用操作系统的bash看到了命令行提示符的组成为[用户名@主机名 当前工作目录]$,获取用户名、主机名和当前工作目录的函数在系统调用中都有,这里我们自己设计一个,这三个数据都是环境变量,我们可以通过getenv来获取到他们,获取后将他们按照操作系统bash的格式输出出来即可,通过下图我们可以发现,我们的当前工作目录与操作系统的有所区别,我们的当前工作目录是一条路径,可以通过裁剪得到与操作系统一样的效果,我这里为了区分与操作系统的区别,这里就不做裁剪了。

在这里插入图片描述

输出完命令行提示符后,就需要向bash中输入命令了,这里我们就需要一个输入函数来读取命令字符串,需要注意的是这里不能使用scanf函数,因为scanf函数不能读取空格之后的内容,可以选择gets/fgets函数来读取,当我们输入完命令字符串后需要按回车,那么获取到的字符串中也会获取到这个’\n’,所以我们还需要将这个’\n’处理掉。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#define NUM 1024const char* getUsername()
{char* username = getenv("USER");if(username)return username;elsereturn "none"; 
}const char* getHostname()
{char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "none"; 
}const char* getCwd()
{char* cwd = getenv("PWD");                                                                                                               if(cwd)                                                                                                                                  return cwd;                                                                                                                                      else                                                                                                                                     return "none";     
}int main()  
{char usercommand[NUM]; printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());// scanf("%s",usercommand); scanf读到空格就会自动停止,所以这里不使用scanffgets(usercommand,sizeof(usercommand),stdin);// 读取字符串时,会自动获取到'\n',我们需要将这个'\n'去掉usercommand[strlen(usercommand)-1] = '\0';// 用于测试输入的字符串是否符合我们的预期printf("%s",usercommand);return 0;
}

1.2 封装

这里将打印命令行提示符与获取命令行字符串的工作统一封装到getUserCommand这个函数中。

int getUserCommand(char* command , int num)    
{    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());    // scanf("%s",usercommand); scanf读到空格就会自动停止,所以这里不使用scanf    char* r = fgets(command,num,stdin);    if(r == NULL) return -1;  // 读取失败返回-1    // 读取字符串时,会自动获取到'\n',我们需要将这个'\n'去掉    command[strlen(command)-1] = '\0';    // 由于读入字符串时,必定会有一个\n所以这么不可能会越界                                                                                            // 用于测试输入的字符串是否符合我们的预期printf("%s",command);return 1;                 
}int main()
{char usercommand[NUM]; getUserCommand(usercommand,NUM);return 0;
}

二、分割字符串

2.1 设计

当我们获取到了命令字符串后,需要将字符串以空格为分隔符将字符串分割为子字符串,并将每一个子字符串的地址存入到一个指针数组中,这里给出一个字符串被分割的例子:"ls -l -a" -> "ls" "-l" "-a"。我们可以使用strtok函数来将字符串分割,使用strtok函数处理同一个字符串时,第一次需要传入字符串的地址,后面再次调用则只需要传入NULL即可。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#define NUM 1024                    
#define SIZE 64     
#define SEP " "const char* getUsername()
{char* username = getenv("USER");if(username)return username;elsereturn "none"; 
}const char* getHostname()
{char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "none"; 
}const char* getCwd()
{char* cwd = getenv("PWD");                                                                                                               if(cwd)                                                                                                                                  return cwd;                                                                                                                                      else                                                                                                                                     return "none";     
}int main()  
{                                                                                                                                                        while(1)  {         char usercommand[NUM];  char* argv[SIZE];       int x = getUserCommand(usercommand,NUM);  if(x == -1) continue;                                int argc = 0;                argv[argc++] = strtok(usercommand,SEP);  while(argv[argc++] = strtok(NULL,SEP));          // strtok函数分割失败时会返回NULL,正好是我们字符串数组结尾所需要的    // 所以分割的方式可以使用赋值作为循环判断条件进行循序    // 若是大家不习惯可以使用下面这一种分割方式    // while(argv[argc++] = strtok(NULL,SEP));  // while(1)                                    // {                                           //     argv[argc] = strtok(NULL,SEP);          //     if(argv[argc] == NULL)                  //         break;                      //     argc++;                         // }                                   for(int i = 0 ; argv[i] ; i++)  {                               printf("%d : %s \n",i,argv[i]);  }                                    }return 0;
}

2.2 封装

void SplitCommand(char* in , char* out[])      
{      int argc = 0;      out[argc++] = strtok(in,SEP);      while(out[argc++] = strtok(NULL,SEP));        // strtok函数分割失败时会返回NULL,正好是我们字符串数组结尾所需要的    // 所以分割的方式可以使用赋值作为循环判断条件进行循序    // 若是大家不习惯可以使用下面这一种分割方式    // while(1)    // {    //     out[argc] = strtok(NULL,SEP);    //     if(out[argc] == NULL)                                                                                                              //         break;    //     argc++;                                                                                                                            }                                                                                                                                                  // 用于测试字符串是否被分割                                                                                                                                             
#ifdef debug for(int i = 0 ; out[i] ; i++)                                                                                                          {                                                                                                                                      printf("%d : %s \n",i,out[i]);                                                                                                     }        
#endif                                                                                                                                     
}                                                                                                                                          int main()                                                                                                                                 
{                                                                                                                                          while(1)                                                                                                                               {                                                                                                                                      char usercommand[NUM];                                                                                                             char* argv[SIZE];    int x = getUserCommand(usercommand,NUM);    SplitCommand(usercommand,argv); }return 0;
}

三、执行指令

3.1 设计

将命令字符串分割后,就需要执行命令了,我们知道bash需要一直运行,这里添加一个循环让他一直运行,我们可以使用前面学习过的进程替换来执行命令,但是不能使用当前进程来进程替换,当前进程还需要继续运行,所以我们可以创建一个子进程来执行命令,由于我们将字符串分割为子字符串存储在了指针数组中,这里可以使用execvp函数来进行进场替换。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024
#define SIZE 64 
#define SEP " " 
#define debug 1const char* getUsername()
{char* username = getenv("USER");if(username)return username;elsereturn "none"; 
}const char* getHostname()
{char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "none"; 
}const char* getCwd()
{char* cwd = getenv("PWD");                                                                                                               if(cwd)                                                                                                                                  return cwd;                                                                                                                                      else                                                                                                                                     return "none";     
}int getUserCommand(char* command , int num)    
{    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());    // scanf("%s",usercommand); scanf读到空格就会自动停止,所以这里不使用scanf    char* r = fgets(command,num,stdin);    if(r == NULL) return -1;  // 读取失败返回-1    // 读取字符串时,会自动获取到'\n',我们需要将这个'\n'去掉    command[strlen(command)-1] = '\0';    // 由于读入字符串时,必定会有一个\n所以这么不可能会越界    // 用于测试输入的字符串是否符合我们的预期// printf("%s",command);                                                                                        return 1;                 
}void SplitCommand(char* in , char* out[])      
{      int argc = 0;      out[argc++] = strtok(in,SEP);      while(out[argc++] = strtok(NULL,SEP));        // strtok函数分割失败时会返回NULL,正好是我们字符串数组结尾所需要的    // 所以分割的方式可以使用赋值作为循环判断条件进行循序    // 若是大家不习惯可以使用下面这一种分割方式    // while(1)    // {    //     out[argc] = strtok(NULL,SEP);    //     if(out[argc] == NULL)                                                                                                              //         break;    //     argc++;                                                                                                                            }                                                                                                                                                  #ifdef debug for(int i = 0 ; out[i] ; i++)                                                                                                          {                                                                                                                                      printf("%d : %s \n",i,out[i]);                                                                                                     }        #endif                                                                                                                                    
}  int main()                                                                                                                                 
{                                                                                                                                          while(1)                                                                                                                               {                                                                                                                                                  char usercommand[NUM];                                                                                                             char* argv[SIZE];    // 打印命令提示符和获取命令行命令字符串    int x = getUserCommand(usercommand,NUM);    if(x <= 0) continue;    // 分割命令字符串SplitCommand(usercommand,argv); pid_t id = fork();if(id < 0) return -1;else if(id == 0){execvp(argv[0],argv);// 若替换失败则子进程退出exit(-1);}else {pid_t rid = wait(NULL);if(rid>0){};       }}return 0;
}

3.2 封装

int execute(char* argv[])
{pid_t id = fork();if(id < 0) return -1;else if(id == 0){    execvp(argv[0],argv);    // 若替换失败则子进程退出    exit(-1);    }    else    {    pid_t rid = wait(NULL);    if(rid>0){};     }    return 0;    
}    int main()    
{    while(1)    {    char usercommand[NUM];    char* argv[SIZE];    // 打印命令提示符和获取命令行命令字符串        int x = getUserCommand(usercommand,NUM);    if(x <= 0) continue;    // 分割命令字符串SplitCommand(usercommand,argv); // 执行命令execute(argv);}return 0;
}

四、处理內键命令的执行

当我们使用上面的代码执行命令时,发现大部分命令都可以被执行,但是例如cd、export、echo这样的内建命令却不能被执行,原因是内建命令是作用与bash的也就是这里的父进程,并且内建命令是bash的一部分,与常见命令不同,执行内建命令时不需要创建新的子进程,所以这些内建命令需要被特殊处理一下。将命令字符串分割后就判断当前命令是否为内建命令,是则直接执行内建命令,否则认定为常见命令向下继续执行。内建命令如何处理,这里就不多讲解,详细处理方法在下面的代码中有详细的注释,有兴趣的可以看一下。

#include <stdio.h>    
#include <string.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/wait.h>    #define NUM 1024    
#define SIZE 64     
#define SEP " "     
// #define debug 1    // 由于环境变量PWD需要一直存在,这里定义一个全局变量来存储
char cwd[1024];    
// 定义一个全局二维数组中,用于存储添加的环境变量
char myenv[128][1024];    
// 记录二维数组中有多少个环境变量
int cnt = 0;    
int lastcode = 0; // 记录退出码    char* getHomename()    
{                                       char* homename = getenv("HOME");    if(homename)            return homename;    else                          return (char*)"none";    
}                                const char* getUsername()    
{                                       char* username = getenv("USER");if(username)            return username;    else                                                                                                                                               return "none";    
}    const char* getHostname()
{char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "none"; 
}const char* getCwd()
{char* cwd = getenv("PWD");if(cwd)return cwd;elsereturn "none"; 
}int getUserCommand(char* command , int num)
{printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());// scanf("%s",usercommand); scanf读到空格就会自动停止,所以这里不使用scanf                                                                         char* r = fgets(command,num,stdin);if(r == NULL) return -1;  // 读取失败返回-1// 读取字符串时,会自动获取到'\n',我们需要将这个'\n'去掉command[strlen(command)-1] = '\0';// 由于读入字符串时,必定会有一个\n所以这么不可能会越界// 用于测试输入的字符串是否符合我们的预期   // printf("%s\n",command);    // 当命令行只输入回车时,则没有必要创建子进程来执行任务if(strlen(command) == 0)return 0;return 1;
}void SplitCommand(char* in , char* out[])    
{    int argc = 0;    out[argc++] = strtok(in,SEP);    while(out[argc++] = strtok(NULL,SEP));                                                                                                             // strtok函数分割失败时会返回NULL,正好是我们字符串数组结尾所需要的// 所以分割的方式可以使用赋值作为循环判断条件进行循序// 若是大家不习惯可以使用下面这一种分割方式//while(1)    //{    //    out[argc] = strtok(NULL,SEP);    //    if(out[argc] == NULL)                                                                                                              //        break;    //    argc++;                                                                                                                            //}                                                                                                                                      
#ifdef debug for(int i = 0 ; out[i] ; i++)                                                                                                          {                                                                                                                                      printf("%d : %s \n",i,out[i]);                                                                                                     }        
#endif
}int execute(char* argv[])
{pid_t id = fork();if(id < 0) return -1;else if(id == 0){execvp(argv[0],argv);// 若替换失败则子进程退出exit(-1);}else {// sleep(1);int status = 0;pid_t rid = wait(&status);if(rid>0){lastcode = WEXITSTATUS(status);}}return 0;
}void cd(char* path)
{// 将当前的工作目录改为pathchdir(path);// tmp作为一个临时空间char tmp[1024];// 将当前工作目录写入到tmp中getcwd(tmp,sizeof(tmp));// 将PWD=与tmp进行组合,形成环境变量的格式存储到全局变量cwd                                                                                                                     sprintf(cwd,"PWD=%s",tmp);// 将cwd添加到环境变量中,覆盖掉原来的环境变量PWDputenv(cwd);
}// 内键命令并执行1,非内键命令0
int dobuildin(char* argv[])
{if(strcmp(argv[0],"cd") == 0){char* path = NULL;// cd命令后面没有添加路径,默认更改当前工作目录为家目录if(argv[1] == NULL)path = getHomename();else path = argv[1];cd(path);return 1;}else if(strcmp(argv[0],"export") == 0){// 如果export后面没有内容则不做任何处理if(argv[1] == NULL)return 1;else {// 将需要添加的环境变量保存到全局的二维数组中strcpy(myenv[cnt],argv[1]);// 将这个环境变量添加到进程的环境变量表中putenv(myenv[cnt++]);return 1;}}else if(strcmp(argv[0],"echo") == 0){// 当echo后面没有内容时,默认输出回车if(argv[1] == NULL){printf("\n");return 1;}// 当echo后面的字符串的第一个字符为$时// 就是查看进程的退出码或是环境变量的                                                                                                                                              else if(*(argv[1]) == '$' && strlen(argv[1]) >= 2){// 输出退出码if(*(argv[1]+1) == '?'){printf("%d\n",lastcode);            lastcode = 0;}else  // 输出环境变量  {const char* enval = getenv(argv[1]+1);if(enval){printf("%s",enval);}else {printf("\n");}}}// 不符合上面情况,通常就是将echo后面的字符串直接输出else {printf("%s",argv[1]);}return 1;}                                                                                                                                                  else if(0){}return 0;
}int main()                                                                                                                                 
{                                                                                                                                          while(1)                                                                                                                               {                                                                                                                                      char usercommand[NUM];                                                                                                             char* argv[SIZE];    // 打印命令提示符和获取命令行命令字符串    int x = getUserCommand(usercommand,NUM);    if(x <= 0) continue;    SplitCommand(usercommand,argv); x = dobuildin(argv);if(x == 1)continue;execute(argv);}return 0;
}

五、重定向(本文章所有代码)

写完前面的代码后,发现这个代码并不能解决重定向的问题,没了解重定向的最好看一下后面一篇文章了解一下重定向是什么,在分割命令字符串之前,我们需要判断这个命令字符串是否需要进行重定向,需要重定向则需要对字符串进行处理,例如"ls -l -a > fortest.txt" -> "ls -l -a" 重定向类型 "fortest.txt",我们会得到三个部分,命令字符串、重定向类型、和文件名,在代码定义四种重定向类型,无重定向、输入重定向、输出重定向和追加重定向,默认情况下是无重定向,定义一个全局变量存储重定向类型,定义一个全局指针来指向文件名,然后我们针对不同的重定向类型使用dup2函数进行不同的处理,详细不同重定向的处理过程请查看代码中重定向的一部分。

#include <stdio.h>                                                                                                                                     
#include <string.h>                   
#include <stdlib.h>    
#include <unistd.h>    
#include <sys/types.h>                         
#include <sys/wait.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <ctype.h>    #define NUM 1024    
#define SIZE 64        
#define SEP " "     
// #define debug 1    #define NoneRedir   0           
#define InputRedir  1             
#define OutputRedir 2    
#define AppendRedir 3    int redir = NoneRedir;             
char* filename = NULL;         // 由于环境变量PWD需要一直存在,这里定义一个全局变量来存储
char cwd[1024];    
// 定义一个全局二维数组中,用于存储添加的环境变量
char myenv[128][1024];    
// 记录二维数组中有多少个环境变量
int cnt = 0;    
int lastcode = 0; // 记录退出码   char* getHomename()
{                              char* homename = getenv("HOME");    if(homename)return homename;    else    return (char*)"none";                                                                                                                          
}const char* getUsername()
{char* username = getenv("USER");if(username)return username;elsereturn "none"; 
}const char* getHostname()
{char* hostname = getenv("HOSTNAME");if(hostname)return hostname;elsereturn "none"; 
}
const char* getCwd()
{char* cwd = getenv("PWD");                                                                                                                         if(cwd)return cwd;elsereturn "none"; 
}int getUserCommand(char* command , int num)
{printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());// scanf("%s",usercommand); scanf读到空格就会自动停止,所以这里不使用scanfchar* r = fgets(command,num,stdin);if(r == NULL) return -1;  // 读取失败返回-1// 读取字符串时,会自动获取到'\n',我们需要将这个'\n'去掉command[strlen(command)-1] = '\0';// 由于读入字符串时,必定会有一个\n所以这么不可能会越界// 用于测试输入的字符串是否符合我们的预期   // printf("%s\n",command);    // 当命令行只输入回车时,则没有必要创建子进程来执行任务if(strlen(command) == 0)return 0;return 1;
}void SplitCommand(char* in , char* out[])    
{    int argc = 0;    out[argc++] = strtok(in,SEP);    while(out[argc++] = strtok(NULL,SEP));    #ifdef debug for(int i = 0 ; out[i] ; i++)                                                                                                          {                                                                                                                                      printf("%d : %s \n",i,out[i]);                                                                                                     }        
#endif
}  
int execute(char* argv[])
{pid_t id = fork();if(id < 0) return -1;                                                                                                                              else if(id == 0){int fd = 0;if(redir == InputRedir){fd = open(filename,O_RDONLY);dup2(fd,0);}else if(redir == OutputRedir){fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}else if(redir == AppendRedir){fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);dup2(fd,1);}else {// do nothing}execvp(argv[0],argv);// 若替换失败则子进程退出exit(-1);}else {// sleep(1);int status = 0;pid_t rid = wait(&status);if(rid>0){lastcode = WEXITSTATUS(status);}}return 0;
}void cd(char* path)
{chdir(path);char tmp[1024];// char* tmp = getenv("PWD");getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);
}// 内键命令并执行1,非内键命令0
int dobuildin(char* argv[])
{if(strcmp(argv[0],"cd") == 0){char* path = NULL;if(argv[1] == NULL)path = getHomename();else path = argv[1];cd(path);                                                                                                                                      return 1;}else if(strcmp(argv[0],"export") == 0){if(argv[1] == NULL)return 1;else {strcpy(myenv[cnt],argv[1]);putenv(myenv[cnt++]);return 1;}}else if(strcmp(argv[0],"echo") == 0){                                                                                                                                                  if(argv[1] == NULL){printf("\n");return 1;}else if(*(argv[1]) == '$' && strlen(argv[1]) >= 2){// 输出退出码if(*(argv[1]+1) == '?'){printf("%d\n",lastcode);lastcode = 0;}else  // 输出环境变量  {const char* enval = getenv(argv[1]+1);if(enval){printf("%s",enval);}else {printf("\n");}}}else {printf("%s",argv[1]);}return 1;}else if(0){}return 0;
}// 用于跳过空格
#define SkipSpace(pos) do{while(isspace(*pos)) pos++;}while(0)// 判断是否为重定向
void CheckRedir(char command[])
{char* start = command;                                                                                                                             char* end = command +  strlen(command);while(start < end){// 输入重定向if(*end == '<'){*end = '\0';filename = end + 1;SkipSpace(filename);redir = InputRedir;}else if(*end == '>'){// 追加重定向if(*(end-1) == '>'){*(end-1) = '\0';filename = end + 1;SkipSpace(filename);redir = AppendRedir;}// 输出重定向else {*end = '\0';filename = end + 1;                                                                                                                    SkipSpace(filename);redir = OutputRedir;}}end--;}
}int main()                                                                                                                                 
{                                                                                                                                           while(1)                                                                                                                               {                                                                                                                                                  // 初始默认为非重定向redir = NoneRedir;filename = NULL;char usercommand[NUM];                                                                                                             char* argv[SIZE];    // 打印命令提示符和获取命令行命令字符串    int x = getUserCommand(usercommand,NUM);    if(x <= 0) continue;    // 判断是否为重定向CheckRedir(usercommand);// 分割命令行  "ls -a -l" -> "ls" "-a" "-l"SplitCommand(usercommand,argv); // 判断是否为內键命令x = dobuildin(argv);if(x == 1)continue;// 执行指令execute(argv);}return 0;
}

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

在这里插入图片描述


http://www.ppmy.cn/server/150125.html

相关文章

Spring中的单例多线程与ExecutorService的结合

Spring的单例模式 在 Spring 中&#xff0c;“单例”和“多线程”是两个不同的概念&#xff0c;分别涉及对象的生命周期管理和并发执行。在一个多线程应用中&#xff0c;即使一个类是单例模式的&#xff0c;多个线程也可以并发地访问该类的实例。这就涉及到 Spring 单例多线程…

鸿蒙分享(六):文件视频图片选择+保存到相册

代码仓库&#xff1a;https://gitee.com/linguanzhong/share_harmonyos 鸿蒙api:12 引用的harmony-utils地址&#xff1a;OpenHarmony三方库中心仓 1.拷贝文件到缓存目录 import { FileUtil, ObjectUtil } from pura/harmony-utils import { common } from kit.AbilityKit i…

【组件介绍】FITKPlotWindow

一、组件简介 二维图表组件FITKPlotWindow基于Qwt开发&#xff0c;是用于直观二维数据的组件。目前的二维图表组件支持四种类型的图表&#xff0c;分别为标准直角坐标图、柱状图、频谱图和极坐标图。该组件的数据渲染效果示例如下&#xff1a; 二、主要接口 组件中针对不同的…

JavaCV之FFmpegFrameFilter视频转灰度

1、代码 package com.example.demo.ffpemg;import lombok.SneakyThrows; import org.bytedeco.javacv.*;public class FFmpegFrameFilterVideoExample {SneakyThrowspublic static void main(String[] args) {// 输入视频文件路径String inputVideoPath "f:/2222.mp4&qu…

51c大模型~合集89

我自己的原文哦~ https://blog.51cto.com/whaosoft/12815167 #OpenAI很会营销 而号称超强AI营销的灵感岛实测成效如何&#xff1f; OpenAI 是懂营销的&#xff0c;连续 12 天发布&#xff0c;每天一个新花样&#xff0c;如今刚过一半&#xff0c;热度依旧不减。 毫无疑问&…

【Spring】日志类Logger的使用

在Spring框架中&#xff0c;日志记录是一个重要的组成部分&#xff0c;通常使用不同的日志框架来处理应用程序的日志。Spring 本身并直接提供一个名为Logger 的类&#xff0c;而是通过抽象的日志 API 让开发者能够选择和使用不同的日志实现&#xff08;如 Log4j、Logback、SLF4…

力扣1049.最后一块石头的重量(01背包)之理解篇

1049. 最后一块石头的重量 II class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sumNum 0;for(int i 0;i < stones.size();i){sumNum stones[i];}int target sumNum / 2;vector<int>dp(target 1, 0);for(int i 0;i < st…

后端接受前端传递数组进行批量删除

问题描述&#xff1a;当我们需要做批量删除功能的时候&#xff0c;我们循环单次删除的接口也能进行批量删除&#xff0c;但要删除100条数据就要调用100次接口&#xff0c;或者执行100次sql&#xff0c;这样系统开销是比较大的&#xff0c;那么我们直接采用接收的数组格式数据sq…