详细介绍:Linux 自定义shell命令解释器

news/2025/10/4 10:21:01/文章来源:https://www.cnblogs.com/yxysuanfa/p/19125359

本章的目的是:

1.模块化实现一个具备基本命令行解释功能的自定义bash。

2.通过实现自定义bash串讲先前的重要知识,尤其是环境变量和命令行参数的理解。

首先我们对大致的变量和核心功能做一个大概介绍。

一.功能概览

1. 命令行参数相关

#define MAXARGC 128
char *g_argv[MAXARGC];  // 存储解析后的命令行参数数组
int g_argc = 0;         // 参数个数计数器
  • 功能:存储用户输入命令解析后的各个参数

  • 示例:输入 ls -l /home 会被解析为 g_argv[0]="ls"g_argv[1]="-l"g_argv[2]="/home"

2. 环境变量相关

#define MAX_ENVS 100
char *g_env[MAX_ENVS];  // 环境变量存储数组
int g_envs = 0;         // 环境变量计数器
  • 功能:存储和管理 shell 的环境变量

  • 作用:为子进程提供执行环境

3.命令行参数表和环境变量表性质详解

特性环境变量表 (Environment Variables)命令行参数表 (Command-line Arguments)
级别进程级进程级
存储形式KEY=VALUE 字符串数组字符串指针数组
终止标记以 NULL 指针结尾以 NULL 指针结尾
继承性子进程继承父进程的环境变量子进程不继承父进程的参数,需要显式传递
修改性运行时可以动态修改通常在进程启动后只读

1.环境变量实际上是进程级的概念,但可以通过继承机制实现“系统级”的效果。

// 环境变量的继承链
系统启动 → init进程 → 登录shell → 当前shell → 子进程

2.环境变量表的内存布局

// 环境变量表在内存中的结构
char *environ[] = {"PATH=/usr/bin:/bin","HOME=/home/user","USER=john","SHELL=/bin/bash",NULL  // 结束标记
};

3.本次实现bash中,对于初始化环境变量表的操作

#define MAX_ENVS 100
char *g_env[MAX_ENVS];  // 自定义环境变量表
int g_envs = 0;         // 环境变量计数器
void InitEnv()
{extern char **environ;  // 系统全局环境变量表指针// 从父进程复制环境变量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] = NULL;  // 必须的结束标记// 注册到系统for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;  // 重定向全局指针
}
  1. 深拷贝:避免直接使用父进程的环境变量指针

  2. 内存管理:需要手动管理 g_env 中字符串的内存

  3. 指针重定向:修改全局 environ 指向自定义表

对于之后要讲解的路径获取函数GetPwd,我们想实现实时更新系统环境变量中路径的值,就需要用到之前讲解的putenv进行修改,具体操作:

