軟體開發



程式語言

定義

程式語言(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 的限制與做法

    實務建議



    Visual Studio

    開發環境簡介

    Visual Studio 是由微軟開發的一個整合式開發環境(IDE),支援多種程式語言如 C++、C#、VB.NET、Python、JavaScript 等。適用於開發桌面應用程式、網站、雲端服務及行動應用程式。

    主要功能

    版本區別

    平台支援

    Visual Studio 支援 Windows 作業系統,另有 Visual Studio for Mac,專為 macOS 設計。

    常見用途



    Visual Studio 的語法格式化

    1. 自動格式化代碼

    Visual Studio 提供快捷鍵來快速格式化代碼,適用於各種編程語言: - 使用快捷鍵 `Ctrl + K, Ctrl + D` 格式化整個檔案。 - 使用快捷鍵 `Ctrl + K, Ctrl + F` 格式化選中的代碼區塊。

    2. 啟用自動格式化

    Visual Studio 支援在檔案儲存時自動格式化代碼: 1. 點擊 **工具 > 選項**。 2. 前往 **文字編輯器 > [語言] > 編碼樣式 > 常規**。 3. 勾選 **在檔案儲存時重新格式化代碼**。

    3. 修改格式化設定

    1. 打開 **工具 > 選項**。 2. 前往 **文字編輯器 > [語言] > 編碼樣式 > 格式化**。 3. 在此可以調整縮排、括號樣式、空白行等詳細格式化設定。

    4. 使用編碼樣式設定檔

    Visual Studio 支援編碼樣式設定檔(.editorconfig)來統一團隊的代碼風格: 1. 在專案的根目錄新增一個 `.editorconfig` 檔案。 2. 定義格式規則,例如:
       [*.cs]
       indent_style = space
       indent_size = 4
       
    3. 檔案儲存後,格式化會自動遵循這些規則。

    5. 安裝擴展工具

    如果內建格式化功能無法滿足需求,可以安裝擴展工具: - 在 **擴展管理員** 中搜尋並安裝工具,例如 **CodeMaid** 或 **ReSharper**。 - 這些工具提供更多代碼格式化與重構選項。

    6. 快速修正格式問題

    當格式問題出現時,可以使用快速修正功能: - 在問題代碼上點擊右鍵,選擇 **快速動作與重構**。 - 選擇 **格式化文件** 或 **格式化選區**。

    7. 總結

    - 使用快捷鍵快速格式化代碼。 - 啟用檔案儲存時自動格式化功能。 - 使用 `.editorconfig` 統一代碼風格。 - 安裝擴展工具提升格式化能力。 - 善用快速修正功能改善代碼可讀性。

    .NET SDK 版本

    檢查已安裝的 .NET SDK

    您可以透過命令列工具檢查電腦上安裝的 .NET SDK 版本:

    dotnet --list-sdks

    若出現類似下列結果,表示已安裝的 SDK 版本:

    8.0.100 [C:\Program Files\dotnet\sdk]
    9.0.100-preview.3.24172.9 [C:\Program Files\dotnet\sdk]
    

    如果未出現您想使用的版本,表示該版本尚未安裝。

    下載與安裝 .NET SDK

    1. 前往 .NET 官方下載頁面
    2. 選擇對應版本(例如 .NET 9)與作業系統平台(Windows、macOS、Linux)
    3. 下載並執行安裝程式

    在 Visual Studio 中開啟命令提示字元

    1. 開啟 Visual Studio
    2. 選單列點選:工具 > 命令列 > 開發人員命令提示字元
    3. 將會開啟一個已設定好開發環境路徑的命令提示字元視窗,可直接使用 dotnet 命令

    若未看到此選項,您也可以使用 Windows 的開始選單搜尋「Developer Command Prompt for VS」開啟。

    使用 Visual Studio 的內建終端機(2022 起支援)

    1. 在 Visual Studio 中點選:檢視 > 終端機 或使用快速鍵 Ctrl + `
    2. 選擇終端機類型(例如 PowerShell 或 CMD)
    3. 可直接輸入 dotnet 命令檢查或操作 SDK


    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"/*/ )
    

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



    -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 IFS= read -r -d $'\0')

    問題說明

    以下寫法在某些 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 編碼保存,避免出現編碼錯誤。



    C與C++語言

    高效能

    靜態型別系統

    資源控制

    語法靈活性

    廣泛應用領域

    跨平台



    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;
    }

    注意事項



    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# 語言

    語言特色

    基本範例

    using System;
    
    class Program {
        static void Main() {
            string name = "世界";
            Console.WriteLine($"哈囉, {name}!");
        }
    }
    

    常見應用領域

    進階功能



    CS8618 非Nullable未初始化

    警告內容

    CS8618 意思是:在建構函式結束時,非 Nullable 的屬性沒有被初始化,因此 C# 編譯器警告它可能會是 null

    範例問題

    public class Product {
        public string Name { get; set; }  // 警告 CS8618
    }
    

    解決方式

    方法一:在建構函式中賦值(推薦)

    public class Product {
        public string Name { get; set; }
    
        public Product(string name) {
            Name = name;
        }
    }
    

    方法二:給屬性預設值

    public class Product {
        public string Name { get; set; } = string.Empty;
    }
    

    方法三:允許 Null

    public class Product {
        public string? Name { get; set; }
    }
    

    → 適用於 Name 合理允許為 null 的情況。

    方法四:使用 required 修飾元(C# 11+ 支援)

    public class Product {
        public required string Name { get; set; }
    }
    
    // 呼叫端必須初始化
    var p = new Product { Name = "手機" }; // OK
    

    建議



    .NET 程式

    將 float 轉成字串避免科學記號

    問題說明

    在 C++/CLI 中,如果直接用 floatSystem::String 相加,例如:
    
    System::String^ tmpStr = "Value: " + someFloat;
    
    someFloat 很大或很小時,可能會自動以科學記號(e.g. 1.23E+05)顯示。

    解決方式

    可用 System::String::FormatToString 搭配格式字串,指定小數位數並避免科學記號。

    範例程式

    
    using namespace System;
    
    float value = 123456.789f;
    
    // 方法1:String::Format
    String^ tmpStr1 = String::Format("Value: {0:F2}", value); // F2 表示小數點後 2 位
    Console::WriteLine(tmpStr1);
    
    // 方法2:ToString 搭配格式
    String^ tmpStr2 = "Value: " + value.ToString("F2");
    Console::WriteLine(tmpStr2);
    
    // 方法3:更多位數
    String^ tmpStr3 = "Value: " + value.ToString("F6");
    Console::WriteLine(tmpStr3);
    

    格式化代碼說明



    使用 .NET C++ 取得目錄中最新的檔案

    簡介

    在 .NET C++ 中,可以使用 System::IO 命名空間提供的功能來操作檔案與目錄。 取得目錄中最新的檔案,可以透過讀取所有檔案資訊並比較最後修改日期實現。

    範例程式碼

    以下是一個完整的範例,展示如何取得指定目錄中最新的檔案:

    #include "stdafx.h"
    #include <iostream>
    #include <cliext/vector>
    #include <System.IO>
    
    using namespace System;
    using namespace System::IO;
    using namespace cliext;
    
    int main()
    {
        try
        {
            // 指定目錄路徑
            String^ directoryPath = "C:\\Your\\Directory\\Path";
    
            // 檢查目錄是否存在
            if (!Directory::Exists(directoryPath))
            {
                Console::WriteLine("目錄不存在: {0}", directoryPath);
                return -1;
            }
    
            // 取得目錄中的所有檔案
            array^ files = Directory::GetFiles(directoryPath);
    
            // 如果目錄中沒有檔案
            if (files->Length == 0)
            {
                Console::WriteLine("目錄中沒有檔案。");
                return 0;
            }
    
            // 找到最新的檔案
            String^ newestFile = nullptr;
            DateTime newestTime = DateTime::MinValue;
    
            for each (String^ file in files)
            {
                // 取得檔案的最後修改時間
                DateTime lastWriteTime = File::GetLastWriteTime(file);
    
                // 比較時間並更新最新檔案資訊
                if (lastWriteTime > newestTime)
                {
                    newestTime = lastWriteTime;
                    newestFile = file;
                }
            }
    
            // 輸出最新檔案資訊
            Console::WriteLine("最新檔案: {0}", newestFile);
            Console::WriteLine("最後修改時間: {0}", newestTime);
        }
        catch (Exception^ ex)
        {
            Console::WriteLine("發生錯誤: {0}", ex->Message);
        }
    
        return 0;
    }
    

    程式碼解說

    應用場景

    注意事項



    .NET: System.Reflection

    1. System.Reflection 是什麼?

    System.Reflection 是 .NET 框架中的一個命名空間,提供了檢查和操作元數據的工具,使開發者可以在運行時動態檢查類型、方法、屬性等,並動態創建和操縱對象。

    2. System.Reflection 的用途

    3. 常見的 System.Reflection 類別

    4. 使用範例

    以下是一個使用 System.Reflection 的範例程式,展示如何動態檢查類型和方法,並調用方法。

    
    // 定義一個簡單的範例類別
    public class SampleClass {
        public string SayHello(string name) {
            return $"Hello, {name}!";
        }
    }
    
    // 使用 Reflection 來動態調用方法
    using System;
    using System.Reflection;
    
    class Program {
        static void Main() {
            // 創建 SampleClass 類別的實例
            Type sampleType = typeof(SampleClass);
            object sampleInstance = Activator.CreateInstance(sampleType);
    
            // 獲取 SayHello 方法資訊
            MethodInfo methodInfo = sampleType.GetMethod("SayHello");
    
            // 動態調用 SayHello 方法
            object result = methodInfo.Invoke(sampleInstance, new object[] { "World" });
            Console.WriteLine(result); // 輸出: Hello, World!
        }
    }
            

    在上述範例中,我們使用 Activator.CreateInstance 來創建類別的實例,並使用 MethodInfo.Invoke 來調用方法 SayHello

    5. 常見應用場景



    System::Management::ManagementClass

    用途

    System::Management::ManagementClass 是 .NET Framework 中提供用於操作 Windows Management Instrumentation (WMI) 的類別之一。它允許開發者讀取、操作和管理系統資訊,例如硬體、作業系統、網路設定等。

    命名空間

    System::Management 是位於 System.Management.dll 中的命名空間,使用此功能需先加入參考。

    常見用途

    簡單範例

    // C++/CLI 寫法
    using namespace System;
    using namespace System::Management;
    
    int main() {
        try {
            ManagementClass^ mc = gcnew ManagementClass("Win32_OperatingSystem");
            for each (ManagementObject^ mo in mc->GetInstances()) {
                Console::WriteLine("OS 名稱: {0}", mo["Caption"]);
            }
        }
        catch (Exception^ ex) {
            Console::WriteLine("錯誤: {0}", ex->Message);
        }
        return 0;
    }
    

    常見錯誤與排除

    相關類別



    修復 Win32_NetworkAdapterConfiguration 發生 Invalid class 錯誤

    錯誤代碼說明

    當使用 `System::Management::ManagementClass("Win32_NetworkAdapterConfiguration")` 時出現錯誤代碼 `0x80041010`,表示 WMI 無法找到該類別,這通常代表:

    解決步驟

    以下為完整修復方式,可重新建立 WMI 儲存庫,適用於 Windows 10/11:
    net stop winmgmt
    winmgmt /resetrepository
    net start winmgmt
    

    說明

    修復後驗證

    可使用下列任一方式確認是否修復成功: 方式一:PowerShell 查詢
    Get-WmiObject Win32_NetworkAdapterConfiguration
    
    方式二:WMI 測試工具 (wbemtest)
    1. 按 Win + R,輸入 wbemtest
    2. 點選「Connect」,輸入 root\cimv2 並連線
    3. 點選「Query」,輸入 SELECT * FROM Win32_NetworkAdapterConfiguration

    補充建議



    .NET 程式在 Chromebook 上執行方法

    1. 使用 Chromebook 的 Linux 支援執行 .NET

    大多數現代 Chromebook 支援透過 Crostini 專案來運行 Linux 應用程式。

    1. 啟用 Linux 功能:
      • 進入 設定 > 進階 > 開發人員 > 啟用 Linux (Beta)
    2. 安裝 .NET SDK:
      • 打開 Linux 終端機,依照 Microsoft 官方網站的指引安裝。
      • 例如:
        sudo apt update
        sudo apt install -y dotnet-sdk-7.0
    3. 在終端機中編譯並執行 .NET 應用程式。

    2. 使用雲端開發環境

    無需在本地安裝任何軟體,即可在 Chromebook 上使用雲端平台運行 .NET 程式。

    3. 使用 Docker 容器運行 .NET

    Chromebook 支援使用容器運行應用程式,您可以透過 Docker 啟動 .NET 環境。

    1. 在 Linux 環境中安裝 Docker。
    2. 下載 .NET 容器映像檔:
      docker pull mcr.microsoft.com/dotnet/runtime:7.0
    3. 在容器內運行您的 .NET 程式。

    4. 使用跨平台支援功能

    從 .NET 6 開始,應用程式可以跨多平台運行,包括 Linux。將您的應用程式編譯為跨平台格式並部署至 Chromebook。

    1. 在任何機器上編譯您的應用程式:
      dotnet publish -c Release -r linux-x64 --self-contained
    2. 將編譯好的檔案傳輸到 Chromebook,使用附帶的運行時執行。

    注意事項



    .NET UI 程式設計

    主要技術

    簡單 WinForms 範例

    using System;
    using System.Windows.Forms;
    
    public class MyForm : Form {
        public MyForm() {
            Button btn = new Button();
            btn.Text = "點我";
            btn.Click += (s, e) => MessageBox.Show("你點了按鈕!");
            Controls.Add(btn);
        }
    
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.Run(new MyForm());
        }
    }
    

    簡單 WPF 範例 (XAML + C#)

    // MainWindow.xaml
    <Window x:Class="WpfApp.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            Title="WPF 範例" Height="200" Width="300">
        <StackPanel>
            <Button Content="點我" Click="Button_Click"/>
        </StackPanel>
    </Window>
    
    // MainWindow.xaml.cs
    private void Button_Click(object sender, RoutedEventArgs e) {
        MessageBox.Show("你點了按鈕!");
    }
    

    簡單 MAUI 範例

    // MainPage.xaml
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 x:Class="MyApp.MainPage">
        <VerticalStackLayout Spacing="25" Padding="30">
            <Button Text="點我" Clicked="OnButtonClicked"/>
        </VerticalStackLayout>
    </ContentPage>
    
    // MainPage.xaml.cs
    private void OnButtonClicked(object sender, EventArgs e) {
        await DisplayAlert("通知", "你點了按鈕!", "OK");
    }
    

    UI 技術選擇建議

    技術 用途 平台
    WinForms 快速開發桌面應用 Windows
    WPF 複雜桌面應用、MVVM 模式 Windows
    MAUI 跨平台應用 Windows、macOS、Android、iOS
    Blazor Web 前端開發 跨平台(Web)


    .NET 遍歷子控制項以批量修改屬性

    以下範例展示了如何使用 Controls->GetEnumerator() 方法來逐一遍歷 .NET 表單中的所有子控制項並批量修改其屬性。

    範例程式碼:

    
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    
    public class FormExample : Form
    {
        public FormExample()
        {
            // 初始化一些控制項
            Button button1 = new Button { Text = "Button 1", Location = new Point(10, 10) };
            TextBox textBox1 = new TextBox { Location = new Point(10, 50) };
            
            Controls.Add(button1);
            Controls.Add(textBox1);
    
            // 使用 GetEnumerator() 遍歷並修改控制項屬性
            ModifyControls();
        }
    
        private void ModifyControls()
        {
            var enumerator = Controls.GetEnumerator();
            
            while (enumerator.MoveNext())
            {
                Control control = (Control)enumerator.Current;
    
                // 設定範例:將所有控制項的背景色設為淺藍
                control.BackColor = Color.LightBlue;
                
                // 若控制項為 TextBox,將其設為不可編輯
                if (control is TextBox)
                {
                    control.Enabled = false;
                }
            }
        }
        
        // 啟動應用程式
        public static void Main()
        {
            Application.Run(new FormExample());
        }
    }
    

    說明

    **GetEnumerator()**: 利用 `Controls.GetEnumerator()` 方法來取得控制項的列舉器,這樣可以遍歷所有子控制項。

    **條件修改**: 在 `while` 迴圈中,對每個 `Control` 物件進行屬性修改,例如將背景色設為淺藍,並根據控制項類型進行特定修改,如將 `TextBox` 設為不可編輯。

    **用途**: 這種方法在需要批量修改屬性時非常有效,比如調整 UI 風格或在特定情況下禁用多個控制項。

    執行效果

    執行後,所有控制項的背景色會變成淺藍色,且所有 `TextBox` 控制項將被設為不可編輯。



    .NET StackTrace中取得 Message m 的內容

    問題背景

    當堆疊追蹤中出現以下錯誤訊息時,開發者可能需要檢查 Message 物件的內容:

       at ....MainForm.Dispose(Boolean A_0)
       at System.Windows.Forms.Form.WmClose(Message& m)
       at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       ...
        

    此時需要透過覆寫或檢測 WndProc 方法中的 Message m 來檢查其詳細資訊。

    解決方法

    以下提供幾種方法來檢查或記錄 Message 物件的內容。

    1. 覆寫 WndProc 方法

    如果可以存取表單或控制項的原始碼,建議覆寫 WndProc 方法,直接記錄或檢查 Message m 的內容。

    
    protected override void WndProc(ref Message m)
    {
        try
        {
            // 記錄 Message 的內容
            Console.WriteLine($"Message Details: hWnd={m.HWnd}, Msg={m.Msg}, WParam={m.WParam}, LParam={m.LParam}");
            base.WndProc(ref m);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex}");
            throw; // 保持原有例外行為
        }
    }
    
        

    這段程式碼會在每次接收到訊息時記錄相關資訊,並允許開發者進一步分析。

    2. 在 Dispose 方法中加入例外處理

    如果錯誤發生在 Dispose 方法中,可以在方法內加入例外處理來檢查相關資訊。

    
    protected override void Dispose(bool disposing)
    {
        try
        {
            if (disposing)
            {
                // 釋放資源
            }
            base.Dispose(disposing);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception in Dispose: {ex}");
            throw;
        }
    }
    
        

    這樣可以確保在釋放資源時不會忽略重要的錯誤資訊。

    3. 使用全域例外處理

    如果無法確定錯誤發生的位置,可以透過全域例外處理來記錄堆疊追蹤和相關資訊。

    
    AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
    {
        Exception ex = (Exception)args.ExceptionObject;
        Console.WriteLine($"Unhandled Exception: {ex}");
        Environment.Exit(1);
    };
    
        

    4. 使用 Debug 工具檢查 Message

    可以使用 Visual Studio 將斷點設置在 WndProcDispose 方法中,並檢查 Message 物件的內容。

    Message 物件的關鍵屬性

    注意事項



    覆寫 Form.WmClose(Message& m) 在 .NET C++

    問題背景

    在 .NET C++/CLI 中,WmClose(Message& m)System.Windows.Forms.Form 的內部保護方法,無法直接覆寫。不過,可以透過覆寫 WndProc 方法來攔截並處理 WM_CLOSE 訊息,以達到類似覆寫 WmClose 的效果。

    完整範例

    
    #include <Windows.h>
    #include <System.Windows.Forms.h>
    
    using namespace System;
    using namespace System::Windows::Forms;
    
    public ref class CustomForm : public Form
    {
    protected:
        // 模擬 WmClose 行為的覆寫
        void WmClose(Message% m)
        {
            // 在這裡加入自定義的 WM_CLOSE 行為
            if (MessageBox::Show("確定要關閉視窗嗎?", "確認", MessageBoxButtons::YesNo) == DialogResult::Yes)
            {
                // 繼續調用基底行為以進行正常關閉
                this->Form::WndProc(m);
            }
            else
            {
                // 阻止關閉視窗
                return;
            }
        }
    
        // 覆寫 WndProc,攔截 WM_CLOSE 訊息並調用 WmClose
        virtual void WndProc(Message% m) override
        {
            const int WM_CLOSE = 0x0010;
    
            if (m.Msg == WM_CLOSE)
            {
                WmClose(m); // 調用自定義的 WmClose 方法
            }
            else
            {
                // 處理其他訊息
                Form::WndProc(m);
            }
        }
    };
    
    [STAThread]
    int main(array<String^>^ args)
    {
        Application::EnableVisualStyles();
        Application::SetCompatibleTextRenderingDefault(false);
    
        CustomForm^ form = gcnew CustomForm();
        form->Text = "覆寫 WmClose 行為範例";
        Application::Run(form);
    
        return 0;
    }
    
        

    程式碼解釋

    1. WmClose 方法

    模擬覆寫 WmClose 方法,在這裡自定義視窗關閉的行為。透過確認對話框詢問使用者是否要關閉視窗。

    2. 覆寫 WndProc

    覆寫 WndProc 方法來攔截 WM_CLOSE 訊息,並將其委派給自定義的 WmClose 方法。

    3. 繼續處理其他訊息

    在沒有攔截 WM_CLOSE 的情況下,調用基底類別的 WndProc 方法處理其他訊息。

    注意事項



    System::Windows::Forms::Control::InvokeRequired

    用途說明

    InvokeRequiredSystem::Windows::Forms::Control 的屬性,用來判斷目前執行緒是否為控制項所屬的 UI 執行緒。當從背景執行緒存取 UI 控件時,應使用 Invoke 將操作封送回 UI 執行緒。

    完整範例:設定 PictureBox 圖片

    
    using namespace System;
    using namespace System::Windows::Forms;
    using namespace System::Drawing;
    
    ref class ImageHelper {
    public:
        static void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
            if (pPictureBox == nullptr)
                return;
    
            if (pPictureBox->InvokeRequired) {
                // 使用 MethodInvoker 呼叫同一函式,但於 UI 執行緒執行
                pPictureBox->Invoke(
                    gcnew MethodInvoker(gcnew Action(ImageHelper::InvokeCallback), 
                    gcnew Tuple(pPictureBox, b))
                );
            } else {
                ApplyImage(pPictureBox, b);
            }
        }
    
    private:
        // 封送的函式代理
        static void InvokeCallback(Object^ state) {
            Tuple^ args = static_cast^>(state);
            ApplyImage(args->Item1, args->Item2);
        }
    
        // 實際執行設定圖片邏輯
        static void ApplyImage(PictureBox^ pPictureBox, Bitmap^ b) {
            try {
                if (b != nullptr) {
                    if (pPictureBox->Image != nullptr)
                        delete pPictureBox->Image;
                    pPictureBox->Image = b;
                }
            }
            catch (System::Exception^ ex) {
                Console::WriteLine("設定圖片失敗: " + ex->Message);
            }
        }
    };
    

    重點整理



    保護 pPictureBox->Image = b 避免程式崩潰

    可能發生崩潰的原因

    安全設定圖片的建議作法

    
    using namespace System;
    using namespace System::Windows::Forms;
    using namespace System::Drawing;
    
    void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
        if (pPictureBox->InvokeRequired) {
            pPictureBox->Invoke(gcnew MethodInvoker(
                gcnew EventHandler(nullptr, &SetImageInvoker)
            ), gcnew array { pPictureBox, b });
        } else {
            SetImageInternal(pPictureBox, b);
        }
    }
    
    void SetImageInvoker(Object^ sender, EventArgs^ e) {
        // 不使用此版本,可忽略,保留為完整形式參考
    }
    
    void SetImageInternal(PictureBox^ pPictureBox, Bitmap^ b) {
        try {
            if (b != nullptr) {
                if (pPictureBox->Image != nullptr)
                    delete pPictureBox->Image;
    
                pPictureBox->Image = b;
            }
        }
        catch (System::Exception^ ex) {
            Console::WriteLine("設定圖片失敗: " + ex->Message);
        }
    }
    

    簡化版本(建議使用匿名委派)

    如果你只是要快速保護 `pPictureBox->Image = b;`,以下寫法更直接有效:
    
    using namespace System;
    using namespace System::Windows::Forms;
    using namespace System::Drawing;
    
    void SafeAssignImage(PictureBox^ pPictureBox, Bitmap^ b) {
        if (pPictureBox->InvokeRequired) {
            pPictureBox->Invoke(gcnew MethodInvoker(gcnew delegate {
                try {
                    if (b != nullptr) {
                        if (pPictureBox->Image != nullptr)
                            delete pPictureBox->Image;
                        pPictureBox->Image = b;
                    }
                } catch (System::Exception^ ex) {
                    Console::WriteLine("設定圖片失敗: " + ex->Message);
                }
            }));
        } else {
            try {
                if (b != nullptr) {
                    if (pPictureBox->Image != nullptr)
                        delete pPictureBox->Image;
                    pPictureBox->Image = b;
                }
            } catch (System::Exception^ ex) {
                Console::WriteLine("設定圖片失敗: " + ex->Message);
            }
        }
    }
    

    補充說明



    以 Blazor 建立統一語言的前端開發環境

    什麼是 Blazor?

    Blazor 是由微軟推出的前端框架,允許開發者使用 C# 和 Razor 語法來建立互動式 Web 應用程式,無需使用 JavaScript,即可在瀏覽器中執行 C# 程式碼。

    開發者效益

    Blazor 模式

    與 .NET 的整合

    Blazor 無縫整合 ASP.NET Core,可與現有的 .NET 應用程式結合,並支援依賴注入、路由、元件架構等功能。

    部署方式

    小結

    對於以 .NET 為主要技術的開發者而言,Blazor 提供了與 Node.js 統一 JavaScript 語言相對應的解決方案,使前後端都能使用 C#,建立一致性更高的開發體驗。



    使用 .NET MAUI 建立跨平台應用

    什麼是 .NET MAUI?

    .NET MAUI(Multi-platform App UI)是微軟推出的跨平台應用框架,可使用 C# 和 XAML 撰寫一次程式碼,部署到 Windows、Android、iOS 甚至 macOS。

    開發者效益

    支援的平台

    Blazor Hybrid 模式

    可使用 .NET MAUI 的 Blazor Hybrid 模式,在原生應用中嵌入 Web UI,讓開發者用 Razor 組件開發跨平台使用者介面,仍可存取原生 API。

    整合與部署

    小結

    .NET MAUI 是目前最完整的 .NET 跨平台解決方案,可結合 Blazor 達成 Web、桌面與行動平台的統一應用開發,提供給 .NET 開發者一個語言與技術一致的全端體驗。



    ASP.NET Core 與 .NET MAUI 的比較

    定位

    支援平台

    開發語言

    UI 與互動

    適用場景

    整合性

    .NET MAUI 可透過 API 呼叫與 ASP.NET Core 建立的後端服務整合,形成完整的「前端 App + 後端 API」架構。

    總結

    ASP.NET Core 解決「後端服務」與「Web 應用」問題,.NET MAUI 則專注在「用戶端跨平台 App」。兩者並不互斥,反而常搭配使用,構成完整解決方案。



    簡單的 .NET MAUI 應用範例

    建立基本的 .NET MAUI 應用

    以下是一個使用 .NET MAUI 建立的跨平台應用程式範例,這個應用在畫面上顯示一個按鈕,點擊後會更新文字計數。

    主要檔案:MainPage.xaml

    
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="MauiApp.MainPage">
    
        <VerticalStackLayout Spacing="25" Padding="30">
            <Label x:Name="counterLabel"
                   Text="你尚未點擊按鈕"
                   FontSize="24"
                   HorizontalOptions="Center" />
    
            <Button Text="點我"
                    Clicked="OnCounterClicked"
                    HorizontalOptions="Center" />
        </VerticalStackLayout>
    </ContentPage>
    

    後端程式碼:MainPage.xaml.cs

    
    namespace MauiApp;
    
    public partial class MainPage : ContentPage
    {
        int count = 0;
    
        public MainPage()
        {
            InitializeComponent();
        }
    
        private void OnCounterClicked(object sender, EventArgs e)
        {
            count++;
            counterLabel.Text = $"你已經點擊了 {count} 次";
        }
    }
    

    執行方式

    小結

    這個簡單的範例展示了 .NET MAUI 的跨平台能力,開發者只需撰寫一次 XAML 與 C# 程式碼,即可同時在多個平台運作。



    在 Visual Studio 中建立 .NET MAUI 專案

    步驟一:安裝必要工具

    步驟二:建立新的 MAUI 專案

    1. 開啟 Visual Studio
    2. 點選「建立新專案」
    3. 搜尋並選擇「.NET MAUI App」範本
    4. 按「下一步」,輸入專案名稱與位置
    5. 點選「建立」完成專案初始化

    步驟三:了解專案結構

    步驟四:執行應用程式

    補充說明

    小結

    使用 Visual Studio 建立 .NET MAUI 專案只需幾個步驟,且能一套程式碼同時部署到多個平台,是現代 C# 全端開發者的理想選擇。



    MainPage.xaml 實例與解釋

    範例:MainPage.xaml

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="MyMauiApp.MainPage">
    
        <VerticalStackLayout Spacing="25" Padding="30"
                             VerticalOptions="Center">
    
            <Label 
                Text="歡迎使用 .NET MAUI!" 
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />
    
            <Button 
                x:Name="counterBtn"
                Text="點我!" 
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />
    
        </VerticalStackLayout>
    
    </ContentPage>
    

    對應的 MainPage.xaml.cs

    using System;
    namespace MyMauiApp;
    
    public partial class MainPage : ContentPage
    {
        int count = 0;
    
        public MainPage()
        {
            InitializeComponent();
        }
    
        private void OnCounterClicked(object sender, EventArgs e)
        {
            count++;
            counterBtn.Text = $"你已經點了 {count} 次";
        }
    }
    

    元件說明

    C# 程式碼說明

    結果效果

    執行後,畫面中央會顯示一個歡迎訊息與一個按鈕。每次點擊按鈕,按鈕上的文字就會更新為「你已經點了 X 次」。



    MauiProgram.cs 實例與解釋

    範例:MauiProgram.cs

    using Microsoft.Extensions.Logging;
    
    namespace MyMauiApp;
    
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            
            builder
                .UseMauiApp() // 指定應用程式的進入點
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
    
    #if DEBUG
            builder.Logging.AddDebug();
    #endif
    
            return builder.Build();
        }
    }
    

    功能說明

    常見擴充

    總結

    MauiProgram.cs 就像 ASP.NET Core 的 Startup.csProgram.cs,是應用啟動與服務註冊的主要設定中心。開發大型 MAUI 應用時,擴充此檔案來加入 DI、日誌、組件設定等是非常常見的做法。



    多媒體

    什麼是多媒體?

    多媒體是指同時使用多種媒介(如文字、圖像、音頻、視頻和動畫)來傳達信息和內容的技術。它提供了一種豐富的方式來呈現和交流信息,並在教育、娛樂和廣告等領域得到了廣泛應用。

    多媒體的組成要素

    多媒體的應用領域

    多媒體技術的發展

    隨著技術的進步,多媒體技術也在不斷演變。從早期的靜態圖像和音頻到現在的高解析度視頻和虛擬實境(VR),多媒體的表達方式變得越來越豐富和多樣。

    結論

    多媒體不僅提高了信息傳遞的效率和趣味性,還為用戶創造了更為沉浸式的體驗。未來,隨著技術的進一步發展,多媒體將在更多領域中發揮更大的作用。



    OpenCV

    1. OpenCV 是什麼?

    OpenCV (Open Source Computer Vision Library) 是一個開源的計算機視覺與機器學習軟件庫,用於即時影像處理與分析。

    2. 支援的功能

    3. 支援的平台

    4. 使用範例

    # 讀取影像並顯示
    import cv2
    image = cv2.imread("image.jpg")
    cv2.imshow("Image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    5. 資源與文件



    Halcon

    特點

    Halcon 是由 MVTec 公司開發的一款強大的工業視覺軟體,專為影像處理和機器視覺應用而設計。

    功能

    應用領域

    資源



    開源影片編輯軟體

    1. Shotcut

    Shotcut 是一款免費且開源的影片編輯軟體,支援多種格式且具有許多強大的編輯工具。其特色包括:

    適用平台:Windows、Mac、Linux

    2. OpenShot

    OpenShot 是一款易於上手的開源影片編輯工具,功能強大且支持多種格式。其主要特點包括:

    適用平台:Windows、Mac、Linux

    3. Blender

    Blender 是一款知名的開源 3D 建模和動畫軟體,內建功能強大的影片編輯器,適合進行影片剪輯和特效製作。其功能包括:

    適用平台:Windows、Mac、Linux

    4. Kdenlive

    Kdenlive 是 Linux 上廣泛使用的開源影片編輯軟體,也支援 Windows。其主要功能包括:

    適用平台:Windows、Mac、Linux

    5. Lightworks

    Lightworks 提供免費和付費版本,免費版本具備基本編輯功能。其特色包括:

    適用平台:Windows、Mac、Linux

    . Avidemux
    . Cinelerra
    . LiVES
    . Losslesscut
    . Natron
    . Pitivi

    以上這些開源影片編輯軟體提供了強大的功能,適合不同層次的影片編輯需求,從簡單的家庭影片剪輯到專業級的影片製作都可以滿足。

    Google 搜尋量排名

    軟體名稱 大約搜尋量
    OpenShot 110,000
    Kdenlive 90,500
    Shotcut 49,500
    Avidemux 18,100
    Losslesscut 14,800
    Blender VSE 10,000
    Natron 6,600
    Cinelerra 5,400
    Pitivi 3,600
    LiVES 1,600


    影片編輯好用的程式庫

    FFmpeg

    MoviePy(Python)

    OpenCV(C++/Python)

    GStreamer

    AVFoundation(macOS/iOS)

    Microsoft Media Foundation(Windows)

    Kapwing API / Shotstack / Cloudinary

    Adobe Premiere Pro API(Adobe UXP)



    OpenShot/openshot-qt - GitHub 專案

    專案簡介

    OpenShot 是一款免費且開源的影片編輯器,專案名稱為 OpenShot/openshot-qt,主要基於 PythonQt 開發。該專案旨在提供一個易於使用且功能豐富的影片編輯工具,適合不同水平的使用者。

    功能特色

    技術架構

    OpenShot 使用 PyQt 作為圖形用戶界面,並結合 libopenshot (C++ 實現) 來處理影片編輯的核心邏輯。此外,OpenShot 還利用了 FFmpeg 來支援多種格式的解碼與編碼。

    使用情境

    OpenShot 適用於需要簡單操作、但功能強大的影片編輯需求的用戶。無論是業餘影片創作者還是教育用途,OpenShot 都提供了靈活的工具和插件,便於進行剪輯和創作。

    社群和貢獻

    OpenShot 專案擁有活躍的開源社群,使用者和開發者可以透過 GitHub 貢獻程式碼、報告問題或提交新功能建議。歡迎所有人參與,以協助提升 OpenShot 的功能與穩定性。

    如何取得 OpenShot

    使用者可以透過 GitHub 頁面下載源碼,或從 OpenShot 官方網站下載可執行檔。詳細安裝指引和說明文件也可在 GitHub 上找到。



    FFmpeg

    簡介

    常見功能

    優點

    使用方式

    官網與下載



    Unity

    Unity 是一個功能強大的遊戲開發引擎和平臺,專門設計用於創建 2D 和 3D 遊戲、互動應用程式及虛擬現實 (VR) 和增強現實 (AR) 體驗。它提供簡單易用的介面和豐富的工具,適合初學者和專業開發人員使用。

    1. Unity 的主要特點
    2. Unity 的核心組件
    3. Unity 的應用範疇
    4. Unity 的優勢

    Unity 是一個強大且靈活的開發引擎,為開發者提供了廣泛的應用場景和工具支持。無論是初學者還是專業開發人員,都可以利用 Unity 快速創建高質量的 2D、3D 遊戲及互動應用。



    週邊控制程式開發

    定義

    週邊控制程式是用來與電腦或主機連接的外部硬體裝置(例如:印表機、掃描器、馬達、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 基本指令

    分支策略

    好處



    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 提交歷史整潔的重要工具,特別適合多人協作時,能夠避免產生冗餘的合併提交,並保持代碼庫的提交記錄線性。




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