【Linux】1w详解如何实现一个简单的shell

 目录 

实现思路

1. 交互 获取命令行

2. 子串分割 解析命令行

3. 指令的判断 内建命令

4. 普通命令的执行

 补充:vim 文本替换

整体代码

重点思考

1.getenv和putenv是什么意思

2.代码extern char **environ;

3.内建命令是什么

4.lastcode = WEXITSTATUS(status);

5.execvp(_argv[0], _argv);的调用

6._argc&_argv


实现思路

1. 交互 获取命令行

显示提示符和获取用户输入

Shell本质是一个死循环,不断地显示提示符和获取用户输入

memset 函数

memset 函数用于将一段内存区域设置为指定的值。它的原型是:

void *memset(void *s, int c, size_t n);

参数说明:

  • s:指向要填充的内存区域的指针。

  • c:要设置的值(以无符号字符形式传递,但实际存储在内存中的每个字节的值是该无符号字符的值)。

  • n:要设置的字节数。

示例用法:

char command_line[NUM];
memset(command_line, '\0', sizeof(command_line) * sizeof(char));

这里的代码表示将 command_line 数组的每个字节都设置为 \0(空字符),确保初始化整个数组。

fgets 函数

fgets 函数用于从指定的输入流读取字符串。它的原型是:

char *fgets(char *s, int n, FILE *stream);

参数说明:

  • s:指向存储读取数据的字符数组的指针。

  • n:要读取的最大字符数(包括终止字符 \0)。

  • stream:输入流,通常是 stdin 用于标准输入。

示例用法:

fgets(command_line, NUM, stdin);

这行代码表示从标准输入读取最多 NUM-1 个字符(预留一个字符用于终止字符 \0)到 command_line 数组中。

综合示例

结合起来,代码片段如下所示:

char command_line[NUM];
memset(command_line, '\0', sizeof(command_line) * sizeof(char));
fgets(command_line, NUM, stdin);

这段代码的作用是:

  1. 使用 memset 函数将 command_line 数组的所有字节都设置为 \0,即初始化数组。

  2. 使用 fgets 函数从标准输入读取最多 NUM-1 个字符并存储在 command_line 数组中。

这样处理后,command_line 数组会包含从输入读取的字符串,并且如果字符串的长度小于 NUM,数组中剩余的字节会保持为 \0

以下是实现这两个步骤的代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024char command_line[NUM]; // 用来接收命令行内容int main(void) {while (1) {/* Step1:显示提示符 */printf("[用户@主机 当前目录] # ");fflush(stdout);/* Step2:获取用户输入 */memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);  // 从键盘获取输入command_line[strlen(command_line) - 1] = '\0'; // 消除 '\n'printf("%s\n", command_line);}
}

通过上述代码,我们可以实现提示用户输入,并获取用户输入。

注意点:

执行发现有空行怎么办

 我们利用 fgets 函数从键盘上获取,标准输入 stdin,获取到 C 风格的字符串,

注意默认会添加 \0 ,我们先把获取到的结果 command_line 打印出来看看:

 因为 command_line 里有一个 \n,我们把它替换成 \0 即可:

 command_line[strlen(command_line) - 1] = '\0';  // 消除 '\0'

2. 子串分割 解析命令行

获取用户输入后,我们需要将接收到的字符串拆分为命令及其参数。

将接收到的字符串拆开

通过 strtok 函数,我们可以将一个字符串按照特定的分隔符打散,依次返回子串:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void) {while (1) {/* 显示提示符和获取用户输入 */printf("[用户@主机 当前目录] # ");fflush(stdout);memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1] = '\0';/* 将接收到的字符串拆开 */command_args[0] = strtok(command_line, SEP);int idx = 1;while ((command_args[idx++] = strtok(NULL, SEP)));/* 打印拆分结果 */for (int i = 0; i < idx - 1; i++) {printf("%d : %s\n", i, command_args[i]);}}
}

通过这段代码,我们可以将输入的命令行字符串拆分成多个子字符串,并打印出来。

strtok 函数的原型为:

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

参数说明:

  • str:要进行分割的字符串,第一次调用时传入要分割的字符串,后续调用时传入 NULL 即可。

  • delim:分隔符,用于指定分割字符串的字符。

