『 Linux 』进程替换( Process replacement ) 及 简单Shell的实现(万字)

文章目录

    • 🦄 进程替换
      • 🦩 execl()函数
      • 🦩 execlp()函数
      • 🦩 execle()函数
      • 🦩 execv()函数
      • 🦩 execvp()函数
      • 🦩 execvpe()函数
      • 🦩 execve()函数
    • 🦄 简单Shell命令行解释器的实现
      • 🦩 大致框架与命令行提示符
      • 🦩 获取用户输入信息
      • 🦩 将缓冲区内的字符串进行分块
      • 🦩 分析并执行指令
      • 🦩 对cd命令进行处理
      • 🦩 简单Shell实现代码演示(供参考)


🦄 进程替换

请添加图片描述

在『 Linux 』Process Control进程控制(万字)-CSDN博客 中提到了些进程控制中的概念,但是在这篇文章当中对于进程替换的概念以及用法并没有完全;

在本篇文章中将对上篇文章中的进程替换的各个接口进行补充;

进程替换,按照字面意义上即为一个进程在运行过程当中替换为另一个进程;

在之前的博客当中可能提到过, 当一个程序被加载进内存当中时对应的内存会新生成一个对应的进程;

而在进程替换当中可以完美的对上面的理论进行一个反驳,即并不是每个程序加载到内存当中都会新生成一个对应的进程;

以该图为例,该图中一个正在执行的进程经过了进程替换,将磁盘中的程序的代码和数据加载到了被替换的进程对应的PCB结构体当中;

当然在物理内存当中需要对应的为该新载入的进程的数据代码开辟一块新的内存空间;

但实际上在进程地址空间来看的话也仅仅只是将对应的映射关系进行修改;

当新的程序代码数据被加载进物理内存时,随着进程逐渐发生替换,对应的原有的代码和数据也将渐渐被释放;

因为只是仅仅的发生映射关系的转换,故对应的PIDmm_struct内的数据都不会作修改;

在上篇文章中简单的使用了execl()进程替换函数进行了进程替换的演示;

#include <unistd.h>

#include <iostream>

using namespace std;

int main() {
  cout << "hello world1" << endl;
  cout << "hello world1" << endl;
  cout << "hello world1" << endl;
  printf("当前程序为myproc 且PID为:%d \n", getpid());
  execl("./test_/mytest", "mytest", NULL);
  cout << "hello world2" << endl;
  cout << "hello world2" << endl;
  cout << "hello world2" << endl;
  return 0;
}

这段程序中替换的程序的代码如下:

#include <iostream>
#include<unistd.h>

using namespace std;

int main() {
  printf("当前程序为mytest 且PID为:%d\n", getpid());
  return 0;
 }

且该程序运行后的最终结果为:

$ ./myproc 
hello world1
hello world1
hello world1
当前程序为myproc 且PID为:14115 
当前程序为mytest 且PID为:14115

该段程序可以清楚证明对应的发生进程替换时对应的PID不会发生变化;

在上篇博客当中只介绍了一个exec家族的函数;

但是这样的函数一共有7个;

虽然7个接口函数实际的功能结果相同,但是对应的在传参上中有所不同;


🦩 execl()函数

请添加图片描述

  • execl()的函数原型:

    int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
    

    该函数的功能为执行指定的路径下的可执行文件,并用传递给它的参数替换原有的程序;

    这意味着原始的程序将被新程序替换,原始程序的代码将不再执行,而被path参数所指定的可执行文件加载并开始执行;

  • 参数:

    path参数是可执行文件的路径;

    arg0表示要传递给程序的第一个参数,通常是新程序的名称且他为一个字符串;

    ...可选的参数列表,这些参数将作为进程替换后新程序的命令行参数传递,且参数列表必须以空指针(char*)NULL结尾;

  • 示例:

    #include <unistd.h>
    #include <stdio.h>
    
    int main() {
        printf("This is the original program\n");
        
        execl("/bin/ls", "ls", "-l", NULL);
        
        printf("This is the original program\n");
        perror("execl");
        exit(-1);
    }
    

    在该段代码中的原始程序若是未被新进程所替换时将会打印出两次This is the original program\n;

    而运行该段代码的结果为:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76536 Mar 14 13:51 mytest
    -rw-rw-r-- 1 _USER _USER   357 Mar 14 13:51 test.cpp
    

    当进程发生替换了之后,原有进程的代码数据将被替换,故对应的代码不会执行;

    在使用进程替换时需要使用errno指定出对应的问题;


