当前位置: 首页 > article >正文

Linux(10)——第二个小程序(自制shell)

目录

​编辑

一、引言与动机

📝背景

📝主要内容概括

二、全局数据

三、环境变量的初始化

✅ 代码实现

 四、构造动态提示符

✅ 打印提示符函数

✅ 提示符生成函数 

✅获取用户名函数

✅获取主机名函数

✅获取当前目录名函数

五、命令的读取与解析

✅读取用户输入函数

✅命令解析函数

六、内建命令的检测与执行

💡先来回答两个疑问:

✅检测函数

✅cd的实现

 ✅echo的实现

七、重定向的处理

✅读取函数

✅去空格函数:

✅执行函数

八、执行流程 

九、源码


一、引言与动机

📝背景

我们从之前的文章中学习了linux的相关知识,包括但不限于进程管理、文件的重定向以及环境变量,基于此我们来自制一个简化版的shell,也是对前期学习的内容的一个运用。

📝主要内容概括

我们将实现以下功能:

  • 全局数据和环境变量初始化

  • 命令提示符的构建

  • 命令行输入与解析机制

  • 内建命令(cdecho)的实现

  • 输入/输出重定向的处理

  • 外部程序的执行流程(fork + execvp + waitpid

二、全局数据

  • #define COMMAND_SIZE 1024

  • #define FORMAT "[%s@%s %s]# ",格式化字符串

  • char *g_argv[MAXARGC];int g_argc:保存切分后的命令及参数

  • char *g_env[MAX_ENVS]int g_envs:复制并维护环境变量列表

  • std::unordered_map<std::string,std::string> alias_list:(预留的)别名映射

  • 重定向相关:int redir; std::string filename;

  • 记录当前目录:char cwd[1024]; char cwdenv[1024];

  • 记录上次命令退出码:int lastcode;

 

三、环境变量的初始化

✅ 代码实现

这里主要是为了后续实现的功能提供自己环境变量,这样也可使得修改更加方便。

void InitEnv()    
{    extern char **environ;//从#include <cstdlib>中获取环境变量表    memset(g_env, 0, sizeof(g_env));//清空自建的环境变量表    g_envs = 0;    //1. 获取环境变量    for(int i = 0; environ[i]; i++)    {        g_env[i] = (char*)malloc(strlen(environ[i])+1);    strcpy(g_env[i], environ[i]);    g_envs++;    }    g_env[g_envs++] = (char*)"HAHA=for_test"; //测试导入环境变量   g_env[g_envs] = NULL;//注意末尾置空//2. 导成环境变量    for(int i = 0; g_env[i]; i++)    {    putenv(g_env[i]);    }    environ = g_env; //3.配置为全局的变量 
}

 四、构造动态提示符

我们在使用linux时常要变换所在路径,所以我们这里也实现一个动态变换的提示符:

示例效果:

[alice@myhost project]#  //为了区分这里用#

✅ 打印提示符函数

刷新缓冲区来打印。

void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}

✅ 提示符生成函数 

void MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());//snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}

这里提一下snprintf函数:

在 C/C++ 中,snprintf 是一个用于格式化字符串的函数:

int snprintf(char *str, size_t size, const char *format, ...);

参数:

str:目标字符数组,格式化的字符串将写入此处。

size:目标字符数组的最大长度(包括结尾的空字符 \0),用于限制写入字符数以防止缓冲区溢出。

format:格式化字符串,类似于 printf 的格式说明符(如 %d, %s, %f 等)。

...:可变参数列表,对应格式化字符串中的占位符。

返回值:

成功时:返回格式化后字符串的长度(不包括结尾的 \0),即使部分字符因 size 限制未写入。

失败时:返回负值(某些实现中可能不同,需检查文档)。

敲黑板:

返回的长度是完整格式化字符串的长度,即使因 size 限制只写入部分字符。

✅获取用户名函数

const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}

✅获取主机名函数

const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}

✅获取当前目录名函数

这里我们要做一下根目录的判断,如果是根目录就直接返回就行,如果不是根目录就找出/后面的字符串,没找到就报错。

