【演算法】遠方來信,從數學表達式演算法到彙編語法解釋器

来源:https://www.cnblogs.com/lan80/archive/2023/12/04/17872677.html
-Advertisement-
Play Games

Autofac 是一個功能豐富的 .NET 依賴註入容器,用於管理對象的生命周期、解決依賴關係以及進行屬性註入。本文將詳細講解 Autofac 的使用方法,包括多種不同的註冊方式,屬性註入,以及如何使用多個 ContainerBuilder 來註冊和合併組件。我們將提供詳細的源代碼示例來說明每個概念 ...


在繁華的都市中,小悅作為一名軟體工程師,每天都在這座鋼筋水泥的森林里忙碌。她的生活似乎被工作和各種瑣碎的事情填滿了,但在這個繁忙的生活中,她總能在工作之餘找到一些小小的樂趣。

這天下班後,小悅收到了一封來自國外同學蘇菲的email。郵件的內容讓她的思緒一下子飄回了那個學習彙編語言的大學時代。

蘇菲是一個非常聰明的女孩,她們倆在大學時期成為了要好的朋友。蘇菲對編程有著濃厚的興趣,而小悅則是對理論知識情有獨鍾。在大學最後一年的上機考試中,她們倆通過逆波蘭表達式演算法,合作完成了一個數學算式表達式的演算法。

這個算式表達式演算法是小悅在上機考試中實現的,它主要用於解決數學算式計算的問題。但現在回想起來,她覺得這個演算法還有很多可以完善的地方,甚至可以在它基礎上開發出自己的腳本語言解釋器。於是,她決定利用業餘時間來改進這個演算法。

在眾多實用的編程項目中,頁面腳本解釋器和SQL語言解釋器是兩個比較熱門的選擇。但是這兩個項目的語法也相對複雜,因此小悅決定從更簡單的彙編語言解釋器開始研究。

小悅開始仔細研究彙編語言的語法和指令集,她想要用cSharp寫一個自己的彙編語法解釋器。她知道,實現一個彙編語法解釋器並不是一件容易的事情,但她也堅信,只要自己不斷努力和探索,一定能夠成功。

在實現彙編語法解釋器的過程中,小悅參考了大量的資料和文檔,不斷調整和完善自己的代碼。她遇到了很多困難和挫折,但她都沒有放棄。每當遇到問題時,她都會想起蘇菲和那個算式表達式演算法,這讓她有了繼續前進的動力。

經過一段時間的努力,小悅終於成功地寫出了一個自己的彙編語法解釋器。這個解釋器能夠解析彙編語言語法,並根據程式員輸入的語法執行相應的操作。

小悅的閨蜜小欣看到她的成果後,打趣說:“你真是編程界的女俠啊!”小悅聽後笑了笑,心中不禁想起了蘇菲。她想:“如果蘇菲還在國內,我們一定會有更多的合作機會。”

然而,生活總是充滿了未知和變數。蘇菲畢業後選擇了留在國外工作和生活,而小悅則在國內繼續著她的軟體工程師生涯。雖然兩人已經很少聯繫,但小悅一直珍藏著她們之間的友誼和那個上機考試中的算式表達式演算法。

在小悅實現彙編語法解釋器的過程中,她不僅提升了自己的編程能力,還進一步理解了算式表達式演算法的原理和實現方式。她相信,這個經驗將會成為她未來職業生涯中的一筆寶貴財富。

如今的小悅已經不再是那個單純為了應付考試而編程的女孩了。她在編程領域有著自己的追求和夢想。她希望通過自己的努力和不斷的學習,成為一個更加優秀的軟體工程師,為這個數字化時代貢獻自己的力量。


小悅需要實現的彙編解釋器語法如下:

mov x, y - 將y(無論是整數還是寄存器的值)複製到寄存器(變數)x中。
inc x - 使寄存器x的內容增加1。
dec x - 使寄存器x的內容減少1。
add x, y - 將寄存器x的內容與y(無論是整數還是寄存器的值)相加,並將結果存儲在x中(即register[x] += y)。
sub x, y - 從寄存器x中減去y(無論是整數還是寄存器的值),並將結果存儲在x中(即register[x] -= y)。
mul x, y - 與乘法相同(即register[x] *= y)。
div x, y - 與整數除法相同(即register[x] /= y)。
label: - 定義函數標簽位置,一般用於函數定位(標簽 = 標識符 + 冒號,標識符是與任何其他命令不匹配的字元串)。跳轉命令和調用針對程式中的這些標簽位置。
jmp lbl - 跳轉到自定義函數標簽lbl。
cmp x, y - 比較x(無論是整數還是寄存器的值)和y(無論是整數還是寄存器的值)。結果用於條件跳轉(jne,je,jge,jg,jle和jl)。
jne lbl - 如果上一個cmp命令的值不相等,則跳轉到標簽lbl。
je lbl - 如果上一個cmp命令的值相等,則跳轉到標簽lbl。
jge lbl - 如果在之前的cmp命令中x大於或等於y,則跳轉到標簽lbl。
jg lbl - 如果在之前的cmp命令中x大於y,則跳轉到標簽lbl。
jle lbl - 如果在之前的cmp命令中x小於或等於y,則跳轉到標簽lbl。
jl lbl - 如果在之前的cmp命令中x小於y,則跳轉到標簽lbl。
call lbl - 調用由lbl標識的子程式。當在子程式中找到ret時,指令指針應返回到此call命令之後的指令。
ret - 當在子程式中找到ret時,指令指針應返回到調用當前函數的指令。
msg '輸出結果:  ',x - 這條指令存儲程式的輸出。它可能包含文本字元串(以單引號分隔)和寄存器。參數的數量不限,會因程式而異。
end - 這條指令表示程式正確結束,因此返回存儲的輸出(如果程式在沒有此指令的情況下終止,它應返回預設輸出:參見下文)。
; - comment註釋指令在代碼執行程式時不運行。

示例語法:

//代碼定義
var program = @"
; My first program
mov a, 5
inc a
call function
msg '計算結果:(5+1)/2 = ', a ; output message
end

function:
div a, 2
ret
";
//執行代碼
AssemblerInterpreter.Interpret(program);
//msg命令輸出結果 
計算結果:(5+1)/2 = 3