🦩 execlp()函数

请添加图片描述

  • execlp()函数原型:

    int execlp(const char *file, const char *arg0, ... /* (char *) NULL */);
    

    execlp()函数的工作方式与execl()类似,但不同之处在于它不需要指定文件的完整路径;

    它会在系统的PATH环境变量中搜索file参数所指定的可执行文件,找到后执行它;

  • 参数:

    file为要执行的可执行文件,可以是一个简单的文件名而不需要完整的路径名;

    arg0为要传递给新程序的第一个参数,一般来说这个参数为需要执行新程序的名称;

    ...为可选参数,这些参数将作为新程序的命令行参数并进行传递;

  • 示例:

    int main() {
      printf("This is the original program\n");
      execlp("ls", "ls", "-l", NULL);
      printf("This is the original program\n");
      perror("execlp");
      exit(1);
    }
    

    在该段程序当中原始程序中将输出一条消息后调用execlp()函数执行ls命令;

    若是进程替换成功将会执行ls -l的命令,若是未替换成功将会退出并返回1同时打印出第二句This is the original program;

    该程序运行的结果如下:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76528 Mar 14 14:07 mytest
    -rw-rw-r-- 1 _USER _USER   582 Mar 14 14:07 test.cpp
    

🦩 execle()函数

请添加图片描述

  • execle()函数原型

    int execle(const char *path, const char *arg0, ..., char *const envp[]);
    

    该函数与execl()函数和execlp()函数类似,execle()函数会将当前的进程替换为指定路径下的可执行文件;

    但是该函数与前两者不同的是,execle()函数允许你传递一个自定义的环境变量数组给新程序;

    一般这个环境变量数组通过envp参数进行传递;

  • 参数:

    path参数表示要执行的可执行文件路径的字符串;

    arg0表示要传递给新程序的第一个参数,一般情况下该参数为新程序的名字;

    ...表示可选参数列表,这些参数将作为新程序的命令行参数并进行传递,且参数列表必须以空指针(char*)NULL进行结尾;

    envp[]指向一个以NULL结束的环境变量数组,其中每个元素都是形如NAME=VALUE的字符串;

  • 示例:

    int main() {
      printf("This is the original program\n");
    
      char *env[] = {(char *)"MYVAR=Hello", NULL};
    
      execle("/usr/bin/env", "env", NULL, env);
    
      printf("This is the original program\n");
      perror("execle");
      exit(1);
    }
    

    在该示例当中,原始程序将输出一条消息后调用execle()函数来执行/usr/bin/env的命令;

    由于第一个参数指定了完整的路径,故execle()函数将会直接执行该命令;

    同时通过env参数传递了一个自定义的环境变量数组给新的程序;

    最终的执行结果为:

    $ ./mytest 
    This is the original program
    MYVAR=Hello
    

🦩 execv()函数

请添加图片描述

  • execv()函数原型

    int execv(const char *path, char *const argv[]);
    

    该函数的工作方式与execlexeclp函数类似,与之不同的是该函数使用了不同的参数传递方式;

    execv()函数将参数作为一个字符串数组传给新的程序而不是通过函数参数列表进行传递使得该函数在传参时能够更加灵活;

  • 参数:

    path参数表示要执行的可执行文件的路径的字符串;

    argv[]指向一个以NULL结尾的字符串数组,每个元素都表示新程序的命令行参数;argv[0]通常是新程序的名称,后序的参数依次排列且最后一个元素必须是NULL;

  • 示例:

    int main() {
      printf("This is the original program\n");
    
      char *args[] = {(char *)"ls", (char *)"-l", NULL};
    
      execv("/bin/ls", args);
    
      printf("This is the original program\n");
    
      perror("execv");
      return 1;
    }
    

    在该示例当中,原始程序将输出一条消息并用execv()函数执行/bin/ls的命令且带-l参数;

    args数组包含了要传递给ls命令的参数列表;

    execl()execlp()函数不同,execv()函数将参数作为一个字符串传递给新的程序;

    最终的执行结果为:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76568 Mar 14 14:41 mytest
    -rw-rw-r-- 1 _USER _USER  1142 Mar 14 14:41 test.cpp
    