示例用法

在代码中,使用了 strtok 函数将 command_line 字符串按照 SEP 分隔符进行切割,并将每个子字符串存储在 command_args 数组中。

command_args[0] = strtok(command_line, SEP);
int idx = 1;

这里的代码首先将 command_line 字符串按照 SEP 分隔符切割成子字符串,并将第一个子字符串的指针存储在 command_args[0] 中。然后,

利用循环逐个获取剩余的子字符串,并将它们存储在 command_args 数组中(使用 idx 来索引)

3. 指令的判断 内建命令

为了实现一些特定功能,如路径切换,我们需要在Shell中实现内建命令。

内建命令:实现路径切换

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];/* Shell 内置函数: 路径跳转 */
int ChangeDir(const char* new_path) {return chdir(new_path);
}int main(void) {while (1) {/* 显示提示符和获取用户输入 */printf("[用户@主机 当前目录] # ");fflush(stdout);memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1] = '\0';/* 将接收到的字符串拆开 */command_args[0] = strtok(command_line, SEP);int idx = 1;while ((command_args[idx++] = strtok(NULL, SEP)));/* 判断并执行内建命令 */if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) {ChangeDir(command_args[1]);continue;}/* 执行普通命令 */}
}

这段代码通过判断输入的命令是否为 cd 来执行路径切换,而无需创建子进程。

getcwd用于获取当前工作目录(当前目录)的路径。该函数的声明如下:

char *getcwd(char *buf, size_t size);

函数参数说明:

  • buf:指向存储当前工作目录路径的缓冲区
  • size:缓冲区的大小

函数返回值: 如果函数调用成功,则返回指向存储当前工作目录路径的缓冲区的指针;如果函数调用失败,则返回NULL。

通过调用getcwd函数,可以获取当前程序所在的工作目录路径。


chdir用于改变当前工作目录(当前目录)的路径。该函数的声明如下:

int chdir(const char *path);

函数参数说明:

  • path:要设置为当前工作目录的路径

函数返回值: 如果函数调用成功,则返回0;如果函数调用失败,则返回-1,并设置errno来指示错误的类型。

4. 普通命令的执行

最后,我们实现普通命令的执行,包括创建子进程并执行用户输入的命令。

