軟體開發



程式語言

定義

程式語言(Programming Language)是人與電腦溝通的橋樑,用來編寫指令、控制電腦運作與實現各種應用程式。

分類方式

常見程式語言

選擇語言的考量因素

程式語言的應用領域

學習建議



綜合程式開發連結 list

  • 微軟技術學習中心/Microsoft Learn
  • vscode/Vidual Studio Code
  • Meta/Facebook開發人員
  • Chrome開發人員

    程式語言排名

    根據最新的程式語言排名,以下是 2024 年度排名前 20 的程式語言:



    Lambda 表達式

    1. 什麼是 Lambda 表達式?

    Lambda 表達式是一種匿名函數,通常用於簡化代碼,特別是在需要傳遞小型函數或回調的情況下。Lambda 表達式的語法簡潔,可以在一行中定義函數邏輯。Lambda 表達式最常見於 C++、JavaScript、Python 和 C# 等語言。

    2. Lambda 表達式的基本語法

    Lambda 表達式的基本語法通常包括參數、箭頭符號 => 和函數體,例如:

    (參數) => 函數體

    具體語法因語言而異,例如:

    3. 各語言中的 Lambda 表達式範例

    C++ 範例

    
    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    int main() {
        std::vector numbers = {1, 2, 3, 4, 5};
    
        // 使用 lambda 表達式來計算偶數的和
        int sum = 0;
        std::for_each(numbers.begin(), numbers.end(), [&sum](int n) {
            if (n % 2 == 0) sum += n;
        });
    
        std::cout << "偶數的總和: " << sum << std::endl;
        return 0;
    }
            

    Python 範例

    
    # 使用 lambda 表達式來計算兩數的和
    add = lambda x, y: x + y
    print(add(5, 10))  # 輸出: 15
            

    C# 範例

    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    class Program {
        static void Main() {
            List numbers = new List { 1, 2, 3, 4, 5 };
            
            // 使用 Lambda 表達式篩選出偶數並計算總和
            int sum = numbers.Where(n => n % 2 == 0).Sum();
            
            Console.WriteLine($"偶數的總和: {sum}");
        }
    }
            

    4. Lambda 表達式的應用場景

    5. 優缺點



    Reflection

    概念

    Reflection(反射)允許程式在執行期動態檢查與操作型別(類別/結構)、屬性、欄位、方法與註解(metadata)。不需要在編譯期就知道確切型別,即可讀寫成員或呼叫方法。

    常見用途

    優缺點

    語言支援情況

    典型範例

    # Python:列舉並讀取物件屬性
    class User:
        def __init__(self): self.id = 0; self.name = "Ann"
    u = User()
    for k, v in vars(u).items():
        print(k, v)  # id 0 / name Ann
    
    // C#:取得欄位/屬性並讀值
    using System;
    using System.Reflection;
    
    var t = typeof(MyType);
    foreach (var p in t.GetProperties(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic))
        Console.WriteLine($"{p.Name}={p.GetValue(obj)}");
    
    // JavaScript:動態列舉與呼叫
    const obj = { x: 1, y: 2, add(){ return this.x + this.y; } };
    for (const [k,v] of Object.entries(obj)) console.log(k, v);
    console.log(obj["add"]()); // 3
    
    // Java:使用反射讀/寫欄位
    import java.lang.reflect.*;
    class U { private int id = 42; }
    U u = new U();
    Field f = U.class.getDeclaredField("id");
    f.setAccessible(true);
    System.out.println(f.getInt(u)); // 42
    
    // Go:reflect 走訪結構
    import "reflect"
    func Dump(v any) {
        val := reflect.ValueOf(v)
        for i := 0; i < val.NumField(); i++ {
            name := val.Type().Field(i).Name
            fmt.Println(name, val.Field(i).Interface())
        }
    }
    

    最佳實務

    何時避免



    檢查任意結構是否全為0

    觀念總結

    語言支援度比較

    語言支援度說明
    Python動態、完整反射,輕鬆遞迴物件/容器檢查。
    JavaScript / TypeScript物件為 key-value,可用 Object.values 遞迴。
    Rubyinstance_variables 反射成員。
    PHPget_object_vars() 或 Reflection。
    C# (.NET)Reflection 取得欄位/屬性,型別安全佳。
    Javajava.lang.reflect 可掃描欄位。
    KotlinJVM 反射完善,與 Java 類似。
    Goreflect 可走訪 struct 欄位。
    SwiftMirror 可走訪,但場景有限、需額外處理型別。
    C++ (至 C++23)無 runtime reflection,需手動為型別撰寫檢查。
    Rust無 runtime reflection,常以 derive/trait 自行實作。

    Python 範例

    def is_all_zero(obj, eps=1e-6):
        if isinstance(obj, (int, float)):  # 基本數值
            return abs(obj) < eps
        elif isinstance(obj, (list, tuple, set)):  # 序列/集合
            return all(is_all_zero(x, eps) for x in obj)
        elif isinstance(obj, dict):  # 字典
            return all(is_all_zero(v, eps) for v in obj.values())
        elif hasattr(obj, "__dict__"):  # 一般物件
            return all(is_all_zero(v, eps) for v in vars(obj).values())
        else:
            return False
    
    # 測試
    class Point: 
        def __init__(self, x=0, y=0): self.x, self.y = x, y
    class Line:
        def __init__(self, p1=None, p2=None): 
            self.p1 = p1 or Point()
            self.p2 = p2 or Point()
    
    print(is_all_zero([[0,0],[0,0]]))        # True
    print(is_all_zero(Line(Point(0,0),Point(0,0))))  # True
    print(is_all_zero(Line(Point(1,0),Point(0,0))))  # False
    

    JavaScript 範例

    function isAllZero(obj, eps = 1e-6) {
      const isNumZero = n => Math.abs(n) < eps;
      if (typeof obj === "number") return isNumZero(obj);
      if (Array.isArray(obj)) return obj.every(v => isAllZero(v, eps));
      if (obj && typeof obj === "object")
        return Object.values(obj).every(v => isAllZero(v, eps));
      return false;
    }
    
    console.log(isAllZero([[0,0],[0,0]]));     // true
    console.log(isAllZero({x:0, y:{z:0}}));    // true
    console.log(isAllZero({x:0.000001, y:0})); // 視 eps 而定
    

    C# (.NET) 範例

    using System;
    using System.Linq;
    using System.Reflection;
    
    public static class ZeroCheck {
        public static bool IsAllZero(object obj, double eps = 1e-6) {
            if (obj == null) return true;
            switch (obj) {
                case int i:    return i == 0;
                case long l:   return l == 0;
                case float f:  return Math.Abs(f) < eps;
                case double d: return Math.Abs(d) < eps;
            }
    
            var t = obj.GetType();
            if (t.IsArray)
                return ((Array)obj).Cast<object>().All(x => IsAllZero(x, eps));
    
            // 掃描欄位與屬性
            foreach (var f in t.GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic))
                if (!IsAllZero(f.GetValue(obj), eps)) return false;
    
            foreach (var p in t.GetProperties(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic))
                if (p.CanRead && !IsAllZero(p.GetValue(obj, null), eps)) return false;
    
            return true;
        }
    }
    

    Go 範例

    package main
    
    import (
        "math"
        "reflect"
    )
    
    func IsAllZero(v interface{}, eps float64) bool {
        val := reflect.ValueOf(v)
        switch val.Kind() {
        case reflect.Float32, reflect.Float64:
            return math.Abs(val.Float()) < eps
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
            return val.Int() == 0
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
            return val.Uint() == 0
        case reflect.Slice, reflect.Array:
            for i := 0; i < val.Len(); i++ {
                if !IsAllZero(val.Index(i).Interface(), eps) { return false }
            }
            return true
        case reflect.Map:
            for _, k := range val.MapKeys() {
                if !IsAllZero(val.MapIndex(k).Interface(), eps) { return false }
            }
            return true
        case reflect.Struct:
            for i := 0; i < val.NumField(); i++ {
                if !IsAllZero(val.Field(i).Interface(), eps) { return false }
            }
            return true
        case reflect.Pointer, reflect.Interface:
            if val.IsNil() { return true }
            return IsAllZero(val.Elem().Interface(), eps)
        default:
            return false
        }
    }
    

    C++ / Rust 的限制與做法

    實務建議



    訊息佇列

    定義與作用

    訊息佇列 (Message Queue,簡稱 MQ) 是一種用於軟體系統之間異步通信的架構模式。它允許獨立的應用程式或服務透過傳送和接收訊息來交換資訊,而無需直接相互調用或依賴於對方的即時狀態。MQ 的核心作用是作為一個**中間層**,暫時儲存訊息,直到目標接收者準備好處理它們。

    主要組成要素

    常見的訊息模式

    模式名稱 描述 典型應用場景
    點對點 (Point-to-Point, P2P) 訊息發送到一個佇列 (Queue),**僅有一個**接收者會從該佇列中取出並消費此訊息。一旦被消費,訊息即被刪除。 訂單處理、任務派發、工作負載均衡。
    發布/訂閱 (Publish/Subscribe, Pub/Sub) 訊息發佈到一個主題 (Topic),**所有**訂閱了該主題的接收者 (Subscriber) 都會收到該訊息的副本。 系統事件廣播、日誌收集、資料變更通知。

    訊息佇列的關鍵優勢

    結語

    訊息佇列是建構現代分佈式系統、微服務架構和高可用性應用程式的基石。它通過引入時間和空間上的間接性,顯著提高了系統的彈性、可伸縮性和穩健性。



    訊息佇列與 HTTP API 比較

    通信模式差異

    特性 訊息佇列 (MQ) HTTP API (REST/RPC)
    通信類型 異步 (Asynchronous) 同步 (Synchronous)
    耦合性 低耦合(發送者與接收者不直接互動) 高耦合(客戶端需要知道伺服器的位址並等待回覆)
    資料流程 單向,經由中間的 Broker 傳遞 雙向,請求與回應 (Request-Response)
    容錯性 高,Broker 儲存訊息,接收者離線也不會丟失 低,服務端離線或超時會導致請求失敗
    擴展性 高,可輕鬆增加多個消費者來處理負載 相對較低,需依賴負載均衡器來分配請求

    適用情境比較

    訊息佇列 (MQ) 的優勢與應用場景

    HTTP API 的優勢與應用場景

    結論

    MQ 和 HTTP API 並非互相取代的技術,而是針對不同問題而設計的。

    在現代的分佈式系統中,兩種模式經常並存,互相配合以滿足不同的業務需求。




    訊息佇列適合的應用實例

    1. 異步任務處理

    在許多網路應用中,有些操作是耗時的,如果讓使用者同步等待,會造成糟糕的使用者體驗。MQ 允許將這些任務轉為異步執行。

    2. 流量削峰與緩衝

    MQ 在處理瞬時高流量的場景中至關重要,可以保護後端服務免於崩潰。

    3. 系統解耦與微服務通信

    在複雜的分佈式系統和微服務架構中,MQ 用於隔離服務,減少相互依賴。

    4. 日誌收集與監控

    將大量的日誌資料從前端應用程式或伺服器收集到集中的處理系統。

    5. 數據串流處理

    特別是像 Apache Kafka 這樣的高吞吐量 MQ/串流平台,非常適合處理連續不斷的即時資料流。



    HTTP API 傳輸圖片與影片

    傳輸大型二進位資料的挑戰

    使用 HTTP API 傳輸圖片 (Images) 和影片 (Videos) 這些大型二進位資料 (Binary Data),主要的挑戰在於:

    HTTP API 上傳(發送)圖片/影片的方法

    1. 使用 multipart/form-data 格式 (最常用於檔案上傳)

    這是瀏覽器或客戶端應用程式上傳檔案最標準和最常用的方法。

    2. 直接使用二進位主體 (Binary Body)

    如果只需上傳一個單一檔案,可以直接將檔案的二進位內容作為請求的主體。

    3. 使用 Base64 編碼 (不建議用於大檔案)

    將二進位資料轉換成 ASCII 字串,嵌入到 JSON 或 XML 等文本格式中傳輸。

    HTTP API 下載(接收)圖片/影片的方法

    下載二進位資料相對簡單,伺服器直接將檔案的原始二進位內容作為 HTTP 回應的主體 (Response Body) 返回。

    影片傳輸的優化:分塊傳輸與串流

    對於特別大的檔案(尤其是影片),推薦使用以下技術來提高可靠性和效率:



    串流關鍵技術

    1. 傳輸協議 (Protocols)

    串流傳輸的核心在於如何高效、穩定且低延遲地將影音內容從伺服器傳輸到客戶端。

    2. 核心技術與概念

    適應性位元速率 (Adaptive Bitrate Streaming, ABR)

    這是現代串流服務的基石。伺服器會將同一段影音內容編碼成多個不同品質(位元速率、解析度)的版本。

    內容傳遞網路 (Content Delivery Network, CDN)

    對於面向全球使用者的串流服務來說,CDN 是不可或缺的。

    影音編解碼器 (Codecs)

    用於壓縮和解壓縮影音資料,以減少檔案大小。

    3. 內容保護與貨幣化



    Shell

    Bash - $?

    在 Bash 中,$? 代表上一個執行的命令的退出狀態碼(Exit Status)。這是一個整數值,通常用來判斷上一個命令是否成功執行。

    退出狀態碼的意義

    範例 1:檢查成功執行的命令

    #!/bin/bash
    ls
    echo "上一個命令的退出狀態碼是: $?"
        

    在這個範例中,ls 命令會列出目錄內容,執行成功後,$? 的值將是 0。

    範例 2:檢查失敗的命令

    #!/bin/bash
    ls /nonexistent-directory
    echo "上一個命令的退出狀態碼是: $?"
        

    在這個範例中,ls 嘗試列出不存在的目錄,命令會失敗,$? 的值將是一個非 0 數字。

    範例 3:使用退出狀態碼進行條件判斷

    #!/bin/bash
    cp file1.txt /some/directory/
    if [ $? -eq 0 ]; then
        echo "文件複製成功。"
    else
        echo "文件複製失敗。"
    fi
        

    此範例會根據命令的退出狀態來決定要顯示哪一條訊息。



    Bash 中 if 條件語句的 AND / OR 用法

    基本語法

    if [ 條件 ]; then
        指令
    fi

    使用 AND(且)

    要在 if 中同時滿足多個條件,可使用:

    1. 使用 -a(過時但可用)

    if [ "$a" -gt 0 -a "$b" -gt 0 ]; then
        echo "a 和 b 都大於 0"
    fi

    2. 使用 [[ ]] &&(推薦)

    if [[ "$a" -gt 0 && "$b" -gt 0 ]]; then
        echo "a 和 b 都大於 0"
    fi

    3. 使用多重 if 配合 &&

    if [ "$a" -gt 0 ] && [ "$b" -gt 0 ]; then
        echo "a 和 b 都大於 0"
    fi

    使用 OR(或)

    只要其中一個條件成立即可執行:

    1. 使用 -o(過時但可用)

    if [ "$a" -eq 0 -o "$b" -eq 0 ]; then
        echo "a 或 b 為 0"
    fi

    2. 使用 [[ ]] ||(推薦)

    if [[ "$a" -eq 0 || "$b" -eq 0 ]]; then
        echo "a 或 b 為 0"
    fi

    3. 使用多重 if 配合 ||

    if [ "$a" -eq 0 ] || [ "$b" -eq 0 ]; then
        echo "a 或 b 為 0"
    fi

    混合使用 AND 與 OR

    可搭配括號來控制優先順序:

    if [[ ( "$a" -gt 0 && "$b" -gt 0 ) || "$c" -eq 1 ]]; then
        echo "a 和 b 都大於 0,或 c 等於 1"
    fi

    注意事項



    Bash 檢查變數是否為空

    在 Bash 中,可以使用條件判斷來檢查變數是否為空,以下提供幾種常見的方式:

    範例 1:使用 -z 檢查變數是否為空

    #!/bin/bash
    
    var=""
    if [ -z "$var" ]; then
        echo "變數是空的"
    else
        echo "變數不是空的"
    fi
        

    -z 用來檢查變數是否為空,如果變數為空,條件成立。

    範例 2:使用 -n 檢查變數是否不為空

    #!/bin/bash
    
    var="some value"
    if [ -n "$var" ]; then
        echo "變數不是空的"
    else
        echo "變數是空的"
    fi
        

    -n 用來檢查變數是否不為空,如果變數有值,條件成立。

    範例 3:使用雙引號比較來檢查變數是否為空

    #!/bin/bash
    
    var=""
    if [ "$var" == "" ]; then
        echo "變數是空的"
    else
        echo "變數不是空的"
    fi
        

    這種方式直接將變數與空字串進行比較,來檢查變數是否為空。



    Bash 的陣列結構

    簡介

    Bash 支援兩種陣列:

    索引陣列

    定義方式

    fruits=("apple" "banana" "cherry")

    讀取元素

    echo "${fruits[0]}"     # apple
    echo "${fruits[1]}"     # banana

    列出全部元素

    echo "${fruits[@]}"

    取得陣列長度

    echo "${#fruits[@]}"

    新增元素

    fruits+=("date")

    遍歷元素

    for fruit in "${fruits[@]}"; do
        echo "$fruit"
    done

    關聯陣列

    定義與使用(需 Bash 4+)

    declare -A capital
    capital["Taiwan"]="Taipei"
    capital["Japan"]="Tokyo"

    存取與遍歷

    echo "${capital["Japan"]}"  # Tokyo
    
    for key in "${!capital[@]}"; do
        echo "$key 的首都是 ${capital[$key]}"
    done

    注意事項

    結論

    Bash 陣列是儲存多筆資料的重要結構,適合用於參數清單、目錄集、鍵值對等資料處理。



    Bash 合併兩個陣列

    基本語法

    combined=("${array1[@]}" "${array2[@]}")

    這會將兩個陣列中的所有元素合併為一個新陣列 combined

    完整範例

    array1=("apple" "banana")
    array2=("cherry" "date")
    
    combined=("${array1[@]}" "${array2[@]}")
    
    echo "合併後的陣列:"
    for item in "${combined[@]}"; do
        echo "$item"
    done

    合併至原陣列

    array1+=("${array2[@]}")

    這會將 array2 的內容直接加到 array1 後方。

    注意事項



    Bash 判斷字串是否以某字開頭

    使用字串模式比對(推薦)

    str="hello world"
    
    if [[ "$str" == hello* ]]; then
        echo "字串以 hello 開頭"
    fi

    說明: 使用 [[ ]] 支援 shell 的模式比對,* 代表任意字元。

    使用正規表示式

    if [[ "$str" =~ ^hello ]]; then
        echo "字串以 hello 開頭"
    fi

    說明: ^ 表示開頭,[[ ]] 中的 =~ 是正則運算。

    使用 case 敘述

    case "$str" in
      hello*) echo "字串以 hello 開頭" ;;
      *) echo "不是以 hello 開頭" ;;
    esac

    說明: case 是處理字串模式的好工具,簡潔且可讀性高。

    使用 expr(舊式寫法)

    if expr "$str" : '^hello' &> /dev/null; then
        echo "字串以 hello 開頭"
    fi

    說明: 使用 expr 的正則比對功能,適用於較舊版本的 shell。

    注意事項



    Bash 判斷檔案或目錄是否存在與不存在

    判斷是否存在:-e

    -e 用來檢查檔案或目錄是否存在(不分類型)。

    FILE="/etc/passwd"
    
    if [ -e "$FILE" ]; then
        echo "$FILE 存在"
    else
        echo "$FILE 不存在"
    fi

    判斷目錄是否不存在:! -d

    -d 用來檢查是否為目錄,! 是否定運算。

    DIR="/tmp/myfolder"
    
    if [ ! -d "$DIR" ]; then
        echo "$DIR 不存在,正在建立..."
        mkdir -p "$DIR"
    else
        echo "$DIR 已存在"
    fi

    其他常用的條件選項

    範例:結合檢查與建立

    PATH_TO_CHECK="/home/user/config"
    
    if [ -e "$PATH_TO_CHECK" ]; then
        echo "$PATH_TO_CHECK 已存在"
    else
        echo "$PATH_TO_CHECK 不存在,建立中..."
        mkdir -p "$PATH_TO_CHECK"
    fi

    建議



    列出所有子目錄存入陣列

    範例:儲存到陣列

    target_dir="/path/to/your/dir"
    subdirs=()
    
    while IFS= read -r -d $'\0' dir; do
        subdirs+=("$dir")
    done < <(find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0)

    說明

    範例輸出

    for dir in "${subdirs[@]}"; do
        echo "$dir"
    done

    替代簡易寫法(若無特殊字元)

    subdirs=( "$target_dir"/*/ )
    

    此寫法使用通配符匹配子目錄,但無法排除檔案或處理空格與特殊字元。



    取得包含 ~ 的實際路徑

    方法一:使用 eval 展開

    path="~/myfolder/file.txt"
    realpath "$(eval echo "$path")"
    

    方法二:以 $HOME 取代

    path="~/myfolder/file.txt"
    realpath "$(echo "$path" | sed "s|^~|$HOME|")"
    

    方法三:使用 readlink

    path="~/myfolder/file.txt"
    readlink -f "$(eval echo "$path")"
    

    建議用法

    realpath "$(eval echo "$path")"
    


    grep

    基本搜尋

    grep "關鍵字" 檔案名
    

    忽略大小寫

    grep -i "關鍵字" 檔案名
    

    顯示行號

    grep -n "關鍵字" 檔案名
    

    遞迴搜尋

    grep -r "關鍵字" 目錄路徑
    

    只顯示符合的文字

    grep -o "關鍵字" 檔案名
    

    同時顯示檔名

    grep -H "關鍵字" 檔案名
    

    兩組關鍵字同時出現 (AND)

    grep "關鍵字1" 檔案名 | grep "關鍵字2"
    

    兩組關鍵字任一出現 (OR)

    # 方法一:正規表示式
    grep -E "關鍵字1|關鍵字2" 檔案名
    
    # 方法二:多個 -e 參數
    grep -e "關鍵字1" -e "關鍵字2" 檔案名
    

    解決 binary file matches 問題

    # 方法一:強制將檔案視為文字
    grep -a "關鍵字" 檔案名
    
    # 方法二:轉換檔案編碼為 UTF-8 再搜尋
    iconv -f 原始編碼 -t UTF-8 檔案名 | grep "關鍵字"
    
    # 範例 (從 BIG5 轉成 UTF-8)
    iconv -f BIG5 -t UTF-8 檔案名 | grep "關鍵字"
    

    常用組合

    grep -rin "關鍵字" 目錄路徑
    


    -print0 搭配 read -d $'\0' 的詳解與範例

    用途說明

    在處理包含空格、特殊符號(如空白、引號、換行)的檔名或路徑時,傳統使用 find 配管線 xargsread 可能產生誤判。

    -print0 可讓 find 以 null(\0)字元作為輸出分隔,而 read -d $'\0' 能精準讀取 null 分隔的內容,確保每個檔案/路徑準確分離。

    基本範例:收集所有子目錄

    subdirs=()
    while IFS= read -r -d $'\0' dir; do
        subdirs+=("$dir")
    done < <(find . -type d -print0)

    說明

    範例 1:列出所有 .txt 檔案絕對路徑

    txt_files=()
    while IFS= read -r -d $'\0' file; do
        txt_files+=("$file")
    done < <(find "$(pwd)" -type f -name "*.txt" -print0)

    範例 2:將檔案傳送到某命令處理(安全)

    find . -type f -print0 | while IFS= read -r -d $'\0' file; do
        echo "處理:$file"
        # your_command "$file"
    done

    範例 3:僅處理包含空格的目錄

    find . -type d -print0 | while IFS= read -r -d $'\0' dir; do
        if [[ "$dir" == *" "* ]]; then
            echo "含空格的目錄:$dir"
        fi
    done

    為何不用換行?

    傳統用法如:

    find . -type f | while read file; do ...

    但若檔名內含換行,會導致 read 錯誤解析,甚至多行誤判為多個檔案。

    結論



    while read 只取得一個目錄的問題

    問題說明

    以下寫法在某些 Bash 或 Cygwin 環境中,只讀取第一個項目

    while IFS= read -r -d $'\0' dir; do
        subdirs+=("$dir")
    done < <(find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0)

    這通常是因為 `<(find ...)` 在某些系統(如 Cygwin)不是「真正的檔案描述符」,造成 `read` 無法持續讀取。

    修正方式:使用管線搭配 `read -d ''`(或 `readarray -d`)

    方法 1:使用 `find | while` 管線搭配 -print0

    subdirs=()
    find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0 | while IFS= read -r -d $'\0' dir; do
        subdirs+=("$dir")
    done

    **注意**:若希望 `subdirs` 在主程式中可用,這方法不適合(因 `while ... |` 子 shell 無法回傳陣列)

    方法 2:改存為陣列再處理

    mapfile -d '' -t subdirs < <(find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0)

    mapfile(或 readarray)可正確讀入 null 分隔字串為陣列。
    這是最可靠且能保留在主 shell 中的方式。

    範例輸出

    for dir in "${subdirs[@]}"; do
        echo "找到目錄:$dir"
    done

    總結



    從變數內容讀取資料(用 read)

    假設變數中有多行文字內容

    var="line1
    line2
    line3"

    用 while read 逐行讀取

    while IFS= read -r line; do
        echo "讀到:$line"
    done <<< "$var"

    解說

    單行讀取

    read -r first_line <<< "$var"
    echo "第一行為:$first_line"

    讀成陣列

    readarray -t lines <<< "$var"
    for line in "${lines[@]}"; do
        echo "$line"
    done


    找出以 abc 開頭的子目錄並依日期排序

    完整指令(依目錄修改時間排序)

    find . -mindepth 1 -maxdepth 1 -type d -name "abc*" -printf "%T@ %p\n" | sort -nr | cut -d' ' -f2-

    說明

    範例輸出

    ./abc_latest
    ./abc_old
    ./abc_2020

    若需存入 Bash 陣列

    readarray -t abc_dirs << <(
      find . -mindepth 1 -maxdepth 1 -type d -name "abc*" -printf "%T@ %p\n" |
      sort -nr | cut -d' ' -f2-
    )
    
    for dir in "${abc_dirs[@]}"; do
      echo "找到:$dir"
    done

    附註



    Bash 檢查儲存裝置是否可寫入

    方法 1:檢查目錄是否有寫入權限

    DIR="/mnt/usb"
    
    if [ -w "$DIR" ]; then
        echo "$DIR 可寫入"
    else
        echo "$DIR 不可寫入"
    fi

    -w 為檢查目錄是否有「寫入權限」。但若目錄本身可寫,但實際裝置唯讀(如 mount 成 readonly),這方法可能失效。

    方法 2:實際嘗試寫入測試檔案

    DIR="/mnt/usb"
    TESTFILE="$DIR/.write_test"
    
    if touch "$TESTFILE" 2>/dev/null; then
        echo "$DIR 可寫入"
        rm "$TESTFILE"
    else
        echo "$DIR 不可寫入"
    fi

    此方法最可靠,能檢測 mount 狀態或實體裝置是否真的可寫。

    方法 3:使用 mount 指令檢查是否唯讀掛載

    MNT="/mnt/usb"
    
    if mount | grep "$MNT" | grep -q "(ro,"; then
        echo "$MNT 為唯讀掛載"
    else
        echo "$MNT 為可寫掛載"
    fi

    此方法需檢查裝置是否被以唯讀(ro)方式掛載。

    建議



    Shell 重導向用法說明

    1. 標準輸出重導向:`>`

    在 Shell 中,使用 `>` 可以將指令的標準輸出(stdout)重導向到一個檔案或設備。若檔案已存在,內容會被覆蓋。

    echo "Hello" > output.txt

    這行指令將 "Hello" 寫入 output.txt 檔案。

    2. 追加輸出重導向:`>>`

    使用 `>>` 會將標準輸出附加到指定檔案的末尾,不會覆蓋原有內容。

    echo "Hello again" >> output.txt

    這行指令會將 "Hello again" 附加到 output.txt 的結尾。

    3. 標準錯誤重導向:`2>`

    在 Shell 中,`2>` 用於將標準錯誤(stderr)重導向到指定位置。例如:

    ls non_existent_file 2> error.log

    這行指令會將錯誤訊息寫入 error.log 檔案。

    4. 追加錯誤重導向:`2>>`

    若不想覆蓋錯誤訊息檔案,可使用 `2>>` 將錯誤訊息附加到檔案末尾。

    ls non_existent_file 2>> error.log

    這行指令會將錯誤訊息附加到 error.log

    5. 同時重導向標準輸出與錯誤:`&>`

    使用 `&>` 可以同時將標準輸出與標準錯誤都重導向到同一個檔案或設備。

    command &> all_output.log

    這行指令會將 command 的所有輸出(標準輸出與錯誤)寫入 all_output.log

    6. 合併標準錯誤到標準輸出:`2>&1`

    `2>&1` 將標準錯誤合併到標準輸出,便於統一管理。例如:

    command > output.log 2>&1

    這行指令會將標準輸出和錯誤都寫入 output.log

    7. 禁止所有輸出:`>/dev/null 2>&1`

    若不想顯示任何輸出,可將所有輸出導向到 /dev/null,如:

    command >/dev/null 2>&1

    這行指令會將 command 的所有輸出丟棄。



    iconv - tee 輸出以 UTF-8 編碼寫入文件

    在使用 tee 命令將輸出追加到文件時,可以通過 iconv 將輸出轉換為 UTF-8 編碼,確保文件內容以 UTF-8 保存。以下是具體的指令和範例。

    指令格式

    以下是將輸出保存為 UTF-8 編碼的 tee 指令格式:

    command | iconv -t utf-8 | tee -a output.txt

    範例

    以下範例演示如何將 ls 命令的輸出以 UTF-8 編碼寫入到 output.txt

    ls | iconv -t utf-8 | tee -a output.txt

    執行該指令後,output.txt 的內容將以 UTF-8 編碼保存,避免出現編碼錯誤。



    Windows cmd

    開啟方式

    常用指令

    進階操作

    管理員模式



    cmd /k 後執行第二個指令

    在同一個視窗執行多個指令

    可以使用 &&&|| 來接續指令:

    cmd /k "第一個指令 & 第二個指令"
    

    呼叫其他批次檔

    在第一個指令後加上 call,即可接續執行另一個批次檔:

    cmd /k "第一個指令 & call second.bat"
    

    使用 /c 執行後關閉

    若不需要保持視窗開啟,可以用 /c

    cmd /c "第一個指令 & 第二個指令"
    

    /c 會執行完所有指令後自動關閉視窗,而 /k 則會保留視窗。



    CMD 中篩選關鍵字

    使用 find 指令

    範例

    dir | find "txt"          :: 只顯示包含 "txt" 的檔案列
    ipconfig | find "IPv4"    :: 只顯示含有 IPv4 的行
    tasklist | find "chrome"  :: 篩出含有 chrome 的執行程序
    

    常用參數

    多關鍵字搜尋

    type log.txt | find "Error" | find "2025"
    type log.txt | findstr /I "error warning fail"

    進階:使用 findstr

    dir | findstr /R ".*\.txt$"   :: 使用正規表示式找出 .txt 檔
    type log.txt | findstr /I "timeout error fail"
    


    CMD 啟動時自動執行的設定方式

    概念說明

    方法一:使用 AutoRun 登錄值

    HKEY_CURRENT_USER\Software\Microsoft\Command Processor
    HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor
    
    AutoRun = "C:\Users\YourName\cmd_startup.bat"

    方法二:修改 CMD 快捷方式

    %SystemRoot%\System32\cmd.exe /k "C:\Users\YourName\cmd_startup.bat"

    方法三:使用登入啟動批次檔

    %AppData%\Microsoft\Windows\Start Menu\Programs\Startup

    對應說明

    Linux bash Windows CMD 對應方式
    ~/.bash_profile 或 ~/.bashrc 登錄表 AutoRun 或 CMD /k 啟動批次檔
    自動執行自定指令 批次檔內可放自定義環境變數與路徑設定


    自動設定 CMD AutoRun 的批次檔

    功能說明

    範例批次檔內容

    @echo off
    setlocal
    
    :: 設定 AutoRun 要執行的批次檔路徑
    set "AUTORUN_PATH=C:\Users\%USERNAME%\cmd_startup.bat"
    set "REG_PATH=HKCU\Software\Microsoft\Command Processor"
    
    echo 檢查 CMD AutoRun 設定中...
    
    :: 檢查登錄機碼是否存在
    reg query "%REG_PATH%" >nul 2>&1
    if errorlevel 1 (
        echo [資訊] 找不到 Command Processor 機碼,建立中...
        reg add "%REG_PATH%" /f >nul
    )
    
    :: 檢查 AutoRun 是否已設定
    reg query "%REG_PATH%" /v AutoRun >nul 2>&1
    if errorlevel 1 (
        echo [資訊] 未找到 AutoRun,建立新值...
        reg add "%REG_PATH%" /v AutoRun /d "%AUTORUN_PATH%" /f >nul
        echo [完成] AutoRun 已設定為:%AUTORUN_PATH%
    ) else (
        echo [OK] AutoRun 已存在,未做變更。
    )
    
    endlocal
    pause
    

    說明



    判斷批次檔運行終端機

    範例批次檔

    @echo off
    
    :: 偵測 PowerShell
    if defined PSModulePath (
        echo 目前在 PowerShell 執行
        goto :eof
    )
    
    :: 偵測 Cygwin
    if defined CYGWIN (
        echo 目前在 Cygwin 執行
        goto :eof
    )
    if defined SHELL (
        echo 目前在 Cygwin 執行 (SHELL=%SHELL%)
        goto :eof
    )
    
    :: 預設為 CMD
    echo 目前在 CMD 執行
    

    判斷依據



    PowerShell

    開啟方式

    常用指令

    進階操作

    管理員模式



    列出 PowerShell 中的所有變數

    指令方法

    範例

    # 列出所有變數
    Get-Variable
    
    # 只顯示變數名稱
    Get-Variable | Select-Object Name
    
    # 顯示特定變數的值
    Get-Variable PATH | Format-List *
    
    # 使用變數驅動器方式
    Get-ChildItem variable:
    

    說明



    C與C++語言

    高效能

    靜態型別系統

    資源控制

    語法靈活性

    廣泛應用領域

    跨平台



    C/C++ 預處理器指令

    概述

    預處理器指令 (Preprocessor Directives)是在 C 或 C++ 程式碼被編譯器正式處理(編譯)之前,由一個稱為「預處理器 (Preprocessor)」的程式模組所執行的命令。這些指令以井號(#)開頭,並且不以分號(;)結尾。

    預處理器的作用是執行文字替換、檔案包含、條件編譯等操作,從而產生最終的「翻譯單元 (Translation Unit)」供編譯器使用。

    主要指令類別與範例

    1. 檔案包含 (File Inclusion)

    用於將其他檔案的內容插入到當前檔案中。

    
    #include <iostream>  // 包含標準 I/O 庫
    #include "my_utility.h" // 包含自定義標頭檔
    

    2. 巨集定義與替換 (Macro Definition and Substitution)

    用於定義符號常數或代碼片段的文字替換巨集。

    
    #define MAX_SIZE 100 // 定義一個符號常數
    #define SUM(a, b) ((a) + (b)) // 定義一個帶參數的巨集
    
    int main() {
        int size = MAX_SIZE; // 預處理後變為 int size = 100;
        int total = SUM(5, 3); // 預處理後變為 int total = ((5) + (3));
        return 0;
    }
    

    3. 條件編譯 (Conditional Compilation)

    允許根據預處理器巨集的值,來決定程式碼的特定區塊是否應被編譯。

    
    #define DEBUG_MODE 1
    
    #if DEBUG_MODE
        std::cout << "偵錯模式已啟用" << std::endl;
    #else
        std::cout << "生產模式運行中" << std::endl;
    #endif
    
    #ifndef MY_HEADER_H
    #define MY_HEADER_H 
        // ... 標頭檔內容 (Include Guard 範例)
    #endif
    

    4. 錯誤與警告 (Error and Warning)

    
    #ifndef VERSION_DEFINED
    #error "必須定義版本號巨集 VERSION_DEFINED!"
    #endif
    

    5. 其他指令 (Other Directives)



    #pragma once

    定義與功能

    #pragma once 是 C 和 C++ 語言中的一種預處理器指令 (Preprocessor Directive)。它的核心功能是確保包含此指令的標頭檔 (Header File) 在單次編譯過程中只會被編譯器處理一次

    此指令的目的是為了解決標頭檔重複包含的問題,防止因為多個原始碼檔案或多層級的標頭檔包含關係,導致同一段程式碼(例如類別定義、函式原型或常數宣告)被編譯器看到多次,進而引發重複定義 (Redefinition) 的編譯錯誤。

    與 Include Guard 的比較

    在 C/C++ 標準中,傳統上透過使用 Include Guard (包含衛兵) 來達到防止重複包含的目的。#pragma once 提供了更簡潔的替代方案。

    Include Guard (標準方法)

    這是標準且可移植的方法,使用條件編譯指令:

    
    #ifndef MY_HEADER_H
    #define MY_HEADER_H
    
    // 標頭檔內容
    
    #endif // MY_HEADER_H
    

    它依賴於一個唯一的巨集名稱(例如 MY_HEADER_H)來控制內容是否被包含。

    #pragma once (非標準方法)

    使用單行指令:

    
    #pragma once
    
    // 標頭檔內容
    

    編譯器會自動追蹤它在當前編譯會話中是否已經處理過這個檔案,如果處理過,則直接跳過該檔案的剩餘內容。

    優點與缺點

    #pragma once 的優點:

    #pragma once 的缺點:

    結論

    在現代的開發環境中,特別是使用 Visual Studio 或主流編譯器時,#pragma once 因其簡潔和便利性,是防止標頭檔重複包含的常見且推薦的做法。



    #pragma warning

    定義與用途

    #pragma warning 是 C 和 C++ 語言中的預處理器指令,專門用於在程式碼的特定區塊內,選擇性地抑制、恢復或修改編譯器警告的嚴重程度

    此指令的主要目的在於提供比專案級別設定更精細的控制。例如,當您需要包含一個會產生大量警告的舊版程式庫,但又不希望因此而全域性地關閉這些警告時,就可以使用此指令。

    主要操作語法 (MSVC 格式)

    雖然 #pragma warning 的具體實作在不同編譯器間可能略有差異(尤其 MSVC 使用最廣),但其核心動作分為以下幾種:

    1. 禁用警告 (Disable)

    從該點開始,編譯器將忽略指定的警告代碼。

    
    #pragma warning( disable : 4996 ) // 禁用 C4996 (例如:關於使用不安全函式的警告)
    // 包含或撰寫會產生 C4996 的程式碼
    

    2. 恢復警告 (Default)

    將指定的警告恢復到專案或命令列設定的預設行為。

    
    #pragma warning( default : 4996 ) // 將 C4996 恢復為預設狀態
    

    3. 儲存與還原狀態 (Push and Pop)

    這是最安全且最推薦的模式。它允許在處理特定程式碼區塊時修改警告設定,然後確保原始的警告狀態在區塊結束後立即被還原,避免副作用。

    
    #pragma warning( push )             // 1. 儲存目前的警告設定
    #pragma warning( disable : 4996 4244 ) // 2. 禁用多個特定警告
    // 包含第三方標頭檔或舊版程式碼
    #pragma warning( pop )              // 3. 還原到原始設定
    // 後續的程式碼將使用還原後的設定
    

    4. 提升警告等級 (Error)

    將特定的警告代碼提升為編譯錯誤,若發生該警告,編譯將失敗。

    
    #pragma warning( error : 4005 ) // 將警告 4005 視為錯誤
    

    注意事項



    C++ 標準命名空間

    概述

    在 C++ 程式設計中,std:: 是指 Standard Namespace(標準命名空間)。它是一個容器,包含了 C++ 語言核心和其標準程式庫(Standard Library)中的所有標準實體,包括函式、類別、模板、巨集和物件。

    使用 std:: 的主要目的是為了避免命名衝突 (Name Collisions)。如果標準函式(例如 coutvector)沒有被放在單獨的命名空間內,當使用者自己定義了同名的實體時,編譯器就會不知道該使用哪一個。

    std:: 的主要內容

    C++ 標準程式庫的大部分功能都位於 std:: 命名空間內。主要類別和功能包括:

    1. 輸入/輸出串流 (I/O Streams)

    2. 標準容器 (Containers)

    用於儲存資料的集合類別:

    3. 演算法 (Algorithms)

    一組用於容器和範圍操作的通用函式:

    4. 其他核心工具 (Utilities)

    使用 std:: 的方式

    要存取 std:: 命名空間中的實體,有兩種主要方法:

    1. 完整限定名稱 (Fully Qualified Names)

    每次使用時都明確寫出 std:: 前綴。這是最安全且推薦的做法,尤其是在標頭檔中,以避免污染全域命名空間。

    
    int main() {
        std::cout << "Hello World" << std::endl;
        std::vector<int> numbers;
        return 0;
    }
    

    2. using 宣告 (using Directives)

    使用 using namespace std; 可以將整個 std:: 命名空間的內容引入到當前作用域,之後可以直接使用名稱而不需要 std:: 前綴。

    
    #include <iostream>
    using namespace std; // 將 std:: 命名空間引入
    
    int main() {
        cout << "Hello World" << endl; // 不需 std:: 前綴
        vector<int> numbers;
        return 0;
    }
    

    儘管 using namespace std; 很方便,但在大型專案或標頭檔中應極力避免使用,因為它會增加命名衝突的風險。



    檢查 std::vector 是否為空

    概述

    在 C++ 中,要檢查 std::vector 容器是否不包含任何元素(即大小為零),最標準且推薦的方法是使用其成員函式 empty()。這是因為 empty() 函式通常比直接檢查 size() 是否等於零更高效,特別是在某些容器實作中。

    檢查方法

    1. 使用 empty() 函式 (推薦)

    這是檢查 vector 是否為空的首選方法。它返回一個布林值:如果 vector 沒有元素,則返回 true;否則返回 false

    
    #include <vector>
    #include <iostream>
    #include <string>
    
    void check_empty(const std::vector<std::string>& vec)
    {
        if (vec.empty()) {
            std::cout << "Vector 是空的 (empty() == true)." << std::endl;
        } else {
            std::cout << "Vector 不是空的 (empty() == false). 元素數量: " << vec.size() << std::endl;
        }
    }
    
    int main()
    {
        std::vector<std::string> empty_vec;
        std::vector<std::string> non_empty_vec = {"apple", "banana"};
    
        check_empty(empty_vec);
        check_empty(non_empty_vec);
    
        return 0;
    }
    

    2. 檢查 size() 函式

    雖然有效,但這不是檢查空狀態的最慣用或潛在最高效的方法。它直接檢查 vector 中的元素數量是否為零。

    
    void check_size(const std::vector<int>& vec)
    {
        if (vec.size() == 0) {
            std::cout << "Vector 是空的 (size() == 0)." << std::endl;
        } else {
            std::cout << "Vector 不是空的 (size() != 0)." << std::endl;
        }
    }
    

    效率考量

    對於 std::vector 而言,empty()size() == 0 在時間複雜度上都是 $O(1)$,因為 vector 會將其大小作為成員變數儲存。然而,對於某些其他 C++ 標準容器(例如 std::liststd::forward_list),標準建議始終優先使用 empty(),因為它通常是 C++ 標準容器庫中檢查空狀態的通用且最快的慣用法。



    C++ 解析字串到字串向量

    概述

    在 C++ 中,要將一個包含空格(Space)或 Tab 字元(\t)分隔的字串解析成一個個獨立的單詞或標記,並儲存到 std::vector<std::string> 中,最常用的方法是利用 **std::stringstream**。檔案串流類別 std::stringstream 就像一個記憶體中的串流,它預設使用空白字元(包括空格、Tab 和換行符)作為分隔符來進行讀取操作。

    程式碼範例

    以下是使用 std::stringstream 完成此任務的 C++ 程式碼範例:

    
    #include <iostream>
    #include <sstream>   // 包含 stringstream
    #include <string>
    #include <vector>
    
    using namespace std;
    
    /**
     * 將輸入字串依據任何空白字元 (空格或 Tab) 分隔,並存入 vector。
     * @param input_str 要解析的字串。
     * @return 包含所有分離出的單詞的 vector。
     */
    vector<string> split_string_by_whitespace(const string& input_str)
    {
        vector<string> tokens;
        
        // 1. 建立 stringstream 物件,並用輸入字串初始化
        stringstream ss(input_str); 
        
        string token;
        
        // 2. 迴圈讀取
        // 串流提取運算子 (>>) 會自動以空白字元 (空格、Tab 等) 為分隔符讀取下一個 token。
        // 當成功讀取一個 token 時,運算子返回 true,否則 (到達字串結尾) 返回 false。
        while (ss >> token) 
        {
            tokens.push_back(token);
        }
        
        return tokens;
    }
    
    int main()
    {
        // 輸入字串包含多個空格和 Tab 符號 (\t)
        string test_string = "Hello \tWorld   this is \ta test string";
        
        cout << "原始字串: " << test_string << endl;
        
        vector<string> result = split_string_by_whitespace(test_string);
        
        cout << "--- 分隔結果 ---" << endl;
        
        for (size_t i = 0; i < result.size(); ++i)
        {
            cout << "Token " << i + 1 << ": [" << result[i] << "]" << endl;
        }
        
        return 0;
    }
    

    關鍵機制解釋



    .NET 與 std str 轉碼亂碼

    原因

    在 C++/CLI 中,gcnew System::String(stdStr.c_str()) 會將 std::string(通常是 ANSI / UTF-8 編碼)直接轉成 .NET System::String,而 System::String 預期是 UTF-16
    如果 stdStr 內含非 ASCII 字元(例如中文),就會出現亂碼。

    解法一:使用 marshal_as(建議)

    需引用命名空間:

    #include <msclr/marshal_cppstd.h>
    using namespace msclr::interop;
    
    std::string stdStr = "中文測試";
    System::String^ netStr = marshal_as<System::String^>(stdStr);
    

    ✅ 此方法能自動依 UTF-8 / ANSI 正確轉成 .NET Unicode。

    ---

    解法二:如果確定 std::string 是 UTF-8,可手動轉換

    #include <codecvt>
    #include <locale>
    
    std::string utf8Str = u8"中文測試";
    std::wstring wideStr = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>{}.from_bytes(utf8Str);
    System::String^ netStr = gcnew System::String(wideStr.c_str());
    
    ---

    解法三:如果來源是 std::wstring

    若 C++ 端原本使用寬字元字串,直接使用即可:

    std::wstring wstr = L"中文測試";
    System::String^ netStr = gcnew System::String(wstr.c_str());
    
    ---

    建議



    C++ 多維陣列初始化為 0

    使用 std::array 初始化

    在 C++ 中,可以使用 std::array 初始化多維陣列為 0:

    #include <iostream>
    #include <array>
    using namespace std;
    
    int main() {
        array<array<int, 4>, 3> arr = {0}; // 初始化所有元素為 0
    
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 4; j++) {
                cout << arr[i][j] << " ";
            }
            cout << endl;
        }
        return 0;
    }

    注意事項



    複製多維 std::array 的內容方法

    問題情境

    std::array<std::array<std::array<float, 2>, 2>, 2> twoLines;
    std::array<std::array<std::array<float, 2>, 2>, 2> twoLinesCopy;
    

    解法一:直接指定(= 運算子)

    std::array 支援淺層到深層的完整複製,因此可以直接使用 =

    twoLinesCopy = twoLines;
    

    解法二:使用 std::copy

    std::copy(twoLines.begin(), twoLines.end(), twoLinesCopy.begin());
    

    解法三:使用 std::memcpy(適合 POD 型別,這裡 float 適用)

    std::memcpy(&twoLinesCopy, &twoLines, sizeof(twoLines));
    

    建議



    .NET List

    基本介紹

    在 .NET(C++/CLI、C#、VB.NET 等語言)中,List<T> 是一個泛型集合類別, 可儲存任意型別的資料(T),並提供動態調整大小的功能。 它屬於 System::Collections::Generic 命名空間。

    宣告與初始化

    
    // 匯入命名空間
    using namespace System;
    using namespace System::Collections::Generic;
    
    int main()
    {
        // 宣告一個 List 儲存 int
        List<int>^ numbers = gcnew List<int>();
    
        // 初始化時直接加入元素
        List<String^>^ names = gcnew List<String^>({ "Alice", "Bob", "Charlie" });
    }
    

    新增、移除與存取元素

    
    List<int>^ nums = gcnew List<int>();
    
    // 新增元素
    nums->Add(10);
    nums->Add(20);
    nums->Add(30);
    
    // 插入指定位置
    nums->Insert(1, 15); // 在索引 1 插入 15
    
    // 移除指定值
    nums->Remove(20);
    
    // 移除指定索引
    nums->RemoveAt(0);
    
    // 取得元素
    int value = nums[1]; // 取得索引 1 的元素
    
    // 檢查是否包含
    if (nums->Contains(30))
        Console::WriteLine("找到 30");
    
    // 清空所有元素
    nums->Clear();
    

    遍歷 List

    
    // for 迴圈
    for (int i = 0; i < nums->Count; i++)
    {
        Console::WriteLine("第 {0} 個元素: {1}", i, nums[i]);
    }
    
    // for each 迴圈
    for each (int n in nums)
    {
        Console::WriteLine(n);
    }
    

    常用屬性與方法

    屬性 / 方法說明
    Count目前元素數量
    Capacity內部容量(可自動成長)
    Add(item)加入一個元素
    AddRange(collection)加入另一個集合的所有元素
    Insert(index, item)在指定位置插入元素
    Remove(item)移除指定值(找到第一個匹配項)
    RemoveAt(index)移除指定索引的元素
    Clear()移除所有元素
    Contains(item)檢查是否包含指定值
    IndexOf(item)回傳元素索引(若不存在則為 -1)
    Sort()排序元素(適用於可比較型別)
    Reverse()反轉元素順序

    範例輸出

    10
    15
    30
    


    計算 List 的標準差

    範例程式

    
    #include <cmath>
    #include <iostream>
    using namespace System;
    using namespace System::Collections::Generic;
    
    int main()
    {
        // 建立測試資料
        Dictionary<int, List<float>^>^ data = gcnew Dictionary<int, List<float>^>();
        data->Add(1, gcnew List<float>({ 1.2f, 2.3f, 3.4f }));
        data->Add(2, gcnew List<float>({ 4.5f, 5.5f, 6.5f, 7.5f }));
        data->Add(3, gcnew List<float>()); // 測試空清單
    
        // 計算每個 key 的標準差
        for each (KeyValuePair<int, List<float>^> entry in data)
        {
            int key = entry.Key;
            List<float>^ values = entry.Value;
    
            if (values == nullptr || values->Count == 0)
            {
                Console::WriteLine("Key {0}: 無資料", key);
                continue;
            }
    
            // 計算平均值
            double sum = 0.0;
            for each (float v in values)
            {
                sum += v;
            }
            double mean = sum / values->Count;
    
            // 計算變異數(母體變異數)
            double variance = 0.0;
            for each (float v in values)
            {
                double diff = v - mean;
                variance += diff * diff;
            }
            variance /= values->Count; // 若要樣本變異數,改為 (values->Count - 1)
    
            // 標準差
            double stddev = Math::Sqrt(variance);
    
            Console::WriteLine("Key {0}: 標準差 = {1:F4}", key, stddev);
        }
    
        return 0;
    }
    

    說明

    輸出結果範例

    Key 1: 標準差 = 0.8981
    Key 2: 標準差 = 1.1180
    Key 3: 無資料
    


    .NET Dictionary

    基本介紹

    在 .NET(C++/CLI、C#、VB.NET 等語言)中,Dictionary<TKey, TValue> 是一種泛型集合, 用於儲存「鍵值對 (key-value pair)」。每個 key 必須唯一,而 value 則可重複。 它屬於 System::Collections::Generic 命名空間。

    宣告與初始化

    
    // 匯入命名空間
    using namespace System;
    using namespace System::Collections::Generic;
    
    int main()
    {
        // 建立一個 Dictionary,Key 為 int,Value 為 String
        Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
    
        // 直接初始化
        users->Add(1, "Alice");
        users->Add(2, "Bob");
        users->Add(3, "Charlie");
    
        return 0;
    }
    

    新增、修改、刪除與存取元素

    
    Dictionary<String^, int>^ ages = gcnew Dictionary<String^, int>();
    
    // 新增元素
    ages->Add("Tom", 25);
    ages->Add("Jerry", 30);
    
    // 修改值(透過索引器)
    ages["Tom"] = 26;
    
    // 新增或修改(若 key 不存在則自動新增)
    ages["Spike"] = 40;
    
    // 刪除元素
    ages->Remove("Jerry");
    
    // 存取元素
    if (ages->ContainsKey("Tom"))
    {
        Console::WriteLine("Tom 的年齡是 {0}", ages["Tom"]);
    }
    
    // 嘗試取得值(避免例外)
    int age;
    if (ages->TryGetValue("Spike", age))
    {
        Console::WriteLine("Spike 的年齡是 {0}", age);
    }
    

    遍歷 Dictionary

    
    Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
    users->Add(1, "Alice");
    users->Add(2, "Bob");
    users->Add(3, "Charlie");
    
    // 使用 KeyValuePair 迴圈
    for each (KeyValuePair<int, String^> entry in users)
    {
        Console::WriteLine("Key = {0}, Value = {1}", entry.Key, entry.Value);
    }
    
    // 只取 Keys
    for each (int key in users->Keys)
    {
        Console::WriteLine("Key: {0}", key);
    }
    
    // 只取 Values
    for each (String^ name in users->Values)
    {
        Console::WriteLine("Name: {0}", name);
    }
    

    常用屬性與方法

    屬性 / 方法說明
    Add(key, value)新增一組鍵值對
    Remove(key)移除指定的鍵及其對應的值
    Clear()清空所有項目
    ContainsKey(key)檢查是否存在指定的鍵
    ContainsValue(value)檢查是否存在指定的值
    TryGetValue(key, out value)安全地取得值,不會拋出例外
    Count目前字典中的元素數量
    Keys回傳所有鍵的集合
    Values回傳所有值的集合

    範例輸出

    Key = 1, Value = Alice
    Key = 2, Value = Bob
    Key = 3, Value = Charlie
    Tom 的年齡是 26
    Spike 的年齡是 40
    


    C++ 模板 (Template)

    1. 什麼是模板 (Template)?

    在 C++ 中,模板 (Template) 是一種泛型編程的工具,允許我們在編寫函數或類別時,不指定具體的資料型別,從而使程式碼更具重用性。模板有助於在單一程式碼中處理不同型別的資料,避免重複的函數或類別定義。

    2. 函數模板 (Function Template)

    函數模板允許我們撰寫可以處理不同型別的函數。函數模板的語法如下:

    template <typename T>
    T add(T a, T b) {
        return a + b;
    }

    在此例中,add 函數可以處理任何支持加法操作的型別,如 intfloatdouble

    使用時,可以這樣呼叫:

    int result = add(3, 4);  // 使用 int 型別
    double result2 = add(3.5, 2.7);  // 使用 double 型別

    3. 類別模板 (Class Template)

    類別模板允許我們建立可以適用於多種型別的類別。類別模板的語法如下:

    template <typename T>
    class MyClass {
    private:
        T data;
    public:
        MyClass(T data) : data(data) {}
        T getData() { return data; }
    };

    此例中的 MyClass 類別可以使用任何型別的資料作為 data

    使用時可以這樣:

    MyClass<int> obj1(5);      // int 型別
    MyClass<double> obj2(3.14);  // double 型別

    4. 多個模板參數

    模板可以接受多個參數,例如:

    template <typename T, typename U>
    class Pair {
    private:
        T first;
        U second;
    public:
        Pair(T first, U second) : first(first), second(second) {}
        T getFirst() { return first; }
        U getSecond() { return second; }
    };

    這樣的模板類別可以存儲兩種不同型別的資料:

    Pair<int, double> pair1(1, 3.14);

    5. 模板特化 (Template Specialization)

    模板特化允許我們對特定型別的模板進行特別定義。例如:

    template <>
    class MyClass<int> {
    public:
        MyClass(int data) { /* 特化行為 */ }
    };

    這段程式碼特化了 MyClassint 型別的行為,使其與其他型別不同。

    6. 非型別模板參數 (Non-Type Template Parameters)

    模板還可以接受非型別的參數,例如常數值:

    template <typename T, int Size>
    class Array {
    private:
        T data[Size];
    public:
        int getSize() const { return Size; }
    };

    在這裡,Size 是一個非型別參數,表示陣列的大小。

    使用範例:

    Array<int, 10> arr;  // 建立大小為 10 的 int 型別陣列

    7. 結論

    C++ 模板功能強大,使得程式碼可以更具泛用性並減少重複。了解如何利用函數模板、類別模板及模板特化等技術,將大大提升程式設計的彈性與效能。



    C++ 中互相參照的 Class 解決方案

    1. 問題背景

    在 C++ 中,當兩個類別相互依賴並需要同時引用對方的成員時,直接在各自的 .h 檔案中 #include 對方的定義會造成循環引用問題,導致無法編譯。解決方法是使用「前向宣告」(forward declaration)來避免循環引用。

    2. 解決方案步驟

    3. 程式碼範例

    ClassA.h

    
    // ClassA.h
    #ifndef CLASSA_H
    #define CLASSA_H
    
    // 前向宣告 ClassB
    class ClassB;
    
    class ClassA {
    public:
        ClassA();
        void setB(ClassB* b); // 設定指向 ClassB 的指標
        void showBData();      // 顯示 ClassB 的數據
    
    private:
        ClassB* b; // 指向 ClassB 的指標
    };
    
    #endif
            

    ClassB.h

    
    // ClassB.h
    #ifndef CLASSB_H
    #define CLASSB_H
    
    // 前向宣告 ClassA
    class ClassA;
    
    class ClassB {
    public:
        ClassB(int data);
        int getData();         // 取得數據
        void setA(ClassA* a);  // 設定指向 ClassA 的指標
        void showAInfo();      // 顯示 ClassA 的資訊
    
    private:
        int data;
        ClassA* a; // 指向 ClassA 的指標
    };
    
    #endif
            

    ClassA.cpp

    
    #include "ClassA.h"
    #include "ClassB.h"
    #include <iostream>
    
    ClassA::ClassA() : b(nullptr) {}
    
    void ClassA::setB(ClassB* b) {
        this->b = b;
    }
    
    void ClassA::showBData() {
        if (b != nullptr) {
            std::cout << "ClassB data: " << b->getData() << std::endl;
        }
    }
            

    ClassB.cpp

    
    #include "ClassB.h"
    #include "ClassA.h"
    #include <iostream>
    
    ClassB::ClassB(int data) : data(data), a(nullptr) {}
    
    int ClassB::getData() {
        return data;
    }
    
    void ClassB::setA(ClassA* a) {
        this->a = a;
    }
    
    void ClassB::showAInfo() {
        if (a != nullptr) {
            a->showBData();
        }
    }
            

    4. 實作說明



    C++ Friend

    在 C++ 中,friend 關鍵字可以用於函數或類別,以允許其他函數或類別訪問類別的私有成員(private)和保護成員(protected)。這樣的設計可讓外部函數或類別進行操作,而不違反封裝原則。

    1. Friend 函數

    Friend 函數是一種被授權訪問另一類別的私有成員和保護成員的外部函數。在類別內部聲明時,以 friend 關鍵字修飾即可。

    範例如下:

    #include <iostream>
    using namespace std;
    
    class Box {
    private:
        double width;
    
    public:
        Box(double w) : width(w) {}
    
        // 聲明 friend 函數
        friend void showWidth(Box &b);
    };
    
    // friend 函數定義,能訪問 Box 類別的私有成員
    void showWidth(Box &b) {
        cout << "Box 寬度: " << b.width << endl;
    }
    
    int main() {
        Box box(10.5);
        showWidth(box);  // 訪問私有成員 width
        return 0;
    }
            

    在此範例中,showWidth 函數雖然是 Box 類別外的普通函數,但因為被聲明為 friend 函數,仍然能夠訪問 Box 類別的私有成員 width

    2. Friend 類別

    Friend 類別允許某一類別訪問另一類別的所有成員。這樣的設置在類別需要緊密協作時非常有用,但使用時應該謹慎,以避免過多暴露內部細節。

    範例如下:

    #include <iostream>
    using namespace std;
    
    class Square;  // 前置聲明
    
    class Rectangle {
    private:
        double width, height;
    
    public:
        Rectangle(double w, double h) : width(w), height(h) {}
    
        // 聲明 Square 類別為 friend
        friend class Square;
    };
    
    class Square {
    public:
        double areaOfRectangle(Rectangle &rect) {
            return rect.width * rect.height;
        }
    };
    
    int main() {
        Rectangle rect(5.0, 3.0);
        Square square;
        cout << "矩形面積: " << square.areaOfRectangle(rect) << endl;
        return 0;
    }
            

    在此範例中,Square 類別被宣告為 Rectangle 類別的 friend 類別,因此 Square 類別中的成員函數可以直接訪問 Rectangle 類別的私有成員 widthheight

    3. Friend 函數與 Friend 類別的應用情境

    在 C++ 中使用 friend 函數和 friend 類別需謹慎,過多使用會破壞類別的封裝性。因此,friend 關鍵字通常只在設計需要密切配合的類別或函數時使用。



    C++ 逐行讀取檔案

    概述

    在 C++ 中,最標準和推薦的逐行讀取文字檔案的方法是使用標準程式庫中的檔案串流類別 std::ifstream 配合 std::getline 函式。

    std::ifstream 負責開啟和管理檔案串流,而 std::getline 則負責從串流中讀取一整行文字(直到遇到換行符 \n),並將其儲存到 std::string 物件中。

    程式碼範例

    以下是一個完整的 C++ 範例,演示如何逐行讀取一個名為 example.txt 的檔案內容。

    
    #include <iostream>
    #include <fstream> // 包含檔案串流函式庫
    #include <string>  // 包含字串類別
    
    using namespace std; 
    
    void read_file_line_by_line(const string& filename)
    {
        // 1. 建立 std::ifstream 物件
        // 嘗試開啟指定的檔案。
        ifstream input_file(filename); 
        
        // 2. 檢查檔案是否成功開啟
        if (!input_file.is_open()) 
        {
            cerr << "錯誤: 無法開啟檔案 " << filename << endl;
            return;
        }
        
        string line;
        int line_number = 1;
        
        // 3. 使用 std::getline 函式進行逐行讀取
        // 循環條件 (getline(stream, string)) 在每次讀取成功時返回 true。
        // 當到達檔案結尾 (EOF) 或發生錯誤時,返回 false,循環結束。
        while (getline(input_file, line)) 
        {
            cout << "第 " << line_number << " 行: " << line << endl;
            line_number++;
        }
        
        // 4. 關閉檔案串流
        // 當 input_file 物件超出其作用域時,析構函式會自動關閉檔案,
        // 但明確呼叫 close() 也是可接受的做法。
        input_file.close(); 
        
        cout << "檔案讀取完成。" << endl;
    }
    
    int main()
    {
        read_file_line_by_line("example.txt");
        return 0;
    }
    

    關鍵概念



    C++ 逐行寫入檔案與欄位對齊

    概述

    在 C++ 中,要將資料結構(例如從 std::vector<std::string> 中讀取的 tokens)逐行寫入檔案,並確保每個欄位(token)在行中保持固定寬度的對齊,需要使用 **std::ofstream** 配合 **I/O 串流操縱器 (Stream Manipulators)** 來控制輸出格式。

    主要的格式控制工具有:

    程式碼範例

    以下是一個 C++ 範例,示範如何將一個包含多個欄位的二維數據結構(使用 std::vector<std::vector<std::string>> 模擬)寫入檔案,並確保每個 token 欄位的寬度固定為 15 個字元且靠左對齊。

    
    #include <iostream>
    #include <fstream> // 包含檔案輸出串流
    #include <string>
    #include <vector>
    #include <iomanip> // 包含 I/O 串流操縱器 (setw, left/right)
    
    using namespace std;
    
    // 模擬一個二維資料結構,每行包含多個欄位 (tokens)
    using TableData = vector<vector<string>>;
    
    void write_aligned_data(const string& filename, const TableData& data, int column_width)
    {
        // 1. 建立 std::ofstream 物件並開啟檔案
        ofstream output_file(filename); 
        
        // 2. 檢查檔案是否成功開啟
        if (!output_file.is_open()) 
        {
            cerr << "錯誤: 無法開啟檔案 " << filename << " 進行寫入。" << endl;
            return;
        }
    
        // 設定全域對齊方式:靠左對齊 (L-Justified)
        output_file << left; 
        
        // 3. 逐行寫入資料
        for (const auto& row : data) 
        {
            // 4. 逐 token 寫入並設定寬度
            for (const auto& token : row) 
            {
                // setw(width) 只影響緊隨其後的下一個輸出項
                output_file << setw(column_width) << token;
            }
            
            // 5. 行結束後插入換行符,開始新的一行
            output_file << "\n"; 
        }
        
        // 6. 關閉檔案串流
        output_file.close(); 
        
        cout << "資料已成功寫入檔案: " << filename << endl;
    }
    
    int main()
    {
        // 範例資料:包含四行,每行三個欄位
        TableData data = {
            {"Name", "Item", "Price"},
            {"Alice", "Book", "19.99"},
            {"BobJohnson", "PenSet", "4.50"},
            {"Charlie", "Notebook", "800.75"}
        };
        
        const int COLUMN_WIDTH = 15; // 定義每個欄位的寬度
        
        write_aligned_data("output_aligned.txt", data, COLUMN_WIDTH);
        
        return 0;
    }
    

    關鍵技術解析



    .NET C++ 讀取 HTML 表格

    概述

    在 .NET C++ (或更常見的 C++/CLI) 專案中,讀取和解析檔案中的 HTML 表格,通常需要藉助一個外部的 HTML 解析函式庫,因為標準的 .NET 框架和 C++ 本身沒有內建強大的 HTML DOM 解析功能。一個常見且高效的選擇是使用 C# 或其他 .NET 語言的函式庫,然後在 C++/CLI 中引用。

    對於 C++/CLI 專案,最實用且推薦的方法是使用 Html Agility Pack (HAP),這是一個非常流行的 .NET HTML 解析器。雖然 HAP 是用 C# 編寫的,但它可以無縫地在任何 .NET 語言(包括 C++/CLI)中作為參考使用。

    使用 Html Agility Pack 的步驟

    1. 建立 C++/CLI 專案: 確保您的專案是 C++/CLI 專案(例如:CLR Console Application 或 Windows Forms App)。
    2. 獲取 Html Agility Pack:
      • 透過 Visual Studio 的 NuGet 套件管理器,搜尋並安裝 HtmlAgilityPack
    3. 程式碼實作(C++/CLI):

    程式碼範例

    以下是一個 C++/CLI 的程式碼片段,演示如何讀取一個本地 HTML 檔案並解析其中的第一個表格 (<table>) 的內容:

    
    #using <System.dll>
    #using <System.Xml.dll>
    #using <HtmlAgilityPack.dll> // 確保已加入參考
    
    using namespace System;
    using namespace System::IO;
    using namespace HtmlAgilityPack;
    
    void ParseHtmlTable(String^ filePath)
    {
        // 檢查檔案是否存在
        if (!File::Exists(filePath))
        {
            Console::WriteLine("錯誤:檔案不存在。");
            return;
        }
    
        // 1. 載入 HTML 文件
        HtmlDocument^ doc = gcnew HtmlDocument();
        try
        {
            // 從檔案載入 HTML
            doc->Load(filePath);
        }
        catch (Exception^ ex)
        {
            Console::WriteLine("載入檔案時發生錯誤:" + ex->Message);
            return;
        }
    
        // 2. 選擇第一個 <table> 節點
        HtmlNode^ table = doc->DocumentNode->SelectSingleNode("//table");
    
        if (table != nullptr)
        {
            Console::WriteLine("找到 HTML 表格,開始解析...");
    
            // 3. 選擇所有的 <tr> (表格列) 節點
            HtmlNodeCollection^ rows = table->SelectNodes(".//tr");
    
            if (rows != nullptr)
            {
                for each (HtmlNode^ row in rows)
                {
                    // 4. 選擇 <td> 或 <th> (儲存格) 節點
                    // 使用 | 運算子選擇 <td> 或 <th>
                    HtmlNodeCollection^ cells = row->SelectNodes("td | th"); 
    
                    if (cells != nullptr)
                    {
                        String^ rowData = "";
                        for each (HtmlNode^ cell in cells)
                        {
                            // 獲取儲存格的內部文字並去除首尾空白
                            rowData += cell->InnerText->Trim() + "\t"; 
                        }
                        Console::WriteLine(rowData);
                    }
                }
            }
            else
            {
                Console::WriteLine("表格中未找到 <tr> 標籤。");
            }
        }
        else
        {
            Console::WriteLine("檔案中未找到 <table> 標籤。");
        }
    }
    
    int main(array<String^>^ args)
    {
        // 請將 "your_html_file.html" 替換為您的實際檔案路徑
        String^ htmlFilePath = "C:\\path\\to\\your_html_file.html"; 
        ParseHtmlTable(htmlFilePath);
        return 0;
    }
    

    關鍵技術點解釋



    .NET C++ HTTP API 伺服器

    使用 HttpListener 類別

    HttpListener 類別允許您在 C++/CLI 應用程式中建立一個簡單的、自託管(Self-Hosted)的 HTTP 伺服器。

    專案要求與設定

    在 Visual Studio 中建立一個 C++/CLI 專案(例如 CLR Console Application),並確保已引用 System.dll

    程式碼範例

    以下是使用 HttpListener 建立一個簡單 API 伺服器並處理 GET 請求的 C++/CLI 程式碼。

    
    #using <System.dll>
    
    using namespace System;
    using namespace System::Net;
    using namespace System::Threading::Tasks;
    using namespace System::Text;
    
    // 處理傳入 HTTP 請求的函式
    void HandleRequest(HttpListenerContext^ context)
    {
        HttpListenerRequest^ request = context->Request;
        HttpListenerResponse^ response = context->Response;
    
        // 預設回應設定
        response->ContentType = "application/json; charset=utf-8";
        String^ responseString = "";
        response->StatusCode = 200; // 預設為 OK
    
        // 檢查請求的路徑和方法
        String^ url = request->RawUrl->ToLower();
        
        if (url == "/api/status" && request->HttpMethod == "GET")
        {
            // 處理 GET /api/status 請求
            responseString = "{\"status\": \"Server Running\", \"language\": \"C++/CLI\"}";
        }
        else
        {
            // 處理其他未定義的請求
            response->StatusCode = 404; // Not Found
            responseString = "{\"error\": \"404 Not Found\", \"path\": \"" + url + "\"}";
        }
    
        // 將字串回應轉換為位元組,並寫入輸出串流
        array<Byte>^ buffer = Encoding::UTF8->GetBytes(responseString);
        response->ContentLength64 = buffer->Length;
        
        try
        {
            response->OutputStream->Write(buffer, 0, buffer->Length);
            response->OutputStream->Close();
        }
        catch (Exception^)
        {
            // 忽略寫入錯誤,通常是客戶端斷開連線
        }
    }
    
    // 啟動 HttpListener 的函式
    void StartListener(String^ prefix)
    {
        HttpListener^ listener = gcnew HttpListener();
        
        try
        {
            listener->Prefixes->Add(prefix);
            listener->Start();
            Console::WriteLine("C++/CLI API Server 啟動,正在監聽: {0}", prefix);
        }
        catch (Exception^ ex)
        {
            Console::WriteLine("啟動 HttpListener 發生錯誤。請確認權限或位址是否已被佔用。");
            Console::WriteLine("錯誤訊息: {0}", ex->Message);
            return;
        }
    
        // 異步迴圈,持續接收請求
        while (listener->IsListening)
        {
            try
            {
                // 同步等待下一個請求
                HttpListenerContext^ context = listener->GetContext();
                
                // 使用 Task 異步處理請求,避免阻塞監聽迴圈
                Task::Factory->StartNew(gcnew Action<HttpListenerContext^>(&HandleRequest), context);
            }
            catch (Exception^)
            {
                // 監聽器停止時會拋出例外
                break;
            }
        }
        
        if (listener->IsListening)
        {
            listener->Close();
        }
    }
    
    int main(array<String^>^ args)
    {
        // 設定監聽的 URL 前綴
        // 注意:在 Windows 中,可能需要管理員權限或使用 netsh 註冊 URL 命名空間。
        String^ listeningPrefix = "http://localhost:8080/"; 
        StartListener(listeningPrefix);
        
        Console::WriteLine("按下任意鍵結束伺服器...");
        Console::ReadKey(true);
        
        return 0;
    }
    

    執行權限注意

    在 Windows 系統中,如果程式碼沒有以管理員權限運行,HttpListener 監聽特定埠時可能會拋出存取拒絕的例外。您可以透過以下命令註冊 URL 命名空間,以允許非管理員帳戶運行伺服器:

    
    netsh http add urlacl url=http://+:8080/ user=Everyone
    

    其中 http://+:8080/ 應替換為您程式碼中實際使用的前綴。



    ASP.NET Core C# HTTP API 伺服器

    概述

    在 ASP.NET Core 中,建立 HTTP API 伺服器的標準專案類型是 Web API。最新的 .NET 版本(.NET 6 及更高版本)推薦使用 Minimal APIs 來快速且輕量地建立 API 端點。

    Minimal APIs 使用最少的檔案和程式碼行,將啟動邏輯 (Startup) 和路由定義 (Routing) 合併到一個檔案 Program.cs 中。

    專案設定 (使用 CLI)

    要建立一個新的 Minimal API 專案,您可以使用 .NET CLI (命令列介面):

    
    dotnet new web -n MinimalApiServer
    cd MinimalApiServer
    

    程式碼範例:Program.cs

    以下是 Minimal API 伺服器核心檔案 Program.cs 的完整內容。它定義了伺服器的啟動、中介軟體和兩個 API 端點 (一個 GET,一個 POST)。

    
    // 1. 建立 WebApplication 實例 (取代了傳統的 Startup 類別)
    var builder = WebApplication.CreateBuilder(args);
    
    // 2. 註冊服務 (Service Configuration)
    // 這裡可以加入資料庫連線、驗證服務等
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(); // 啟用 Swagger/OpenAPI 支援
    
    var app = builder.Build();
    
    // 3. 配置 HTTP 請求管線 (Middleware Configuration)
    
    // 開發環境下,啟用 Swagger UI 以供測試
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    // 啟用 HTTPS 重新導向 (生產環境推薦)
    // app.UseHttpsRedirection(); 
    
    // 4. 定義 API 端點 (Endpoint Definition)
    // 實體類別用於 POST 請求
    public record Product(int Id, string Name, decimal Price);
    
    // GET 請求端點:/api/hello
    app.MapGet("/api/hello", () => 
    {
        return Results.Ok(new { message = "Hello from ASP.NET Core Minimal API!", timestamp = DateTime.Now });
    });
    
    // GET 請求端點:/api/products/{id}
    app.MapGet("/api/products/{id}", (int id) => 
    {
        // 假設從資料庫查詢產品
        if (id == 1)
        {
            return Results.Ok(new Product(1, "筆記型電腦", 1200.00m));
        }
        return Results.NotFound(new { error = $"Product ID {id} not found" });
    });
    
    // POST 請求端點:/api/products
    app.MapPost("/api/products", (Product product) => 
    {
        // ASP.NET Core 會自動將 JSON 請求主體解序列化為 Product 實體
        Console.WriteLine($"收到新產品: {product.Name} (ID: {product.Id})");
        
        // 實際應用中,這裡會執行資料庫新增操作
        // 回傳 201 Created 狀態碼
        return Results.Created($"/api/products/{product.Id}", product);
    });
    
    // 5. 啟動應用程式,開始監聽 HTTP 請求
    app.Run();
    

    執行與測試

    1. 運行伺服器: 在專案目錄下執行 dotnet run
    2. 測試端點: 伺服器通常預設在 http://localhost:5000https://localhost:7000 運行。
      • 使用瀏覽器或工具 (如 Postman) 訪問 /api/hello (GET)。
      • 訪問 /swagger 路徑可以查看 Swagger UI,用於測試所有定義的 API 端點。

    關鍵技術點



    週邊控制程式開發

    定義

    週邊控制程式是用來與電腦或主機連接的外部硬體裝置(例如:印表機、掃描器、馬達、PLC、感測器等)進行通訊與操作的應用程式。

    常見通訊介面

    常見開發語言與環境

    語言適用情境備註
    Python快速開發、測試自動化適用 Serial、USB HID
    C/C++嵌入式控制、驅動開發可存取低階記憶體與硬體
    C#Windows UI + 控制裝置適用於 COM Port、USB 通訊
    Java跨平台控制較少用於低階裝置

    範例一:使用 Python 控制串列埠

    import serial
    
    ser = serial.Serial('COM3', 9600, timeout=1)
    ser.write(b'ON\n')  # 傳送控制命令
    response = ser.readline()
    print("裝置回應:", response.decode())
    ser.close()

    範例二:C# 控制 COM Port 裝置

    SerialPort port = new SerialPort("COM4", 9600);
    port.Open();
    port.WriteLine("MOVE 100");
    string response = port.ReadLine();
    Console.WriteLine("回應:" + response);
    port.Close();

    範例三:控制 USB HID 裝置

    範例四:TCP 通訊控制週邊

    import socket
    
    s = socket.socket()
    s.connect(('192.168.1.100', 5000))
    s.sendall(b'START\n')
    data = s.recv(1024)
    print("回應:", data.decode())
    s.close()

    應用案例

    注意事項

    結論

    週邊控制程式開發需依據裝置的介面與協定選擇適合的語言與函式庫,結合串列、USB、網路等通訊方式,可廣泛應用於各種自動化與工控領域。



    Barcode Reader 程式開發

    常見平台支援

    常用條碼類型

    常見開發套件與工具

    Android 開發範例(使用 ZXing)

    // build.gradle
    implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
    
    // Java 呼叫掃描畫面
    IntentIntegrator integrator = new IntentIntegrator(this);
    integrator.setPrompt("請掃描條碼");
    integrator.setBeepEnabled(true);
    integrator.setOrientationLocked(false);
    integrator.initiateScan();
    // onActivityResult 接收結果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if(result != null) {
            if(result.getContents() != null) {
                String barcode = result.getContents();
                Log.d("條碼內容", barcode);
            }
        }
    }

    Web 開發範例(使用 QuaggaJS)

    <script src="https://unpkg.com/[email protected]/dist/quagga.min.js"></script>
    
    <script>
    Quagga.init({
        inputStream: {
            name: "Live",
            type: "LiveStream",
            target: document.querySelector('#scanner')
        },
        decoder: {
            readers: ["code_128_reader", "ean_reader", "upc_reader"]
        }
    }, function(err) {
        if (!err) {
            Quagga.start();
        }
    });
    
    Quagga.onDetected(function(result) {
        console.log("條碼內容: ", result.codeResult.code);
    });
    </script>

    平台與套件比較

    平台建議套件是否免費
    AndroidZXing / ML Kit
    WebQuaggaJS / jsQR
    WindowsDynamsoft / ZXing.NETDynamsoft 為商用
    PythonZBar / pyzbar

    結論

    開發 Barcode Reader 可根據平台選擇合適的開源套件,ZXing 是最廣泛支援的選擇,Web 可用 QuaggaJS,若需商業級支援則可考慮 Dynamsoft Barcode SDK。



    條碼帶控制字元

    基本原理

    條碼讀取器本質上是模擬鍵盤輸入的裝置。當掃描到條碼時,它會將條碼中的內容「輸出」成字元流,就像是鍵盤輸入一樣。

    因此,條碼中是可以包含控制碼(Control Code),但需要條碼讀取器與條碼格式支援才行。

    條碼中常見的控制碼類型

    條件一:條碼格式需支援控制碼

    例如以下格式可編碼控制碼:

    條件二:條碼編碼工具需支援嵌入控制碼

    部分條碼產生器允許嵌入特殊字元,例如:

    條件三:條碼掃描器需支援輸出控制碼

    多數專業條碼機(例如 Zebra、Honeywell)在出廠時預設不啟用控制碼輸出,需透過掃描器提供的「設定條碼」啟用:

    條件四:作業系統與應用程式需正確接收

    例如在 Windows 應用程式中:

    示例:編碼 Ctrl-C 的條碼內容

    用 Code128 編碼 ASCII 3:

    輸入:\x03Hello World

    條碼掃描後會觸發 Ctrl+C 再輸出 "Hello World"。

    結論



    Android 開發

    Android 開發平台

    平台簡介

    Android 是一個由 Google 開發的開放原始碼行動作業系統,廣泛應用於智慧型手機、平板電腦和其他智慧裝置。開發者可利用其豐富的 API 和工具創建多樣化的應用程式。

    開發工具

  • Android Studio:Google 官方提供的整合開發環境 (IDE),支援程式碼編輯、模擬器測試及除錯功能。
  • Kotlin 和 Java:Android 開發主要使用的程式語言,其中 Kotlin 是官方推薦語言。
  • Android SDK:提供模擬器、API 庫及工具集,支援應用程式的開發與測試。
  • Gradle:用於管理相依性及建置自動化的工具。

    應用程式生命週期

  • 啟動 (onCreate):應用程式啟動並初始化資源。
  • 運行 (onStart):畫面顯示給使用者。
  • 活動 (onResume):應用程式進入互動狀態。
  • 暫停 (onPause):應用程式部分隱藏或失去焦點。
  • 停止 (onStop):完全隱藏,可能被系統回收。
  • 銷毀 (onDestroy):應用程式被關閉並釋放所有資源。

    優勢

  • 開放性:支援自訂與廣泛的裝置相容性。
  • 市場規模:Google Play 提供全球範圍的應用程式發佈機會。
  • 強大工具:豐富的開發工具和文件資源,有助於開發效率提升。

    學習資源

  • 官方文件:Android Developers 提供詳細的文件與範例。
  • 開發社群:如 Stack Overflow、Reddit 等平臺提供技術支援。
  • 線上課程:可選擇 Udemy、Coursera 或 YouTube 的相關課程。

    結語

    Android 開發平台以其靈活性和強大功能吸引了眾多開發者,成為行動應用程式開發的首選之一。

    Chromebook 使用 Android Studio

    安裝步驟

    1. 開啟 設定 → 開發人員 → Linux 開發環境(Crostini),分配至少 20GB 磁碟空間與 8GB 記憶體。
    2. 更新套件:
      sudo apt update && sudo apt upgrade -y
      sudo apt install -y wget curl unzip zip git ca-certificates
    3. 安裝 JDK(建議版本 17):
      sudo apt install -y openjdk-17-jdk
    4. 下載並安裝 Android Studio:
      sudo dpkg -i ~/Downloads/android-studio-*.deb || sudo apt -f install -y
      android-studio
      或解壓縮 .tar.gz
      tar -xzf ~/Downloads/android-studio-*.tar.gz -C ~
      ~/android-studio/bin/studio.sh
    5. 依照安裝精靈完成 SDK 與工具設定。

    使用實體裝置測試

    1. 在 ChromeOS:設定 → 開發人員 → Linux → USB 裝置,勾選手機共享給 Linux。
    2. 手機開啟 開發人員選項 → USB 偵錯,連線後允許 RSA 指紋。
    3. Linux 環境確認裝置:
      sudo apt install -y android-sdk-platform-tools
      adb kill-server
      adb start-server
      adb devices

    效能與模擬器建議

    替代方案



    Chromebook 使用 VS Code + Android SDK

    安裝 VS Code

    1. 在 ChromeOS 開啟 設定 → 開發人員 → Linux 開發環境(Crostini)
    2. 下載 VS Code Linux 版(.deb 檔),並於 Linux 容器安裝:
      sudo dpkg -i ~/Downloads/code_*.deb || sudo apt -f install -y
    3. 啟動 VS Code,安裝常用擴充套件(如 KotlinFlutterJava Extension Pack)。

    安裝 Android SDK 與工具

    1. 安裝必要套件:
      sudo apt update && sudo apt install -y openjdk-17-jdk unzip git
    2. 下載 Android SDK Command Line Tools:
      wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
      unzip commandlinetools-linux-*_latest.zip -d ~/android-sdk
    3. 設定環境變數(可加入 ~/.bashrc~/.zshrc):
      export ANDROID_SDK_ROOT=$HOME/android-sdk
      export PATH=$ANDROID_SDK_ROOT/cmdline-tools/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH
    4. 使用 sdkmanager 安裝平台工具與必要套件:
      sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"

    連接實體手機測試

    1. 在 ChromeOS:設定 → 開發人員 → Linux → USB 裝置,勾選手機共享。
    2. 手機開啟 開發人員選項 → USB 偵錯
    3. Linux 容器確認裝置:
      adb devices
      看到 device 即可部署測試。

    在 VS Code 中開發

    適合情境



    Android App 範例

    建立新專案

    在 Android Studio 中建立一個新的專案,選擇 "Empty Activity" 模板,然後設定專案名稱及其他基本資訊。

    編寫介面 (activity_main.xml)

    res/layout/activity_main.xml 檔案中設計簡單的使用者介面,例如包含一個按鈕和一個文字顯示區域:

            <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:gravity="center">
            
                <Button
                    android:id="@+id/button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="點我" />
            
                <TextView
                    android:id="@+id/textView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Hello, World!"
                    android:layout_marginTop="20dp" />
            
            </LinearLayout>
    

    編寫程式邏輯 (MainActivity.java)

    MainActivity.java 中,設定按鈕的點擊事件,使其更改文字顯示區域的內容:

            package com.example.simpleapp;
    
            import android.os.Bundle;
            import android.view.View;
            import android.widget.Button;
            import android.widget.TextView;
            import androidx.appcompat.app.AppCompatActivity;
    
            public class MainActivity extends AppCompatActivity {
                @Override
                protected void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.activity_main);
    
                    Button button = findViewById(R.id.button);
                    TextView textView = findViewById(R.id.textView);
    
                    button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            textView.setText("你點了按鈕!");
                        }
                    });
                }
            }
        

    執行應用程式

    在 Android Studio 中點擊執行按鈕,即可在模擬器或連接的實體裝置上測試應用程式。點擊按鈕後,文字將更改為 "你點了按鈕!"。



    Android 取得目前GPS位置

    權限設定

    AndroidManifest.xml 中加入以下權限:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    檢查與請求權限(Android 6.0 以上)

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
    }

    取得 LocationManager

    LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    取得目前位置

    LocationListener locationListener = new LocationListener() {
        @Override
        public void onLocationChanged(@NonNull Location location) {
            double latitude = location.getLatitude();
            double longitude = location.getLongitude();
            Log.d("GPS", "Latitude: " + latitude + ", Longitude: " + longitude);
        }
    };
    
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 
            1000, // 毫秒間隔
            1,    // 最小距離(公尺)
            locationListener);
    }

    使用 FusedLocationProviderClient(推薦)

    FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
    
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
        fusedLocationClient.getLastLocation()
            .addOnSuccessListener(this, location -> {
                if (location != null) {
                    double lat = location.getLatitude();
                    double lng = location.getLongitude();
                    Log.d("GPS", "Lat: " + lat + ", Lng: " + lng);
                }
            });
    }


    音控 Android App

    基本概念

    要實作一個類似 Siri 或 Hey Google 的語音助理功能,你需要結合以下元件:

    加入必要權限

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.INTERNET"/>

    語音辨識初始化

    SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(this);
    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
    
    recognizer.setRecognitionListener(new RecognitionListener() {
        @Override
        public void onResults(Bundle results) {
            ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            if (matches != null && !matches.isEmpty()) {
                String command = matches.get(0).toLowerCase();
                if (command.contains("打開相機")) {
                    // 執行操作
                }
            }
        }
        // 其他必要的覆寫方法略
    });
    
    recognizer.startListening(intent);

    語音合成(回應用戶)

    TextToSpeech tts = new TextToSpeech(this, status -> {
        if (status == TextToSpeech.SUCCESS) {
            tts.setLanguage(Locale.TAIWAN);
            tts.speak("你好,我在這裡。", TextToSpeech.QUEUE_FLUSH, null, null);
        }
    });

    常駐背景監聽(選擇性)

    如需常駐背景並語音喚醒,需使用:

    注意事項



    常駐背景監聽

    目的

    常駐背景監聽的目標,是讓 App 即使在未開啟畫面時也能偵測語音喚醒詞(如「Hey 助理」),並啟動對應功能。

    挑戰

    解法架構

    1. 使用 前景服務(Foreground Service)保持持續運作。
    2. 語音喚醒採用 Porcupine 等離線 Hotword Engine。
    3. 喚醒成功後,啟動 SpeechRecognizer 辨識完整語音命令。

    Step 1: 前景服務建立

    public class VoiceService extends Service {
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Notification notification = new NotificationCompat.Builder(this, "voice_channel")
                .setContentTitle("語音助手運作中")
                .setSmallIcon(R.drawable.ic_mic)
                .build();
            startForeground(1, notification);
    
            // 初始化 Hotword 檢測
            startHotwordDetection();
            return START_STICKY;
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }

    Step 2: 建立通知頻道(Android 8+)

    NotificationChannel channel = new NotificationChannel("voice_channel",
        "Voice Assistant", NotificationManager.IMPORTANCE_LOW);
    NotificationManager manager = getSystemService(NotificationManager.class);
    manager.createNotificationChannel(channel);

    Step 3: 使用 Porcupine 進行語音喚醒

    Porcupine 提供 Android SDK,可辨識自訂關鍵詞並在完全離線情況下運作。

    PorcupineManager porcupineManager = new PorcupineManager.Builder()
        .setAccessKey("你的金鑰")
        .setKeywordPath("hey_assistant.ppn")
        .setSensitivity(0.7f)
        .build((keywordIndex) -> {
            // 被喚醒時呼叫 SpeechRecognizer 進行語音辨識
            startSpeechRecognition();
        });
    
    porcupineManager.start();

    Step 4: 啟動服務

    Intent serviceIntent = new Intent(this, VoiceService.class);
    ContextCompat.startForegroundService(this, serviceIntent);

    注意事項



    Hey Google 語音助理的優化

    使用者端最佳化建議

    開發者端最佳化方式

    語音喚醒觸發條件優化

    注意限制



    Apple 產品程式開發

    開發工具

    開發環境設定

    1. 下載並安裝最新版本的 Xcode

    2. 開啟 Xcode 並前往 Preferences > Accounts,登入 Apple ID 以啟用開發者功能。

    3. 透過 Xcode 安裝 Command Line Tools 以使用 Swift 命令列工具。

    開發平台

    建立專案

    1. 開啟 Xcode,選擇「Create a new Xcode project」。

    2. 選擇適合的應用程式模板,例如 App (iOS/macOS)。

    3. 設定專案名稱、識別碼 (Bundle Identifier) 及語言 (Swift 或 Objective-C)。

    4. 選擇 UI 框架 (SwiftUI 或 UIKit)。

    模擬與測試

    應用程式發布

    1. 註冊 Apple Developer Program (需年費 USD $99)。

    2. 透過 Xcode 設定 App Store Connect 並提交 App。

    3. 遵循 Apple 的 App Store Review Guidelines 確保應用程式符合上架規範。



    iOS開發

    開發工具

    iOS 開發主要使用 Xcode,這是 Apple 提供的官方整合開發環境 (IDE)。

    開發流程

    1. 設計應用程式架構與介面
    2. 編寫程式碼實現功能
    3. 使用模擬器或實機進行測試
    4. 進行錯誤排除與性能優化
    5. 通過 App Store 提交與發佈

    必備知識

    學習 iOS 開發需要掌握以下基礎:

    開發資源

    以下是一些實用的學習與開發資源:



    Xcode

    功能特色

    Xcode 是 Apple 提供的整合開發環境 (IDE),用於 macOS、iOS、watchOS 和 tvOS 應用程式的開發。

    主要組件

    1. Code Editor: 提供語法高亮、代碼補全與錯誤提示功能
    2. Interface Builder: 支援直覺式拖放設計介面
    3. Simulator: 用於測試與模擬應用程式在不同裝置上的運行
    4. Debugging 工具: 支援斷點、記憶體檢測與性能分析

    安裝與更新

    可從 Mac App Store 或 Apple 開發者官網下載最新版本的 Xcode。

    使用技巧

    提升開發效率的實用技巧:

    資源

    相關學習與參考資源:



    Swift

    語言特點

    Swift 是 Apple 推出的現代化程式語言,用於開發 iOS、macOS、watchOS 和 tvOS 應用程式。

    語法基礎

    核心概念

    1. 使用 Protocol 實現介面與協議
    2. 利用 Extension 擴展類別功能
    3. 泛型支援提升代碼重用性
    4. 運用閉包 (Closures) 實現高階函數
    5. 支援自定義操作符與元組

    應用場景

    Swift 不僅適用於 Apple 生態系統,還可以用於伺服器端開發與跨平台工具。

    資源

    相關學習與參考資源:



    Objective-C

    特點

    Objective-C 是一種以 C 為基礎的物件導向程式語言,最初由 NeXT 公司開發,後來被 Apple 廣泛用於 macOS 和 iOS 應用程式開發。

    語法結構

    Objective-C 的語法結合了 C 和 Smalltalk 的特性,使用 @ 符號來標示語言擴展。

    核心概念

    1. 物件導向程式設計,包括類別與繼承
    2. 協議 (Protocols) 用於定義方法集合
    3. 分類 (Categories) 用於擴展類別功能
    4. Block 語法用於閉包與回呼

    開發工具

    Objective-C 的開發主要使用 Apple 的 Xcode。

    資源

    以下是一些學習與參考的資源:



    GNews

    什麼是GNews?

    GNews是一個由Google開發的新聞聚合平台,旨在幫助用戶獲取最新的全球新聞資訊。它整合了來自各種新聞來源的內容,使用人工智慧技術來個性化推薦用戶感興趣的新聞。

    GNews的主要功能

    如何使用GNews?

    用戶可以通過訪問GNews的網站或下載其應用程式來使用此平台。在平台上,用戶可以選擇感興趣的主題、追蹤特定的新聞來源,並根據自己的需求自訂新聞推送。

    GNews的優勢

    結論

    GNews是一個強大的新聞聚合工具,透過人工智慧技術,為用戶提供個性化的新聞體驗。隨著新聞資訊的快速變化,GNews幫助用戶快速跟上世界動態,獲取所需的資訊。



    自動化流程工具

    工具名稱 主要特色 適用場景 價格
    n8n 開源的自動化工作流工具,提供高度可定制的流程設計,支持自建節點。 適用於企業內部流程自動化、大型自動化系統、API集成等。 免費開源,提供付費雲端版本。
    Make 提供視覺化工作流建構,支持多種第三方應用和服務的集成,強調簡單易用。 適用於小型到中型企業的工作流程自動化,快速集成各種服務。 提供免費計劃,付費計劃根據用戶需求提供更高級功能。
    Zapier 支持大多數應用程序的集成,簡單易用,能夠創建觸發器和自動化工作流程。 適用於各種業務領域的自動化,特別是小型企業和初創企業。 提供免費計劃,付費計劃依據用戶需求提供更多功能和運行次數。


    AI開發程式

    Bolt

    Bolt 是一個快速、輕量的 AI 開發框架,專注於提供開發者簡單且高效的工具來建構應用程式。特點包括:

    Cursor

    Cursor 是專為 AI 程式開發設計的編輯器工具,提供智能輔助程式碼撰寫與調試功能。特點包括:

    v0

    v0 是一個基於視覺化開發的 AI 平台,允許使用者透過拖放介面構建模型與應用。特點包括:

    Codeium

    Codeium 是一款結合 AI 智能輔助的程式編輯器,專注於提升程式開發效率與準確性。特點包括:



    軟體工程

    定義

    軟體工程是一門系統性、規劃性地開發、操作與維護軟體的工程學科,目標是建構高品質、可維護、可靠且符合需求的軟體系統。

    核心目標

    軟體開發生命週期(SDLC)

    1. 需求分析:蒐集與分析使用者需求,製作需求規格書
    2. 系統設計:設計系統架構、模組、資料庫與介面
    3. 實作:依據設計撰寫程式碼
    4. 測試:進行單元測試、整合測試、系統測試與驗收測試
    5. 部署與上線:將系統部署至生產環境
    6. 維護與更新:修正錯誤、更新功能、優化效能

    常見開發方法論

    常見工具與技術

    品質屬性

    角色分工



    敏捷開發

    核心價值

    主要原則

    1. 以最高優先級滿足客戶需求,通過早期且持續的交付提供價值。
    2. 歡迎需求變更,即使在開發後期,也能利用變更為客戶創造競爭優勢。
    3. 頻繁交付可工作的軟體,交付周期通常以數周或數月計算。
    4. 開發與業務團隊應該每天合作,促進溝通與理解。
    5. 以有動機的個人為核心,提供支持並信任他們完成工作。
    6. 面對面交流是最有效的溝通方式。
    7. 衡量進度的主要指標是可工作的軟體。
    8. 提倡可持續的開發節奏,所有參與者應能維持穩定的步調。
    9. 追求技術卓越與設計簡潔,提升靈活性與質量。
    10. 保持簡單,專注於完成必要的工作。
    11. 自組織團隊能產生最佳設計與架構。
    12. 定期反思並調整行為以提升效率。

    常用框架

    適用場景



    軟體開發需求文件範本

    專案名稱

    請填入本專案的正式名稱。

    版本紀錄

    專案背景與目標

    說明此專案的緣由、背景問題以及欲解決的核心問題,並定義明確的專案目標。

    系統概要

    概述系統功能與整體架構,可搭配系統架構圖。

    功能需求

    非功能性需求

    使用者角色與權限

    畫面與介面需求

    可附上主要畫面草圖或線框圖,描述各畫面的元素與互動流程。

    資料需求

    列出主要資料表結構、欄位、關聯性等。

    整合與相依性

    列出需整合的外部系統、API或其他軟體元件。

    時程與里程碑

    風險與限制

    列出潛在風險、限制條件(如預算、人力、技術等)。

    附錄

    相關文件連結、參考資料、名詞定義等。



    設計模式

    定義

    設計模式(Design Patterns)是一組經過實踐驗證的軟體設計解決方案,主要應用於物件導向程式設計中,用來解決在特定情境下反覆出現的設計問題。

    三大類別

    常見建立型模式

    常見結構型模式

    常見行為型模式

    設計模式的優點

    使用建議



    版本控制

    定義

    版本控制(Version Control)是一種管理檔案變更歷史的系統,廣泛應用於軟體開發中,讓多位開發者能同時協作,並保留每次變更的完整記錄。

    主要功能

    版本控制系統類型

    常見工具

    Git 基本指令

    分支策略

    好處



    Git

    簡介

    Git 是一種分散式版本控制系統,由 Linus Torvalds(Linux 之父)於 2005 年開發,用於追蹤程式碼變更、協同開發與版本管理。它是現今最廣泛使用的版本控制工具,被應用於個人開發、團隊專案與開源社群中。

    核心概念

    安裝 Git

    1. 前往 Git 官方網站 下載適合的版本。
    2. 安裝後開啟命令提示字元(CMD)或 PowerShell。
    3. 輸入 git --version 以確認安裝成功。

    初始設定

    第一次使用 Git 時,需設定開發者資訊:

    git config --global user.name "你的名稱"
    git config --global user.email "你的[email protected]"
    

    基本指令

    git init              # 初始化本地儲存庫
    git clone URL         # 複製遠端儲存庫
    git add .             # 將變更加入暫存區
    git commit -m "說明"  # 建立一次提交
    git status            # 查看當前狀態
    git log               # 查看提交紀錄
    git branch            # 查看分支列表
    git checkout 分支名   # 切換分支
    git merge 分支名      # 合併指定分支
    git push              # 將變更推送到遠端
    git pull              # 從遠端拉取更新
    

    常見工作流程

    1. 建立或複製儲存庫(git initgit clone
    2. 編輯或新增檔案
    3. 將修改加入暫存區(git add
    4. 提交變更(git commit
    5. 與遠端同步(git push / git pull

    與 Visual Studio 整合

    優點

    常見遠端服務



    GitHub

    GitHub 是一個基於雲端的版本控制和協作平台,主要用於軟體開發。它使用 Git 進行版本控制,讓開發者能夠管理專案的源代碼、跟蹤變更以及與他人協作。

    主要功能

    使用 GitHub 的好處

    適用對象

    GitHub 適合各類開發者,無論是單獨開發者、開源社區還是企業團隊。它能夠滿足小型專案到大型軟體專案的版本控制和協作需求。

  • GitHub

    GitHub 使用 git pull --rebase

    git pull --rebase 是從遠端分支拉取最新變更,並將本地的更改重新應用到最新的遠端提交之上,而不是使用傳統的合併。

    使用方式

    git pull --rebase

    工作流程

    1. 從遠端分支獲取最新更改。
    2. 暫時移除本地的提交。
    3. 將遠端提交應用到本地分支。
    4. 重新應用本地的提交,保持歷史記錄線性。

    為什麼使用 --rebase

    使用範例

    假設你在本地提交了一些更改,而遠端也有新的提交,使用 git pull --rebase 會:

    解決衝突

    如果在重放本地提交時發生衝突,Git 會要求手動解決衝突:

    1. 解決衝突並使用 git add 暫存已解決的文件。
    2. 使用 git rebase --continue 繼續重放提交。
    3. 若想取消操作,使用 git rebase --abort 回到原狀。

    總結

    git pull --rebase 是保持 Git 提交歷史整潔的重要工具,特別適合多人協作時,能夠避免產生冗餘的合併提交,並保持代碼庫的提交記錄線性。



    Visual Studio 使用 Git

    概述

    Visual Studio 內建 Git 整合功能,但仍需在系統中安裝 Git 執行檔,才能進行版本控制操作(如 Commit、Push、Pull、Merge 等)。

    步驟一:確認是否已安裝 Git

    1. 開啟命令提示字元(CMD)或 PowerShell。
    2. 輸入以下指令:
      git --version
    3. 若顯示類似 git version 2.x.x,代表已安裝;若顯示「無法辨識 git 指令」,則需安裝 Git。

    步驟二:下載並安裝 Git

    1. 前往 Git 官方下載頁面
    2. 選擇對應的作業系統版本(Windows、macOS 或 Linux)。
    3. 下載後執行安裝程式,建議使用預設設定一路按「下一步」。
    4. 安裝完成後重新啟動 Visual Studio。

    步驟三:設定 Git 使用者資訊

    開啟命令提示字元,設定您的使用者名稱與電子郵件:

    git config --global user.name "你的名稱"
    git config --global user.email "你的[email protected]"
    

    這些資訊會附加在每次提交(commit)中,用於識別開發者身份。

    步驟四:在 Visual Studio 啟用 Git

    1. 開啟 Visual Studio。
    2. 點選上方選單的 視圖 (View) → Team Explorer
    3. 選擇 連線 (Connect) → Clone a repository 以複製遠端儲存庫。
    4. 或在現有專案中點擊 Git → 建立 Git 儲存庫,讓專案成為版本控制的一部分。

    步驟五:驗證整合狀態

    補充:使用 Visual Studio 內建 Git 安裝

    若使用 Visual Studio 2022 或更新版本,在安裝過程中可勾選 「Git for Windows」 選項。這樣 Visual Studio 會自動安裝與整合 Git,無需手動設定。

    完成後效果

    設定完成後,您可以直接在 Visual Studio 中完成以下操作:



    Azure DevOps

    平台簡介

    Azure DevOps 是由 Microsoft 提供的一套整合式開發與協作平台,支援從程式碼管理、建置、自動化測試到部署的完整 DevOps 流程。它適用於多種程式語言與框架,可用於個人專案或大型企業團隊開發。

    主要服務組成

    主要特色

    常見用途

    使用方式

    1. 前往 Azure DevOps 官方網站
    2. 登入或註冊 Microsoft 帳號。
    3. 建立新組織(Organization)與專案(Project)。
    4. 設定 Repos、Pipelines、Boards 等模組開始開發與部署流程。

    優點

    適用場景



    Azure DevOps 存取層級

    步驟說明

    1. 登入 Azure DevOps
    2. 在左下角點選 Organization Settings(組織設定)
    3. 在左側選單中選擇 Users(使用者)
    4. 系統會列出組織中所有使用者及其對應的 Access Level(存取層級)

    常見的 Access Level 類型

    進階操作

    提示

    若您在使用 Azure DevOps Pipelines、Repos 或 Boards 時遇到權限限制問題,請先確認您的 Access Level 是否為 Basic 或以上等級。



    程式部署

    定義

    程式部署(Application Deployment)是將開發完成的應用程式從開發環境移轉至測試或生產環境,讓使用者可以實際使用的過程。它確保系統能在不同環境中穩定、可重現地執行。

    部署流程

    1. 建置(Build):將原始程式碼編譯、打包成可執行檔或映像檔。
    2. 測試(Test):驗證功能、效能及相容性。
    3. 部署(Deploy):將應用與設定檔部署至伺服器。
    4. 監控(Monitor):持續監控運行狀況、記錄日誌及效能指標。

    常見部署方式

    常見部署架構

    自動化部署工具

    部署範例

    # 使用 GitHub Actions 部署範例
    name: Deploy Web App
    on:
      push:
        branches: [ "main" ]
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: 建立 Docker 映像
            run: docker build -t myapp .
          - name: 部署至伺服器
            run: docker run -d -p 80:80 myapp
    

    部署時的考量

    部署策略

    最佳實踐



    容器化技術

    定義

    Docker(容器技術)是一個開放原始碼的平台,用於自動化應用程式的部署、擴展與管理。它利用「容器」(Container)將應用與其依賴封裝在一起,確保在不同環境中能一致執行。

    核心概念

    Docker 的優點

    基本指令

    Dockerfile 範例

    # 使用 Python 映像檔
    FROM python:3.10
    
    # 設定工作目錄
    WORKDIR /app
    
    # 複製程式檔案
    COPY . /app
    
    # 安裝依賴套件
    RUN pip install -r requirements.txt
    
    # 指定容器啟動時執行的指令
    CMD ["python", "app.py"]
    

    Docker Compose

    Docker Compose 是一個可定義多個容器應用的工具,使用 docker-compose.yml 檔案定義各服務。

    version: '3'
    services:
      web:
        build: .
        ports:
          - "8000:8000"
      db:
        image: mysql:8
        environment:
          MYSQL_ROOT_PASSWORD: example
    

    應用場景

    相關技術



    .NET C++ 程式部署至 Ubuntu

    前言

    在 Ubuntu 系統上部署 .NET C++(C++/CLI 或以 .NET 為基底的 C++ 應用)需要根據專案類型分別處理。若使用純 C++/CLI(依賴 .NET Framework),將無法直接於 Linux 執行,需改為使用 .NET 6/7/8 的跨平台技術(如 C++/CLR → C# 或使用 C++/Native 搭配 .NET 互通)。

    部署方式概述

    根據應用性質,部署可分為三種情境:

    1. 純 C++ 應用:使用 g++ 編譯,直接於 Ubuntu 執行。
    2. .NET Managed C++ (C++/CLI):需重新設計為 .NET 6 以上版本支援的 C# 或 C++/CLI for Core(僅 Windows 支援)。
    3. 混合式應用:C# 為主,C++ 以動態函式庫(.so)提供性能模組。

    環境安裝

    在 Ubuntu 中安裝 .NET SDK 與必要工具:

    sudo apt update
    sudo apt install -y dotnet-sdk-8.0
    sudo apt install -y build-essential
    

    (可依版本替換 dotnet-sdk-8.0dotnet-sdk-7.0 或其他版本)

    建立專案

    若使用 .NET C# 為主程式,並呼叫 C++ 函式庫:

    # 建立主應用
    dotnet new console -n MyApp
    cd MyApp
    
    # 建立 native 函式庫
    mkdir native
    cd native
    nano add.cpp
    

    add.cpp 範例:

    extern "C" int add(int a, int b) {
        return a + b;
    }
    

    編譯為共享函式庫:

    g++ -fPIC -shared -o libadd.so add.cpp
    

    在 C# (.NET) 呼叫 C++ 函式庫

    Program.cs 加入:

    using System;
    using System.Runtime.InteropServices;
    
    class Program
    {
        [DllImport("libadd.so", EntryPoint="add")]
        public static extern int Add(int a, int b);
    
        static void Main()
        {
            Console.WriteLine("3 + 5 = " + Add(3, 5));
        }
    }
    

    執行與測試

    回到專案根目錄執行:

    dotnet run
    

    若輸出結果為 3 + 5 = 8,表示部署成功。

    發佈部署版本

    可將程式打包為可獨立執行的部署檔:

    dotnet publish -c Release -r linux-x64 --self-contained true
    

    生成的可執行檔位於 bin/Release/net8.0/linux-x64/publish/

    Docker 容器部署(選用)

    可使用 Docker 將應用容器化,以便在任何 Ubuntu 系統運行:

    # Dockerfile 範例
    FROM mcr.microsoft.com/dotnet/runtime:8.0
    WORKDIR /app
    COPY ./publish .
    COPY ./native/libadd.so /usr/lib/
    ENTRYPOINT ["./MyApp"]
    

    建構與執行容器:

    docker build -t myapp .
    docker run --rm myapp
    

    注意事項

    總結

    部署 .NET C++ 程式至 Ubuntu 的可行方式通常是採「C# + C++ 原生函式庫」架構。若原始程式依賴 C++/CLI,需重新設計以支援跨平台 .NET。最推薦的方式是使用 .NET 8 + 原生 C++ 模組搭配 Docker,達到穩定、可攜且一致的部署流程。



    軟體開發相關法律

    著作權法

    軟體程式碼、設計文件、使用手冊等均屬著作權保護範疇。著作權保護自創作完成時自動成立,無需註冊,禁止未經授權的複製、修改、散布或商業利用。

    專利法

    若軟體涉及技術創新或獨特演算法,可申請專利保護。專利需經審查批准,保障發明人對技術方案的專有權利,但純粹的程式邏輯通常不受專利保護。

    商標法

    軟體名稱、圖示(Logo)、品牌識別可註冊為商標,用以保護品牌形象與市場區隔,防止他人冒用造成混淆。

    營業秘密保護法

    未公開的程式設計細節、演算法、資料庫結構等屬營業秘密。公司應透過保密協議、資訊管理制度等方式保護。

    資訊安全相關法規

    如個資法、資通安全管理法等,要求軟體開發過程中妥善處理使用者個人資料、保障資訊安全,違規可能面臨行政或刑事責任。

    契約法與授權條款

    軟體開發常涉及承攬契約、合作開發契約、軟體授權條款等,明確雙方權利義務、程式碼所有權、維護責任、保密義務等,避免法律爭議。

    網路與電子商務相關法規

    軟體上線運營涉及網路服務、電子支付、智慧財產權線上保護等,需遵守電子簽章法、電子交易法等規範。

    軟體開發的智慧財產權

    著作權

    軟體的程式碼屬於著作權保護的範疇,開發者自動擁有著作權,無需額外登記。著作權保護涵蓋原始碼、設計文件、使用手冊等。

    專利權

    若軟體涉及新穎性與技術創新,可能可申請專利保護。例如獨特的演算法或解決技術問題的方法,但純粹的程式碼邏輯通常不在專利保護範圍。

    商標權

    軟體名稱、圖示(Logo)可註冊為商標,用於區分不同來源的產品,避免市場混淆。

    營業秘密

    未公開的程式設計細節、資料庫結構、演算法可透過營業秘密方式保護。需依靠企業內部保密協議與管理措施維護。

    授權模式

    軟體可透過不同授權模式發行,例如專有軟體、自由軟體、開源授權(如GPL、MIT)。不同授權模式影響使用者的修改與再散布權利。

    侵權風險

    未經授權使用他人程式碼、圖像或演算法可能構成侵權。開發過程應確保程式碼來源合法,避免法律糾紛。

    國際保護

    智慧財產權具地域性,但可透過國際條約如伯恩公約、TRIPS協議在多國獲得一定程度的保護。




    email: [email protected]
    
    T:0000
    資訊與搜尋
    email: Yan Sa [email protected] Line: 阿央
    電話: 02-27566655 ,03-5924828