🦩 execvp()函数

请添加图片描述

  • execvp()函数原型:

    int execvp(const char *file, char *const argv[]);
    

    该函数的工作方式与execv()函数类似,但该函数不要求指定可执行文件的完整路径;

    该函数将在系统的PATH环境变量中搜索file参数指定的可执行文件并执行;

  • 参数:

    file表示要执行的可执行文件的名称,该参数可以是一个简单的文件名而不需要包含完整的路径;

    argv[]指向一个以NULL结束的字符串数组,每个元素表示新程序的命令行参数,argv[0]通常表示新程序的名字,后面的参数依次排列且最后一个元素必须是NULL;

  • 示例:

    int main() {
      printf("This is the original program\n");
    
      char *args[] = {(char *)"ls", (char *)"-l", NULL};
    
      execvp("ls", args);
    
      printf("This is the original program\n");
    
      perror("execvp");
      return 1;
    }
    

    在该示例中原始程序将输出一条消息并调用execvp()函数执行ls命令并带有-l参数;

    由于ls并未指出完整的路径故execvp()将在PATH中搜索ls可执行文件并执行找到的第一个匹配项;

    execv()函数类似,若是execvp()函数调用失败 (例如指定的可执行文件不存在) 将返回-1并设置errno指示错误类型;

    最终的执行结果为:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76560 Mar 14 14:53 mytest
    -rw-rw-r-- 1 _USER _USER  1416 Mar 14 14:53 test.cpp
    

🦩 execvpe()函数

请添加图片描述

  • execvpe()函数原型:

    int execvpe(const char *file, char *const argv[], char *const envp[]);
    

    execvpe()函数的工作方式与execvp()函数类似,与之不同的是该函数额外提供了一个参数允许指定自定义的环境变量;

  • 参数:

    file表示要执行的可执行文件的名称,它可以是一个简单的文件名而不需要包含完整的路径;

    argv[]指向一个以NULL结束的字符串数组,每个元素表示新程序的命令行参数且argv[0]通常表示新程序的名称,后序的参数依次排列;数组的最后一个元素必须是NULL指针;

    envp[]指向一个以NULL结束的环境变量数组,其中每个元素都是形如NAME=VALUE的字符串;

  • 示例:

    int main() {
      printf("This is the original program\n");
    
      char *args[] = {(char*)"ls", (char*)"-l", NULL};
    
      char *env[] = {(char *)"MYVAR=Hello", NULL};
    
      execvpe("ls", args, env);
      printf("This is the original program\n");
    
      perror("execvpe");
      return 1;
    }
    

    在该示例中,原始程序将输出一条消息并调用execvp()函数执行ls命令并带有-l参数;

    同时传递了一个自定义的环境变量数组给新的程序;

    最终的执行结果:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76600 Mar 14 15:24 mytest
    -rw-rw-r-- 1 _USER _USER  1696 Mar 14 15:24 test.cpp
    

🦩 execve()函数