std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if (dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if (pos == std::string::npos) return "BUG?";return dir.substr(pos + 1);
}

实现展示:

五、命令的读取与解析

✅读取用户输入函数

这里需要注意将\n删除。

bool GetCommandLine(char* out, int size)
{char* c = fgets(out, size, stdin);if (c == NULL) return false;out[strlen(out) - 1] = 0; // 清理\nif (strlen(out) == 0) return false;return true;
}

简单提一下fgets函数:

在 C/C++ 中,fgets 是一个用于从文件中读取字符串的函数。

char *fgets(char *str, int size, FILE *stream);

参数:

str:目标字符数组,用于存储读取的字符串(包括换行符 \n 和结尾的空字符 \0)。

size:最多读取的字符数(包括 \0),防止缓冲区溢出。

stream:文件流指针(如 stdin、文件句柄等)。

返回值:

成功:返回 str(指向读取的字符串)。

失败或文件末尾(EOF):返回 nullptr。

敲黑板:

读取到换行符 \n 或文件末尾会停止,换行符(如果存在)会包含在 str 中。 

✅命令解析函数

bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0;// 命令行分析 "ls -a -l" -> "ls" "-a" "-l"g_argv[g_argc++] = strtok(commandline, SEP);while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--;return g_argc > 0 ? true : false;
}

 这里简单提一下strtok函数:

在 C/C++ 中,strtok 是一个用于字符串分割的函数。

char *strtok(char *str, const char *delim);

参数:

str:要分割的字符串(第一次调用时传入,之后传入 nullptr 以继续处理同一字符串)。

delim:包含分隔符的字符串,每个字符都被视为一个分隔符。

返回值:

成功:返回指向下一个 token 的指针。

失败或无更多 token:返回 nullptr。

敲黑板:

strtok 会修改原字符串(在分隔符处插入 \0),因此输入字符串必须是可修改的(非 const 或字符串字面量)。 

实现展示:

六、内建命令的检测与执行

💡先来回答两个疑问:

第一个疑问:什么是内建命令

内建命令是在shell自身实现的命令,不依赖系统外部的可执行文件。例如:

cd:切换当前目录

alias:设置别名

export:设置环境变量

echo:打印信息

exit:退出 shell

我们这里会实现前三个。

第二个疑问:为什么内建命令单独执行

主要原因是他们只有在当前shell进程中执行才可以真正的影响到shell的状态。

比如:cd 改变当前目录(影响 shell),export 改变环境变量(供子进程使用)以及exit 终止当前shell,这类命令交给子进程执行就完全失去作用了。

✅检测函数

bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if (cmd == "cd"){Cd();return true;}else if (cmd == "echo"){Echo();return true;}else if (cmd == "alias"){std::string nickname = g_argv[1];alias_list.insert(k, v);}else if (cmd == "export"){// todo}return false;
}

✅cd的实现