创建进程 & 程序替换

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void)
{while (1) {/* Step1:显示提示符 */printf("[lvy@我的主机名 当前目录] # ");fflush(stdout);/* Step2:获取用户输入 */memset (command_line, '\0', sizeof(command_line) * sizeof(char));fgets(command_line, NUM, stdin);  /* 从键盘获取,标准输入,stdin 获取到 C 风格的字符串,默认添加 '\0' */command_line[strlen(command_line) - 1] = '\0';  // 消除 '\0'/* Step3: 将接收到的字符串拆开 - 字符串切分 */command_args[0] = strtok(command_line, SEP);int idx = 1;/* 这里的 = 是故意这么写的,因为 strtok 截取成功返回字符串起始地址截取失败,返回 NULL */while (command_args[idx++] = strtok(NULL, SEP));//我们来测试一下看看 // for (int i = 0; i < idx; i++) {//     printf("%d : %s\n", i, command_args[i]);// }// printf("%s\n", command_line);/* Step4. TODO *//* Step5. 创建进程,执行 */pid_t id = fork();if (id == 0) {/* child *//* Step6: 程序替换 */execvp (command_args[0],  // 保存的是我们要执行的程序名字command_args);exit(1);   // 只要执行到这里,子进程一定是替换失败了,直接退出。}/* Father */int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret > 0) {   // 等待成功printf("等待成功!sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);}} // end while}

通过上述代码,我们可以创建一个进程来执行用户输入的命令,并等待子进程结束。

给命令带颜色

为了增强Shell的用户体验,可以给一些常用命令添加颜色,例如 ls 命令:

/* 将接收到的字符串拆开 */
command_args[0] = strtok(command_line, SEP);
int idx = 1;
while ((command_args[idx++] = strtok(NULL, SEP)));/* 给 ls 命令添加颜色 */
if (strcmp(command_args[0], "ls") == 0) {command_args[idx++] = (char*)"--color=auto";
}

以上实现了一个简单的Shell,具备了基本的提示符显示、用户输入获取、命令解析、内建命令和普通命令的执行功能。

内建命令 环境变量

/* Shell 内置函数: 路径跳转 */
int ChangeDir(const char* new_path) {chdir(new_path);return 0;  // 调用成功
}int main(void) 
{.../* Step4. TODO 编写后面的逻辑,内建命令 */if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) {ChangeDir(command_args[1]);  // 让调用方进行路径切换continue;}...
}

保存环境变量的字符串,不能是易变的,所以 strcpy mycommand,实现与argv的分离

 补充:vim 文本替换

如何快速将mycmd换为myshell呢

通过如下操作

: %s/mycmd/myshell/g

就可以啦


细节设置的思考,在最后一部分,让我们先来看一下整体

整体代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>//创建子进程
#include <stdlib.h>//这些文件都是什么意思
#include <sys/types.h>
#include <sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];//存储切割之后的命令行
char pwd[LINE_SIZE];// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表const char *getusername()
{return getenv("USER");
}const char *gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd, sizeof(pwd));//获取当前工作目录
}void interact(char *cline, int size)//交互
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin);输入流进行输入assert(s);//断言不为空(void)s;//调用s避免报错// "abcd\n\0"cline[strlen(cline)-1] = '\0';//取消自动换行
}// ls -a -l | wc -l | head 
int splitstring(char cline[], char *_argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);//区分全局变量和形参_while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=//NULL的设置才能实现往后移的切割return i - 1;//去除NULL
}void NormalExcute(char *_argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){//让子进程执行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);//系统调用exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {//返回正确执行lastcode = WEXITSTATUS(status);}}
}
//切换路径,内建命令
//shell执行的内建命令,一个一个判断
int buildCommand(char *_argv[], int _argc)
{if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);//切换路径的函数getpwd();//获取当前路径sprintf(getenv("PWD"), "%s", pwd);return 1;}//导入环境变量exportelse if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);//为什么要进行一个拷贝 是什么意思呢!!!!putenv(myenv);//argv 是我们定义的,每次都是变化的//所以不要写我们的地址,要提字符串的地址return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);//查看退出码lastcode=0;}else if(*_argv[1] == '$'){//打印环境变量char *val = getenv(_argv[1]+1);//获取if(val) printf("%s\n", val);}else{printf("%s\n", _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0; //带有颜色之后返回,再执行普通命令
}int main()
{while(!quit){//1.许多软件启动起来就是死循环// 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txtinteract(commandline, sizeof(commandline));//对函数做了一下封装// commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"// 3. 子串分割的问题,解析命令行int argc = splitstring(commandline, argv);//如何将字串打散呢//strtok需要循环调用//while(argv[i++]=strtok(commandline,DELIM);//故意写的等号if(argc == 0) continue;// 4. 指令的判断 // debug//for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);//内键命令,本质就是一个shell内部的一个函数int n = buildCommand(argv, argc);// ls -a -l | wc -l// 4.0 分析输入的命令行字符串,获取有多少个|, 命令打散多个子命令字符串// 4.1 malloc申请空间,pipe先申请多个管道// 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始. 输出重定向, 1->指定的一个管道的写端 // 中间:输入输出重定向, 0标准输入重定向到上一个管道的读端 1标准输出重定向到下一个管道的写端// 最后一个:输入重定向,将标准输入重定向到最后一个管道的读端// 4.3 分别让不同的子进程执行不同的命令--- exec* --- exec*不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向// 5. 普通命令的执行if(!n) NormalExcute(argv);//让命令0的时候执行}return 0;
}

重点思考

1.getenv和putenv是什么意思

 getenv函数用于获取指定环境变量的值。它的函数定义如下:

char *getenv(const char *name);
  • 参数:

    • name要获取的环境变量的名称。

  • 返回值:

    • 如果指定的环境变量存在,那么返回一个指向该环境变量值的指针

    • 如果指定的环境变量不存在,则返回NULL

以下是一个使用getenv函数的示例:

#include <stdio.h>
#include <stdlib.h>int main() {char *path = getenv("PATH");if (path != NULL) {printf("PATH environment variable: %s\n", path);} else {printf("PATH environment variable not found.\n");}return 0;
}

