Linux——自制shell命令行解釋器

文章目錄

  • 1.打印命令提示符
  • 2.獲取用戶輸入指令
  • 3.重定向分析
  • 4.命令行參數表,環境變量表,初始化
  • 5.命令解析
  • 6.命令執行
    • 6.1.創建子進程
    • 6.2 處理內建命令
    • 6.3 文件重定向
  • 7.源碼

前言

在實現shell的時候我們先創建自己myshell目錄,在目錄中創建myshell.cc文件,因為shell本來是用c語言寫的,但為了方便我們這里使用c和c++混編。

首先我們做一個整體框架:

int main()
{//shell啟動的時候從系統獲得環境變量//我們的環境變量信息應該統一從父shell取InitEnv();while(1){//1.打印提示信息//printf("[%s@%s %s]#", GetUserName(),GetHostName(),GetPwd());PrintCommandPrompt();//2.獲取命令行提示符char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline,sizeof(commandline))){continue;}//3.重定向分析 "ls -a > file.txt" -> "ls -a" "file.txt"//> >> < 判斷重定向的方向//> 輸出   >> 拼接輸出  < 輸入RedirCheck(commandline);//printf("redir = %d filename = %s\n", redir, filename.c_str());//4.命令行分析if(!CommandParse(commandline)){continue;}//PrintArgv();//5.檢測是否是內鍵命令if(CheckAndExecBuiltin()){continue;}//6.執行命令Execute();}//清理工作clear();return 0;
}

1.打印命令提示符

首先我們需要給用戶顯示提示信息,就像我們在使用shell時所看到的提示信息一樣,如下:
在這里插入圖片描述

對它進行分析:
在這里插入圖片描述
所以我們可以這樣定義宏,一個是方便打印的,一個是命令長度:

#define FORMAT "[%s@%s %s]#"
#define COMMAND_SIZE 1024

對于用戶名,主機號我們可以通過getenv從環境變量中得到,但是獲取當前路徑我們不能使用getenv,因為我們myshell的環境變量使用的是父進程的環境變量,我們在當前使用cd命令切換路徑環境變量中的pwd并不會有改變。

所以獲取當前路徑,我們可以使用getcwd,直接查詢操作系統的文件系統,獲取當前進程的工作目錄的絕對路徑。而不用依賴環境變量。當然我們最好單獨設計一個GetPwd把它封裝起來,這樣也方便把當前路徑加入到環境變量中。如下:

//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;const char* GetPwd()
{//const char* pwd = getenv("PWD");const char* pwd = getcwd(cwd,sizeof(cwd));if(pwd != NULL){snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}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* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}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,"[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd());//snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());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);
}

2.獲取用戶輸入指令

獲取用戶輸入,因為用戶輸入的命令行參數是一個字符串,中間含有空格。所以我們不用scanf,cin進行輸入。這里我們使用fgets

bool GetCommandLine(char* out,int size)
{ char* c = fgets(out, size,stdin);if(c == nullptr){return false; }//去掉\nc[strlen(out) -1] = 0;if(strlen(c) == 0){return false;}return true;
}

3.重定向分析

在用戶輸入的指令中可能含有重定向操作,所以我們要提前特殊處理一下字符串,并把它做一個分割。

既然是重定向,也就是我們打開需要重向到的那個文件,所以我們需要獲取打開方式和文件名。

重定向有三種:

  •   <:輸入重定向(以讀的方式打開文件)
    
  •   >:輸出重定向(以寫的方式打開文件)
    
  •   >>:追加重定向(以追加的方式打開文件)
    

所以我們可以使用宏來標記這些情況。

//3.關于重定向的內容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUR_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
string filename;
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;//"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 = OUTPUR_REDIR;}cmd[end++] = 0;TrimSpace(cmd,end);filename = cmd + end;break;}else{end--;}}}

4.命令行參數表,環境變量表,初始化

shell中有兩張表命令行參數表和環境變量表,實質都是字符串數組。

  • 命令行參數表:用來儲存用戶輸入的命令行參數。
  • 環境變量表:用來儲存當前進程的屬性和狀態。

所以我們可以這樣做一個全局變量:

//shell自定義的全局變量
//1.命令行參數表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//2.環境變量表
#define MAXENVS 100
char* g_env[MAXENVS];
int g_envs = 0;

環境變量表需要我們在程序啟動時就將它導入, 當然程序啟動后環境變量默認是父進程的,所以我們可以重新開辟空間把原環境變量的數據拷貝過來,然后再把environ更新為新的地址。具體實現請參考下文源碼。

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++;}//for testg_env[g_envs++] = (char*)"MIHAYOU=666"; g_env[g_envs] = NULL; //導成環境變量for(int i = 0;g_env[i];i++){putenv(g_env[i]);}environ = g_env;
}

5.命令解析