演算法實現1:

  1 public class AssemblerInterpreter
  2 {
  3      public static string Interpret(string input)
  4     {
  5       // 存儲寄存器的字典
  6       var register = new Dictionary<string, int>();
  7       // 存儲比較結果的字元串
  8       var compare  = "";
  9       // 存儲標簽的字典
 10       var label    = new Dictionary<string, int>();
 11       // 整數類型的棧,存儲代碼當前位置
 12       var stack    = new Stack<int>();
 13       // 存儲消息的字元串構建器
 14       var messages = new StringBuilder();
 15       
 16       // 將輸入的程式按行分割
 17       var program = input.Split("\n");
 18       
 19       // 遍歷程式,找到標簽並存儲其位置
 20       for(var i = 0; i < program.Length; i++)
 21       {
 22         var parts = program[i].Split(":");
 23         if (parts.Length == 2) label.Add(parts[0], i);
 24       }
 25         
 26       // 遍歷程式的每一行指令
 27       for (var i = 0; i < program.Length; i++)
 28       {
 29         // 將當前行的指令解析為操作符和操作數
 30         var token = TokensFrom(program[i]);
 31         if (token.Length == 0) continue; // 跳過空行
 32         
 33         // 根據操作符執行相應的操作
 34         if (token[0] == "mov")
 35         {
 36           // 將操作數存儲到寄存器中
 37           var r = token[1];
 38           var val = ValueOf(token[2]);
 39           if (register.ContainsKey(r)) register[r] = val;
 40           else register.Add(r, val);
 41         }
 42         else if (token[0] == "inc") register[token[1]++;
 43         else if (token[0] == "dec") register[token[1]--;
 44         else if (token[0] == "add") register[token[1]] += ValueOf(token[2]);
 45         else if (token[0] == "sub") register[token[1]] -= ValueOf(token[2]);
 46         else if (token[0] == "mul") register[token[1]] *= ValueOf(token[2]);
 47         else if (token[0] == "div") register[token[1]] /= ValueOf(token[2]);
 48         else if (token[0] == "msg")
 49         {
 50           // 將消息內容添加到消息字元串構建器中
 51           var args = ParseMsg(program[i])
 52             .Select(s => s.First() == '\''
 53                       ? s.Length >= 2 ? s[1..^1] : s
 54                       : ValueOf(s).ToString());
 55           messages.Append(string.Concat(args));
 56         }
 57         else if (token[0] == "call")
 58         {
 59           // 將當前位置壓入棧中,並跳轉到指定標簽位置
 60           stack.Push(i);
 61           i = label[token[1]];
 62         }
 63         else if (token[0] == "ret") i = stack.Pop(); // 從棧中彈出位置並跳轉
 64         else if (token[0] == "cmp")
 65         {
 66           // 比較兩個值並存儲比較結果
 67           var x = ValueOf(token[1]);
 68           var y = ValueOf(token[2]);
 69           
 70           if (x == y) compare = "=";
 71           else if (x > y) compare = ">";
 72           else if (x < y) compare = "<";
 73           else compare = "!=";
 74         }
 75         // 根據比較結果進行條件跳轉
 76         else if (token[0] == "jmp") i = label[token[1]];
 77         else if (token[0] == "jne")
 78         {
 79           if (compare != "=") i = label[token[1]];
 80         }
 81         else if (token[0] == "je")
 82         {
 83           if (compare == "=") i = label[token[1]];
 84         }
 85         else if (token[0] == "jge")
 86         {
 87           if (compare == ">" || compare == "=") i = label[token[1]];
 88         }
 89         else if (token[0] == "jg")
 90         {
 91           if (compare == ">") i = label[token[1]];
 92         }
 93         else if (token[0] == "jle")
 94         {
 95           if (compare == "<" || compare == "=") i = label[token[1]];
 96         }
 97         else if (token[0] == "jl")
 98         {
 99           if (compare == "<") i = label[token[1]];
100         }
101         else if (token[0] == "end") return messages.ToString();
102       }
103 
104       return null;
105 
106       // 從輸入的行中提取標記並返回標記數組
107       string[] TokensFrom(string line) =>
108         Regex.Split(RemoveComment(line), "[ ,]+")  // 使用正則表達式分割去除註釋後的行,並去除空格
109             .Select(s => s.Trim())  // 去除每個標記的首尾空格
110             .Where(s => !string.IsNullOrEmpty(s))  // 去除空字元串
111             .ToArray();  // 轉換為數組
112 
113         // 解析消息內容並返回消息參數數組
114         string[] ParseMsg(string line)
115         {
116             var args = Regex.Replace(line, @"^\s*msg\s*", "");  // 去除消息指令並獲取消息內容
117             var result = new List<string>();  // 創建存儲結果的列表
118 
119             var token = new StringBuilder();  // 創建 StringBuilder 存儲當前標記
120             var inQuote = false;  // 標記是否在引號內
121             foreach (var c in args)  // 遍歷消息內容的每個字元
122             {
123                 if (c == '\'' && inQuote)  // 如果是引號且在引號內
124                 {
125                     inQuote = false;  // 標記離開引號狀態
126                     token.Append(c);  // 將引號添加到標記中
127                 }
128                 else if (c == '\'' && !inQuote)  // 如果是引號且不在引號內
129                 {
130                     inQuote = true;  // 標記進入引號狀態
131                     token.Append(c);  // 將引號添加到標記中
132                 }
133                 else if (c == ',' && !inQuote)  // 如果是逗號且不在引號內
134                 {
135                     result.Add(token.ToString().Trim());  // 將當前標記去除首尾空格後添加到結果列表
136                     token.Clear();  // 清空當前標記
137                 }
138                 else if (c == ' ' && !inQuote) continue;  // 如果是空格且不在引號內,則繼續下一次迴圈
139                 else if (c == ';' && !inQuote) break;  // 如果是分號且不在引號內,則結束迴圈
140                 else token.Append(c);  // 其他情況將字元添加到當前標記中
141             }
142             if (token.Length > 0) result.Add(token.ToString().Trim());  // 將最後一個標記去除首尾空格後添加到結果列表
143             return result.ToArray();  // 轉換為數組並返回
144         }
145 
146         // 去除行中的註釋並返回處理後的行
147         string RemoveComment(string line) => Regex.Replace(line, ";.*", "");  // 使用正則表達式去除分號及其後的內容
148 
149         // 獲取變數的值,如果是整數則直接返回,否則從寄存器中獲取
150         int ValueOf(string x) => int.TryParse(x, out var r) ? r : register[x];
151     }
152 }

這段代碼實現了一個基於指令集的虛擬機執行演算法解釋器。它解釋並定義了一系列基本的彙編指令,包括mov、inc、dec、add、sub、mul、div、msg、call、ret、cmp、jmp、jne、je、jge、jg、jle、jl和end。這些指令可以操作寄存器、棧、標簽和消息,並且可以進行條件跳轉和比較操作。

這個演算法是一個彙編解釋器,它模擬了虛擬機的功能。在演算法中,通過使用字典存儲寄存器的值、比較結果的字元串、標簽的位置等信息,以及使用棧存儲代碼當前位置,來模擬虛擬機的處理器、記憶體、寄存器等功能。演算法遍歷輸入的程式,解析每一行指令並執行相應的操作,如將操作數存儲到寄存器中、進行加減乘除運算、比較兩個值等。同時,演算法還實現了條件跳轉和消息輸出等功能,以模擬虛擬機的指令集。通過這種方式,演算法實現了對輸入程式的解釋和執行,從而實現了對虛擬機的模擬。這樣的虛擬機模擬可以使程式在不同的平臺上運行,提高了程式的靈活性和可移植性。

虛擬機程式AssemblerInterpreter.Interpret作為一個.net宿主程式,接收並執行這些外部輸入的彙編語言代碼。

該演算法首先接收一個彙編語法表達式的字元串作為輸入,然後通過解析字元串,逐個獲取操作數和操作符。在解析過程中,演算法會根據操作符的類型和操作數的值,進行相應的計算操作。這些操作包括將指令位置壓入棧中、從棧中彈出指令在寄存器中的位置併進行計算等。

演算法的核心部分是遍歷語法表達式並執行相應的操作。在這個過程中,演算法會逐個解析字元串中的字元,並根據解析的結果執行相應的計算步驟。一旦完成計算,演算法會將最終的結果存儲在棧頂,而棧頂的元素就是最終的指令計算結果。

註1:else if (token[0] == "ret") i = stack.Pop(); // 從棧中彈出位置並跳轉

這行代碼是解釋器中處理ret指令的部分。當解釋器遇到ret指令時,它需要從調用棧中彈出一個位置並跳轉到該位置。

讓我們來解釋一下這行代碼的邏輯:

  1. 首先,解釋器解析到ret指令,並將其作為一個操作符token
  2. 接著,解釋器檢查token[0]是否等於"ret",即檢查標記的第一個元素是否是"ret"。這是為了確保當前指令是ret指令。
  3. 如果token[0]等於"ret",則執行i = stack.Pop(),這意味著從調用棧stack中彈出一個位置,並將其賦值給程式計數器i。這樣程式計數器將跳轉到之前調用指令的下一條指令位置,實現了ret指令的跳轉功能。

註2:在代碼47行,操作符分支處理中,可以根據實際情況擴展新的賦值函數

else if (token[0] == "div") register[token[1]] /= ValueOf(token[2]);
else if (token[0] == "expression") ... //在這裡可擴展其他語法功能,除了加減乘除,還可以添加逆波蘭表達式演算法進行變數賦值或其他自定義函數處理等


基於指令集的虛擬機執行演算法的歷史故事可以追溯到電腦科學和軟體工程的早期發展階段,它經歷了多個關鍵的里程碑和發展趨勢:

1. 早期電腦:在早期的電腦系統中,程式員需要直接編寫機器碼指令,這些指令直接操作電腦的硬體。這種方式對於程式員來說非常繁瑣和複雜,因此人們開始尋找更高級的抽象方式來編寫程式。這個時期,一些先驅者提出了彙編語言,它提供了一種更接近硬體的編程方式,使得程式員可以更容易地編寫機器碼指令。
2. 彙編語言:為了簡化程式員編寫機器碼指令的工作,彙編語言被引入。彙編語言是一種低級的編程語言,它使用助記符(mnemonics)來代替機器碼指令,並提供了一些符號標簽來簡化跳轉和調用等操作。這使得程式員可以更加高效地編寫程式,減少了出錯的可能性。
    高級語言:1957年:IBM的約翰·巴科斯(John Backus)創建全世界第一套高級語言:Fortran(formula translate)。Fortran的跨平臺限制主要是由於其與特定的操作系統和硬體架構的緊密相關性,如windows/linux,以及不同編譯器和平臺的差異和限制。如果需要在不同的操作系統或硬體平臺上運行Fortran程式,可能需要重新編譯或修改程式,以確保它能夠在目標平臺上正確運行。

3. 虛擬機:為了實現跨平臺的程式執行,虛擬機概念被引入。虛擬機是一個軟體實體,它模擬了一臺電腦,包括處理器、記憶體、寄存器等,並提供了一組指令集。程式員可以使用這個指令集來編寫程式,併在虛擬機上執行,如JVM,而不需要關心底層硬體和操作系統。虛擬機的引入使得程式可以在不同的平臺上運行,提高了程式的靈活性和可移植性。
4. 基於指令集的虛擬機執行演算法:虛擬機中的核心部分是基於指令集的虛擬機執行演算法。它負責解釋和執行程式中的指令,包括數據操作、代碼跳轉、函數調用等。執行演算法通常包括指令解析、寄存器操作、記憶體訪問、跳轉控制等步驟。這個演算法的實現除了完成基本指令需求,還需要考慮性能、安全性和穩定性等方面的問題。
5. 發展趨勢:隨著電腦科學和軟體工程的發展,基於指令集的虛擬機執行演算法得到了不斷的改進和優化。例如,引入了即時編譯(JIT)技術、增強了指令集、優化了執行引擎等,使得虛擬機執行演算法在性能和功能上得到了顯著提升。同時,隨著雲計算和移動互聯網的普及,基於指令集的虛擬機執行演算法也在這些領域得到了廣泛的應用和發展。


通過測試用例(5+1)/2,我們將執行AssemblerInterpreter.Interpret(program)來運行這段程式,並期望msg命令輸出"計算結果:(5+1)/2 = 3"。

程式的執行步驟如下:

  1. 首先,AssemblerInterpreter.Interpret(program)將解釋器應用於給定的程式字元串program
  2. 解釋器將逐行解析程式字元串,並執行相應的操作。在解釋器中,會調用TokensFrom函數來解析每一行的標記。
  3. 對於每個標記,解釋器將執行相應的操作,比如mov指令會移動一個值5到一個寄存器,inc指令會增加寄存器中的值,即(5+1),call指令會調用一個函數function,即(5+1)/2,msg指令會輸出常量消息和a的值3。
  4. 在執行msg指令時,解釋器會調用ParseMsg函數來解析消息內容,並輸出最終的消息結果。

在這個測試用例中,當msg指令被執行時,ParseMsg函數會解析消息內容"'計算結果:(5+1)/2 = ', a",並輸出"計算結果:(5+1)/2 = 3"作為最終結果。

因此,通過執行AssemblerInterpreter.Interpret(program),程式將按預期輸出"計算結果:(5+1)/2 = 3"。


演算法實現2:

  1 public class AssemblerInterpreter
  2 {
  3     public static string Interpret(string input)
  4     {
  5         Regex trimmer = new Regex(@"\s\s+");
  6         string[] unparsedInstructions = input.Split("\n");
  7         Dictionary<string, int> registers = new Dictionary<string, int>();
  8         Dictionary<string, int> labels = new Dictionary<string, int>();
  9         IInstruction[] instructions = new IInstruction[unparsedInstructions.Length];
 10         int endOfProgram = 0;
 11         //cast to instructions
 12         for(int i = 0; i < unparsedInstructions.Length;  i++)
 13         {
 14             string cleanString = unparsedInstructions[i].TrimStart();
 15             cleanString = trimmer.Replace(cleanString, " ");
 16             string[] instructionParts = cleanString.Split(" ");
 17             switch(instructionParts[0]){
 18                 case ";":
 19                     instructions[i] = new Idle();
 20                     break;
 21                 case "msg":
 22                     instructions[i] = new Msg(cleanString.Substring(4));
 23                     break;
 24                 case "call":
 25                     instructions[i] = new Call(instructionParts[1]);
 26                     break;
 27                 case "mov":
 28                     instructions[i] = new Mov(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 29                     break;
 30                 case "inc":
 31                     instructions[i] = new Inc(instructionParts[1]);
 32                     break;
 33                 case "dec":
 34                     instructions[i] = new Dec(instructionParts[1]);
 35                     break;
 36                 case "add":
 37                     instructions[i] = new Add(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 38                     break;
 39                 case "sub":
 40                     instructions[i] = new Sub(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 41                     break;
 42                 case "div":
 43                     instructions[i] = new Div(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 44                     break;
 45                 case "mul":
 46                     instructions[i] = new Mul(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 47                     break;
 48                 case "cmp":
 49                     instructions[i] = new Cmp(new Arguement(instructionParts[1].Remove(1)), new Arguement(instructionParts[2]));
 50                     break;
 51                 case "jmp":
 52                     instructions[i] = new Jmp(instructionParts[1]);
 53                     break;
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 記憶體是在程式中打交道不可缺少的存在,或者在GC語言中記憶體概念會被刻意的屏蔽掉,但如果是棧遞歸函數調用自身這種情況,stack overflow這種情況是一樣的會碰到的,所以瞭解下堆與棧碰到問題的時候好解決問題 ...
  • 在.NET Core中,使用Action和Options參數方式配置服務並將配置信息對象註冊到IServiceCollection的好處在於,它提供了更高級別的可配置性和可擴展性。這種模式允許將配置信息與服務的實現分離,使配置更加模塊化和可管理。通過將配置信息對象註冊到IServiceCollect ...
  • 簡介 從C# 3.0起很少需要自己聲明委托。System.Func 是一個泛型委托,它可以表示帶有返回值的方法。它可以接受一個到多個輸入參數,並返回一個指定類型的結果。System.Func 委托的最後一個類型參數表示方法的返回值類型。而System.Action系列代表返回void的方法。 Fun ...
  • 這是我在C#中第一次用到json,以前都用別的替代,但是瞭解了之後發現這個是真的好用。 首先,有幾個網站先貼上來保存一下。 JSON 模式驗證器 - Newtonsoft (jsonschemavalidator.net),顧名思義,就是驗證我們的json格式是否正確。 Introduction ( ...
  • 在本示例中,我們將使用Autofac和AspectC(Autofac.Extras.DynamicProxy2)來演示如何實現AOP(面向切麵編程)。我們將創建一個簡單的C#控制台應用程式,並應用AOP以解決日誌記錄的問題。首先,讓我們討論AOP的用途和目標。 AOP(面向切麵編程)的用途 AOP是 ...
  • 在我們很多應用系統中,往往都需要根據實際情況生成一些編碼規則,如訂單號、入庫單號、出庫單號、退貨單號等等,我們有時候根據規則自行增加一個函數來生成處理,不過我們仔細觀察後,發現它們的編碼規則有很大的共通性,因此可以考慮使用一些通用的業務編碼規則生成,從而在系統中統一維護即可,本篇隨筆介紹如何在WIn... ...
  • 一:背景 1. 講故事 在高級調試的旅程中,經常會有一些朋友問我什麼是 工作集(記憶體),什麼是 提交大小,什麼是 Virtual Size, 什麼是 Working Set 。。。截圖如下: 既然有很多朋友問,這些用口頭也不怎麼好描述,剛好上午有時間就系統的聊一下吧。 二:記憶體術語解讀 1. Vir ...
  • 推薦演算法是機器學習和數據挖掘領域的重要組成部分,用於為用戶提供個性化推薦內容。在.NET中,可以使用不同的演算法來實現推薦系統。在本文中,我將介紹三種常見的推薦演算法:協同過濾、內容過濾和深度學習推薦系統,並提供相應的.NET源代碼示例。 協同過濾推薦演算法 協同過濾演算法基於用戶行為數據,通過分析用戶之間 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...