请添加图片描述

  • execve()函数原型:

    int execve(const char *filename, char *const argv[], char *const envp[]);
    

    execve()函数的工作方式是将当前进程的映像(image)替换为指定路径下的可执行文件;

    与其他exec系列函数不同的是该函数为一个系统调用,将直接与操作系统内核交互并执行新的程序,而其他exec函数通常是标准库提供的函数,最终将调用execve()系统调用来执行新的程序;

    execve()函数直接与操作系统内核进行通信,提供了更直接更底层的接口,可直接控制程序的执行;

    其他exec函数则是再标准库中实现的高层接口,或许会做出一些额外的处理(路径搜索,参数组织等)后再调用execve();

    由于execve()函数直接暴露了系统调用的细节故提供了更大的灵活性和控制性;

    用户程序可以直接操作参数和环境变量使得可以自行管理文件描述符等从而实现更复杂的执行需求;

  • 参数:

    filename指向要执行的可执行文件路径的字符串;

    argv[]参数指向一个以NULL结束的字符串数组,每个元素表示新程序的命令行参数;argv[0]通常为新程序的名称,后序的参数依次排列,数组的最后一个元素必须是NULL指针;

    envp[]指向一个以NULL结束的环境变量数组,其中每个元素都是形如NAME=VALUE的字符串;

  • 示例:

    int main() {
      printf("This is the original program\n");
    
      char *args[] = {(char *)"ls", (char *)"-l", NULL};
    
      char *env[] = {(char*)"MYVAR=Hello", NULL};
    
      execve("/bin/ls", args, env);
    
      printf("This is the original program\n");
      perror("execve");
      return 1;
    }
    

    在这个示例中,原始程序将输出一条消息后调用execve函数来执行/bin/ls命令,并带有-l参数;

    并且传递了一个自定义的环境变量数组给新的程序;

    最终运行结果:

    $ ./mytest 
    This is the original program
    total 84
    -rw-rw-r-- 1 _USER _USER    84 Mar 14 13:49 makefile
    -rwxrwxr-x 1 _USER _USER 76600 Mar 14 16:05 mytest
    -rw-rw-r-- 1 _USER _USER  2073 Mar 14 16:05 test.cpp
    

🦄 简单Shell命令行解释器的实现

请添加图片描述

命令行解释器(Command Line Interpreter) 是一种与操作系统进行交互的软件程序;

其允许用户命令行界面(CLI)输入命令并根据命令控制OS与其对应的应用程序;

命令行解释器通常称为Shell;

它充当了用户和操作系统之间的中间层并提供了一种文本方式来执行各项操作;

命令行解释器的主要功能包括:

  • 解释和执行命令
  • 管理文件系统
  • 进程管理
  • 环境配置
  • 用户交互
  • 脚本执行

🦩 大致框架与命令行提示符

请添加图片描述

在一般的情况下在Shell当中将会显示对应的命令行提示符使用户方便进行输入;

一般的情况下命令行提示符只需要打印即可;

同时Shell必然是一个常驻进程,即一般情况下进程不退出,需要使用循环进行控制;

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
using namespace std;

int main() {
  //   cout << "hello world" << endl;
  /*
     命令行解释器是一个常驻进程,一般情况常驻进程不退出
  */
  while (true) {
    // 1.打印出提示信息
    printf("[SilverChariot@local MyShell]# ");
    fflush(stdout);
  }
}

使用fflush()刷新输出缓冲区防止在循环当中打印换行;


🦩 获取用户输入信息

请添加图片描述

当打印完提示信息时需要获取用户的输入信息;

声名一个数组充当字符串缓冲区并使用fgets()函数获取对应的用户输入信息(需要提前使用memset()对空间进行初始化);

#define NUM 1034  // 保存完整的命令行字符串的大小
char cmd_line[NUM];  // 缓冲区   - 用于保存完整的命令行字符串

/*
	......
*/

memset(cmd_line, '\0', sizeof cmd_line);  // 将缓冲区进行初始化
    // 2.获取用户输入信息(指令 及 选项 )

    if (fgets(cmd_line, sizeof cmd_line, stdin) == nullptr)
      continue;  // 如果从输入流中获取数据失败则进行下一次循环 该次循环不算
    cmd_line[strlen(cmd_line) - 1] = '\0';  
				// 由于输入换行后该缓冲区将会存储一个换行并且进行打印
               // 故需要将改缓冲区的换行修正为'\0'

    // cout << "echo :" << cmd_line << endl;// --debug 用于打印是否正确

当用户输入完输入信息时为了能够让计算机识别结束输入流一般会输入一个\n;

为了防止\n不被打印需要在对应的cmd_line[strlen(cmd_line) - 1]处置为\0;

由于是一个循环,若是从输入流中获取数据失败则进行下一次循环continue;

在该处可以将用户的输入信息进行打印从而判断该处逻辑是否出现对应问题;


🦩 将缓冲区内的字符串进行分块

请添加图片描述

由于需要在后期对用户的输入信息进行分析故需要先将用户的输入信息进行分块;

声名一个字符串数组char* []用户保存分块后的命令行字符串子串;

C++中可以使用substr()对字符串进行分块;

C语言当中则可以使用strtok()对字符串进行分块;