成功实现对环境变量的调用啦

putenv函数

putenv函数用于设置环境变量。它的函数定义如下:

int putenv(char *string);
  • 参数:

    • string:形式为"name=value"的字符串,用于设置具体的环境变量及其值

  • 返回值:

    • 成功时返回0。

    • 失败时返回非零值。

以下是一个使用putenv函数的示例:

#include <stdio.h>
#include <stdlib.h>int main() {char env_str[] = "MY_ENV=hello_world";if (putenv(env_str) == 0) {printf("Environment variable set successfully.\n");} else {perror("putenv");return 1;}char *my_env = getenv("MY_ENV");if (my_env != NULL) {printf("MY_ENV: %s\n", my_env);} else {printf("MY_ENV environment variable not found.\n");}return 0;
}

注意事项

  1. 内存管理

    • getenv返回的指针指向的是环境变量的值,不能直接修改此值,否则可能导致未定义行为。

    • putenv函数参数所指向的字符串在函数调用后仍需存在,因为putenv不会复制这个字符串。因此传递给putenv的字符串应始终位于可修改的全局或堆内存中,而不是局部变量中。

  1. 线程安全性

    • getenvputenv函数在某些实现中不是线程安全的,特别是当修改同一个环境变量时。建议在多线程环境中使用setenvunsetenv函数,它们是现代C库中提供的线程安全的替代函数。

总结

getenvputenv是C语言中用于获取和设置环境变量的基本函数。通过了解并正确使用它们,可以更好地管理进程环境

2.代码extern char **environ;

 extern char **environ; 是C语言中的全局变量声明,用于访问当前进程的环境变量。为了理解这一行代码,我们需要理清以下几个关键概念:

环境变量的存储

在Unix和类Unix操作系统(如Linux)中,环境变量是一组键值对(例如PATH=/usr/bin),用于向进程传递配置信息。每个环境变量项以字符串的形式存储在一个全局变量数组中。这个数组在进程启动时由操作系统初始化,并且每个程序都可以访问和修改它。

环境变量在内存中的表示

在内存中,环境变量通常表示为一个字符串数组,每个字符串保存一个环境变量。例如:

PATH=/usr/bin
HOME=/home/user
USER=user
...

这些字符串指针存储在一个全局变量数组中,即char **environ

extern关键字

extern关键字用于声明一个全局变量,但不定义它。它告诉编译器这个变量是在别处(比如另一个源文件或由操作系统提供)定义的。因此,extern char **environ; 仅仅是一个声明,用来告知编译器这个变量在别处已经定义过,可以在当前文件中使用它。

为什么这样写?

在标准C库中,environ变量实际上在系统库中已经定义,我们只需要在我们的程序中声明一下即可使用。这种方式使我们能够访问和操作环境变量

这里是extern char **environ;的具体含义:

  1. 声明:它声明了一个外部变量environ,是一个指向字符指针的指针。

  2. 外部定义:实际的环境变量数组由操作系统初始化,并定义在某个系统库中。

  3. 全局访问:通过这个声明,我们可以在任何源文件中访问和操作环境变量。

示例

下面是一个具体的例子,展示了如何使用environ来访问并打印所有环境变量:

#include <stdio.h>// 声明外部环境变量数组
extern char **environ;int main(void) {// 指向环境变量数组的指针char **env = environ;// 遍历并打印所有环境变量while (*env) {printf("%s\n", *env);env++;}return 0;
}

就可以成功调用所有环境变量啦

总结

extern char **environ; 这一行代码的作用是声明一个指针数组,用于访问当前进程的环境变量。通过这种方式,我们可以在C程序中方便地读取和操作环境变量

3.内建命令是什么

内建命令是指直接内置在操作系统内核中的一些命令,与普通的外部命令(外部程序文件)不同。这些内建命令是直接由shell解释器(如Bash、Zsh等)所处理,而不需要通过外部文件的方式来执行。这些内建命令通常在操作系统的shell环境中被频繁使用,并且执行速度更快,因为它们不需要创建新的进程来执行。