const char *GetPwd()
{const char *pwd = getcwd(cwd, sizeof(cwd));if(pwd != NULL){// 更新 PWD 环境变量snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);  // 动态修改环境变量表}return pwd;
}

4.命令行参数表的特性:命令行参数表一般由一个参数计数器argc和参数表argv组成。前者记录表中的参数个数(包含命令和选项),后者则是存放具体的命令行参数。

// 命令行参数表的结构示例
// 用户输入: ls -l /home
char *argv[] = {"ls",      // 程序名/命令名"-l",      // 参数1"/home",   // 参数2NULL       // 结束标记
};
int argc = 3;  // 参数个数

5.命令行参数表我们在进行命令行指令的解析和执行时会重点使用。我们使用strtok函数将命令行指令以字符串形式按空格分割,然后以循环方式依次解析指令。

#define MAXARGC 128
char *g_argv[MAXARGC];  // 命令行参数表
int g_argc = 0;         // 参数计数器
bool CommandParse(char *commandline)
{g_argc = 0;// 使用strtok解析命令行字符串g_argv[g_argc++] = strtok(commandline, " ");// 循环解析所有参数while((g_argv[g_argc] = strtok(nullptr, " ")) != nullptr){g_argc++;if(g_argc >= MAXARGC - 1) break;  // 防止溢出}g_argv[g_argc] = NULL;  // 必须的结束标记return g_argc > 0;
}

6.在实现bash过程中,重点应用了环境变量的读取+设置+传递子进程,以及命令行参数的别名展开+命令行解析+命令传递。


4.别名映射表

在解析命令前,先在别名表中以key-value形式查看是否存在指令别名

std::unordered_map alias_list;

5.辅助变量

char cwd[1024];     // 当前工作目录缓冲区
char cwdenv[1024];  // 环境变量格式的工作目录
int lastcode = 0;   // 上一个命令的退出状态码

6.核心功能模块

1. 环境信息获取函数

这些接口主要用于获取环境变量,并模仿系统bash中打印用户名-当前主机-当前工作目录的行为。

const char* GetUserName()    // 获取当前用户名
const char* GetHostName()    // 获取主机名
const char* GetPwd()         // 获取当前工作目录
const char* GetHome()        // 获取家目录路径

2. 环境初始化模块

void InitEnv()
  • 功能

    • 从父进程继承环境变量

    • 复制到自定义环境变量数组

    • 添加测试环境变量 "HAHA=for_test"

    • 设置全局环境变量表

3. 内建命令实现

我们在这里展开什么叫内建命令。先来说什么不是内建命令:之后的代码中大家可以观察到,我们在除了内建命令以外的其他命令(如ls等等)都没有采用创建新的子进程+程序替换的方式执行指令:

那么为什么所谓内建命令不采用这种方式呢?

我们可以试想:初始时我们有bash进程,此时输入了命令cd,功能大家都清楚——更改当前工作路径并回显。但是我们会发现实际上路径并不会改变,这时为什么?因为把cd这样的内建命令交给子进程做,都只会对子进程产生修改,而父进程bash的工作目录并不会改变。其他的一些内建命令也类似如此的理由设计为内建命令。

4. 命令行界面模块

提示符生成

void MakeCommandLine()    // 生成格式化的提示符
void PrintCommandPrompt() // 打印提示符
  • 格式[用户名@主机名 当前目录名]#

  • 示例[user@localhost ~]#

命令输入处理

bool GetCommandLine()  // 读取用户输入的命令行
bool CommandParse()    // 解析命令行为参数数组
void PrintArgv()       // 调试用:打印解析后的参数

5. 命令执行模块

内建命令检查
bool CheckAndExecBuiltin()
  • 功能:检查是否为内建命令,如果是则直接执行

  • 支持的内建命令:cdechoexportalias

  • 返回 true 表示已处理,无需创建子进程

外部命令执行
int Execute()
  • 流程

    1. fork() 创建子进程

    2. 子进程:execvp() 发生程序替换,执行外部命令

    3. 父进程:waitpid() 等待子进程结束

    4. 记录退出状态码到 lastcode

6. 辅助函数

std::string DirName(const char *pwd)  // 提取路径的最后一个目录名

主程序流程

int main()
  1. 初始化:调用 InitEnv() 设置环境变量

  2. 主循环

    • 打印提示符

    • 读取命令

    • 解析命令

    • 检查内建命令

    • 执行外部命令

  3. 循环继续直到用户退出

二.模块内部逻辑详细讲解

1.环境初始化模块(InitEnv)

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;  // 环境变量表必须以NULL结尾// 将自定义环境变量设置到进程环境for(int i = 0; g_env[i]; i++){putenv(g_env[i]);  // 设置到系统环境变量}environ = g_env;  // 重定向全局环境变量指针
}

逻辑详解

  • 首先获取外部全局的 environ 变量(这是一个指向系统环境变量字符串数组的指针)。

  • 将 g_env 数组清零,并重置计数器 g_envs 为0。

  • 遍历 environ 数组,将每个环境变量字符串拷贝到 g_env 中(使用动态分配内存)。

  • 添加一个测试环境变量 "HAHA=for_test",注意g_env的结尾用NULL来表示参数传递完毕。

  • 然后遍历 g_env 数组,使用 putenv 将每个环境变量设置到当前进程的环境中。

  • 最后将全局的 environ 指针指向我们的 g_env,这样后续的环境变量查找都会使用我们自定义的表

注意:这里有一个潜在问题,因为 putenv 的参数是字符串指针,而我们将动态分配的字符串指针传递给它,这些指针在程序运行期间一直有效(因为不会释放),所以没有问题。但是,如果后续要修改环境变量,需要小心处理。