char *g_argv[SIZE];  // 用于保存打散后的命令行字符串子串

#define SIZE 32   // 保存打散后命令字符串子串的数组大小
#define SEP " "   // 作为分隔符// 3.将缓冲区内的字符串进行分块 即命令行字符串解析工作

/*
	......
*/

    g_argv[0] =
        strtok(cmd_line, SEP);  // 第一次调用strtok函数的时候需要传入原始字符串
    int index = 1;
    while (g_argv[index++] = strtok(nullptr, SEP)) {
      ;  // 第二次调用时若是需要分割的是原始字符串则传入空null
    }

根据strtok()函数对字符串进行分块;

strtok()函数参考【std::string::substr】在此不作赘述;


🦩 分析并执行指令

请添加图片描述

当数据拆分完毕后需要对指令进行分析与执行;

一般情况下由子进程对指令进行执行,父进程则负责分析以及等待子进程退出;

使用fork()创建子进程并使用对应的进程替换接口使子进程能够运行对应的命令;

此处使用的进程替换接口为execvp()函数,具体参考上文的对于execvp()函数的解释;

// 5.创建进程 子进程执行指令 父进程等待分析指令
   pid_t id = fork();
   if(id == 0){
    //子进程
    cout << "子进程进行执行" << endl;
    execvp(g_argv[0], g_argv);
    exit(1);
   } else if (id > 0) {
     // 父进程
     int status = 0;
     waitpid(-1, &status,0);
     if(WIFEXITED(status)){
       cout << "WEXITSTATUS:" << WEXITSTATUS(status) << endl;
     }
   } else {
     exit(-1);
   }

🦩 对cd命令进行处理

请添加图片描述

当到这一步时大部分的指令都能够执行;

但是对应的cd命令并不能在该处编写的Shell中起作用;

原因是需要发生目录变化时一般为父进程发生变化,子进程的目录变化并不影响父进程;

故需要在fork()创建子进程前使用strcmp()cd进行特殊处理;

若是遇到cd命令时则可以使用chdir()接口函数进行路径的变化;

// 4.用于cd命令 需要在父进程阶段进行
    if(strcmp("cd",g_argv[0]) == 0){
      // if (g_argv[1] != nullptr && chdir(g_argv[1]) != 0) {
      //   cerr << "chdir failed: " << strerror(errno) << endl;
      // }

      if (g_argv[1] != nullptr) chdir(g_argv[1]);
      continue;
    }

🦩 简单Shell实现代码演示(供参考)

请添加图片描述

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
using namespace std;

#define NUM 1034  // 保存完整的命令行字符串的大小
#define SIZE 32   // 保存打散后命令字符串子串的数组大小
#define SEP " "   // 作为分隔符

char *g_argv[SIZE];  // 用于保存打散后的命令行字符串子串
char cmd_line[NUM];  // 缓冲区   - 用于保存完整的命令行字符串

// shell 运行原理 : 子进程执行命令,父进程等待以及解析命令