在Unix和类Unix操作系统中,通常会有一些内建命令,比如cdechoexit等。这些命令不需要单独的可执行文件,而是直接由shell内核提供支持。当用户在shell中输入这些命令时,shell会直接处理它们,而不需要通过搜索系统路径来找到可执行文件。

值得一提的是,某些shell也允许用户通过自定义的方式添加新的内建命令,这样用户可以根据自己的需求来扩展shell的内建功能。

4.lastcode = WEXITSTATUS(status);

在C语言中,WEXITSTATUS(status) 是一个宏,用于从waitwaitpid返回的状态信息中提取子进程的退出状态。这个宏主要用于处理子进程的退出状态信息

具体来说,WEXITSTATUS(status) 用于提取子进程在终止时传递给exit_exit函数的退出状态。这个宏将状态信息进行适当的位操作,以获取子进程的退出状态值。

一般情况下,status 是由waitwaitpid函数返回的子进程状态,其中包含了有关子进程终止的信息,包括退出状态。通过使用WEXITSTATUS(status),可以将状态转换为子进程的退出状态,以便于后续处理和判断子进程的终止情况。

具体的用法示例如下:

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid;int status;int lastcode;pid = fork();if (pid < 0) {perror("fork failed");exit(1);} else if (pid == 0) {// This is the child processchar *args[] = {"ls", "-l", NULL};execvp(args[0], args);} else {// This is the parent processwaitpid(pid, &status, 0);//获得了子进程的退出码lastcode = WEXITSTATUS(status);printf("子进程的退出状态是:%d\n", lastcode);}return 0;
}

在这个例子中,WEXITSTATUS(status) 会从 status 中提取子进程的退出状态,并将其赋值给 lastcode。然后这个退出状态可以被用来进行一些处理,比如根据不同的退出状态进行不同的操作。

需要注意的是,使用 WEXITSTATUS(status) 的前提是要确保传入的 status 参数是一个子进程终止的状态,因为该宏只能提取终止进程的退出状态信息。

5.execvp(_argv[0], _argv);的调用

在代码中,execvp(_argv[0], _argv) 是一个执行函数 execvp 的调用,用于执行磁盘文件上的程序。这个函数会用指定的程序文件(由 _argv[0] 指定)来覆盖当前进程的镜像,并且用 _argv 数组中的参数替换掉原来的程序参数

相对路径执行指令

  1. 路径搜索根据 PATH 环境变量execvp 会在指定路径中查找可执行文件。
  2. 内存映射:找到可执行文件后,将其映射到当前进程地址空间。
  3. 替换镜像:用新程序的数据、堆栈、代码段替换当前进程的相应部分。
  4. 执行:新程序从其入口点开始执行,覆盖原进程的代码。

下面是对 execvp 函数调用的解释:

  • _argv[0] 表示要执行的程序文件的路径或名称。如果是一个程序的名称而没有路径,execvp 会在 $PATH 环境变量指定的路径中搜索这个程序。
  • _argv 是一个以空指针结尾的字符串数组,用于传递给新程序的命令行参数。数组的第一个元素(_argv[0])通常是被执行的程序的名称,随后的元素是程序的参数。
  • 当调用 execvp 时,操作系统会加载并执行指定的程序文件,并_argv 数组中的参数来替换当前进程的参数(因为默认会在PATH中查询,就和系统连接上了)
  • 如果 execvp 调用成功,则当前进程的镜像将被新程序替换,并且新程序开始执行。原来的程序代码和数据都会被新程序的代码和数据取代。
  • 如果 execvp 调用失败,它会返回-1,并且当前进程的状态不会改变。

在简单的C代码中,execvp 函数通常与 fork 函数一起使用,例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {char *_argv[] = {"ls", "-l", "-a", NULL}; // 要执行的命令及参数组成的数组execvp(_argv[0], _argv);  // 在新的程序中执行 ls 命令// 如果执行成功,下面的代码不会被执行perror("execvp"); // 如果 execvp 失败,打印出错误信息return 1;
}

需要注意的是,execvp 在执行成功后,原进程的代码和数据将会被新进程替换。这就意味着,如果 execvp 后面还有代码,那么这些代码将不会被执行,因为当前的程序已经不再存在。

实现shell, 一行一行的运行,先判断是否为内建命令

