軟體開發



程式語言

定義

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

    實務建議



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

    注意事項



    複製多維 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));
    

    建議



    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 關鍵字通常只在設計需要密切配合的類別或函數時使用。



    週邊控制程式開發

    定義

    週邊控制程式是用來與電腦或主機連接的外部硬體裝置(例如:印表機、掃描器、馬達、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
    資訊與搜尋
    email: Yan Sa [email protected] Line: 阿央
    電話: 02-27566655 ,03-5924828