2.内建命令模块

该模块是我们检测到位内建命令之后,不创建子进程而由bash直接执行的命令。

cd指令

在这里我们仅对默认的cd和具体的路径做了处理,对于特殊字符例如回到上一级目录,回到根目录并没有实现。

bool Cd()
{// cd argc = 1 表示只有"cd"命令,没有参数if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;  // 家目录为空则直接返回chdir(home.c_str());  // 切换到家目录}else  // 有参数的情况:cd {std::string where = g_argv[1];  // 获取目标目录参数// 特殊目录处理(目前未实现)if(where == "-"){// TODO: 应该切换到上一个工作目录// 需要维护一个previous_dir变量}else if(where == "~"){// TODO: 应该展开为用户家目录}else{chdir(where.c_str());  // 切换到指定目录}}return true;
}
  • 如果没有参数(g_argc==1,因为命令名是第一个参数,所以只有命令名时参数个数为1),则切换到家目录。

  • 如果有参数,检查第一个参数(g_argv[1]):

    • 如果是 -,表示切换到上一个目录(未实现)。

    • 如果是 ~,表示切换到家目录(未实现)。

    • 否则,切换到参数指定的目录。

注意chdir 系统调用成功返回0,失败返回-1,但这里没有处理错误

echo指令

我们这里对两个参数的情况(echo ***)做了处理。分别做了查看最近一个可执行程序的退出码,查看环境变量以及普通的printf逻辑,没有对重定向等操作定义和完善。

void Echo()
{if(g_argc == 2)  // 只处理单个参数的情况{std::string opt = g_argv[1];// 情况1: 输出上一条命令的退出码if(opt == "$?"){std::cout << lastcode << std::endl;lastcode = 0;  // 重置退出码}// 情况2: 输出环境变量值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 << std::endl;}// 情况3: 直接输出字符串else{std::cout << opt << std::endl;}}// 注意:当前实现不支持多个参数,如"echo hello world"
}

逻辑

  • 目前只处理一个参数的情况(即 echo 后面只有一个字符串)。

  • 如果参数是 $?,则打印上一个命令的退出码 lastcode,然后将其重置为0。

  • 如果参数以 $ 开头,则将其视为环境变量名,获取并打印该环境变量的值。

  • 否则,直接打印参数字符串。

局限:目前只能处理一个参数,例如 echo hello world 会被解析为两个参数,而该函数只处理第二个参数(即hello),忽略其余参数。

3.命令行解析模块(CommandParse)

bool CommandParse(char *commandline)
{
#define SEP " "  // 使用空格作为分隔符g_argc = 0;// 第一次调用strtok,获取第一个token(命令名)g_argv[g_argc++] = strtok(commandline, SEP);// 循环获取后续所有参数,直到返回NULLwhile((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--;  // 修正计数器(因为最后一次循环会多计数)return g_argc > 0 ? true:false;  // 至少有一个token才返回true
}

在这里,我们定义了分隔符为SEP(这里是空格)进行命令行参数的分割。

逻辑

  • 使用 strtok 函数以空格为分隔符将命令行字符串分割成多个令牌。

  • 第一个令牌通过 strtok(commandline, SEP) 获取,后续令牌通过 strtok(nullptr, SEP) 获取。

  • 每个令牌的指针被存入 g_argv 数组,并增加 g_argc 计数。

  • 当 strtok 返回 NULL 时停止,此时 g_argc 多计了一次,所以减一。

  • 最后返回是否解析到至少一个参数(即命令名)。

示例:

  • 输入:"ls -l /home/user"

  • 处理过程:

    1. strtok(commandline, " ") → "ls" → g_argv[0]

    2. strtok(nullptr, " ") → "-l" → g_argv[1]

    3. strtok(nullptr, " ") → "/home/user" → g_argv[2]

    4. strtok(nullptr, " ") → NULL → 循环结束

  • 结果:g_argc = 3g_argv = ["ls", "-l", "/home/user", NULL]

注意strtok 会修改原始字符串,将分隔符替换为 \0,因此原始命令字符串会被破坏。

4.内建命令检查模块(CheckAndExecBuiltin)

bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];  // 获取命令名// 命令分发逻辑if(cmd == "cd"){Cd();        // 内建命令,直接在当前进程执行return true; // 返回true表示已处理,无需fork}else if(cmd == "echo"){Echo();      // 内建命令return true;}else if(cmd == "export"){// TODO: 设置环境变量功能return true;}else if(cmd == "alias"){// TODO: 别名设置功能// 如:alias ll='ls -l'return true;}return false;  // 返回false表示不是内建命令,需要外部执行
}

逻辑

  • 将第一个参数(命令名)转换为字符串。

  • 与已知内建命令比较,如果匹配则调用相应的函数,并返回 true 表示已处理。

  • 如果不是内建命令,返回 false,以便后续执行外部命令。

5.外部命令执行模块 (Execute)

在经过命令行解析之后,我们需要对解析之后的指令进行执行(这里按逻辑已经确认是外部命令)。

int Execute()
{// 步骤1: 创建子进程pid_t id = fork();if(id == 0)  // 子进程分支{// 在子进程中执行外部命令execvp(g_argv[0], g_argv);  // 执行命令,替换当前进程映像// 如果execvp失败,执行到这里exit(1);  // 以错误码退出子进程}// 父进程分支int status = 0;// 等待子进程结束pid_t rid = waitpid(id, &status, 0);if(rid > 0)  // 成功等待到子进程结束{// 提取子进程的退出状态码lastcode = WEXITSTATUS(status);}return 0;
}
  • 使用 fork 创建子进程。

  • 在子进程中,调用 execvp 执行命令,如果执行失败则退出子进程(退出码1)。

  • 在父进程中,使用 waitpid 等待子进程结束,并获取退出状态。

  • 使用 WEXITSTATUS 宏从状态中提取退出码,并记录到 lastcode 中。

6.主循环(main)

为了不读取一个指令就退出bash程序,我们把bash设计为一个死循环,以便循环读取指令并执行。

int main()
{InitEnv();  // 一次性环境初始化while(true)  // 无限命令循环{// 阶段1: 显示提示符PrintCommandPrompt();  // 显示[user@host dir]#// 阶段2: 读取命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;  // 读取失败或空命令,重新循环// 阶段3: 解析命令if(!CommandParse(commandline))continue;  // 解析失败,重新循环// 阶段4: 检查内建命令if(CheckAndExecBuiltin())continue;  // 如果是内建命令且已执行,跳过外部执行// 阶段5: 执行外部命令Execute();  // 创建子进程执行}return 0;
}

逻辑

  • 初始化环境变量。

  • 进入无限循环:

    1. 打印命令提示符。

    2. 读取用户输入的命令行。

    3. 如果读取失败(如EOF)则跳过本次循环。

    4. 解析命令行,如果解析失败(空命令)则跳过。

    5. 检查是否为内建命令,如果是则执行并跳过后续步骤(外部命令执行)。

    6. 如果不是内建命令,则创建子进程执行外部命令。

7.路径显示处理(DirName)

我们发现以上的打印结果会把完整的当前路径打出,而系统的bash是只取当前的工作目录名。所以我们需要把路径进行处理。

std::string DirName(const char *pwd)
{std::string dir = pwd;// 特殊情况:根目录if(dir == "/") return "/";// 查找最后一个斜杠位置auto pos = dir.rfind("/");if(pos == std::string::npos)return "BUG?";  // 理论上不应该出现// 返回最后一个斜杠后的部分return dir.substr(pos+1);
}

逻辑

  • 如果当前目录是根目录 "/",则返回 "/"。

  • 否则,查找最后一个 "/" 的位置,返回该位置之后的子字符串(即最后一个目录名)。

  • 如果找不到 "/",返回 "BUG?"。

示例

  • /home/user → rfind("/")找到位置5 → substr(6) → "user"

  • /usr/local/bin → 找到位置10 → substr(11) → "bin"

8.提示符生成 (MakeCommandLine)

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

逻辑

  • 使用 snprintf 格式化提示符字符串,格式为 [用户名@主机名 当前目录名]#

  • 其中当前目录名只显示最后一级目录。

三.完整源码与效果展示