bool Cd()
{if (g_argc == 1){std::string home = GetHome();if (home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];if (where == "-"){// Todo}else if (where == "~"){// Todo}else{chdir(where.c_str());}}return true;
}

实现展示:

 ✅echo的实现

void Echo()    
{    if (g_argc >= 2)    {    for (int i = 1; i < g_argc; ++i)    {    std::string opt = g_argv[i];    if (opt == "$?")    {    std::cout << lastcode;    }    else if (opt[0] == '$')    {    std::string env_name = opt.substr(1);    const char *env_value = getenv(env_name.c_str());    if (env_value)    std::cout << env_value;    }    else    {    std::cout << opt;    }    if (i < g_argc - 1)    std::cout << " ";    }    std::cout << std::endl;    }    
} 

七、重定向的处理

✅读取函数

void RedirCheck(char cmd[])
{redir = NONE_REDIR; // 默认初始化为只读filename.clear(); // 将之前的文件名清空int start = 0;int end = strlen(cmd) - 1;//"ls -a -l >> file.txt" > >> <while (end > start){if (cmd[end] == '<'){cmd[end++] = 0;TrimSpace(cmd, end);redir = INPUT_REDIR;filename = cmd + end;break;}else if (cmd[end] == '>'){if (cmd[end - 1] == '>'){//>>cmd[end - 1] = 0;redir = APPEND_REDIR;}else{//>redir = OUTPUT_REDIR;}cmd[end++] = 0;TrimSpace(cmd, end);filename = cmd + end;break;}else{end--;}}
}

✅去空格函数:

void TrimSpace(char cmd[], int& end)
{while (isspace(cmd[end])){end++;}
}

✅执行函数

int Execute()
{pid_t id = fork();if (id == 0){int fd = -1;// 子进程检测重定向情况if (redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if (fd < 0) exit(1);dup2(fd, 0);close(fd);}else if (redir == OUTPUT_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0) exit(2);dup2(fd, 1);close(fd);}else if (redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if (fd < 0) exit(2);dup2(fd, 1);close(fd);}else{// todo}// 进程替换,会影响重定向的结果吗?不影响//childexecvp(g_argv[0], g_argv); // 进程替换函数,执行成功后续代码不执行,失败就调用exit(1)exit(1);}int status = 0;// fatherpid_t rid = waitpid(id, &status, 0); // 阻塞等待子进程退出if (rid > 0){lastcode = WEXITSTATUS(status); // 更新进程退出码}return 0;
}

这里说明一下这几个函数:

第一个函数:int dup2(int oldfd, int newfd);

作用:在 Linux 环境下,dup2 是一个 POSIX 系统调用,用于复制文件描述符,常用于重定向文件描述符(如标准输入、输出或错误输出)。

参数:

oldfd:要复制的现有文件描述符(如打开的文件、管道、标准输入/输出等)。

newfd:目标文件描述符编号,oldfd 将被复制到此编号。

返回值:

成功:返回 newfd(目标文件描述符)。

失败:返回 -1,并设置 errno 表示错误原因(如 EBADF 表示无效文件描述符)。 

敲黑板:

这里可能你会有一个疑问,那就是为什么是oldfd复制到newfd而不是newfd复制到oldfd,这里我们一定要清楚这里的新旧指的是这个文件是否被使用或是否被打开,那么就是被使用的(oldfd)复制到未被使用的(newfd)。

第二个函数:int open(const char *pathname, int flags, mode_t mode);

作用:在 Linux 环境下,open 是一个 POSIX 系统调用,用于打开文件或创建文件,获取文件描述符以进行读写操作。

参数:

pathname:要打开或创建的文件路径(绝对或相对路径)。

flags:控制文件打开方式的标志(如只读、只写、读写等)。

常用标志:

O_RDONLY:只读。

O_WRONLY:只写。

O_RDWR:读写。

O_CREAT:如果文件不存在则创建。

O_TRUNC:如果文件存在且为写模式,清空文件内容。

O_APPEND:写入时追加到文件末尾。

多个标志可通过位或(|)组合使用。

mode:指定新创建文件的权限(如 0644),仅在 flags 包含 O_CREAT 时有效。

返回值:

成功:返回文件描述符(非负整数)。

失败:返回 -1,并设置 errno 表示错误(如 ENOENT 表示文件不存在)。 

第三个函数:int execvp(const char *file, char *const argv[]); 

作用:在 Linux 环境下,execvp 是一个 POSIX 系统调用,用于执行新程序,替换当前进程的镜像。

参数:

file:要执行的程序名(可以是命令名如 "ls",无需完整路径,execvp 会搜索 PATH 环境变量)。argv:指向参数数组的指针,包含程序名和传递给程序的参数,以 nullptr 结尾。

返回值:

成功:不返回(当前进程镜像被替换)。

失败:返回 -1,并设置 errno 表示错误(如 ENOENT 表示程序不存在)。

实现展示:

sort < unsorted.txt

ls -a -l > file.txt

ls -a -l >> file.txt

八、执行流程 

int main()
{InitEnv();while (true){PrintCommandPrompt();char commandline[COMMAND_SIZE];if (!GetCommandLine(commandline, sizeof(commandline)))continue;RedirCheck(commandline);if (!CommandParse(commandline))continue;if (CheckAndExecBuiltin())continue;Execute();}return 0;
}

 主循环:

  1. 打印提示符

  2. 读取一行用户输入(回车前)

  3. 分析是否有重定向,截断原命令并提取文件名

  4. 将命令行拆分为 g_argc/g_argv[]

  5. 检测并执行内建命令(若是则跳过后续步骤)

  6. 启动子进程执行外部命令

九、源码

#include <iostream>
#include <ctype.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;std::unordered_map<std::string, std::string> alias_list;#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
std::string filename;char cwd[1024];
char cwdenv[1024];int lastcode = 0;const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}const char* GetPwd()
{const char* pwd = getcwd(cwd, sizeof(cwd));if (pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}void InitEnv()
{extern char** environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;for (int i = 0; environ[i]; i++){g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = (char*)"HAHA=for_test"; g_env[g_envs] = NULL;for (int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}bool Cd()
{if (g_argc == 1){std::string home = GetHome();if (home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];if (where == "-"){// Todu}else if (where == "~"){// Todu}else{chdir(where.c_str());}}return true;
}void Echo()
{if (g_argc >= 2){for (int i = 1; i < g_argc; ++i){if (std::string(g_argv[i]) == "$?"){std::cout << lastcode;}else if (g_argv[i][0] == '$'){const char* env_value = getenv(g_argv[i] + 1);if (env_value)std::cout << env_value;}else{std::cout << g_argv[i];}if (i < g_argc - 1)std::cout << " ";}std::cout << std::endl;}else{std::cout << std::endl;}
}std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if (dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if (pos == std::string::npos) return "BUG?";return dir.substr(pos + 1);
}void MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}bool GetCommandLine(char* out, int size)
{char* c = fgets(out, size, stdin);if (c == NULL) return false;out[strlen(out) - 1] = 0; if (strlen(out) == 0) return false;return true;
}bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(commandline, SEP);while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--;return g_argc > 0 ? true : false;
}void PrintArgv()
{for (int i = 0; g_argv[i]; i++){printf("argv[%d]->%s\n", i, g_argv[i]);}printf("argc: %d\n", g_argc);
}bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if (cmd == "cd"){Cd();return true;}else if (cmd == "echo"){Echo();return true;}else if (cmd == "export"){}else if (cmd == "alias"){}return false;
}int Execute()
{pid_t id = fork();if (id == 0){int fd = -1;if (redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if (fd < 0) exit(1);dup2(fd, 0);close(fd);}else if (redir == OUTPUT_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0) exit(2);dup2(fd, 1);close(fd);}else if (redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if (fd < 0) exit(2);dup2(fd, 1);close(fd);}else{}execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}void TrimSpace(char cmd[], int& end)
{while (isspace(cmd[end])){end++;}
}void RedirCheck(char cmd[])
{redir = NONE_REDIR;filename.clear();int start = 0;int end = strlen(cmd) - 1;while (end > start){if (cmd[end] == '<'){cmd[end++] = 0;TrimSpace(cmd, end);redir = INPUT_REDIR;filename = cmd + end;break;}else if (cmd[end] == '>'){if (cmd[end - 1] == '>'){//>>cmd[end - 1] = 0;redir = APPEND_REDIR;}else{//>redir = OUTPUT_REDIR;}cmd[end++] = 0;TrimSpace(cmd, end);filename = cmd + end;break;}else{end--;}}
}int main()
{InitEnv();while (true){PrintCommandPrompt();char commandline[COMMAND_SIZE];if (!GetCommandLine(commandline, sizeof(commandline)))continue;RedirCheck(commandline);if (!CommandParse(commandline))continue;if (CheckAndExecBuiltin())continue;Execute();}return 0;
}

相关文章:

Linux(10)——第二个小程序(自制shell)

目录 ​编辑 一、引言与动机 &#x1f4dd;背景 &#x1f4dd;主要内容概括 二、全局数据 三、环境变量的初始化 ✅ 代码实现 四、构造动态提示符 ✅ 打印提示符函数 ✅ 提示符生成函数 ✅获取用户名函数 ✅获取主机名函数 ✅获取当前目录名函数 五、命令的读取与…...

github actions入门指南

GitHub Actions 是 GitHub 提供的持续集成和持续交付&#xff08;CI/CD&#xff09;平台&#xff0c;允许开发者自动化软件工作流程&#xff08;如构建、测试、部署&#xff09;。以下是详细介绍&#xff1a; 一、核心概念 Workflow&#xff08;工作流程&#xff09; 持续集成的…...

代码随想录算法训练营 Day59 图论Ⅸ dijkstra优化版 bellman_ford

图论 题目 47. 参加科学大会&#xff08;第六期模拟笔试&#xff09; 改进版本的 dijkstra 算法&#xff08;堆优化版本&#xff09; 朴素版本的 dijkstra 算法解法的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 时间复杂度与 n 有关系&#xff0c;与边无关系 类似于 prim 对应点多…...

HTML实战:响应式个人资料页面

我将创建一个现代化的响应式个人资料页面,展示HTML在实际应用中的强大功能。这个页面将包含多个实战元素:导航栏、个人简介、技能展示、作品集和联系表单。 设计思路 使用Flexbox和Grid布局实现响应式设计 添加CSS过渡效果增强交互体验 实现深色/浅色模式切换功能 创建悬停动…...

Mac电脑上本地安装 MySQL并配置开启自启完整流程

文章目录 一、mysql安装1.1 使用 Homebrew 安装&#xff08;推荐&#xff09;1.2 手动下载 MySQL 社区版1.3 常见问题1.4 图形化管理工具&#xff08;可选&#xff09; 二、Mac 上配置 MySQL 开机自动启动2.1 使用 launchd 系统服务&#xff08;原生支持&#xff09;2.2 通过 H…...

JavaSE:面向对象进阶之内部类(Inner Class)

JavaSE 面向对象进阶之内部类&#xff08;Inner Class&#xff09; 一、内部类的核心概念 内部类是定义在另一个类内部的类&#xff0c;它与外部类存在紧密的逻辑关联&#xff0c;主要作用&#xff1a; 封装细节&#xff1a;隐藏实现细节&#xff0c;对外提供简洁接口。访问…...

【HW系列】—安全设备介绍(开源蜜罐的安装以及使用指南)

文章目录 蜜罐1. 什么是蜜罐&#xff1f;2. 开源蜜罐搭建与使用3. HFish 开源蜜罐详解安装步骤使用指南关闭方法 总结 蜜罐 1. 什么是蜜罐&#xff1f; 蜜罐&#xff08;Honeypot&#xff09;是一种主动防御技术&#xff0c;通过模拟存在漏洞的系统或服务&#xff08;如数据库…...

汽车总线分析总结(CAN、LIN、FlexRay、MOST、车载以太网)

目录 一、汽车总线技术概述 二、主流汽车总线技术对比分析 1. CAN总线&#xff08;Controller Area Network&#xff09; 2. LIN总线&#xff08;Local Interconnect Network&#xff09; 3. FlexRay总线 4. MOST总线&#xff08;Media Oriented Systems Transport&#x…...

MyBatisPlus--条件构造器及自定义SQL详解

条件构造器 在前面学习快速入门的时候&#xff0c;练习的增删改查都是基于id去执行的&#xff0c;但是在实际开发业务中&#xff0c;增删改查的条件往往是比较复杂的&#xff0c;因此MyBatisPlus就提供了一个条件构造器来帮助构造复杂的条件。 MyBatisPlus支持各种复杂的wher…...

OVD开放词汇检测 Detic 训练COCO数据集实践

0、引言 纯视觉检测当前研究基本比较饱和&#xff0c;继续创新提升空间很小&#xff0c;除非在CNN和transformer上提出更强基础建模方式。和文本结合是当前的一大趋势&#xff0c;也是计算机视觉和自然语言处理结合的未来趋势&#xff0c;目前和文本结合的目标检测工作还是有很…...

docker、ctr、crictl命令简介与使用

概述 在使用k3s过程中&#xff0c;经常需要使用ctr和crictl两个命令&#xff0c;本文记录一下。 ctr 类似docker命令是docker-shim容器运行时的客户端工具&#xff0c;ctr是Containerd的客户端工具。一个简单的CLI接口&#xff0c;用作Containerd本身的一些调试用途&#xf…...

WEB安全--SQL注入--bypass技巧2

继之前文章的补充&#xff1a; WEB安全--SQL注入--bypass技巧_sql注入过滤空格-CSDN博客 Q1&#xff1a;发现sql注入的时间盲注时&#xff0c;如果时间盲注的函数都被过滤了&#xff0c;怎么办&#xff1f; 除了找其他函数替换、编码等方式&#xff0c;还有以下方式绕过&…...

【强化学习哲学 Day 1】Q-Learning - 在不确定中寻找确定

&#x1f3ad; 故事&#xff1a;那些选择的时刻 你还记得那些站在十字路口的时刻吗&#xff1f; 也许是刚进实验室&#xff0c;面对满墙的研究方向海报&#xff0c;不知道哪条路通向你想要的未来&#xff1b;也许是第一份工作的选择&#xff0c;大厂的螺丝钉还是小公司的多面…...

WEB3——什么是ABI

怎么获得ABI&#xff1f; 在编译完合约后&#xff0c;可以在左边下面点击复制ABI ABI&#xff08;Application Binary Interface&#xff0c;应用二进制接口&#xff09;是用来让前端或服务端 JavaScript 代码与智能合约进行交互的桥梁&#xff0c;它描述了合约的函数、事件和…...

嵌入式软件--stm32 DAY 8.5 基础复习总结

1.时钟树 在数据手册里面&#xff0c;有一张密密麻麻的图&#xff0c;正是时钟系统里的时钟树。 对于时钟&#xff0c;我们注意有两点。一个是系统时钟SYSCLK,一个是依赖外部晶振生成的RTC. RTC以外部低速晶振作为时钟源或者外部高速晶振128分频后作为时钟源&#xff0c;又或者…...

MMRL: Multi-Modal Representation Learning for Vision-Language Models(多模态表示学习)

摘要 预训练的VLMs,对于跨任务的迁移学习至关重要&#xff0c;然而&#xff0c;在few-shot数据集上微调会导致过拟合&#xff0c;降低在新任务上的性能。为解决这个问题&#xff0c;提出一种新的多模态表征学习框架&#xff08;MMRL&#xff09;,该框架引入了一个共享、可学习…...

贪心算法求解汽车加油问题

一、问题描述 一辆汽车加满油后可以行驶 n km。在前往目的地的途中&#xff0c;有多个加油站。我们的目标是设计一个有效的算法&#xff0c;确定汽车应该在哪些加油站停靠加油&#xff0c;以使得沿途的加油次数最少。 二、输入输出形式 算法的输入包括两部分&#xff1a;第一…...

JVM Full GC 频繁问题排查、优化及解决方案

引言 在Java应用程序中&#xff0c;JVM&#xff08;Java虚拟机&#xff09;通过垃圾回收机制自动管理内存&#xff0c;确保不再使用的对象能够被及时清理和释放。虽然垃圾回收在大多数情况下运行顺利&#xff0c;但当Full GC频繁发生时&#xff0c;它会严重影响应用性能&#x…...

rsync服务的搭建

目录 一、rsync介绍 rsync的安装 二、rsync的语法 三、rsync命令使用 1. 本机同步 2. 远程同步 四、rsync作为服务使用 1、尝试启动rsync程序 2、rsync的配置文件介绍 注意事项&#xff1a; 3. rsyncinotify实时同步 3.依赖服务托管xinetd&#xff08;CentOS 6中rs…...

JDK21深度解密 Day 8:Spring Boot 3与虚拟线程整合

【JDK21深度解密 Day 8】Spring Boot 3与虚拟线程整合 引言:Spring Boot 3遇上JDK21虚拟线程 在本系列的第8天,我们将聚焦于Spring Boot 3与JDK21虚拟线程的整合实践。作为全网首套完整的JDK21特性解析,我们不仅会探讨虚拟线程如何颠覆传统Java并发模型,还会通过完整的Sp…...

vscode 配置 QtCreat Cmake项目

1.vscode安装CmakeTool插件并配置QT中cmake的路径&#xff0c;不止这一处 2.cmake生成器使用Ninja&#xff08;Ninja在安装QT时需要勾选&#xff09;&#xff0c;可以解决[build] cc1plus.exe: error: too many filenames given; type ‘cc1plus.exe --help’ for usage 编译时…...

排序算法-归并排序与快速排序

归并排序与快速排序 快速排序是利用的递归思想&#xff1a;选取一个基准数&#xff0c;把小于基准数的放左边 大于的放右边直到整个序列有序 。快排分割函数 O(lognn), 空间 :没有额外开辟新的数组但是递归树调用函数会占用栈内存 O(logn) 。 归并排序&#xff1a;在递归返回的…...

HTML实现端午节主题网站:龙舟争渡,凭吊祭江诵君赋。

名人说&#xff1a;龙舟争渡&#xff0c;助威呐喊&#xff0c;凭吊祭江诵君赋。——苏轼《六幺令天中节》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、项目概览&#xff1a;传统与现代的技术碰撞1. 核心特…...

uniapp uni-id 如果是正式项目,需自行实现发送邮件的相关功能

(3) 使用云对象sendEmailCode 发送邮箱验证码&#xff0c;报错送邮箱验证码失败 Error: 已启动测试模式&#xff0c;直接使用&#xff1a;123456作为邮箱验证码即可。 如果是正式项目&#xff0c;需自行实现发送邮件的相关功能 - DCloud问答 uni-id 没有实现邮箱验证码逻辑&am…...

Spring boot 策略模式

public abstract class Node {/*** 执行** param a* param b* return*/public abstract Integer execute(int a, int b); }package my.node;import org.springframework.stereotype.Component;Component("exec") public class ExecNode extends Node {Overridepublic…...

websocket在vue中的使用步骤,以及实现聊天

一、WebSocket集成步骤 ‌连接初始化‌ 在Vue组件中创建WebSocket实例&#xff0c;建议在mounted生命周期中执行&#xff1a; data() {return {socket: null,messages: []} }, mounted() {this.socket new WebSocket(wss://your-server-endpoint); }‌事件监听配置 ‌连接成…...

C++学习-入门到精通【12】文件处理

C学习-入门到精通【12】文件处理 目录 C学习-入门到精通【12】文件处理一、文件和流二、创建顺序文件三、从顺序文件读取数据文件定位指针对之前的程序进行修改&#xff1a;贷款查询程序 四、更新顺序文件五、随机存取文件1.创建随机存取文件2.修改程序&#xff1a;贷款处理程序…...

第十一篇:MySQL 在分布式系统中的一致性保障与中间件实践

随着微服务和分布式架构的发展&#xff0c;单点数据库早已无法满足系统的横向扩展需求。本篇聚焦 MySQL 在分布式系统中的一致性保障机制&#xff0c;以及相关中间件的使用策略与实战经验。 一、一致性问题的由来 在 单机 MySQL 环境 中&#xff0c;事务具有原子性、隔离性&am…...

Java中如何枚举正则表达式捕获组的名字

在使用正则表达式在匹配文本时&#xff0c;除了可以通过表达式捕获命中的文本串外&#xff0c;还可以对捕获的文本串进行命名。尤其是在解析日志的场景中&#xff0c;经常会被用到。表达式如下&#xff1a; \<(?<pri>\d)\>(?<time>.*) (?<host>\S)…...

matlab实现图像压缩编码

一、基于DCT的JPEG压缩&#xff08;有损&#xff09; 1. 核心步骤 图像分块&#xff1a;将图像划分为88的小块。离散余弦变换&#xff08;DCT&#xff09;&#xff1a;对每个块进行DCT变换。量化&#xff1a;对DCT系数进行量化以减少高频信息。熵编码&#xff1a;使用哈夫曼或…...