剛才我們獲取到了用戶的輸入得到一個字符串,需要把它一個一個按空格分開,來得到一張命令行參數表。方便后面做進程替換。

//命令行分析 "ls -a -l" -> "ls" "-a" "-l"
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;
}

6.命令執行

6.1.創建子進程

shell執行命令的實質就是進程替換,我們在做進程替換的時候不想結束父進程,那么需要我們創建一個新的子進程,讓子進程來做替換。

6.2 處理內建命令

有一些命令比如cd,是一個內建命令,子進程是無法完成的,需要系統來執行。我們可以使用chdir來完成,chdir函數聲明如下:

bool Cd()
{if(g_argc == 1){string home = GetHome();if(home.empty()){return true;}chdir(home.c_str());}else{string where = g_argv[1];chdir(where.c_str());}return true;
}

它的作用是進入某個目錄,需要傳一個目錄的路徑。

這里也支持echo的一些操作

void Echo()
{if(g_argc == 2){//echo "hello"//echo $?//echo $PATHstring opt = g_argv[1];if(opt == "$?"){cout << lastcode << endl;}else if(opt[0] == '$'){string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value){cout << env_value << endl;}}else{cout << opt << endl;}}
}
bool CheckAndExecBuiltin()
{string cmd = g_argv[0];if(cmd  == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}return false;
}

6.3 文件重定向

  • pcd文件數組:儲存了這個pcb打開的所有文件信息地址。
  • 文件描述符(記fd):pcb的文件數組中的一個下標,0下標的文件為標準輸入流,1下標的文件為標準輸出流,2下標的文件為標準錯誤流,這三個都是系統默認打開的文件。
  • 重定向:系統在對文件進行操作時只認fd,所以重定向的實質就是一個fd位置的信息被其他fd的信息覆蓋。

" >,>>,指的都是從原來的標準輸出(fd=1)重定向到某個文件,< 從原來的標準輸入(fd=0)重定向到某個文件。所以這里我們只需要打開新的文件并獲取到它的fd,然后使用dup2把新文件的地址信息覆蓋到fd=1的文件上就行。然后關閉新文件的fd。"

int Execute()
{pid_t id = fork();if(id == 0){int fd = 0;//子進程檢測重定向if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0){exit(1);}dup2(fd,0);close(fd);}else if(redir == OUTPUR_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(3);}dup2(fd,1);close(fd);}else{}//進程替換不會改變重定向的內容//子進程執行execvp(g_argv[0],g_argv);//程序替換后面的程序就不執行了exit(1);}//父進程等待int status = 0;pid_t rid = waitpid(id,&status,0);////取消報警//(void)rid;if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}

7.源碼

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]#"//shell自定義的全局變量
//1.命令行參數表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//2.環境變量表
#define MAXENVS 100
char* g_env[MAXENVS];
int g_envs = 0;//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;//3.關于重定向的內容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUR_REDIR 2
#define APPEND_REDIR 3int redir = NONE_REDIR;
string filename;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",pwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}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,"[%s@%s %s]#",GetUserName(),GetHostName(),GetPwd());//snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),GetPwd());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 == nullptr){return false; }//去掉\nc[strlen(out) -1] = 0;if(strlen(c) == 0){return false;}return true;
}//命令行分析 "ls -a -l" -> "ls" "-a" "-l"
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 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++;}//for testg_env[g_envs++] = (char*)"MIHAYOU=666"; 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){string home = GetHome();if(home.empty()){return true;}chdir(home.c_str());}else{string where = g_argv[1];chdir(where.c_str());}return true;
}void Echo()
{if(g_argc == 2){//echo "hello"//echo $?//echo $PATHstring opt = g_argv[1];if(opt == "$?"){cout << lastcode << endl;}else if(opt[0] == '$'){string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value){cout << env_value << endl;}}else{cout << opt << endl;}}
}bool CheckAndExecBuiltin()
{string cmd = g_argv[0];if(cmd  == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}return false;
}void PrintArgv()
{for(int i = 0;g_argv[i];i++){printf("argv[%d]->%s\n", i,g_argv[i]);}printf("size = %d\n", g_argc);
}void clear()
{for(int i = 0;g_env[i];i++){free(g_env[i]);g_env[i] = NULL;}
}int Execute()
{pid_t id = fork();if(id == 0){int fd = 0;//子進程檢測重定向if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0){exit(1);}dup2(fd,0);close(fd);}else if(redir == OUTPUR_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(3);}dup2(fd,1);close(fd);}else{}//進程替換不會改變重定向的內容//子進程執行execvp(g_argv[0],g_argv);//程序替換后面的程序就不執行了exit(1);}//父進程等待int status = 0;pid_t rid = waitpid(id,&status,0);////取消報警//(void)rid;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;//"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 = OUTPUR_REDIR;}cmd[end++] = 0;TrimSpace(cmd,end);filename = cmd + end;break;}else{end--;}}}int main()
{//shell啟動的時候從系統獲得環境變量//我們的環境變量信息應該統一從父shell取InitEnv();while(1){//1.打印提示信息//printf("[%s@%s %s]#", GetUserName(),GetHostName(),GetPwd());PrintCommandPrompt();//2.獲取命令行提示符char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline,sizeof(commandline))){continue;}//3.重定向分析 "ls -a > file.txt" -> "ls -a" "file.txt"//> >> < 判斷重定向的方向//> 輸出   >> 拼接輸出  < 輸入RedirCheck(commandline);//printf("redir = %d filename = %s\n", redir, filename.c_str());//4.命令行分析if(!CommandParse(commandline)){continue;}//PrintArgv();//5.檢測是否是內鍵命令if(CheckAndExecBuiltin()){continue;}//6.執行命令Execute();}//清理工作clear();return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/915492.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/915492.shtml
英文地址,請注明出處:http://en.pswp.cn/news/915492.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Boost庫智能指針boost::shared_ptr詳解和常用場景使用錯誤示例以及解決方法

1、Boost智能指針 —— boost::shared_ptr 詳解一、什么是 boost::shared_ptr boost::shared_ptr 是 Boost 庫中實現的一個智能指針模板類&#xff0c;用于管理動態分配的對象生命周期&#xff0c;采用引用計數機制。多個 shared_ptr 實例可以共享同一個對象的所有權&#xff0…

科學分析指南,如何快速找到并清理磁盤的無用文件

隨著時間的推移&#xff0c;系統中會積累大量的臨時文件、緩存文件、不再需要的安裝包或其他大型文件。磁盤清理可以刪除這些不必要的文件&#xff0c;從而釋放寶貴的磁盤空間。它無需安裝&#xff0c;插上 U 盤就能直接使用。只需勾選需要掃描的磁盤&#xff0c;點擊“開始分析…

Laravel 系統版本查看及artisan管理員密碼找回方法針對各個版本通用方法及原理-優雅草卓伊凡

Laravel 系統版本查看及artisan管理員密碼找回方法針對各個版本通用方法及原理-優雅草卓伊凡一、查看 Laravel 版本的方法優雅草蜻蜓T會議系統專業版 最近又有客戶要了&#xff0c;但是發現 密碼不對 管理員賬戶密碼不對&#xff0c;卓伊凡必須處理下&#xff0c;這里順便講解密…

針對大規模語言模型的上下文工程技術調研與總結(翻譯并摘要)

針對大規模語言模型的上下文工程技術調研與總結聲明摘要部分翻譯介紹部分翻譯相關工作部分翻譯并摘要為什么使用上下文工程&#xff08;翻譯并摘要&#xff09;基礎組件&#xff08;翻譯并摘要&#xff09;RAG&#xff08;翻譯并摘要簡單介紹一下個人認為比較好的技術&#xff…

QT配置Quazip外部庫

1.下載QuaZip源碼網址&#xff1a;https://sourceforge.net/projects/quazip/  注&#xff1a;下載->解壓->打開.pro文件2.編譯QuaZip源碼2.1配置zlib注&#xff1a;QuaZip需zlib的支持&#xff0c;我們需要引用zlib找到本地安裝Qt目錄下zlib目錄&#xff1a;注&#x…

從C++開始的編程生活(4)——類的定義、訪問限定符、類域、類的實例化和this指針

前言 本系列文章承接C語言的學習&#xff0c;需要有C語言的基礎才能學會哦~ 第3篇主要講的是有關于C的類的定義、訪問限定符、類域、類的實例化和this指針。 C才起步&#xff0c;都很簡單呢&#xff01; 目錄 前言 類 基本語法 訪問限定符 基本語法 類域 類的實例化 內…

AD域控制器虛擬化的安全加固最佳實踐

以下是AD域控制器虛擬化安全加固的7項核心實踐&#xff0c;結合最新Windows Server 2022特性與虛擬化環境需求&#xff1a;基礎架構強化? 采用靜態IP分配并確保所有域控節點DNS指向主DC&#xff08;如192.168.1.10&#xff09;? 虛擬機模板需預配置林/域功能級別為Windows Se…

java解析nc氣象數據

1.1pom.xml<dependency><groupId>edu.ucar</groupId><artifactId>netcdfAll</artifactId><version>5.4.1</version></dependency>1.2 netcdf使用/** param type 0 ,1, 2 wind 1 or 2 其他 0 .* return Map* */public Map i…

STC8H8K64U SKDIP28芯片頻率占空比PWM波形

/****PWM輸出任意周期占空比波形*******/ #include "STC8H.h" // #include "intrins.h" // #define uchar unsigned char // #define uint unsig…

【RK3576】【Android14】USB開發調試

獲取更多相關的【RK3576】【Android14】驅動開發&#xff0c;可收藏系列博文&#xff0c;持續更新中&#xff1a; 【RK3576】Android 14 驅動開發實戰指南 硬件接口 RK3576支持兩個USB3.0控制器 驅動開發 dts配置 在“Android14/kernel-6.1/arch/arm64/boot/dts/rockchip/rk…

20. TaskExecutor與ResourceManager心跳

20. TaskExecutor與ResourceManager心跳 現在&#xff0c;需要回過頭看 ResourceManager是如何產生心跳管理服務的。cluster.initializeServices 方法的 heartbeatServices createHeartbeatServices(configuration);產生一個 HeartbeatServicesImpljobmanager的 resourceManag…

OS19.【Linux】進程狀態(上)

目錄 1.情景引入 2.操作系統學科對進程狀態的分類 運行狀態 基于時間片的輪轉調度算法 阻塞狀態 等待IO設備的例子 等待其他進程中需要獲取的數據 進程喚醒 掛起狀態(全稱為阻塞掛起狀態) 簡單談談虛擬內存管理 就緒狀態 筆面試題 3.Linux對進程狀態的分類 R和S狀…

如何優雅地修改項目的 Android 版本(API 級別)

引言 在 Android 開發的日常迭代中&#xff0c;我們經常需要升級或降級項目的 minSdkVersion、targetSdkVersion 與 compileSdkVersion。升級可以解鎖新特性和性能優化&#xff1b;降級則可能為了兼容舊機型或快速驗證問題。本文將手把手演示在 Android Studio 里修改 Android …

GNU Radio多類信號多種參數數據集生成技巧

參考我的這篇博客&#xff0c;我想自制一個多信號數據集&#xff1a; 【多雷達信號硬件模擬】 3臺USRP1臺VSG信號發生器模擬多雷達信號&#xff0c;1臺USRP產生高斯噪聲模擬更多信道環境&#xff0c;1臺USRP采集信號 需要在多個波段對四種信號進行參數設置&#xff0c;帶寬有…

Ansible + Shell 服務器巡檢腳本

腳本概述這是一個用于服務器日常巡檢的 Shell 腳本&#xff0c;主要功能包括&#xff1a;檢查多臺主機的網絡連通性 監控CPU、內存和磁盤使用率 生成詳細的巡檢報告 通過企業微信發送告警通知核心技術點1. 主機批量管理使用Ansible工具遠程執行命令和腳本 通過主機…

Linux-rpm和yum

一、RPMRPM&#xff08;Red Hat Package Manager&#xff09;是一個用于管理 Red Hat 系列 Linux 發行版&#xff08;如 RHEL、CentOS、Fedora&#xff09;軟件包的工具。RPM 允許用戶以統一的格式來安裝、卸載、升級和查詢軟件包。它是 .rpm 文件的主要工具&#xff0c;后綴名…

手推OpenGL相機的正交投影矩陣和透視投影矩陣(附源碼)

概述計算OpenGL的正交投影矩陣和透視投影矩陣是有現成函數的。自己手推不是為了重復造輪子。手推一遍&#xff0c;可以極大的加強對這兩個矩陣的理解。同時也可以滿足一下自己求知欲。正交投影矩陣手推正交投影矩陣源碼 WGMatrix4x4 WGMatrix4x4::BuildOrtho(double l, double …

【跨國數倉遷移最佳實踐2】MaxCompute SQL執行引擎對復雜類型處理全面重構,保障客戶從BigQuery平滑遷移

本系列文章將圍繞東南亞頭部科技集團的真實遷移歷程展開&#xff0c;逐步拆解 BigQuery 遷移至 MaxCompute 過程中的關鍵挑戰與技術創新。本篇為第二篇&#xff0c;跨國數倉遷移背后 MaxCompute 的統一存儲格式創新。 注&#xff1a;客戶背景為東南亞頭部科技集團&#xff0c;…

react(基礎篇)

React由Meta公司研發&#xff0c;用于構建Web和原生交互界面的庫。 React 官方中文文檔 查看JSX &#xff08;一&#xff09;React組件 用戶界面的一部分&#xff0c;通俗的來講&#xff0c;最小的元素組成的單元&#xff0c;可以實現部分邏輯與功能 房子的門就可以看成一個…

數據結構-哈希表(一)哈希函數、哈希表介紹、優缺點

哈希表 哈希函數哈希表使用了哈希函數來完成key到地址的快速映射&#xff0c;所以在了解哈希表之前&#xff0c;需要先明白哈希函數的概念和特點。 哈希函數的定義 哈希函數 哈希函數是一種將任意長度輸入的數據&#xff0c;轉換成固定長度輸出的算法哈希函數H可以表示為yH(x) …