int main() {
  //   cout << "hello world" << endl;
  /*
     命令行解释器是一个常驻进程,一般情况常驻进程不退出
  */
  while (true) {
    // 1.打印出提示信息
    printf("[SilverChariot@local MyShell]# ");
    fflush(stdout);
    memset(cmd_line, '\0', sizeof cmd_line);  // 将缓冲区进行初始化
    // 2.获取用户输入信息(指令 及 选项 )

    if (fgets(cmd_line, sizeof cmd_line, stdin) == nullptr)
      continue;  // 如果从输入流中获取数据失败则进行下一次循环 该次循环不算
    cmd_line[strlen(cmd_line) - 1] =
        '\0';  // 由于输入换行后该缓冲区将会存储一个换行并且进行打印
               // 故需要将改缓冲区的换行修正为'\0'

    // cout << "echo :" << cmd_line << endl;// --debug 用于打印是否正确

    // 3.将缓冲区内的字符串进行分块 即命令行字符串解析工作
    g_argv[0] =
        strtok(cmd_line, SEP);  // 第一次调用strtok函数的时候需要传入原始字符串
    int index = 1;
    while (g_argv[index++] = strtok(nullptr, SEP)) {
      ;  // 第二次调用时若是需要分割的是原始字符串则传入空null
    }

    /*
      //用于debug 
      for (index = 0; g_argv[index]; ++index) {
        printf("g_argv[%d] : %s\n", index, g_argv[index]);
      } 
    */

   // 4.用于cd命令 需要在父进程阶段进行
    if(strcmp("cd",g_argv[0]) == 0){
      // if (g_argv[1] != nullptr && chdir(g_argv[1]) != 0) {
      //   cerr << "chdir failed: " << strerror(errno) << endl;
      // }

      if (g_argv[1] != nullptr) chdir(g_argv[1]);
      continue;
    }

   // 5.创建进程 子进程执行指令 父进程等待分析指令
   pid_t id = fork();
   if(id == 0){
    //子进程
    cout << "子进程进行执行" << endl;
    execvp(g_argv[0], g_argv);
    exit(1);
   } else if (id > 0) {
     // 父进程
     int status = 0;
     waitpid(-1, &status,0);
     if(WIFEXITED(status)){
       cout << "WEXITSTATUS:" << WEXITSTATUS(status) << endl;
     }
   } else {
     exit(-1);
   }
  }

  return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Centos8安装Docker,使用阿里云源

一、前期准备 1.关闭防火墙&#xff0c;SELINUX systemctl stop firewalld.service systemctl disable firewalld.service setenforce 0 sed -i "s/SELINUXenforcing/SELINUXdisabled/g" /etc/selinux/config查看状态 systemctl status firewalld systemctl status…

汇编语言(Assemble Language)学习笔记(更新中)

零.学习介绍和使用工具 【1】我们使用的教材是机械工业出版社的《32位汇编语言程序设计第二版》。 指导老师是福州大学的倪一涛老师。 这门课程教授的是Intel 80*86系列处理器的32位汇编。我们现在的处理器都兼容这个处理器。 这篇博客只是大二下汇编语言学习的总结&#xff…

【C++设计模式】策略模式

文章目录 前言一、策略模式是什么&#xff1f;二、策略模式的实现原理三、UML图四、代码实现总结 前言 策略模式是一种行为设计模式&#xff0c;它允许在运行时选择算法的行为。通过将每个算法封装到具有共同接口的独立类中&#xff0c;客户端可以在不改变自身代码的情况下选择…

css3 实现html样式蛇形布局

文章目录 1. 实现效果2. 实现代码 1. 实现效果 2. 实现代码 <template><div class"body"><div class"title">CSS3实现蛇形布局</div><div class"list"><div class"item" v-for"(item, index) …

【C#】WPF 将string数据导出txt

示例 代码 string allInfo "123"; SaveFileDialog saveFileDialog new SaveFileDialog(); saveFileDialog.Filter "*.txt|*.txt|所有文件(*.*)|*.*"; if (!(bool)saveFileDialog.ShowDialog()) {return; } string fileName saveFileDialog.FileName; …

arcgis 计算某点到其他城市的距离,含要素转点(以北京市到各个地级市的距离为例)

导入地级市的地图导入要计算距离的地带你计算距离&#xff1a;点距离或者近邻分析 以北京市到各个地级市的距离为例 到入地级市&#xff0c;并复制一个同款地级市&#xff0c;导入一个有北京市的Excel 将一个地级市进行要素转点&#xff1a;data management tools——要素——…

【C语言】—— 指针二 : 初识指针(下)

【C语言】——函数栈帧 一、 c o n s t const const 修饰指针1.1、 c o n s t const const 修饰变量1.2、 c o n s t const const 修饰指针 二、野指针2.1野指针的成因&#xff08;1&#xff09;指针未初始化&#xff08;2&#xff09;指针越界访问&#xff08;3&#xff09;指…

4.9.CVAT——用长方体进行注释

文章目录 1.创建长方体1.1.按4点绘制长方体1.2.从长方形画出长方体 2.编辑长方体 它用于注释 3 维物体&#xff0c;例如汽车、盒子等。目前该功能支持单点透视&#xff0c;并具有垂直边缘与侧面完全平行的约束。 1.创建长方体 1.1.按4点绘制长方体 在开始之前&#xff0c;您必…

基于Java+SpringMVC+vue+element宠物管理系统设计实现

基于JavaSpringMVCvueelement宠物管理系统设计实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源…

华为配置中心AP内漫游实验

华为配置中心AP内漫游示例 组网图形 图1 配置中心AP内漫游组网图 配置流程组网需求配置思路数据规划配置注意事项操作步骤配置文件 配置流程 WLAN不同的特性和功能需要在不同类型的模板下进行配置和维护&#xff0c;这些模板统称为WLAN模板&#xff0c;如域管理模板、射频模…

❤ css布局篇

❤ css布局篇 一、基础布局 &#xff08;1&#xff09;居中布局 ① 文字居中 <div class"div1">测试文字居中</div> body {margin: 0;padding: 0;padding: 10%; } .div1 {width: 100px;height: 100px;background: cadetblue;text-align: center; }te…

微信小程序--分享如何与ibeacon蓝牙信标建立联系

ibeacon蓝牙设备 iBeacon是苹果公司2013年9月发布的移动设备用OS&#xff08;iOS7&#xff09;上配备的新功能。其工作方式是&#xff0c;配备有 低功耗蓝牙&#xff08;BLE&#xff09;通信功能的设备使用BLE技术向周围发送自己特有的ID&#xff0c;接收到该ID的应用软件会根…

数据结构(三)——栈和队列的应用

3.3 栈和队列的应用 3.3.1 栈在括号匹配中的应用 用栈实现括号匹配&#xff1a; 最后出现的左括号最先被匹配 &#xff08;栈的特性——LIFO&#xff09;。遇到左括号就入栈&#xff0c;遇到右括号&#xff0c;就“消耗”一个左括号&#xff08;出栈&#xff09;。 匹配失败…

【Numpy】练习题100道(26-50题)

#学习笔记# 在学习神经网络的过程中发现对numpy的操作不是非常熟悉&#xff0c;遂找到了Numpy 100题。 Git-hub链接 1.题目列表 26. 下面的脚本输出什么&#xff1f;(★☆☆) print(sum(range(5),-1)) from numpy import * print(sum(range(5),-1)) 27. 考虑一个整数向量…

检查1个变量是否对另1个变量是否有显著影响

from&#xff1a;SPSS系列|手把手教你做卡方检验 - 知乎 (zhihu.com) 什么时候用&#xff1f; 实例学习 SPSS系列|手把手教你做卡方检验 - 知乎 (zhihu.com)

YOLOv8 | 有效涨点,添加GAM注意力机制,使用Wise-IoU有效提升目标检测效果(附报错解决技巧,全网独家)

目录 摘要 基本原理 通道注意力机制 空间注意力机制 GAM代码实现 Wise-IoU WIoU代码实现 yaml文件编写 完整代码分享&#xff08;含多种注意力机制&#xff09; 摘要 人们已经研究了各种注意力机制来提高各种计算机视觉任务的性能。然而&#xff0c;现有方法忽视了…

Paimon新版本核心特性和生产实践解读

最近Apche Paimon发布了最新版本0.7.0&#xff0c;在这个版本中&#xff0c;Paimon对一些新特性进行了增强。 Paimon在数据湖领域发展迅速&#xff0c;未来会在整个数据开发领域占有很重要的地位&#xff0c;今天我们来盘点一下当前能力的特点以及在生产环境中的使用情况。 Loo…

【C++】手撕AVL树

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能直接手撕AVL树。 > 毒鸡汤&#xff1a;放弃自…

react-native使用FireBase实现google登陆

一、前置操作 首先下载这个包 yarn add react-native-google-signin/google-signin 二、Google cloud配置 Google Cloud 去google控制台新建一个android项目&#xff0c;这时候需要用到你自己创建的keystore的sha1值&#xff0c;然后会让你下载一个JSON文件&#xff0c;先保…

【Linux进阶之路】HTTPS = HTTP + S

文章目录 一、概念铺垫1.Session ID2.明文与密文3.公钥与私钥4.HTTPS结构 二、加密方式1. 对称加密2.非对称加密3.CA证书 总结尾序 一、概念铺垫 1.Session ID Session ID&#xff0c;即会话ID&#xff0c;用于标识客户端与服务端的唯一特定会话的标识符。会话&#xff0c;即客…