源码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "
// 下面是shell定义的全局数据
// 1. 命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;
// 2. 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;
// 3. 别名映射表
std::unordered_map alias_list;
// for test
char cwd[1024];
char cwdenv[1024];
// last exit code
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 = getenv("PWD");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;//本来要从配置文件来//1. 获取环境变量for(int i = 0; environ[i]; i++){// 1.1 申请空间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"; //for_testg_env[g_envs] = NULL;//2. 导成环境变量for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}
//command
bool Cd()
{// cd argc = 1if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];// cd - / cd ~if(where == "-"){// Todu}else if(where == "~"){// Todu}else{chdir(where.c_str());}}return true;
}
void Echo()
{if(g_argc == 2){// echo "hello world"// echo $?// echo $PATHstd::string opt = g_argv[1];if(opt == "$?"){std::cout << lastcode << std::endl;lastcode = 0;}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 << std::endl;}else{std::cout << opt << std::endl;}}
}
// / /a/b/c
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());//snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}
void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}
bool GetCommandLine(char *out, int size)
{// ls -a -l => "ls -a -l\n" 字符串char *c = fgets(out, size, stdin);if(c == NULL) return false;out[strlen(out)-1] = 0; // 清理\nif(strlen(out) == 0) return false;return true;
}
// 3. 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
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;
}
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"){// std::string nickname = g_argv[1];// alias_list.insert(k, v);}return false;
}
int Execute()
{pid_t id = fork();if(id == 0){//childexecvp(g_argv[0], g_argv);exit(1);}int status = 0;// fatherpid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}
int main()
{// shell 启动的时候,从系统中获取环境变量// 我们的环境变量信息应该从父shell统一来InitEnv();while(true){// 1. 输出命令行提示符PrintCommandPrompt();// 2. 获取用户输入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;// 3. 命令行分析 "ls -a -l" -> "ls" "-a" "-l"if(!CommandParse(commandline))continue;//PrintArgv();// 检测别名// 4. 检测并处理内键命令if(CheckAndExecBuiltin())continue;// 5. 执行命令Execute();}//cleanup();return 0;
}

效果如下:

wujiahao@VM-12-14-ubuntu:~/process_test$ ./myshell
[wujiahao@None process_test]# echo hello
hello
[wujiahao@None process_test]# ls
Makefile  myshell  myshell.cc  other.cc  proc.c

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

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

相关文章

bi数据报表发送周期,周报和月报获取日期时间

bi数据报表发送周期,周报和月报获取日期时间bi数据报表发送周期,周报和月报获取日期时间 1.今天是周四,获取上周四-本周三的日期,格式为yyyyMMddHHmmssString startTime = "";String endTime = "&q…

自己站网站可以做h5游戏的网站

一.压榨历史 1.单进程人工切换。纸带机。只能解决简单的数学问题。 2.单道批处理。多进程批处理。多个任务批量执行。解决手动操作时需要人工切换作业导致的系统利用率低的问题 3.多进程并行处理。把程序写在不同的内存位置来回切换。当一个作业在等待I/O处理时&#xff0c;…

美工网站设计门户网站创新的方式有

利用两个管道进行进程间双向通信在第一篇练习已经大致作出说明&#xff0c;下面将进行一个更为综合的练习 首先看题目&#xff1a; 设有二元函数f(x,y) f(x) f(y) 其中&#xff1a; f(x) f(x-1) * x (x >1) f(x)1 (x1) f(y) f(y-1) f(y-2) (y> 2) f(y)1 (y1,2) 请编…

郑州机械网站建设导航滑动整屏网站

一、为什么要进行数据归一化 定义&#xff1a;把所有数据的特征都归到 [0,1] 之间 或 均值0方差1 的过程。原则&#xff1a;样本的所有特征&#xff0c;在特征空间中&#xff0c;对样本的距离产生的影响是同级的&#xff1b;问题&#xff1a;特征数字化后&#xff0c;由于取值…

电脑科技网站模板外链工具在线

1.Java语言的特点&#xff1f; 1.一面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff1b;2.平台无关性&#xff08; Java 虚拟机实现平台无关性&#xff09;&#xff1b;(类是一种定义对象的蓝图或模板)3.支持多线程&#xff08; C 语言没有内置…

技术Leader的1-3-5沟通法则:向上管理的艺术 - 指南

技术Leader的1-3-5沟通法则:向上管理的艺术 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

【Phar反序列化】 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

永年网站建设创建全国文明城市总结

1 rtsp 接入 我们使用unity UE 等三维渲染引擎中使用c编写插件来接入rtsp 视频。同时做融合的时候&#xff0c;和背景的三维颜色要一致&#xff0c;这就要使用视频融合修正技术。包括亮度&#xff0c;对比度&#xff0c;饱和度的修正。在单纯颜色上的修正可以简单使用rgb->…

楚雄自助建站系统地税城市维护建设税网站是什么

Azure 提供了几种托管网站的方式&#xff1a;Azure 应用服务、虚拟机、Service Fabric 和云服务。 本文可帮助你了解这几种方式&#xff0c;并针对 Web 应用程序做出正确的选择。 Azure 应用服务是大多数 Web 应用的最佳选择。 部署和管理都已集成到平台&#xff0c;站点可以快…

济南php网站开发使用element做的网站

Mars 是一个并行和分布式 Python 框架&#xff0c;能轻松把单机大家耳熟能详的的 numpy、pandas、scikit-learn 等库&#xff0c;以及 Python 函数利用多核或者多机加速。这其中&#xff0c;并行和分布式 Python 函数主要利用 Mars Remote API。 启动 Mars 分布式环境可以参考…

柳市网站优化昌江县住房和城乡建设局网站

网上排出此错误方法的很多&#xff0c;但是 都不简洁&#xff0c;找不到根本原因 主要排查两点&#xff1a; 1.代码中jdbc链接的编码规则 urljdbc:mysql://localhost:3306/title?useUnicodetrue&amp;characterEncodingutf8 将characterEncoding设置为utf8 2.设置mysq…

网站备案中是什么意思新能源汽车车型及报价

给出两个正整数&#xff0c;判断他们的大小。 输入格式&#xff1a; 两个正整数。 输出格式&#xff1a; 若前者大&#xff0c;输出>&#xff1b; 若后者大&#xff0c;输出<&#xff1b; 若一样大&#xff0c;输出。 输入&#xff1a; 1412894619244619891 23762842…

完整教程:AI时代如何高效学习Python:从零基础到项目实战de封神之路(2025升级版)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

cannot resolve method add in T 及 T 泛型类型生成Excel文件,区别是数据Model不同

cannot resolve method add in T 及 T 泛型类型生成Excel文件,区别是数据Model不同cannot resolve method add in T 及 T 泛型类型生成Excel文件,区别是数据Model不同 1.通过继承父类来解决public void largeDataCre…

网站开发专员招聘企业网站需要多大带宽

1.继承派生的区别 继承&#xff1a;子继父业&#xff0c;就是子类完全继承父类的全部内容 派生&#xff1a;子类在父类的基础上发展 2.继承方式 1.public继承为原样继承 2.protected继承会把public继承改为protect继承 3.private继承会把public&#xff0c;protected继承改为pr…

备案中网站名称网站建设创客

*本文系SDNLAB编译自瞻博网络技术专家兼高级工程总监Sharada Yeluri领英 在路由器和交换机中&#xff0c;缓冲区至关重要&#xff0c;可以防止网络拥塞期间的数据丢失。缓冲区到底要多大&#xff1f;这个问题在学术界和工业界一直备受争议。本文探讨了高端路由器中数据包缓冲的…

MySQL慢查询深度解析:从诊断到优化的完整指南 - 实践

MySQL慢查询深度解析:从诊断到优化的完整指南 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…

手写MyBatis第88弹:从XML配置到可执行SQL的完整旅程 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

测试环境elasticSearch数据泄露排查

测试环境elasticSearch数据泄露排查测试环境elasticSearch数据泄露排查 es中同一条会员 或者车辆或者交易 主要刷新一次 就会有一条被删除的文档所以几万测试数据 检查那边看到了几百万被删除是记录 以为几百万数据泄露…

深入解析:Spring boot中 限制 Mybatis SQL日志的大字段输出

深入解析:Spring boot中 限制 Mybatis SQL日志的大字段输出pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Conso…