6._argc&_argv

  • _argv:是一个字符指针数组,用于存储命令和参数。
  • _argc:是整型变量,用于存储命令和参数的数量
  • splitstring 函数将命令行字符串分割成多个子字符串,存储在 _argv 中,并返回子字符串的数量 _argc
  • NormalExcute 函数使用 _argv 数组创建子进程并执行命令。
  • buildCommand 函数使用 _argv 和 _argc 处理内建命令。

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

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

相关文章

Java-final关键字详解

Java-final关键字详解 一、引言 二、什么是 final 关键字&#xff1f; 三、final 变量 final 局部变量 final 实例变量 final 静态变量 四、final 方法 五、final 类 六、final 关键字的实际应用 1. 定义常量 2. 防止方法被重写 3. 创建不可变类 4. 优化性能 七、…

昇思学习打卡-8-计算机视觉/FCN图像语义分割

目录 FCN介绍FCN所用的技术训练数据的可视化模型训练模型推理FCN的优点和不足优点不足 FCN介绍 FCN主要用于图像分割领域&#xff0c;是一种端到端的分割方法&#xff0c;是深度学习应用在图像语义分割的开山之作。通过进行像素级的预测直接得出与原图大小相等的label map。因…

【C++基础】初识C++(2)--引用、const、inline、nullptr

目录 一、引用 1.1 引用的概念和定义 1.2 引用的特性 1.3引用的使用 1.4 const引用 1.5 指针和引用的关系 二、inline 三、nullptr 一、引用 1.1 引用的概念和定义 引⽤不是新定义⼀个变量&#xff0c;⽽是给已存在变量取了⼀个别名&#xff0c;编译器不会为引⽤…

微软的人工智能语音生成器在测试中达到与人类同等水平

微软公司开发了一种新的神经编解码语言模型 Vall-E&#xff0c;在自然度、语音鲁棒性和说话者相似性方面都超越了以前的成果。它是同类产品中第一个在两个流行基准测试中达到人类同等水平的产品&#xff0c;而且显然非常逼真&#xff0c;以至于微软不打算向公众开放。 VALL-E …

【每日一练】python类和对象现实举例详细讲解

""" 本节课程目的&#xff1a; 1.掌握类描述现实世界实物思想 2.掌握类和对象的关系 3.理解什么事面向对象 """ #比如设计一个闹钟&#xff0c;在这里就新建一个类 class Clock:idNone #闹钟的序列号&#xff0c;也就是类的属性priceNone #闹…

【开源之美】:WinMerge Files

一、引言 强大的windows端文件比较工具&#xff0c;跟Beyond Compare相比&#xff0c;更为强大。但是这里我们推荐他的原因&#xff0c;不仅是因为作为一个使用的工具&#xff0c;主要是因为他开源&#xff0c;可以通过调试优秀的源代码&#xff0c;进一步的提升C项目设计和编…

Alternative to Receptive field in Transformers and what factors impact it

题意&#xff1a;Transformer中感受野的替代概念及其影响因素 问题背景&#xff1a; I have two transformer networks. One with 3 heads per attention and 15 layers in total and second one with 5 heads per layer and 30 layers in total. Given an arbitrary set of d…

什么是数据模型?数据模型与数据治理有什么关系?

在企业数据治理的广阔领域中&#xff0c;首要且关键的一步是明确沟通数据治理的需求。这包括对企业所持有的数据种类、数据存储位置、以及当前数据管理的具体情况有一个清晰的了解和记录。了解企业的数据资产是制定有效数据治理策略的基础。企业需要识别和盘点所有类型的数据资…

AIGC产品经理学习路径

基础篇&#xff08;课时 2 &#xff09; AIGC 行业视角 AIGC 的行业发展演进&#xff1a;传统模型/深度学习/大模型 AIGC 的产品设计演进&#xff1a;AI Embedded / AI Copilot / AI Agen AIGC 的行业产业全景图 AIGC 的产品应用全景图 AIGC 职业视角 AI 产品经理/ AIGC…

实验9 存储过程与函数的创建管理实验

一、实验目的&#xff1a; 理解存储过程和函数的概念。掌握创建存储过程和函数的方法。掌握执行存储过程和函数的方法。掌握游标的定义、使用方法。 二、实验内容 1&#xff0e;某超市的食品管理的数据库的Food表&#xff0c;Food表的定义如表所示&#xff0c; Food表的定义…

【进阶篇-Day8:JAVA中递归、异常的介绍】

目录 1、递归的介绍和使用1.1 递归的介绍1.2 案例案例一&#xff1a;案例二&#xff1a;案例三&#xff1a;案例四&#xff1a; 1.3 总结 2、异常的介绍和使用2.1 异常的介绍&#xff1a;&#xff08;1&#xff09;能够看懂异常&#xff08;2&#xff09;异常的体系接口和分类&…

苹果入局,AI手机或将实现“真智能”?

【潮汐商业评论/原创】 “AI应用智能手机不就是现在的AI手机。” 当被问到现阶段对AI手机的看法时&#xff0c;John如是说。“术业有专攻&#xff0c;那么多APP在做AI功能&#xff0c;下载用就是了&#xff0c;也用不着现在换个AI手机啊。” 对于AI手机&#xff0c;或许大多…

Windows安装和使用Doccano标注工具

简介 开源链接&#xff1a;GitHub - doccano/doccano: Open source annotation tool for machine learning practitioners. Open source annotation tool for machine learning practitioners. Doccano是一款开源的文本标注工具&#xff0c;由人工智能公司Hironsan开发并在G…

【算法】代码随想录之数组

文章目录 前言 一、二分查找法&#xff08;LeetCode--704&#xff09; 二、移除元素&#xff08;LeetCode--27&#xff09; 三、有序数组的平方&#xff08;LeetCode--977&#xff09; 四、长度最小的子数组&#xff08;LeetCode--209&#xff09; 五、螺旋矩阵II&#x…

花几千上万学习Java,真没必要!(二)

1、注释&#xff1a; java代码注释分3种&#xff1a; 单行注释&#xff1a;//注释信息 多行注释: /*注释信息*/ 文档注释:/**注释信息*/ public class TestComments {// 这是单行注释&#xff0c;用于注释单行代码或解释代码功能/* 这是多行注释&#xff0c;用于注释多行代码…

Kotlin runCatching try-catch耗时比较

Kotlin runCatching try-catch耗时比较 fun main(args: Array<String>) {val lists arrayListOf("z")val idx 10/***纳秒统计** ns&#xff08;nanosecond&#xff09;&#xff1a;纳秒。一秒的10亿分之一&#xff0c;10的-9次方秒。*   1纳秒0.000001 毫秒…

2024年,搞AI就别卷模型了

你好&#xff0c;我是三桥君 2022年11月30日&#xff0c;OpenAI发布了一款全新的对话式通用人工智能工具——ChatGPT。 该工具发布后&#xff0c;仅用5天时间就吸引了100万活跃用户&#xff0c;而在短短2个月内&#xff0c;其活跃用户数更是飙升至1亿&#xff0c;成为历史上增…

ARP协议介绍与ARP协议的攻击手法

ARP是什么&#xff1f; ARP是通过网络地址&#xff08;IP&#xff09;来定位机器MAC地址的协议&#xff0c;它通过解析网络层地址&#xff08;IP&#xff09;来找寻数据链路层地址&#xff08;MAC&#xff09;的网络传输协议。 对个定义不能理解的话&#xff0c;可以结合 TCP/I…

《恋与深空》2.0上线肉鸽模式,乙游玩家会买账吗?

乙游和肉鸽&#xff0c;看似八竿子打不着的两个赛道&#xff0c;被叠纸给融合起来了。 根据《恋与深空》官方消息&#xff0c;即将在7月15日更新的2.0交错视界版本中&#xff0c;会上线全新常驻玩法“混沌深网”&#xff0c;配置高随机性Roguelike模式&#xff0c;并搭载了管理…

理想文档发布了~一个集合了多个优秀开源项目的在线云文档

两年前我做了一个简单的在线云文档项目&#xff0c;选择了开源的思维导图、白板、流程图、幻灯片等项目&#xff0c;在它们基础上添加了云存储的功能&#xff0c;然后写了一个简单的工作台管理文件夹和文件&#xff1a; 放在了自己的个人网站上使用&#xff0c;同时写了一篇水文…