程式語言(Programming Language)是人與電腦溝通的橋樑,用來編寫指令、控制電腦運作與實現各種應用程式。
根據最新的程式語言排名,以下是 2024 年度排名前 20 的程式語言:
Lambda 表達式是一種匿名函數,通常用於簡化代碼,特別是在需要傳遞小型函數或回調的情況下。Lambda 表達式的語法簡潔,可以在一行中定義函數邏輯。Lambda 表達式最常見於 C++、JavaScript、Python 和 C# 等語言。
Lambda 表達式的基本語法通常包括參數、箭頭符號 => 和函數體,例如:
(參數) => 函數體
具體語法因語言而異,例如:
[capture](parameters) { function body }lambda parameters: expression(parameters) => expression
#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;
}
# 使用 lambda 表達式來計算兩數的和
add = lambda x, y: x + y
print(add(5, 10)) # 輸出: 15
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}");
}
}
map、filter 和 reduce 等高階函數來操作集合。# 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())
}
}
| 語言 | 支援度 | 說明 |
|---|---|---|
| Python | ✅ | 動態、完整反射,輕鬆遞迴物件/容器檢查。 |
| JavaScript / TypeScript | ✅ | 物件為 key-value,可用 Object.values 遞迴。 |
| Ruby | ✅ | instance_variables 反射成員。 |
| PHP | ✅ | get_object_vars() 或 Reflection。 |
| C# (.NET) | ✅ | Reflection 取得欄位/屬性,型別安全佳。 |
| Java | ✅ | java.lang.reflect 可掃描欄位。 |
| Kotlin | ✅ | JVM 反射完善,與 Java 類似。 |
| Go | ✅ | reflect 可走訪 struct 欄位。 |
| Swift | ◑ | Mirror 可走訪,但場景有限、需額外處理型別。 |
| C++ (至 C++23) | ❌ | 無 runtime reflection,需手動為型別撰寫檢查。 |
| Rust | ❌ | 無 runtime reflection,常以 derive/trait 自行實作。 |
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
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 而定
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;
}
}
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
}
}
isAllZero,或用 template/巨集輔助展開成員。derive 宏為型別自動生成實作(例如自訂 trait IsAllZero)。eps,避免精度問題導致誤判。isAllZero() 方法,避免昂貴的反射掃描。訊息佇列 (Message Queue,簡稱 MQ) 是一種用於軟體系統之間異步通信的架構模式。它允許獨立的應用程式或服務透過傳送和接收訊息來交換資訊,而無需直接相互調用或依賴於對方的即時狀態。MQ 的核心作用是作為一個**中間層**,暫時儲存訊息,直到目標接收者準備好處理它們。
| 模式名稱 | 描述 | 典型應用場景 |
|---|---|---|
| 點對點 (Point-to-Point, P2P) | 訊息發送到一個佇列 (Queue),**僅有一個**接收者會從該佇列中取出並消費此訊息。一旦被消費,訊息即被刪除。 | 訂單處理、任務派發、工作負載均衡。 |
| 發布/訂閱 (Publish/Subscribe, Pub/Sub) | 訊息發佈到一個主題 (Topic),**所有**訂閱了該主題的接收者 (Subscriber) 都會收到該訊息的副本。 | 系統事件廣播、日誌收集、資料變更通知。 |
訊息佇列是建構現代分佈式系統、微服務架構和高可用性應用程式的基石。它通過引入時間和空間上的間接性,顯著提高了系統的彈性、可伸縮性和穩健性。
| 特性 | 訊息佇列 (MQ) | HTTP API (REST/RPC) |
|---|---|---|
| 通信類型 | 異步 (Asynchronous) | 同步 (Synchronous) |
| 耦合性 | 低耦合(發送者與接收者不直接互動) | 高耦合(客戶端需要知道伺服器的位址並等待回覆) |
| 資料流程 | 單向,經由中間的 Broker 傳遞 | 雙向,請求與回應 (Request-Response) |
| 容錯性 | 高,Broker 儲存訊息,接收者離線也不會丟失 | 低,服務端離線或超時會導致請求失敗 |
| 擴展性 | 高,可輕鬆增加多個消費者來處理負載 | 相對較低,需依賴負載均衡器來分配請求 |
MQ 和 HTTP API 並非互相取代的技術,而是針對不同問題而設計的。
在現代的分佈式系統中,兩種模式經常並存,互相配合以滿足不同的業務需求。
在許多網路應用中,有些操作是耗時的,如果讓使用者同步等待,會造成糟糕的使用者體驗。MQ 允許將這些任務轉為異步執行。
MQ 在處理瞬時高流量的場景中至關重要,可以保護後端服務免於崩潰。
在複雜的分佈式系統和微服務架構中,MQ 用於隔離服務,減少相互依賴。
將大量的日誌資料從前端應用程式或伺服器收集到集中的處理系統。
特別是像 Apache Kafka 這樣的高吞吐量 MQ/串流平台,非常適合處理連續不斷的即時資料流。
使用 HTTP API 傳輸圖片 (Images) 和影片 (Videos) 這些大型二進位資料 (Binary Data),主要的挑戰在於:
這是瀏覽器或客戶端應用程式上傳檔案最標準和最常用的方法。
multipart/form-data; boundary=YourBoundaryString
如果只需上傳一個單一檔案,可以直接將檔案的二進位內容作為請求的主體。
image/jpeg 或 image/png;影片:video/mp4。
將二進位資料轉換成 ASCII 字串,嵌入到 JSON 或 XML 等文本格式中傳輸。
Content-Type,處理簡單文字格式的 API 也能支援。
下載二進位資料相對簡單,伺服器直接將檔案的原始二進位內容作為 HTTP 回應的主體 (Response Body) 返回。
image/jpeg 或 video/mp4。
attachment; filename="example.mp4",指示瀏覽器將內容作為檔案下載而非直接顯示。
對於特別大的檔案(尤其是影片),推薦使用以下技術來提高可靠性和效率:
Range 請求檔案的特定片段(例如 Range: bytes=100-200)。這對於影片串流 (Streaming) 非常重要,允許播放器只下載需要播放的部分,並支持快進/快退。
串流傳輸的核心在於如何高效、穩定且低延遲地將影音內容從伺服器傳輸到客戶端。
這是現代串流服務的基石。伺服器會將同一段影音內容編碼成多個不同品質(位元速率、解析度)的版本。
對於面向全球使用者的串流服務來說,CDN 是不可或缺的。
用於壓縮和解壓縮影音資料,以減少檔案大小。
在 Bash 中,$? 代表上一個執行的命令的退出狀態碼(Exit Status)。這是一個整數值,通常用來判斷上一個命令是否成功執行。
#!/bin/bash
ls
echo "上一個命令的退出狀態碼是: $?"
在這個範例中,ls 命令會列出目錄內容,執行成功後,$? 的值將是 0。
#!/bin/bash
ls /nonexistent-directory
echo "上一個命令的退出狀態碼是: $?"
在這個範例中,ls 嘗試列出不存在的目錄,命令會失敗,$? 的值將是一個非 0 數字。
#!/bin/bash
cp file1.txt /some/directory/
if [ $? -eq 0 ]; then
echo "文件複製成功。"
else
echo "文件複製失敗。"
fi
此範例會根據命令的退出狀態來決定要顯示哪一條訊息。
if [ 條件 ]; then
指令
fi
要在 if 中同時滿足多個條件,可使用:
-a(過時但可用)if [ "$a" -gt 0 -a "$b" -gt 0 ]; then
echo "a 和 b 都大於 0"
fi
[[ ]] &&(推薦)if [[ "$a" -gt 0 && "$b" -gt 0 ]]; then
echo "a 和 b 都大於 0"
fi
&&if [ "$a" -gt 0 ] && [ "$b" -gt 0 ]; then
echo "a 和 b 都大於 0"
fi
只要其中一個條件成立即可執行:
-o(過時但可用)if [ "$a" -eq 0 -o "$b" -eq 0 ]; then
echo "a 或 b 為 0"
fi
[[ ]] ||(推薦)if [[ "$a" -eq 0 || "$b" -eq 0 ]]; then
echo "a 或 b 為 0"
fi
||if [ "$a" -eq 0 ] || [ "$b" -eq 0 ]; then
echo "a 或 b 為 0"
fi
可搭配括號來控制優先順序:
if [[ ( "$a" -gt 0 && "$b" -gt 0 ) || "$c" -eq 1 ]]; then
echo "a 和 b 都大於 0,或 c 等於 1"
fi
在 Bash 中,if 指令通常搭配 [ ](test 指令)或 [[ ]](擴展測試指令)使用。要表示「非」(NOT),使用感嘆號 !。
if [ ! 條件 ]; then
# 當條件為假時執行的程式碼
fi
這是最常見的用法,例如:如果檔案「不存在」則建立它。
# 如果不存在名為 config.txt 的檔案
if [ ! -f "config.txt" ]; then
echo "檔案不存在,正在建立..."
touch config.txt
fi
# 如果目錄不存在
if [ ! -d "/var/log/myapp" ]; then
mkdir -p "/var/log/myapp"
fi
檢查字串是否「不等於」或「非空」。
STR="hello"
# 檢查變數是否不等於 "world"
if [ "$STR" != "world" ]; then
echo "字串不匹配"
fi
# 檢查字串是否不為空 (! -z 等同於 -n)
if [ ! -z "$STR" ]; then
echo "變數內有資料"
fi
雖然數值比較有 -ne (not equal),但也可以配合 ! 使用。
NUM=10
if [ ! "$NUM" -eq 20 ]; then
echo "數字不等於 20"
fi
| 運算子 | 說明 | 範例 |
|---|---|---|
! -f |
檔案不存在 | [ ! -f file ] |
! -d |
目錄不存在 | [ ! -d dir ] |
!= |
字串不等於 | [ "$a" != "$b" ] |
! -z |
字串非空 | [ ! -z "$str" ] |
-ne |
數值不等於 | [ "$n" -ne 5 ] |
在較現代的 Bash 腳本中,建議使用 [[ ]],它在處理 ! 與邏輯組合時更為強大且不易出錯。
# 結合多個條件:如果不是目錄且不可讀取
if [[ ! -d $path && ! -r $path ]]; then
echo "路徑無效或權限不足"
fi
[[ ]] 時支援 && 和 ||[ ] 時建議搭配多個條件語句並用 && / ||case 語句用於將一個變數的值與多個模式(patterns)進行比對,功能類似於其他語言中的 switch。其語法結構如下:
case 變數 in
模式1)
# 執行指令
;;
模式2)
# 執行指令
;;
*)
# 預設執行指令(類似 default)
;;
esac
這是最常見的用法,根據使用者的輸入執行不同的動作。
read -p "請輸入 (yes/no): " input
case "$input" in
"yes")
echo "您選擇了是"
;;
"no")
echo "您選擇了否"
;;
*)
echo "輸入錯誤"
;;
esac
使用 | 符號可以讓多個模式執行相同的指令區塊。
read -p "請輸入月份縮寫: " month
case "$month" in
Jan|Feb|Mar)
echo "第一季"
;;
Apr|May|Jun)
echo "第二季"
;;
*)
echo "其他月份"
;;
esac
模式比對支援類似檔案路徑的擴充符號(如 *, ?, [a-z])。
read -p "請輸入一個字元: " char
case "$char" in
[a-z])
echo "這是小寫字母"
;;
[A-Z])
echo "這是大寫字母"
;;
[0-9])
echo "這是數字"
;;
*)
echo "這是特殊符號或多個字元"
;;
esac
| 符號 | 功能描述 |
|---|---|
) |
模式的結束標記。 |
;; |
指令區塊的結束標記(類似 break)。 |
* |
比對任何字串,通常放於最後作為預設選項。 |
| |
用於分隔多個匹配模式(代表「或」)。 |
esac |
case 的反向拼寫,代表語法區塊結束。 |
"$var"),以避免變數為空值時產生語法錯誤。case 會從上到下進行比對,一旦匹配成功並遇到 ;; 就會停止,因此請將具體的模式放在通用的模式(如 *)之前。預設行為:區分大小寫。在標準的 Bash 環境中,case 語句是大小寫敏感的。這意味著 "A" 與 "a" 會被視為不同的模式。
read -p "輸入一個字元: " char
case "$char" in
a) echo "這是小寫 a" ;;
A) echo "這是大寫 A" ;;
esac
---
如果您希望整個 case 語句忽略大小寫,可以使用 shopt 指令開啟 nocasematch 設定。這會影響後續所有的字串比對與 case 判斷。
# 開啟忽略大小寫
shopt -s nocasematch
read -p "輸入 yes 或 YES: " input
case "$input" in
yes) echo "匹配成功 (忽略大小寫)" ;;
esac
# 關閉忽略大小寫 (恢復預設)
shopt -u nocasematch
---
如果您不想更改全域設定,可以在模式中直接定義大小寫範圍,這是最標準且相容性最高的做法。
read -p "輸入一個字母: " char
case "$char" in
[aA])
echo "您輸入了 a 或 A"
;;
[b-zB-Z])
echo "您輸入了其他字母"
;;
esac
---
對於特定的單字,可以使用 | 來列舉所有可能的拼寫形式。
case "$input" in
stop|STOP|Stop)
echo "停止執行"
;;
esac
---
| 方法 | 優點 | 缺點 |
|---|---|---|
shopt -s nocasematch |
程式碼簡潔,適合大量比對。 | 會影響整個腳本的行為,需記得手動關閉。 |
[a-zA-Z] 範圍 |
精確控制,不影響其他部分。 | 若單字很長,寫法會變得很繁瑣。 |
管道符 | |
可讀性高,適合特定少數單字。 | 無法處理隨機的大小寫組合(如 YeS)。 |
在 Bash 的算術擴展 (( )) 內部,您完全可以使用小括號 ( ) 來明確指定運算的優先順序,這與 C 語言或大多數編程語言的邏輯一致。這在處理複雜的邏輯(結合與、或、加減乘除)時非常重要。
# 範例:(A 且 B) 或 C
if (( (MIN > 91 && MIN < 110) || FORCE_MODE == 1 )); then
echo "條件成立"
fi
在 (( )) 內,運算優先順序遵循標準數學與邏輯規範:
( ) 優先權最高。*, /, %。+, -。<, >, <=, >=。&& (AND) 優先於 || (OR)。假設您需要判斷:數值必須在範圍內,且(必須是特定倍數或是處於強制模式):
if (( MIN > 91 && (MIN % 5 == 0 || FORCE_MODE == 1) )); then
{
echo "觸發複雜條件判斷"
IS_SHUTDOWN=true
} |& tee -a "$LOGFILE"
fi
| 語法類型 | 分組符號 | 注意事項 |
|---|---|---|
(( ... )) |
( ) |
最直覺,符號不需轉義,推薦用於純數字運算。 |
[[ ... ]] |
( ) |
在 [[ ]] 內使用括號進行邏輯分組也是合法的。 |
[ ... ] |
\( ... \) |
傳統 test 語法,括號必須加反斜線轉義,較不建議使用。 |
在 Bash 腳本中,exit 用於終止腳本執行並向父程序傳回一個狀態碼(Exit Status)。
if [ -f "config.txt" ]; then
echo "檔案存在"
exit 0
else
echo "錯誤:找不到設定檔"
exit 1
fi
執行完一個腳本或指令後,可以使用特殊變數 $? 來取得上一個程序的 exit 狀態碼。
./script.sh
echo "腳本結束狀態:$?"
當您呼叫 .sh 檔案並希望將其「輸出內容」(即 echo 的內容)存入變數時,可以使用反引號 或 $()。這稱為指令替換(Command Substitution)。
#!/bin/bash
# 這是輸出內容,會被捕捉
echo "Ubuntu_User"
# 這是結束狀態,不會被反引號捕捉
exit 0
# 使用反引號
RESULT=`./get_name.sh`
# 使用 $() (現代化推薦寫法)
RESULT=$(./get_name.sh)
echo "取得的結果是: $RESULT"
| 標的 | 取得方式 | 用途 |
|---|---|---|
| Exit Status (狀態碼) | $? |
判斷指令執行是否成功 (0 或 1)。 |
| Standard Output (輸出內容) | ` ` 或 $( ) |
取得腳本執行後產生字串數據。 |
如果您想同時取得兩者,可以參考以下範例:
OUTPUT=$(./script.sh)
STATUS=$?
echo "內容為: $OUTPUT"
echo "狀態碼為: $STATUS"
在 Bash 中,return 指令專門用於函式 (Function) 或透過 source 執行的腳本。它會停止執行目前的函式或腳本,並將指定的狀態碼傳回給呼叫者,但不會關閉目前的 Shell 程序。
這是最常見的用法。return 傳回的是狀態碼(0-255),而非字串資料。
#!/bin/bash
# 定義函式
check_file() {
if [ -f "$1" ]; then
return 0 # 成功
else
return 1 # 失敗
fi
}
# 呼叫函式
check_file "test.txt"
RESULT=$?
if [ $RESULT -eq 0 ]; then
echo "檔案存在"
else
echo "檔案不存在"
fi
如果您希望一個腳本執行完後,變數與環境能留在目前的終端機,通常會使用 source。此時必須用 return 而非 exit。
# 模擬邏輯判斷
if [ "$USER" != "root" ]; then
echo "權限不足,僅供 root 使用"
# 如果用 exit 會直接關閉終端機,用 return 則只會停止此腳本執行
return 1
fi
export APP_STATUS="Ready"
return 0
source ./sub_script.sh
echo "腳本傳回狀態: $?"
echo "取得環境變數: $APP_STATUS"
由於 return 只能傳回數字(狀態碼),若要取得字串或大量數據,建議結合 echo 與 指令替換。
#!/bin/bash
calculate() {
local val=$(( $1 + $2 ))
# 輸出結果到 stdout
echo "$val"
# 傳回執行狀態碼
return 0
}
calculate 10 20
# 取得輸出內容
DATA=$(./get_data.sh)
# 取得 return 的狀態碼
STATUS=$?
echo "計算結果為: $DATA"
echo "執行狀態: $STATUS"
| 關鍵字 | 適用範圍 | 對主程序的影響 |
|---|---|---|
exit 0/1 |
獨立腳本、子程序 | 會終止目前的整個 Shell (若用 source 執行)。 |
return 0/1 |
函式、source 載入的腳本 | 僅退出目前範圍,不影響主程序 Shell。 |
echo "..." |
任何地方 | 用於傳遞實際的「資料內容」給呼叫者。 |
在 Bash 中,可以使用條件判斷來檢查變數是否為空,以下提供幾種常見的方式:
-z 檢查變數是否為空#!/bin/bash
var=""
if [ -z "$var" ]; then
echo "變數是空的"
else
echo "變數不是空的"
fi
-z 用來檢查變數是否為空,如果變數為空,條件成立。
-n 檢查變數是否不為空#!/bin/bash
var="some value"
if [ -n "$var" ]; then
echo "變數不是空的"
else
echo "變數是空的"
fi
-n 用來檢查變數是否不為空,如果變數有值,條件成立。
#!/bin/bash
var=""
if [ "$var" == "" ]; then
echo "變數是空的"
else
echo "變數不是空的"
fi
這種方式直接將變數與空字串進行比較,來檢查變數是否為空。
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
declare -A capital
capital["Taiwan"]="Taipei"
capital["Japan"]="Tokyo"
echo "${capital["Japan"]}" # Tokyo
for key in "${!capital[@]}"; do
echo "$key 的首都是 ${capital[$key]}"
done
declare -A 宣告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 後方。
"${array[@]}" 形式,避免元素被錯誤展開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。
[[ ]] 比 [ ] 功能更完整,推薦使用=~ 時可使用正則表達式,但記得不要加引號-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
-e:檔案或目錄是否存在(不管是什麼類型)-f:是否為「一般檔案」-d:是否為「目錄」-L:是否為「符號連結」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
-e-d 或 -ftarget_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)
find:列出子目錄(不遞迴)-mindepth 1:排除自身目錄-maxdepth 1:只列出第一層-type d:只列出目錄-print0 搭配 read -d $'\0':處理有空格的路徑for dir in "${subdirs[@]}"; do
echo "$dir"
done
subdirs=( "$target_dir"/*/ )
此寫法使用通配符匹配子目錄,但無法排除檔案或處理空格與特殊字元。
path="~/myfolder/file.txt"
realpath "$(eval echo "$path")"
path="~/myfolder/file.txt"
realpath "$(echo "$path" | sed "s|^~|$HOME|")"
path="~/myfolder/file.txt"
readlink -f "$(eval echo "$path")"
realpath "$(eval echo "$path")"
grep "關鍵字" 檔案名
grep -i "關鍵字" 檔案名
grep -n "關鍵字" 檔案名
grep -r "關鍵字" 目錄路徑
grep -o "關鍵字" 檔案名
grep -H "關鍵字" 檔案名
grep "關鍵字1" 檔案名 | grep "關鍵字2"
# 方法一:正規表示式
grep -E "關鍵字1|關鍵字2" 檔案名
# 方法二:多個 -e 參數
grep -e "關鍵字1" -e "關鍵字2" 檔案名
# 方法一:強制將檔案視為文字
grep -a "關鍵字" 檔案名
# 方法二:轉換檔案編碼為 UTF-8 再搜尋
iconv -f 原始編碼 -t UTF-8 檔案名 | grep "關鍵字"
# 範例 (從 BIG5 轉成 UTF-8)
iconv -f BIG5 -t UTF-8 檔案名 | grep "關鍵字"
grep -rin "關鍵字" 目錄路徑
在處理包含空格、特殊符號(如空白、引號、換行)的檔名或路徑時,傳統使用 find 配管線 xargs 或 read 可能產生誤判。
-print0 可讓 find 以 null(\0)字元作為輸出分隔,而 read -d $'\0' 能精準讀取 null 分隔的內容,確保每個檔案/路徑準確分離。
subdirs=()
while IFS= read -r -d $'\0' dir; do
subdirs+=("$dir")
done < <(find . -type d -print0)
-print0:將每筆結果用 null 字元結尾,不用換行-d $'\0':read 每次讀取一個 null 結尾的項目IFS=:避免空格被當成欄位分隔-r:避免處理時跳脫字元txt_files=()
while IFS= read -r -d $'\0' file; do
txt_files+=("$file")
done < <(find "$(pwd)" -type f -name "*.txt" -print0)
find . -type f -print0 | while IFS= read -r -d $'\0' file; do
echo "處理:$file"
# your_command "$file"
done
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 錯誤解析,甚至多行誤判為多個檔案。
-print0 與 read -d $'\0' 是處理任何檔名最安全的方式-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` 無法持續讀取。
-print0subdirs=()
find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0 | while IFS= read -r -d $'\0' dir; do
subdirs+=("$dir")
done
**注意**:若希望 `subdirs` 在主程式中可用,這方法不適合(因 `while ... |` 子 shell 無法回傳陣列)
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
mapfile -d '' -t 是處理 -print0 最安全且最 Bash 原生的方式var="line1
line2
line3"
while IFS= read -r line; do
echo "讀到:$line"
done <<< "$var"
IFS= 避免 trim 空白-r 避免跳脫字元被解析<<< 是 here-string,將變數內容當成輸入給 whileread -r first_line <<< "$var"
echo "第一行為:$first_line"
readarray -t lines <<< "$var"
for line in "${lines[@]}"; do
echo "$line"
done
find . -mindepth 1 -maxdepth 1 -type d -name "abc*" -printf "%T@ %p\n" | sort -nr | cut -d' ' -f2-
-mindepth 1 -maxdepth 1:只列出當前目錄下的子目錄-name "abc*":名稱以 abc 開頭-printf "%T@ %p\n":印出修改時間(timestamp)與目錄名稱sort -nr:依時間數值由新到舊排序cut -d' ' -f2-:去掉時間戳,只顯示路徑./abc_latest
./abc_old
./abc_2020
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
-printf 為 GNU find 專屬功能,在 macOS 預設 find 不支援,可用 gfind 替代%T@ 代表「最後修改時間(秒)」,若需建立時間,需額外工具DIR="/mnt/usb"
if [ -w "$DIR" ]; then
echo "$DIR 可寫入"
else
echo "$DIR 不可寫入"
fi
-w 為檢查目錄是否有「寫入權限」。但若目錄本身可寫,但實際裝置唯讀(如 mount 成 readonly),這方法可能失效。
DIR="/mnt/usb"
TESTFILE="$DIR/.write_test"
if touch "$TESTFILE" 2>/dev/null; then
echo "$DIR 可寫入"
rm "$TESTFILE"
else
echo "$DIR 不可寫入"
fi
此方法最可靠,能檢測 mount 狀態或實體裝置是否真的可寫。
mount 指令檢查是否唯讀掛載MNT="/mnt/usb"
if mount | grep "$MNT" | grep -q "(ro,"; then
echo "$MNT 為唯讀掛載"
else
echo "$MNT 為可寫掛載"
fi
此方法需檢查裝置是否被以唯讀(ro)方式掛載。
在 Shell 中,使用 `>` 可以將指令的標準輸出(stdout)重導向到一個檔案或設備。若檔案已存在,內容會被覆蓋。
echo "Hello" > output.txt
這行指令將 "Hello" 寫入 output.txt 檔案。
使用 `>>` 會將標準輸出附加到指定檔案的末尾,不會覆蓋原有內容。
echo "Hello again" >> output.txt
這行指令會將 "Hello again" 附加到 output.txt 的結尾。
在 Shell 中,`2>` 用於將標準錯誤(stderr)重導向到指定位置。例如:
ls non_existent_file 2> error.log
這行指令會將錯誤訊息寫入 error.log 檔案。
若不想覆蓋錯誤訊息檔案,可使用 `2>>` 將錯誤訊息附加到檔案末尾。
ls non_existent_file 2>> error.log
這行指令會將錯誤訊息附加到 error.log。
使用 `&>` 可以同時將標準輸出與標準錯誤都重導向到同一個檔案或設備。
command &> all_output.log
這行指令會將 command 的所有輸出(標準輸出與錯誤)寫入 all_output.log。
`2>&1` 將標準錯誤合併到標準輸出,便於統一管理。例如:
command > output.log 2>&1
這行指令會將標準輸出和錯誤都寫入 output.log。
若不想顯示任何輸出,可將所有輸出導向到 /dev/null,如:
command >/dev/null 2>&1
這行指令會將 command 的所有輸出丟棄。
在使用 tee 命令將輸出追加到文件時,可以通過 iconv 將輸出轉換為 UTF-8 編碼,確保文件內容以 UTF-8 保存。以下是具體的指令和範例。
以下是將輸出保存為 UTF-8 編碼的 tee 指令格式:
command | iconv -t utf-8 | tee -a output.txt
-t 代表「目標編碼」。output.txt 文件。以下範例演示如何將 ls 命令的輸出以 UTF-8 編碼寫入到 output.txt:
ls | iconv -t utf-8 | tee -a output.txt
執行該指令後,output.txt 的內容將以 UTF-8 編碼保存,避免出現編碼錯誤。
Win + R 輸入 cmd 後按下 Entercmd 可直接於該路徑開啟dir:顯示目前目錄下的檔案與資料夾cd:切換目錄,例如 cd C:\Userscls:清除螢幕內容copy:複製檔案,例如 copy a.txt b.txtdel:刪除檔案mkdir:建立新資料夾rmdir:刪除資料夾echo:輸出文字,例如 echo Helloexit:關閉命令提示字元dir > file.txt 將結果輸出至檔案dir | find "txt" 篩選包含 txt 的結果.bat 檔案一次執行多個指令set 查看與設定變數ipconfig /flushdns 或 sfc /scannow 等系統維護指令可以使用 &、&& 或 || 來接續指令:
cmd /k "第一個指令 & 第二個指令"
&:無論第一個成功或失敗都會執行下一個&&:只有第一個成功時才執行下一個||:只有第一個失敗時才執行下一個在第一個指令後加上 call,即可接續執行另一個批次檔:
cmd /k "第一個指令 & call second.bat"
若不需要保持視窗開啟,可以用 /c:
cmd /c "第一個指令 & 第二個指令"
/c 會執行完所有指令後自動關閉視窗,而 /k 則會保留視窗。
在 Windows 命令提示字元(Command Prompt, CMD)中,變數是用來儲存資料或設定值的命名空間。它們可以儲存在環境中(環境變數),也可以在批次檔(Batch File)中定義和使用(區域變數)。
系統或使用者設定的變數,在整個作業系統會話中都可用。例如:%PATH%、%TEMP%。
查看環境變數: 使用 set 命令。
set
設定/修改環境變數 (僅限目前 CMD 會話):
set MY_VAR=Hello
在批次檔(.bat 或 .cmd 檔案)內使用 set 命令定義的變數,只在該批次檔的執行過程中有效。
在使用 for 迴圈時定義的變數,用於迭代檔案、資料夾或數字序列。通常以單個字母命名,前面加兩個百分比符號,例如:%%A(在批次檔中)或單個百分比符號,例如:%A(在命令列直接執行時)。
當執行批次檔時傳遞給它的參數。例如:%1 是第一個參數,%2 是第二個參數,%0 是批次檔本身的路徑和名稱。
要取得變數的值,通常需要在變數名稱前後加上百分比符號 %。
set NAME=CMD User
echo 我的名字是 %NAME%
輸出:我的名字是 CMD User
@echo off
set MESSAGE=這是批次檔訊息
echo %MESSAGE%
pause
您可以使用 IF DEFINED 或 IF NOT DEFINED 語句來檢查變數是否已存在或已定義(非空)。
@echo off
rem 檢查變數 MY_DEFAULT_VALUE 是否已定義 (非空)
IF NOT DEFINED MY_DEFAULT_VALUE (
set MY_DEFAULT_VALUE=Default_Setting
echo 變數 MY_DEFAULT_VALUE 未定義,已設定為預設值。
) ELSE (
echo 變數 MY_DEFAULT_VALUE 已存在,值為: %MY_DEFAULT_VALUE%
)
rem 執行一次,變數會被設定為 Default_Setting
rem 再次執行,會顯示已存在。
set MY_DEFAULT_VALUE=Existing_Setting
rem 再次檢查
IF NOT DEFINED MY_DEFAULT_VALUE (
set MY_DEFAULT_VALUE=Another_Default
echo 變數 MY_DEFAULT_VALUE 未定義,已設定為 Another_Default。
) ELSE (
echo 變數 MY_DEFAULT_VALUE 已存在,值為: %MY_DEFAULT_VALUE%
)
pause
另一個較為簡潔的技巧是利用變數擴充的預設值功能 (但這主要用於迴圈和參數處理,且在標準 set 命令上並不直接適用於「如果未設定就設定」的邏輯。因此,IF NOT DEFINED 是最標準的作法。)
在迴圈或條件語句中,標準的百分比符號變數擴充會在語句解析時發生一次。為了在迴圈的每次迭代中都能讀取變數的新值,需要啟用延遲擴充,並使用驚嘆號 ! 來引用變數。
@echo off
setlocal enabledelayedexpansion
set COUNTER=0
:LOOP
if %COUNTER% LSS 5 (
set /A COUNTER+=1
echo 目前計數: !COUNTER!
goto LOOP
)
endlocal
| 變數 | 描述 |
|---|---|
%DATE% |
目前的日期。 |
%TIME% |
目前的時間。 |
%CD% |
目前的目錄路徑。 |
%ERRORLEVEL% |
前一個命令的結束代碼(通常 0 表示成功)。 |
%RANDOM% |
0 到 32767 之間的一個隨機十進制數字。 |
%~dp0 |
執行中的批次檔所在的驅動器和路徑。 |
對於參數變數(如 %1)或迴圈變數(如 %%A),可以使用修飾符來提取路徑的不同部分:
echo 完整路徑: %~1
echo 驅動器: %~d1
echo 路徑: %~p1
echo 檔名: %~n1
echo 副檔名: %~x1
這在處理檔案路徑時非常有用。
grep,但可以使用內建的 find 指令達到類似效果。指令 | find "關鍵字"dir | find "txt" :: 只顯示包含 "txt" 的檔案列
ipconfig | find "IPv4" :: 只顯示含有 IPv4 的行
tasklist | find "chrome" :: 篩出含有 chrome 的執行程序
/I:忽略大小寫,例如 find /I "error"/V:顯示未包含關鍵字的行/C:只顯示符合的行數find 達成 AND 條件:type log.txt | find "Error" | find "2025"
findstr:type log.txt | findstr /I "error warning fail"
findstr 是 CMD 版的進階搜尋工具,支援多關鍵字與正規表示式。dir | findstr /R ".*\.txt$" :: 使用正規表示式找出 .txt 檔
type log.txt | findstr /I "timeout error fail"
~/.bash_profile 或 ~/.bashrc。AutoRun 設定,若有內容會自動執行。HKEY_CURRENT_USER\Software\Microsoft\Command Processor
HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor
AutoRun,內容可設定為要執行的批次檔路徑:AutoRun = "C:\Users\YourName\cmd_startup.bat"
%SystemRoot%\System32\cmd.exe /k "C:\Users\YourName\cmd_startup.bat"
/k 代表啟動後執行該批次檔並保持視窗開啟。%AppData%\Microsoft\Windows\Start Menu\Programs\Startup
| Linux bash | Windows CMD 對應方式 |
|---|---|
| ~/.bash_profile 或 ~/.bashrc | 登錄表 AutoRun 或 CMD /k 啟動批次檔 |
| 自動執行自定指令 | 批次檔內可放自定義環境變數與路徑設定 |
HKEY_CURRENT_USER\Software\Microsoft\Command Processor 是否存在。AutoRun 值,則自動建立並設定為指定批次檔路徑。@echo off
setlocal
:: 設定 AutoRun 要執行的批次檔路徑
set "AUTORUN_PATH=C:\Users\%USERNAME%\cmd_startup.bat"
set "REG_PATH=HKCU\Software\Microsoft\Command Processor"
echo 檢查 CMD AutoRun 設定中...
:: 檢查登錄機碼是否存在
reg query "%REG_PATH%" >nul 2>&1
if errorlevel 1 (
echo [資訊] 找不到 Command Processor 機碼,建立中...
reg add "%REG_PATH%" /f >nul
)
:: 檢查 AutoRun 是否已設定
reg query "%REG_PATH%" /v AutoRun >nul 2>&1
if errorlevel 1 (
echo [資訊] 未找到 AutoRun,建立新值...
reg add "%REG_PATH%" /v AutoRun /d "%AUTORUN_PATH%" /f >nul
echo [完成] AutoRun 已設定為:%AUTORUN_PATH%
) else (
echo [OK] AutoRun 已存在,未做變更。
)
endlocal
pause
reg query:用來檢查登錄鍵或值是否存在。errorlevel:可用來判斷查詢結果是否成功 (0 為存在,1 為不存在)。reg add:建立登錄鍵或值。/f:強制覆蓋不提示。%AUTORUN_PATH% 改成你要在 CMD 啟動時自動執行的批次檔路徑。@echo off
:: 偵測 PowerShell
if defined PSModulePath (
echo 目前在 PowerShell 執行
goto :eof
)
:: 偵測 Cygwin
if defined CYGWIN (
echo 目前在 Cygwin 執行
goto :eof
)
if defined SHELL (
echo 目前在 Cygwin 執行 (SHELL=%SHELL%)
goto :eof
)
:: 預設為 CMD
echo 目前在 CMD 執行
PSModulePath 會存在CYGWIN 變數,或 SHELL=/bin/bashWin + X 選擇「Windows PowerShell」或「Windows Terminal」powershell 可直接於該路徑開啟Get-ChildItem:列出目前目錄檔案與資料夾 (類似 dir)Set-Location:切換目錄 (縮寫 cd)Clear-Host:清除螢幕內容 (縮寫 cls)Copy-Item:複製檔案或資料夾Remove-Item:刪除檔案或資料夾New-Item:建立新檔案或資料夾Write-Output:輸出文字 (縮寫 echo)Exit:關閉 PowerShellGet-Process | Where-Object {$_.CPU -gt 100}Get-ChildItem > file.txtls、cp、rm 可直接使用$var = "Hello",輸出 $var.ps1 檔案執行多個指令Get-EventLog、Set-ExecutionPolicy 等系統維護指令Get-Variable:顯示目前 PowerShell 工作階段中的所有變數Get-ChildItem variable::透過變數驅動器檢視所有變數# 列出所有變數
Get-Variable
# 只顯示變數名稱
Get-Variable | Select-Object Name
# 顯示特定變數的值
Get-Variable PATH | Format-List *
# 使用變數驅動器方式
Get-ChildItem variable:
variable: 可當作路徑來操作變數,例如 Get-Content variable:PATHWhere-Object,例如:Get-Variable | Where-Object { $_.Name -like "P*" } 只顯示以 P 開頭的變數PowerShell 使用 .NET UI是很常見的用法。
PowerShell 本身建構在 .NET 上,因此可以直接呼叫任何公開的 .NET Class,包括:
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text = "Hello PowerShell"
$form.Width = 400
$form.Height = 300
$button = New-Object System.Windows.Forms.Button
$button.Text = "Click Me"
$button.Left = 100
$button.Top = 100
$button.Add_Click({
[System.Windows.Forms.MessageBox]::Show("Hello World")
})
$form.Controls.Add($button)
$form.ShowDialog()
執行後會出現真正的 Windows 視窗。
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text = "Login"
$label = New-Object System.Windows.Forms.Label
$label.Text = "User Name"
$label.Left = 20
$label.Top = 20
$textbox = New-Object System.Windows.Forms.TextBox
$textbox.Left = 100
$textbox.Top = 20
$button = New-Object System.Windows.Forms.Button
$button.Text = "OK"
$button.Left = 100
$button.Top = 60
$button.Add_Click({
[System.Windows.Forms.MessageBox]::Show(
"Hello " + $textbox.Text
)
})
$form.Controls.Add($label)
$form.Controls.Add($textbox)
$form.Controls.Add($button)
$form.ShowDialog()
WPF 比 WinForms 新,也比較漂亮。
Add-Type -AssemblyName PresentationFramework
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="PowerShell WPF"
Width="300"
Height="200">
<Grid>
<Button Name="btn"
Width="100"
Height="30"
Content="Click"/>
</Grid>
</Window>
"@
$reader = New-Object System.Xml.XmlNodeReader $xaml
$window = [Windows.Markup.XamlReader]::Load($reader)
$button = $window.FindName("btn")
$button.Add_Click({
[System.Windows.MessageBox]::Show("Hello WPF")
})
$window.ShowDialog()
例如自己的 .NET Library:
Add-Type -Path "MyLibrary.dll"
$obj = New-Object MyNamespace.MyClass
$obj.DoSomething()
PowerShell 可動態編譯 C#:
Add-Type @"
using System;
public class MyClass
{
public static string Hello()
{
return "Hello C#";
}
}
"@
[MyClass]::Hello()
| 項目 | WinForms | WPF |
|---|---|---|
| 學習難度 | 低 | 中高 |
| 開發速度 | 快 | 較慢 |
| 介面美觀 | 普通 | 佳 |
| 支援拖拉設計 | Visual Studio | Visual Studio |
| PowerShell 相容性 | 最好 | 很好 |
如果只是:
WinForms 非常適合,而且通常一個 .ps1 就能完成。
若要做大型桌面應用程式(數十個畫面、MVVM、資料繫結、多語系),則通常直接使用 C# + WPF 或 C# + WinUI 會比較容易維護。
甚至可以把 PowerShell 當桌面程式啟動器,內嵌瀏覽器:
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$web = New-Object System.Windows.Forms.WebBrowser
$web.Dock = "Fill"
$web.Navigate("https://www.example.com")
$form.Controls.Add($web)
$form.ShowDialog()
因此 PowerShell 不只是腳本語言,實際上可以直接利用 .NET 建立完整 Windows GUI 程式。
預處理器指令 (Preprocessor Directives)是在 C 或 C++ 程式碼被編譯器正式處理(編譯)之前,由一個稱為「預處理器 (Preprocessor)」的程式模組所執行的命令。這些指令以井號(#)開頭,並且不以分號(;)結尾。
預處理器的作用是執行文字替換、檔案包含、條件編譯等操作,從而產生最終的「翻譯單元 (Translation Unit)」供編譯器使用。
用於將其他檔案的內容插入到當前檔案中。
#include <filename>:用於包含標準程式庫標頭檔。預處理器會在編譯器預設的系統標頭檔路徑中搜尋檔案。#include "filename":用於包含使用者自定義的標頭檔。預處理器會先在當前原始碼檔案的目錄中搜尋,然後才搜尋系統路徑。
#include <iostream> // 包含標準 I/O 庫
#include "my_utility.h" // 包含自定義標頭檔
用於定義符號常數或代碼片段的文字替換巨集。
#define:定義巨集。#undef:取消定義巨集。
#define MAX_SIZE 100 // 定義一個符號常數
#define SUM(a, b) ((a) + (b)) // 定義一個帶參數的巨集
int main() {
int size = MAX_SIZE; // 預處理後變為 int size = 100;
int total = SUM(5, 3); // 預處理後變為 int total = ((5) + (3));
return 0;
}
允許根據預處理器巨集的值,來決定程式碼的特定區塊是否應被編譯。
#ifdef:如果巨集已定義,則編譯後續區塊。#ifndef:如果巨集未定義,則編譯後續區塊。 (常用於 Include Guard)#if:根據給定的常數整數表達式的值來決定是否編譯。#else / #elif:條件編譯的替代或後續判斷。#endif:結束條件編譯區塊。
#define DEBUG_MODE 1
#if DEBUG_MODE
std::cout << "偵錯模式已啟用" << std::endl;
#else
std::cout << "生產模式運行中" << std::endl;
#endif
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ... 標頭檔內容 (Include Guard 範例)
#endif
#error:強制預處理器在遇到此指令時停止編譯,並顯示指定的錯誤訊息。#warning:輸出指定的警告訊息,但不停止編譯 (非標準指令,但許多編譯器支援)。
#ifndef VERSION_DEFINED
#error "必須定義版本號巨集 VERSION_DEFINED!"
#endif
#pragma:用於向編譯器發送特殊指令,行為高度依賴於編譯器 (例如 #pragma once 和 #pragma warning)。#line:用於改變編譯器報告錯誤或警告時,所顯示的當前行號和檔案名稱。#pragma once 是 C 和 C++ 語言中的一種預處理器指令 (Preprocessor Directive)。它的核心功能是確保包含此指令的標頭檔 (Header File) 在單次編譯過程中只會被編譯器處理一次。
此指令的目的是為了解決標頭檔重複包含的問題,防止因為多個原始碼檔案或多層級的標頭檔包含關係,導致同一段程式碼(例如類別定義、函式原型或常數宣告)被編譯器看到多次,進而引發重複定義 (Redefinition) 的編譯錯誤。
在 C/C++ 標準中,傳統上透過使用 Include Guard (包含衛兵) 來達到防止重複包含的目的。#pragma once 提供了更簡潔的替代方案。
這是標準且可移植的方法,使用條件編譯指令:
#ifndef MY_HEADER_H
#define MY_HEADER_H
// 標頭檔內容
#endif // MY_HEADER_H
它依賴於一個唯一的巨集名稱(例如 MY_HEADER_H)來控制內容是否被包含。
使用單行指令:
#pragma once
// 標頭檔內容
編譯器會自動追蹤它在當前編譯會話中是否已經處理過這個檔案,如果處理過,則直接跳過該檔案的剩餘內容。
在現代的開發環境中,特別是使用 Visual Studio 或主流編譯器時,#pragma once 因其簡潔和便利性,是防止標頭檔重複包含的常見且推薦的做法。
#pragma warning 是 C 和 C++ 語言中的預處理器指令,專門用於在程式碼的特定區塊內,選擇性地抑制、恢復或修改編譯器警告的嚴重程度。
此指令的主要目的在於提供比專案級別設定更精細的控制。例如,當您需要包含一個會產生大量警告的舊版程式庫,但又不希望因此而全域性地關閉這些警告時,就可以使用此指令。
雖然 #pragma warning 的具體實作在不同編譯器間可能略有差異(尤其 MSVC 使用最廣),但其核心動作分為以下幾種:
從該點開始,編譯器將忽略指定的警告代碼。
#pragma warning( disable : 4996 ) // 禁用 C4996 (例如:關於使用不安全函式的警告)
// 包含或撰寫會產生 C4996 的程式碼
將指定的警告恢復到專案或命令列設定的預設行為。
#pragma warning( default : 4996 ) // 將 C4996 恢復為預設狀態
這是最安全且最推薦的模式。它允許在處理特定程式碼區塊時修改警告設定,然後確保原始的警告狀態在區塊結束後立即被還原,避免副作用。
#pragma warning( push ):儲存當前的警告設定堆疊狀態。#pragma warning( pop ):還原到最近一次 push 所儲存的狀態。
#pragma warning( push ) // 1. 儲存目前的警告設定
#pragma warning( disable : 4996 4244 ) // 2. 禁用多個特定警告
// 包含第三方標頭檔或舊版程式碼
#pragma warning( pop ) // 3. 還原到原始設定
// 後續的程式碼將使用還原後的設定
將特定的警告代碼提升為編譯錯誤,若發生該警告,編譯將失敗。
#pragma warning( error : 4005 ) // 將警告 4005 視為錯誤
4996)是編譯器特定的。您必須針對您使用的編譯器(如 MSVC)查閱正確的編號。#pragma warning 的功能,但它們通常更傾向於使用 -W... 命令列旗標或特殊的 #pragma GCC diagnostic 結構來控制警告。在 C++ 程式設計中,std:: 是指 Standard Namespace(標準命名空間)。它是一個容器,包含了 C++ 語言核心和其標準程式庫(Standard Library)中的所有標準實體,包括函式、類別、模板、巨集和物件。
使用 std:: 的主要目的是為了避免命名衝突 (Name Collisions)。如果標準函式(例如 cout 或 vector)沒有被放在單獨的命名空間內,當使用者自己定義了同名的實體時,編譯器就會不知道該使用哪一個。
C++ 標準程式庫的大部分功能都位於 std:: 命名空間內。主要類別和功能包括:
std::cout:標準輸出串流物件,用於將資料列印到控制台。std::cin:標準輸入串流物件,用於從控制台讀取資料。std::endl:輸出換行符並刷新緩衝區。std::ifstream / std::ofstream:檔案輸入/輸出串流。用於儲存資料的集合類別:
std::vector:動態大小的陣列。std::list:雙向連結串列。std::map:鍵值對的排序集合(紅黑樹實作)。std::unordered_map:鍵值對的無序集合(哈希表實作)。std::set:唯一鍵值的排序集合。std::string:用於處理字串的類別。一組用於容器和範圍操作的通用函式:
std::sort:排序範圍內的元素。std::find:在範圍內尋找指定的值。std::copy:將元素從一個範圍複製到另一個範圍。std::shared_ptr / std::unique_ptr:智慧型指標,用於自動化記憶體管理。std::thread:用於多執行緒程式設計。std::function:通用函式包裝器。std::pair / std::tuple:用於儲存固定數量不同類型值的模板。要存取 std:: 命名空間中的實體,有兩種主要方法:
每次使用時都明確寫出 std:: 前綴。這是最安全且推薦的做法,尤其是在標頭檔中,以避免污染全域命名空間。
int main() {
std::cout << "Hello World" << std::endl;
std::vector<int> numbers;
return 0;
}
使用 using namespace std; 可以將整個 std:: 命名空間的內容引入到當前作用域,之後可以直接使用名稱而不需要 std:: 前綴。
#include <iostream>
using namespace std; // 將 std:: 命名空間引入
int main() {
cout << "Hello World" << endl; // 不需 std:: 前綴
vector<int> numbers;
return 0;
}
儘管 using namespace std; 很方便,但在大型專案或標頭檔中應極力避免使用,因為它會增加命名衝突的風險。
在 C++ 中,std::string 是一個類別 (Class)。當你宣告一個 std::string 變數但沒有給予初始值時,它會呼叫預設建構子 (Default Constructor)。
"")。0。NULL 指標,而是一個有效的物件。
#include <string>
#include <iostream>
void initialization_example() {
std::string s; // 宣告但未給值
std::cout << "長度: " << s.length() << std::endl; // 輸出: 0
std::cout << "內容: '" << s << "'" << std::endl; // 輸出: ''
}
檢查字串是否不含任何字元,最推薦的方法是使用 empty() 成員函式,這比檢查 length() == 0 更具語意化且在某些容器中效率更高。
void check_empty(std::string s) {
if (s.empty()) {
std::cout << "這是一個空字串。" << std::endl;
}
}
這是一個常見的誤區:std::string 物件本身永遠不會是 null。
在 C++ 中,只有「指標」可以是 nullptr。如果你指的是檢查字串是否尚未賦值,通常就是檢查它是否 empty()。如果你處理的是 C 風格的字串指標 (char*),才需要檢查 null。
| 類型 | 檢查方式 | 備註 |
|---|---|---|
| std::string s; | s.empty() | 檢查內容長度是否為 0 |
| std::string* ptr; | ptr == nullptr | 檢查「指標」本身是否指向空位 |
| char* c_str; | c_str == nullptr | 檢查 C 風格字串指標是否為空 |
如果你想將一個已有內容的字串恢復到初始的空狀態,可以使用以下方法:
void clear_example() {
std::string s = "Hello";
s.clear(); // 方法 1:最常用
s = ""; // 方法 2:重新賦值
s = std::string(); // 方法 3:賦予一個預設物件
}
這是 C++11 引入最直接的方法,將數值轉換為字串。預設情況下,它會保留 6 位小數點。
#include <string>
#include <iostream>
void simple_convert() {
float val = 3.14159f;
std::string s = std::to_string(val);
std::cout << s << std::endl; // 輸出範例: "3.141590"
}
如果你需要控制小數點的位數或特定格式,stringstream 是最彈性的選擇。它結合了 <iomanip> 庫來精確控制輸出。
#include <sstream>
#include <iomanip>
void precision_convert() {
float val = 3.1415926f;
std::stringstream ss;
// 設定固定點計數法 (fixed) 並保留 2 位小數
ss << std::fixed << std::setprecision(2) << val;
std::string s = ss.str();
std::cout << s << std::endl; // 輸出: "3.14"
}
在 C++20 中,你可以使用格式化字串來同時處理型別轉換與精度控制,語法最為精煉。
#include <format>
void modern_format() {
float val = 123.456f;
// {:.2f} 代表 float 型別且保留兩位小數
std::string s = std::format("{:.2f}", val);
std::cout << s << std::endl; // 輸出: "123.46" (會自動四捨五入)
}
std::to_string 經常會產生如 "3.140000" 這樣多餘的零。你可以結合 find_last_not_of 和 erase 來清理字串。
std::string remove_trailing_zeros(float val) {
std::string s = std::to_string(val);
// 移除末尾的 '0',直到遇到非 0 字元
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
// 如果最後一個字元是小數點,也一併移除
if (s.back() == '.') {
s.pop_back();
}
return s;
}
| 方法 | 特點 | 建議場景 |
|---|---|---|
| std::to_string | 語法最短,效能尚可 | 快速除錯、對小數位數無要求時 |
| std::stringstream | 高度可客製化(補零、寬度、進位) | 需要嚴格控制輸出格式時 |
| std::format | 類型安全、效能優、語法現代 | C++20 環境下的首選方案 |
| sprintf (C 風格) | 極速但較不安全 | 極度追求效能的老舊程式碼維護 |
這是 C++ 中最標準且不依賴外部庫的方法。透過 find 尋找分隔符的位置,並利用 substr 擷取子字串。
#include <iostream>
#include <vector>
#include <string>
void split_by_three_chars() {
std::string text = "Apple---Banana---Cherry---Date";
std::string delimiter = "---";
std::vector<std::string> tokens;
size_t pos = 0;
size_t last_pos = 0;
// 循環尋找分隔符
while ((pos = text.find(delimiter, last_pos)) != std::string::npos) {
tokens.push_back(text.substr(last_pos, pos - last_pos));
last_pos = pos + delimiter.length();
}
// 放入最後一個片段
tokens.push_back(text.substr(last_pos));
// 輸出結果
for (const auto& t : tokens) std::cout << "[" << t << "]" << std::endl;
}
如果你的字串非常龐大,使用 std::string_view 可以避免在拆分過程中產生大量的字串副本,從而大幅提升效能。
#include <string_view>
#include <vector>
std::vector<std::string_view> split_sv(std::string_view str, std::string_view del) {
std::vector<std::string_view> output;
size_t first = 0;
while (first < str.size()) {
const auto second = str.find(del, first);
if (second == std::string_view::npos) {
output.emplace_back(str.substr(first));
break;
}
output.emplace_back(str.substr(first, second - first));
first = second + del.size();
}
return output;
}
如果你希望程式碼看起來更簡潔,或是分隔符可能會有變動,可以使用 std::regex。但注意正則表達式的執行效能通常比手動 find 慢。
#include <regex>
void regex_split() {
std::string text = "One###Two###Three";
std::regex ws_re("###"); // 定義 3 字元分隔符
std::copy(std::sregex_token_iterator(text.begin(), text.end(), ws_re, -1),
std::sregex_token_iterator(),
std::ostream_iterator<std::string>(std::cout, "\n"));
}
| 方法 | 優點 | 缺點 |
|---|---|---|
| std::find + substr | 相容性最高 (C++98+),效能穩定。 | 程式碼較冗長,需手動處理最後一個片段。 |
| string_view | 效能最強,不產生額外記憶體拷貝。 | 需 C++17 支持,且需注意原始字串生命週期。 |
| std::regex | 語法最簡潔,擴充性強。 | 執行速度最慢,編譯時間較長。 |
在 C++ 中,要檢查 std::vector 容器是否不包含任何元素(即大小為零),最標準且推薦的方法是使用其成員函式 empty()。這是因為 empty() 函式通常比直接檢查 size() 是否等於零更高效,特別是在某些容器實作中。
這是檢查 vector 是否為空的首選方法。它返回一個布林值:如果 vector 沒有元素,則返回 true;否則返回 false。
#include <vector>
#include <iostream>
#include <string>
void check_empty(const std::vector<std::string>& vec)
{
if (vec.empty()) {
std::cout << "Vector 是空的 (empty() == true)." << std::endl;
} else {
std::cout << "Vector 不是空的 (empty() == false). 元素數量: " << vec.size() << std::endl;
}
}
int main()
{
std::vector<std::string> empty_vec;
std::vector<std::string> non_empty_vec = {"apple", "banana"};
check_empty(empty_vec);
check_empty(non_empty_vec);
return 0;
}
雖然有效,但這不是檢查空狀態的最慣用或潛在最高效的方法。它直接檢查 vector 中的元素數量是否為零。
void check_size(const std::vector<int>& vec)
{
if (vec.size() == 0) {
std::cout << "Vector 是空的 (size() == 0)." << std::endl;
} else {
std::cout << "Vector 不是空的 (size() != 0)." << std::endl;
}
}
對於 std::vector 而言,empty() 和 size() == 0 在時間複雜度上都是 $O(1)$,因為 vector 會將其大小作為成員變數儲存。然而,對於某些其他 C++ 標準容器(例如 std::list 或 std::forward_list),標準建議始終優先使用 empty(),因為它通常是 C++ 標準容器庫中檢查空狀態的通用且最快的慣用法。
在 C++ 中,要將一個包含空格(Space)或 Tab 字元(\t)分隔的字串解析成一個個獨立的單詞或標記,並儲存到 std::vector<std::string> 中,最常用的方法是利用 **std::stringstream**。檔案串流類別 std::stringstream 就像一個記憶體中的串流,它預設使用空白字元(包括空格、Tab 和換行符)作為分隔符來進行讀取操作。
以下是使用 std::stringstream 完成此任務的 C++ 程式碼範例:
#include <iostream>
#include <sstream> // 包含 stringstream
#include <string>
#include <vector>
using namespace std;
/**
* 將輸入字串依據任何空白字元 (空格或 Tab) 分隔,並存入 vector。
* @param input_str 要解析的字串。
* @return 包含所有分離出的單詞的 vector。
*/
vector<string> split_string_by_whitespace(const string& input_str)
{
vector<string> tokens;
// 1. 建立 stringstream 物件,並用輸入字串初始化
stringstream ss(input_str);
string token;
// 2. 迴圈讀取
// 串流提取運算子 (>>) 會自動以空白字元 (空格、Tab 等) 為分隔符讀取下一個 token。
// 當成功讀取一個 token 時,運算子返回 true,否則 (到達字串結尾) 返回 false。
while (ss >> token)
{
tokens.push_back(token);
}
return tokens;
}
int main()
{
// 輸入字串包含多個空格和 Tab 符號 (\t)
string test_string = "Hello \tWorld this is \ta test string";
cout << "原始字串: " << test_string << endl;
vector<string> result = split_string_by_whitespace(test_string);
cout << "--- 分隔結果 ---" << endl;
for (size_t i = 0; i < result.size(); ++i)
{
cout << "Token " << i + 1 << ": [" << result[i] << "]" << endl;
}
return 0;
}
std::stringstream ss(input_str):這個類別將輸入字串視為一個輸入串流 (Input Stream),功能類似於從檔案讀取時使用的 std::ifstream。>>):
while (ss >> token):這是一個慣用的 C++ 串流讀取迴圈。只要串流成功提取並將數據儲存到 token 變數中,迴圈就會繼續執行。在 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 是 C++ 標準庫中的容器類別,適合用於大小在編譯期固定的情況。std::array 提供更多的功能和安全性。在 C++ 中,迴圈 (Looping) 是遍歷容器或陣列中所有元素的基本操作。std::vector(動態大小容器)和 std::array(固定大小容器)都是 C++ 標準程式庫 (STL) 中的容器,可以使用多種標準且安全的方法來遍歷它們的元素。
這是 C++11 引入的最現代、最安全、最簡潔的遍歷方法。它適用於所有標準容器(包括 std::vector 和 std::array)。
std::vector:**首選**std::array:**首選**
#include <iostream>
#include <vector>
#include <array>
void range_based_loop_example()
{
std::vector<int> vec = {10, 20, 30};
std::array<int, 4> arr = {1, 2, 3, 4};
// 使用 auto& 以引用方式遍歷,效率高且允許修改元素
std::cout << "--- Vector (可修改) ---" << std::endl;
for (auto& element : vec) {
element += 1; // 修改元素
std::cout << element << " ";
}
std::cout << std::endl;
// 使用 const auto& 以常數引用方式遍歷,安全且不允許修改元素
std::cout << "--- Array (唯讀) ---" << std::endl;
for (const auto& element : arr) {
std::cout << element << " ";
}
std::cout << std::endl;
}
這是一種傳統且通用的方法。適用於需要存取元素索引(例如:需要同時操作兩個容器或陣列、或在特定索引處終止)的情況。
std::vector:適用,使用 size() 取得邊界。std::array:適用,使用 size() 取得邊界。
#include <iostream>
#include <vector>
#include <array>
void index_loop_example()
{
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
std::array<double, 3> prices = {10.5, 20.99, 5.0};
// 使用 size_t 或 auto 搭配 size(),確保索引類型正確且邊界安全
for (size_t i = 0; i < names.size(); ++i) {
// 使用 operator[] 存取元素
std::cout << "名字: " << names[i] << ", 價格: " << prices[i] << std::endl;
}
}
注意:使用 [] 運算子存取元素時,C++ 不執行邊界檢查。如果需要邊界檢查,請使用 at() 函式(它會在越界時拋出 std::out_of_range 異常)。
這是 C++ 中最靈活的遍歷方法,適用於所有標準容器。當需要與 C++ 標準程式庫 (STL) 演算法互動時,迭代器是必須的。
std::vector:**推薦** (當需要 STL 演算法或特定控制時)std::array:**推薦** (當需要 STL 演算法時)
#include <iostream>
#include <vector>
void iterator_loop_example()
{
std::vector<double> values = {100.0, 50.0, 10.0};
// 使用 auto 簡化迭代器類型,並使用 begin() 和 end() 取得範圍
for (auto it = values.begin();
it != values.end();
++it)
{
*it /= 10.0; // *it 解引用迭代器,存取元素
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用 cbegin() 和 cend() 確保迭代器是 const,用於唯讀遍歷
for (auto cit = values.cbegin(); cit != values.cend(); ++cit)
{
// 嘗試 *cit = 5; 會導致編譯錯誤
std::cout << *cit << " ";
}
std::cout << std::endl;
}
| 迴圈方法 | 適用容器 | 優點 | 適用時機 |
|---|---|---|---|
| 基於範圍 (Range-based) | std::vector, std::array, 所有標準容器 | 最簡潔、最安全、現代 C++ 慣用法。 | 不需要索引、只需要遍歷每個元素時。 |
| 索引式 (Index-based) | std::vector, std::array | 可以存取索引,通用性高。 | 需要知道或操作元素的索引時。 |
| 迭代器 (Iterator) | 所有標準容器 | 極度靈活,能與 STL 演算法 (如 std::find) 完美配合。 |
需要使用 STL 演算法或需要對容器進行複雜操作時。 |
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(twoLines.begin(), twoLines.end(), twoLinesCopy.begin());
std::memcpy(&twoLinesCopy, &twoLines, sizeof(twoLines));
twoLinesCopy = twoLines;std::copy 適用於需要 STL 泛型方法。memcpy 僅適用於 POD(Plain Old Data),且無建構子/解構子邏輯。在 C++ 中,當您操作指標或使用索引存取陣列/記憶體時,如果存取了無效或已越界的記憶體位置,就會發生未定義行為 (Undefined Behavior)。這種行為通常表現為程式崩潰 (Crash),例如分段錯誤 (Segmentation Fault) 或存取違規 (Access Violation) 錯誤。
C++ 的標準 try-catch 機制主要用於捕獲程式碼中明確拋出的 C++ 異常 (Exceptions),而不能直接捕獲由作業系統發出的硬體或記憶體存取錯誤。
當 C++ 程式試圖存取無效記憶體時,例如:
nullptr 的記憶體。delete 或 free 釋放的記憶體。這些操作會觸發底層作業系統的保護機制(例如 Windows 上的 Access Violation 或 Unix/Linux 上的 SIGSEGV 信號),程式會被作業系統終止。這些錯誤不是 C++ 語言層級的異常對象(如 std::exception),因此標準的 try-catch 無法捕獲它們。
#include <iostream>
#include <vector>
using namespace std;
void crash_function()
{
// 陣列越界存取範例
vector<int> vec = {10, 20};
cout << "嘗試存取越界索引..." << endl;
// 這是一個未定義行為,很可能導致存取違規崩潰
int value = vec[100]; // 錯誤: 存取無效的索引
cout << "值: " << value << endl;
}
int main()
{
try
{
crash_function();
// 程式會在上面的 vec[100] 處崩潰,不會執行到這裡,
// 且 catch 區塊將不會被執行。
}
catch (const exception& e)
{
// 此處無法捕獲作業系統層級的錯誤
cerr << "捕獲到 C++ 異常: " << e.what() << endl;
}
catch (...)
{
// 仍然無法捕獲記憶體存取錯誤
cerr << "捕獲到未知異常" << endl;
}
return 0;
}
最好的方法是在程式碼設計階段就防止這類錯誤發生,而不是試圖捕獲它們:
std::vector 和 std::string,使用 at() 成員函式代替 [] 運算子。當 at() 越界時,它會拋出 C++ 異常 std::out_of_range,可以被標準 try-catch 捕獲。std::unique_ptr, std::shared_ptr)來自動化記憶體生命週期。永遠不要解引用 nullptr,並在解引用之前檢查指標是否有效。
void safe_function()
{
vector<int> vec = {10, 20};
try
{
int value = vec.at(100); // 拋出 std::out_of_range 異常
cout << "值: " << value << endl;
}
catch (const std::out_of_range& e)
{
// 成功捕獲 C++ 異常
cerr << "安全捕獲: " << e.what() << endl;
}
}
在 Windows 環境下,您可以使用微軟擴展的 **結構化異常處理 (SEH)** 來捕獲作業系統層級的錯誤,包括存取違規 (Access Violation)。這通常使用 __try 和 __except 關鍵字。
注意:SEH 是非標準的,且不建議在可移植的 C++ 程式碼中使用。在 C++/CLI 專案中,可以使用 .NET 的 try/catch 捕獲部分 SEH 異常,但這需要特定的編譯器設定 (例如 /EHa)。
在 C++ 中,當您使用原始陣列(C-style array)或指標進行索引操作時,如果存取了超出陣列範圍或無效的記憶體位置,就會發生未定義行為 (Undefined Behavior)。這種行為通常會導致程式在作業系統層級發生崩潰 (Crash),例如存取違規 (Access Violation) 或分段錯誤 (Segmentation Fault)。
關鍵點在於:C++ 標準的 try-catch 機制專門用於捕獲程式碼中明確拋出的 C++ 異常 (Exceptions),而無法捕獲作業系統發出的硬體或記憶體保護錯誤。
當 C++ 程式試圖存取無效記憶體時(例如,存取靜態陣列 arr[100],但陣列只有 10 個元素),它違反了作業系統對記憶體的保護規則。作業系統會介入,發出信號或異常來終止該程序,防止其破壞系統或其他程序。這些錯誤不是 C++ 語言層級的 std::exception 對象,因此無法被標準的 try-catch 區塊捕獲。
#include <iostream>
#include <stdexcept> // 包含標準異常
using namespace std;
void crash_function()
{
// C 風格的原始陣列
int raw_array[5] = {1, 2, 3, 4, 5};
cout << "嘗試存取越界索引..." << endl;
// 陣列越界存取:這是一個典型的未定義行為
// 如果存取到程序沒有權限的記憶體,將立即觸發存取違規,導致程式崩潰。
int value = raw_array[100]; // 錯誤: 存取無效的索引
cout << "值 (如果能到達): " << value << endl;
}
int main()
{
try
{
crash_function();
// 程式會在 raw_array[100] 處終止,不會執行到這裡。
}
catch (const exception& e)
{
// 此處無法捕獲作業系統層級的記憶體存取錯誤
cerr << "捕獲到 C++ 異常: " << e.what() << endl;
}
return 0;
}
最好的做法是完全避免越界存取,或者使用 C++ 提供的安全容器和方法,它們會在發生錯誤時拋出可捕獲的 C++ 異常:
對於 std::vector 容器,使用 at() 成員函式代替原始的 [] 運算子。當存取越界時,at() 會拋出 **std::out_of_range** 異常,這是標準 C++ 異常,可以被 try-catch 捕獲並安全處理。
#include <vector>
// ... (其他 include)
void safe_vector_access()
{
std::vector<int> vec = {10, 20};
try
{
// 使用 at() 進行邊界檢查
int value = vec.at(100);
std::cout << "值: " << value << std::endl;
}
catch (const std::out_of_range& e)
{
// 成功捕獲 C++ 異常
std::cerr << "安全捕獲到越界異常: " << e.what() << std::endl;
}
}
使用 C++11 引入的 std::array 代替 C 風格的陣列。雖然 std::array 的 [] 運算子不進行檢查,但它的 at() 函式同樣會拋出 std::out_of_range 異常。
#include <array>
// ...
void safe_array_access()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
try
{
int value = arr.at(5); // 存取越界,拋出異常
}
catch (const std::out_of_range& e)
{
std::cerr << "std::array 越界捕獲: " << e.what() << std::endl;
}
}
在 Windows 等特定平台上,可以使用 **結構化異常處理 (Structured Exception Handling, SEH)** 來捕獲由作業系統發出的硬體異常(如存取違規)。這通常涉及使用微軟擴展的 __try 和 __except 關鍵字。此方法是平台非標準的,會犧牲程式碼的可移植性,且一般不推薦作為常規的錯誤處理機制。
在 C++/CLI 的 System::Collections::Generic::List<T> 中,如果你嘗試存取不存在的索引(例如 size 為 1 但存取 index 1),程式不會給予一個預設值(如 0 或 null),而是會立即拋出異常。
System::ArgumentOutOfRangeException在進行索引存取之前,應始終檢查 Count 屬性,或者使用 try-catch 區塊捕捉潛在錯誤。
// 方法 A:事前檢查(最推薦,效能最好)
if (nums->Count > 1) {
int value = nums[1];
// 處理邏輯
} else {
// 處理索引不足的情況
}
// 方法 B:異常捕捉
try {
int value = nums[1];
} catch (System::ArgumentOutOfRangeException^ ex) {
System::Diagnostics::Debug::WriteLine("索引超出範圍!");
}
| 情境 | 結果 | 備註 |
|---|---|---|
| 索引存在 (e.g., Count=5, index=1) | 回傳正確數值 | 正常運作。 |
| 索引不存在 (e.g., Count=1, index=1) | 拋出 ArgumentOutOfRangeException | 程式會中斷執行。 |
| 原生 C++ 陣列/std::vector [ ] | 未定義行為 (Undefined Behavior) | 可能拿到亂碼或 Crash,不會主動拋出 .NET 異常。 |
nums[i] 前確認 i < nums->Count。int value = (nums->Count > 1) ? nums[1] : 0;
System::Linq),可以使用 ElementAtOrDefault(1),這在索引不存在時會回傳型別的預設值 (int 為 0)。在 C++ 中,最常使用的工具是定義在 <algorithm> 中的 std::min 與 std::max。它們支援基本型別、物件,甚至是初始化列表。
#include <algorithm>
#include <iostream>
#include <vector>
void basic_usage() {
int a = 10, b = 20;
// 1. 比較兩個數
int small = std::min(a, b);
int large = std::max(a, b);
// 2. 比較初始化列表 (C++11)
int lowest = std::min({5, 1, 9, 3}); // 回傳 1
// 3. std::minmax (C++11) - 同時取得最大與最小值
auto result = std::minmax({10, 20, 30, 40});
std::cout << "Min: " << result.first << ", Max: " << result.second;
}
當你需要初始化一個「最小值」變數時,通常會將其設定為該型別能表達的最大正數,以確保第一次比較時一定會被更新。
float 的最大值(約 $3.4 \times 10^{38}$)。double 的最大值。int 的最大值。std::numeric_limits 提供了一致且模板化的方式來獲取數值屬性,這在撰寫泛型程式碼時非常有用。
#include <limits>
void limits_example() {
// 獲取最大值
float max_f = std::numeric_limits<float>::max();
int max_i = std::numeric_limits<int>::max();
// 獲取「最小正數」 (注意:對於浮點數,min() 回傳的是最小正數而非最大負數)
float min_positive_f = std::numeric_limits<float>::min();
// 獲取「最低值」 (真正的最小負數)
float lowest_f = std::numeric_limits<float>::lowest();
}
如果你要處理的是 std::vector 或陣列,則需要使用 std::min_element 或 std::max_element,它們回傳的是迭代器 (Iterator)。
#include <vector>
#include <algorithm>
void collection_example() {
std::vector<float> scores = {88.5f, 92.0f, 79.5f, 100.0f};
// 取得最大值的迭代器
auto it = std::max_element(scores.begin(), scores.end());
if (it != scores.end()) {
std::cout << "最高分: " << *it;
}
}
| 名稱 | 用途 | 注意事項 |
|---|---|---|
| FLT_MAX | 初始化最小值搜尋 | 定義在 <cfloat>,是 C 的遺留宏。 |
| std::min | 兩數或列表比較 | 若參數型別不一致 (例如 int 與 long),需顯式指定模板:std::min<long>(a, b)。 |
| lowest() | 獲取最小負數 | 在浮點數中,min() 是靠近 0 的最小正數,lowest() 才是負的最大值。 |
| min_element | 搜尋容器 | 回傳的是迭代器,使用前需檢查容器是否為空。 |
在 C++ 的標準數學函式庫中,雖然直到 C++20 才正式標準化了 $\pi$ 的常數定義,但在不同的 C++ 版本和編譯器環境下,有幾種常用的方法來獲取圓周率的近似值。
為了確保程式碼的可移植性和準確性,最推薦的做法是使用 C++20 引入的 std::numbers::pi。
從 C++20 開始,標準庫在 <numbers> 標頭檔中提供了精確且類型安全的數學常數。
#include <numbers>
#include <iostream>
#include <numbers> // C++20 引入
void use_cpp20_pi()
{
// std::numbers::pi_v<T> 是模板變數,根據 T 類型提供精確值
// std::numbers::pi (無 <T>) 等同於 std::numbers::pi_v<double>
double pi_double = std::numbers::pi;
float pi_float = std::numbers::pi_v<float>;
long double pi_long_double = std::numbers::pi_v<long double>;
std::cout.precision(16); // 設置輸出精度
std::cout << "C++20 (double): " << pi_double << std::endl;
std::cout << "C++20 (float): " << pi_float << std::endl;
}
在 C++20 之前,許多開發者依賴 C 語言的數學函式庫(<cmath> 或 <math.h>)提供的非標準巨集。雖然這些巨集在大多數現代系統上都存在,但它們並非 C++ 標準的一部分,因此不保證在所有環境下都能使用。
#include <cmath> (或 <math.h>)
#include <iostream>
#include <cmath> // 傳統 C 數學庫
void use_c_macro_pi()
{
// M_PI 是最常見的 PI 巨集,通常被定義為 double 類型。
// 注意:要啟用這個巨集,在某些系統上可能需要定義 _USE_MATH_DEFINES 宏。
#ifdef M_PI
double pi_value = M_PI;
std::cout.precision(16);
std::cout << "C Macro (M_PI): " << pi_value << std::endl;
#else
std::cout << "M_PI 巨集未定義。您可能需要定義 _USE_MATH_DEFINES。" << std::endl;
#endif
}
如果無法使用 C++20 或 C 巨集,可以自己定義 $\pi$ 作為一個常數,或使用數學函式計算它(例如 $\arccos(-1)$)。
#include <iostream>
#include <cmath>
void define_or_calculate_pi()
{
// 透過數學運算定義:
const double PI_CUSTOM_CALC = std::acos(-1.0);
// 直接定義為常數:
const double PI_CUSTOM_DEFINE = 3.14159265358979323846;
std::cout.precision(16);
std::cout << "Custom Calculate: " << PI_CUSTOM_CALC << std::endl;
std::cout << "Custom Define: " << PI_CUSTOM_DEFINE << std::endl;
}
std::numbers::pi。const double PI = 3.14159...;M_PI 巨集,但需注意其非標準性。
#include <cmath>
#include <iostream>
using namespace System;
using namespace System::Collections::Generic;
int main()
{
// 建立測試資料
Dictionary<int, List<float>^>^ data = gcnew Dictionary<int, List<float>^>();
data->Add(1, gcnew List<float>({ 1.2f, 2.3f, 3.4f }));
data->Add(2, gcnew List<float>({ 4.5f, 5.5f, 6.5f, 7.5f }));
data->Add(3, gcnew List<float>()); // 測試空清單
// 計算每個 key 的標準差
for each (KeyValuePair<int, List<float>^> entry in data)
{
int key = entry.Key;
List<float>^ values = entry.Value;
if (values == nullptr || values->Count == 0)
{
Console::WriteLine("Key {0}: 無資料", key);
continue;
}
// 計算平均值
double sum = 0.0;
for each (float v in values)
{
sum += v;
}
double mean = sum / values->Count;
// 計算變異數(母體變異數)
double variance = 0.0;
for each (float v in values)
{
double diff = v - mean;
variance += diff * diff;
}
variance /= values->Count; // 若要樣本變異數,改為 (values->Count - 1)
// 標準差
double stddev = Math::Sqrt(variance);
Console::WriteLine("Key {0}: 標準差 = {1:F4}", key, stddev);
}
return 0;
}
values->Count - 1。Key 1: 標準差 = 0.8981 Key 2: 標準差 = 1.1180 Key 3: 無資料
std::sort 是 C++ 標準模板庫 (STL) 中最常用的排序演算法。它位於 <algorithm> 標頭檔中,預設會將範圍內的元素以升冪 (Ascending) 進行排序。
#include <algorithm>
#include <vector>
#include <iostream>
void basic_sort() {
std::vector<int> nums = {5, 2, 9, 1, 5, 6};
// 排序範圍:從開始迭代器到結束迭代器
std::sort(nums.begin(), nums.end());
// 結果: 1, 2, 5, 5, 6, 9
}
你可以透過傳入第三個參數(比較函式或 Lambda 表達式)來改變排序邏輯。
// 1. 使用預定義的 std::greater 進行降冪
std::sort(nums.begin(), nums.end(), std::greater<int>());
// 2. 使用 Lambda 表達式自定義邏輯
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 當回傳 true 時,a 會排在 b 前面
});
對於自定義類型,必須提供比較邏輯,否則編譯器會因為不知道如何比較大小而報錯。
struct Student {
std::string name;
int score;
};
void sort_students() {
std::vector<Student> students = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
// 依分數從高到低排序
std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.score > b.score;
});
}
std::sort 並非單一的排序演算法,而是一種混合演算法,旨在結合各演算法的優點並避免最壞情況發生。其運作邏輯如下:
| 特性 | 說明 |
|---|---|
| 平均時間複雜度 | $O(n \log n)$ |
| 最壞時間複雜度 | $O(n \log n)$ (受惠於 Introsort 機制) |
| 空間複雜度 | $O(\log n)$ (遞迴呼叫堆疊) |
| 穩定性 (Stability) | 不穩定 (相等元素的相對位置可能改變)。若需穩定排序請使用 std::stable_sort。 |
給定直線方程式 $ax + by + c = 0$ 以及一個點 $P(x_0, y_0)$,判斷點與線的關係最快的方法是計算 距離係數 $d$:
$$d = a \cdot x_0 + b \cdot y_0 + c$$
根據 $d$ 的正負號,我們可以判斷點在直線的哪一側。但要注意的是,「上方」或「右方」的定義會受到 $a$ 和 $b$ 正負號的影響。
在大多數電腦圖形學座標系中,y 軸向下為正(y 越小代表越靠畫面上方)。若要判斷點 $P$ 是否在線的上方,我們比較點的 $y_0$ 與直線上相同 $x_0$ 時的 $y_{line}$。
// 直線方程:ax + by + c = 0 => y = (-ax - c) / b
bool isAbove(std::array<float, 3> line, float x0, float y0) {
float a = line[0];
float b = line[1];
float c = line[2];
// 若 b 為 0,代表垂直線,無法定義上下
if (std::abs(b) < 1e-6) return false;
float y_on_line = (-a * x0 - c) / b;
// y 較小代表在上方
return y0 < y_on_line;
}
同理,我們比較點的 $x_0$ 與直線上相同 $y_0$ 時的 $x_{line}$。
// 直線方程:ax + by + c = 0 => x = (-by - c) / a
bool isRight(std::array<float, 3> line, float x0, float y0) {
float a = line[0];
float b = line[1];
float c = line[2];
// 若 a 為 0,代表水平線,無法定義左右
if (std::abs(a) < 1e-6) return false;
float x_on_line = (-b * y0 - c) / a;
// x 較大代表在右方
return x0 > x_on_line;
}
如果你能確保 $b < 0$ (透過將整個方程式乘上 -1),那麼 $ax + by + c > 0$ 直接就代表點在線的上方。以下是通用邏輯:
| 目標 | 判斷邏輯 | 限制條件 |
|---|---|---|
| 上方 (y 較小) | $y_0 < \frac{-ax_0 - c}{b}$ | $b \neq 0$ (非垂直線) |
| 右方 (x 較大) | $x_0 > \frac{-by_0 - c}{a}$ | $a \neq 0$ (非水平線) |
a 或 b 是否為 0 時,建議使用一個極小值(如 1e-6)進行比較,避免精確度問題。給定直線方程式 $ax + by + c = 0$,判斷它是偏向「橫線」還是「直線」,最直觀的方法是看它與座標軸的夾角。以 45度 為分界線,等同於比較 $|a|$ 與 $|b|$ 的大小。
使用 std::abs 比較 line[0] ($a$) 與 line[1] ($b$) 的絕對值。
#include <iostream>
#include <array>
#include <cmath>
enum class LineOrientation {
Horizontal, // 橫線 (< 45度)
Vertical, // 直線 (> 45度)
Diagonal // 剛好 45度
};
LineOrientation checkOrientation(std::array<float, 3> line) {
float a = std::abs(line[0]);
float b = std::abs(line[1]);
if (a < b) {
return LineOrientation::Horizontal;
} else if (a > b) {
return LineOrientation::Vertical;
} else {
return LineOrientation::Diagonal;
}
}
從斜率公式 $m = -\frac{a}{b}$ 來看:
| 條件 | 分類 | 特徵 |
|---|---|---|
| |a| < |b| | 橫線 (Horizontal) | 與 X 軸夾角較小,適合判斷「點在線的上方/下方」。 |
| |a| > |b| | 直線 (Vertical) | 與 Y 軸夾角較小,適合判斷「點在線的左方/右方」。 |
| a = 0 | 絕對水平線 | 完全平行於 X 軸。 |
| b = 0 | 絕對垂直線 | 完全平行於 Y 軸。 |
在進行點對線的位置判斷時,建議先執行此檢查:
在 C++ 中,模板 (Template) 是一種泛型編程的工具,允許我們在編寫函數或類別時,不指定具體的資料型別,從而使程式碼更具重用性。模板有助於在單一程式碼中處理不同型別的資料,避免重複的函數或類別定義。
函數模板允許我們撰寫可以處理不同型別的函數。函數模板的語法如下:
template <typename T>
T add(T a, T b) {
return a + b;
}
在此例中,add 函數可以處理任何支持加法操作的型別,如 int、float 或 double。
使用時,可以這樣呼叫:
int result = add(3, 4); // 使用 int 型別
double result2 = add(3.5, 2.7); // 使用 double 型別
類別模板允許我們建立可以適用於多種型別的類別。類別模板的語法如下:
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 型別
模板可以接受多個參數,例如:
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);
模板特化允許我們對特定型別的模板進行特別定義。例如:
template <>
class MyClass<int> {
public:
MyClass(int data) { /* 特化行為 */ }
};
這段程式碼特化了 MyClass 對 int 型別的行為,使其與其他型別不同。
模板還可以接受非型別的參數,例如常數值:
template <typename T, int Size>
class Array {
private:
T data[Size];
public:
int getSize() const { return Size; }
};
在這裡,Size 是一個非型別參數,表示陣列的大小。
使用範例:
Array<int, 10> arr; // 建立大小為 10 的 int 型別陣列
C++ 模板功能強大,使得程式碼可以更具泛用性並減少重複。了解如何利用函數模板、類別模板及模板特化等技術,將大大提升程式設計的彈性與效能。
在 C++ 中,當兩個類別相互依賴並需要同時引用對方的成員時,直接在各自的 .h 檔案中 #include
對方的定義會造成循環引用問題,導致無法編譯。解決方法是使用「前向宣告」(forward declaration)來避免循環引用。
.h 檔案中只宣告對方類別的存在,不直接包含對方的頭文件。.cpp 檔案中包含對方的頭文件,讓編譯器取得完整的類別定義。
// 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
#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
#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;
}
}
#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();
}
}
ClassA.h 和 ClassB.h 中,我們僅使用前向宣告來指出對方類別的存在,這樣可以避免循環引用。
.cpp 檔案中包含對方的頭文件,保證在使用指標時獲得對方的完整類別定義。在 C++ 中,friend 關鍵字可以用於函數或類別,以允許其他函數或類別訪問類別的私有成員(private)和保護成員(protected)。這樣的設計可讓外部函數或類別進行操作,而不違反封裝原則。
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。
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 類別的私有成員 width 和
height。
在 C++ 中使用 friend 函數和 friend 類別需謹慎,過多使用會破壞類別的封裝性。因此,friend 關鍵字通常只在設計需要密切配合的類別或函數時使用。
在 C++ 中,最標準和推薦的逐行讀取文字檔案的方法是使用標準程式庫中的檔案串流類別 std::ifstream 配合 std::getline 函式。
std::ifstream 負責開啟和管理檔案串流,而 std::getline 則負責從串流中讀取一整行文字(直到遇到換行符 \n),並將其儲存到 std::string 物件中。
以下是一個完整的 C++ 範例,演示如何逐行讀取一個名為 example.txt 的檔案內容。
#include <iostream>
#include <fstream> // 包含檔案串流函式庫
#include <string> // 包含字串類別
using namespace std;
void read_file_line_by_line(const string& filename)
{
// 1. 建立 std::ifstream 物件
// 嘗試開啟指定的檔案。
ifstream input_file(filename);
// 2. 檢查檔案是否成功開啟
if (!input_file.is_open())
{
cerr << "錯誤: 無法開啟檔案 " << filename << endl;
return;
}
string line;
int line_number = 1;
// 3. 使用 std::getline 函式進行逐行讀取
// 循環條件 (getline(stream, string)) 在每次讀取成功時返回 true。
// 當到達檔案結尾 (EOF) 或發生錯誤時,返回 false,循環結束。
while (getline(input_file, line))
{
cout << "第 " << line_number << " 行: " << line << endl;
line_number++;
}
// 4. 關閉檔案串流
// 當 input_file 物件超出其作用域時,析構函式會自動關閉檔案,
// 但明確呼叫 close() 也是可接受的做法。
input_file.close();
cout << "檔案讀取完成。" << endl;
}
int main()
{
read_file_line_by_line("example.txt");
return 0;
}
std::ifstream:繼承自 std::istream,專門用於處理檔案輸入串流。在建構時(或使用 open() 函式時)會嘗試開啟檔案。input_file.is_open():這是標準的錯誤檢查。如果檔案不存在或程式沒有讀取權限,這個函式會返回 false。std::getline(stream, string):
input_file)中讀取資料。std::string 物件,例如 line)中。\n,因此 line 變數中不包含換行符。true。std::ifstream 物件 (input_file) 被銷毀時,它會自動呼叫其析構函式來關閉底層的檔案控制代碼,這是一種可靠的資源管理方式(RAII, Resource Acquisition Is Initialization)。在 C++ 中,要將資料結構(例如從 std::vector<std::string> 中讀取的 tokens)逐行寫入檔案,並確保每個欄位(token)在行中保持固定寬度的對齊,需要使用 **std::ofstream** 配合 **I/O 串流操縱器 (Stream Manipulators)** 來控制輸出格式。
主要的格式控制工具有:
std::setw(width):設定下一個輸出欄位的最小寬度。std::left / std::right:設定文字在欄位中的對齊方式(靠左或靠右)。以下是一個 C++ 範例,示範如何將一個包含多個欄位的二維數據結構(使用 std::vector<std::vector<std::string>> 模擬)寫入檔案,並確保每個 token 欄位的寬度固定為 15 個字元且靠左對齊。
#include <iostream>
#include <fstream> // 包含檔案輸出串流
#include <string>
#include <vector>
#include <iomanip> // 包含 I/O 串流操縱器 (setw, left/right)
using namespace std;
// 模擬一個二維資料結構,每行包含多個欄位 (tokens)
using TableData = vector<vector<string>>;
void write_aligned_data(const string& filename, const TableData& data, int column_width)
{
// 1. 建立 std::ofstream 物件並開啟檔案
ofstream output_file(filename);
// 2. 檢查檔案是否成功開啟
if (!output_file.is_open())
{
cerr << "錯誤: 無法開啟檔案 " << filename << " 進行寫入。" << endl;
return;
}
// 設定全域對齊方式:靠左對齊 (L-Justified)
output_file << left;
// 3. 逐行寫入資料
for (const auto& row : data)
{
// 4. 逐 token 寫入並設定寬度
for (const auto& token : row)
{
// setw(width) 只影響緊隨其後的下一個輸出項
output_file << setw(column_width) << token;
}
// 5. 行結束後插入換行符,開始新的一行
output_file << "\n";
}
// 6. 關閉檔案串流
output_file.close();
cout << "資料已成功寫入檔案: " << filename << endl;
}
int main()
{
// 範例資料:包含四行,每行三個欄位
TableData data = {
{"Name", "Item", "Price"},
{"Alice", "Book", "19.99"},
{"BobJohnson", "PenSet", "4.50"},
{"Charlie", "Notebook", "800.75"}
};
const int COLUMN_WIDTH = 15; // 定義每個欄位的寬度
write_aligned_data("output_aligned.txt", data, COLUMN_WIDTH);
return 0;
}
std::ofstream:檔案輸出串流類別。用於將資料寫入檔案。#include <iomanip>:此標頭檔必須包含,才能使用 I/O 串流操縱器。output_file << left;:
right 或 internal 覆蓋。output_file << setw(column_width) << token;:
setw(width):設定接下來要輸出的資料的最小寬度。如果輸出的字串長度小於設定的寬度,則會用空白字元填充。setw 只對緊隨其後的下一個輸出操作有效,因此必須在每次輸出 token 之前呼叫一次。vcpkg 並非透過安裝檔安裝,而是直接從 GitHub 複製原始碼到本地。建議將其放置在磁碟根目錄(如 C:\vcpkg),避免路徑過長或包含空格。請開啟終端機(CMD 或 PowerShell)執行:
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
下載後需要執行腳本來編譯 vcpkg 的執行檔,這會生成 vcpkg.exe:
.\bootstrap-vcpkg.bat./bootstrap-vcpkg.sh這一步能讓開發工具(如 Visual Studio)自動辨識 vcpkg 安裝的套件:
執行 .\vcpkg integrate install。完成後,你在 VS 中新建的專案就能直接 #include 套件,無需手動設定路徑。
在執行 CMake 配置時,加入以下參數來連結 vcpkg 的工具鏈檔案:
-DCMAKE_TOOLCHAIN_FILE=[vcpkg路徑]/scripts/buildsystems/vcpkg.cmake
| 指令 | 說明 | 範例 |
|---|---|---|
search |
搜尋可用的套件 | vcpkg search libuv |
install |
安裝指定套件 | vcpkg install libuv:x64-windows |
list |
列出已安裝的套件 | vcpkg list |
update |
檢查套件版本更新 | vcpkg update |
為了方便在任何路徑使用 vcpkg,建議將 vcpkg 的資料夾路徑加入系統的 PATH 環境變數中。此外,若希望預設安裝 64 位元套件,可以新增環境變數 VCPKG_DEFAULT_TRIPLET 並設定其值為 x64-windows。
在 Windows 上使用 vcpkg 前,請確保已安裝 Visual Studio 2015 或更新版本,並勾選「使用 C++ 的桌面開發」工作負載(包含英文語言包),否則編譯套件時可能會出現錯誤。
週邊控制程式是用來與電腦或主機連接的外部硬體裝置(例如:印表機、掃描器、馬達、PLC、感測器等)進行通訊與操作的應用程式。
| 語言 | 適用情境 | 備註 |
|---|---|---|
| Python | 快速開發、測試自動化 | 適用 Serial、USB HID |
| C/C++ | 嵌入式控制、驅動開發 | 可存取低階記憶體與硬體 |
| C# | Windows UI + 控制裝置 | 適用於 COM Port、USB 通訊 |
| Java | 跨平台控制 | 較少用於低階裝置 |
import serial
ser = serial.Serial('COM3', 9600, timeout=1)
ser.write(b'ON\n') # 傳送控制命令
response = ser.readline()
print("裝置回應:", response.decode())
ser.close()
SerialPort port = new SerialPort("COM4", 9600);
port.Open();
port.WriteLine("MOVE 100");
string response = port.ReadLine();
Console.WriteLine("回應:" + response);
port.Close();
libusb(C)、pyusb(Python)、HidSharp(C#)等函式庫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、網路等通訊方式,可廣泛應用於各種自動化與工控領域。
// 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);
}
}
}
<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>
| 平台 | 建議套件 | 是否免費 |
|---|---|---|
| Android | ZXing / ML Kit | 是 |
| Web | QuaggaJS / jsQR | 是 |
| Windows | Dynamsoft / ZXing.NET | Dynamsoft 為商用 |
| Python | ZBar / pyzbar | 是 |
開發 Barcode Reader 可根據平台選擇合適的開源套件,ZXing 是最廣泛支援的選擇,Web 可用 QuaggaJS,若需商業級支援則可考慮 Dynamsoft Barcode SDK。
條碼讀取器本質上是模擬鍵盤輸入的裝置。當掃描到條碼時,它會將條碼中的內容「輸出」成字元流,就像是鍵盤輸入一樣。
因此,條碼中是可以包含控制碼(Control Code),但需要條碼讀取器與條碼格式支援才行。
例如以下格式可編碼控制碼:
部分條碼產生器允許嵌入特殊字元,例如:
\x0D 表示 Enter\x03 表示 Ctrl-C^C、[CTRL+C] 等特殊語法依工具而定多數專業條碼機(例如 Zebra、Honeywell)在出廠時預設不啟用控制碼輸出,需透過掃描器提供的「設定條碼」啟用:
例如在 Windows 應用程式中:
keydown 與 Ctrl 配合的快捷鍵處理。用 Code128 編碼 ASCII 3:
輸入:\x03Hello World
條碼掃描後會觸發 Ctrl+C 再輸出 "Hello World"。
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget curl unzip zip git ca-certificates
sudo apt install -y openjdk-17-jdk
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
sudo apt install -y android-sdk-platform-tools
adb kill-server
adb start-server
adb devices
sudo dpkg -i ~/Downloads/code_*.deb || sudo apt -f install -y
sudo apt update && sudo apt install -y openjdk-17-jdk unzip git
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
unzip commandlinetools-linux-*_latest.zip -d ~/android-sdk
~/.bashrc 或 ~/.zshrc):
export ANDROID_SDK_ROOT=$HOME/android-sdk
export PATH=$ANDROID_SDK_ROOT/cmdline-tools/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
adb devices
看到 device 即可部署測試。
adb install 或框架 CLI(如 flutter run)將 App 部署至手機。在 Android Studio 中建立一個新的專案,選擇 "Empty Activity" 模板,然後設定專案名稱及其他基本資訊。
在 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 中,設定按鈕的點擊事件,使其更改文字顯示區域的內容:
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 中點擊執行按鈕,即可在模擬器或連接的實體裝置上測試應用程式。點擊按鈕後,文字將更改為 "你點了按鈕!"。
在 AndroidManifest.xml 中加入以下權限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
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) 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 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);
}
});
}
要實作一個類似 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);
}
});
如需常駐背景並語音喚醒,需使用:
RECORD_AUDIO 權限。常駐背景監聽的目標,是讓 App 即使在未開啟畫面時也能偵測語音喚醒詞(如「Hey 助理」),並啟動對應功能。
SpeechRecognizer 長時間背景運行。SpeechRecognizer 辨識完整語音命令。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;
}
}
NotificationChannel channel = new NotificationChannel("voice_channel",
"Voice Assistant", NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
Porcupine 提供 Android SDK,可辨識自訂關鍵詞並在完全離線情況下運作。
PorcupineManager porcupineManager = new PorcupineManager.Builder()
.setAccessKey("你的金鑰")
.setKeywordPath("hey_assistant.ppn")
.setSensitivity(0.7f)
.build((keywordIndex) -> {
// 被喚醒時呼叫 SpeechRecognizer 進行語音辨識
startSpeechRecognition();
});
porcupineManager.start();
Intent serviceIntent = new Intent(this, VoiceService.class);
ContextCompat.startForegroundService(this, serviceIntent);
RECORD_AUDIO 權限。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 開發主要使用 Xcode,這是 Apple 提供的官方整合開發環境 (IDE)。
學習 iOS 開發需要掌握以下基礎:
以下是一些實用的學習與開發資源:
Xcode 是 Apple 提供的整合開發環境 (IDE),用於 macOS、iOS、watchOS 和 tvOS 應用程式的開發。
可從 Mac App Store 或 Apple 開發者官網下載最新版本的 Xcode。
提升開發效率的實用技巧:
相關學習與參考資源:
Swift 是 Apple 推出的現代化程式語言,用於開發 iOS、macOS、watchOS 和 tvOS 應用程式。
var 和 let? 和 ! 處理值的存在與否if、switch、for、whilefunc 定義,支援參數標籤與多返回值class 和 structSwift 不僅適用於 Apple 生態系統,還可以用於伺服器端開發與跨平台工具。
相關學習與參考資源:
Objective-C 是一種以 C 為基礎的物件導向程式語言,最初由 NeXT 公司開發,後來被 Apple 廣泛用於 macOS 和 iOS 應用程式開發。
Objective-C 的語法結合了 C 和 Smalltalk 的特性,使用 @ 符號來標示語言擴展。
@interface 和 @implementation[object method]@property 和 @synthesizeObjective-C 的開發主要使用 Apple 的 Xcode。
以下是一些學習與參考的資源:
GNews是一個由Google開發的新聞聚合平台,旨在幫助用戶獲取最新的全球新聞資訊。它整合了來自各種新聞來源的內容,使用人工智慧技術來個性化推薦用戶感興趣的新聞。
用戶可以通過訪問GNews的網站或下載其應用程式來使用此平台。在平台上,用戶可以選擇感興趣的主題、追蹤特定的新聞來源,並根據自己的需求自訂新聞推送。
GNews是一個強大的新聞聚合工具,透過人工智慧技術,為用戶提供個性化的新聞體驗。隨著新聞資訊的快速變化,GNews幫助用戶快速跟上世界動態,獲取所需的資訊。
| 工具名稱 | 主要特色 | 適用場景 | 價格 |
|---|---|---|---|
| n8n | 開源的自動化工作流工具,提供高度可定制的流程設計,支持自建節點。 | 適用於企業內部流程自動化、大型自動化系統、API集成等。 | 免費開源,提供付費雲端版本。 |
| Make | 提供視覺化工作流建構,支持多種第三方應用和服務的集成,強調簡單易用。 | 適用於小型到中型企業的工作流程自動化,快速集成各種服務。 | 提供免費計劃,付費計劃根據用戶需求提供更高級功能。 |
| Zapier | 支持大多數應用程序的集成,簡單易用,能夠創建觸發器和自動化工作流程。 | 適用於各種業務領域的自動化,特別是小型企業和初創企業。 | 提供免費計劃,付費計劃依據用戶需求提供更多功能和運行次數。 |
Bolt 是一個快速、輕量的 AI 開發框架,專注於提供開發者簡單且高效的工具來建構應用程式。特點包括:
Cursor 是專為 AI 程式開發設計的編輯器工具,提供智能輔助程式碼撰寫與調試功能。特點包括:
v0 是一個基於視覺化開發的 AI 平台,允許使用者透過拖放介面構建模型與應用。特點包括:
Codeium 是一款結合 AI 智能輔助的程式編輯器,專注於提升程式開發效率與準確性。特點包括:
軟體工程是一門系統性、規劃性地開發、操作與維護軟體的工程學科,目標是建構高品質、可維護、可靠且符合需求的軟體系統。
請填入本專案的正式名稱。
說明此專案的緣由、背景問題以及欲解決的核心問題,並定義明確的專案目標。
概述系統功能與整體架構,可搭配系統架構圖。
可附上主要畫面草圖或線框圖,描述各畫面的元素與互動流程。
列出主要資料表結構、欄位、關聯性等。
列出需整合的外部系統、API或其他軟體元件。
列出潛在風險、限制條件(如預算、人力、技術等)。
相關文件連結、參考資料、名詞定義等。
設計模式(Design Patterns)是一組經過實踐驗證的軟體設計解決方案,主要應用於物件導向程式設計中,用來解決在特定情境下反覆出現的設計問題。
版本控制(Version Control)是一種管理檔案變更歷史的系統,廣泛應用於軟體開發中,讓多位開發者能同時協作,並保留每次變更的完整記錄。
git init:初始化版本庫git clone [網址]:複製遠端專案git add [檔案]:加入變更至暫存區git commit -m "訊息":提交變更git push:將變更推送至遠端儲存庫git pull:取得並合併遠端變更git branch:查看或建立分支git merge:合併分支Git 是一種分散式版本控制系統,由 Linus Torvalds(Linux 之父)於 2005 年開發,用於追蹤程式碼變更、協同開發與版本管理。它是現今最廣泛使用的版本控制工具,被應用於個人開發、團隊專案與開源社群中。
git --version 以確認安裝成功。第一次使用 Git 時,需設定開發者資訊:
git config --global user.name "你的名稱" git config --global user.email "你的[email protected]"
git init # 初始化本地儲存庫 git clone URL # 複製遠端儲存庫 git add . # 將變更加入暫存區 git commit -m "說明" # 建立一次提交 git status # 查看當前狀態 git log # 查看提交紀錄 git branch # 查看分支列表 git checkout 分支名 # 切換分支 git merge 分支名 # 合併指定分支 git push # 將變更推送到遠端 git pull # 從遠端拉取更新
git init 或 git clone)git add)git commit)git push / git pull)如果您想完全捨棄本地所有尚未提交(Uncommitted)以及已經提交但尚未推送(Local Commits)的更改,將分支重設為與遠端完全一致,請按照以下步驟執行。
首先,確保您的本地索引已經更新,知道遠端目前最新的進度:
git fetch --all
使用 --hard 參數將目前分支指向遠端的對應分支(假設您的分支是 main):
git reset --hard origin/main
git reset --hard 只會處理 Git 已經追蹤的檔案。如果您本地有新建立但尚未 git add 的檔案或資料夾,它們會殘留下來。若要一併刪除,請使用 clean 指令:
# 先執行測試,查看會刪除哪些檔案
git clean -n
# 正式刪除檔案 (-f) 與目錄 (-d)
git clean -fd
| 目標 | 適用指令 |
|---|---|
| 放棄所有本地修改 | git reset --hard origin/<branch_name> |
| 僅放棄單一檔案修改 | git checkout HEAD -- <file_path> |
| 暫存目前的更改 (稍後可恢復) | git stash |
| 移除所有 gitignore 之外的雜物 | git clean -fdx |
git reset --hard 是一個危險的操作,執行後所有本地尚未提交的代碼將無法找回。origin/main 替換為您目前實際使用的遠端分支名稱(例如 origin/master 或 origin/dev)。當 Git 合併分支發生衝突時,檔案會被標記為「Unmerged」狀態。此時 Git 會在檔案中插入衝突標記,我們需要透過比較指令來定位問題並手動修復。
在解決衝突前,先確認哪些檔案處於衝突狀態:
git status
衝突的檔案會列在 Unmerged paths 區段下。
當檔案內含有衝突標記時,可以使用以下方式查看差異:
git diff<<<<<<<、======= 與 >>>>>>> 標記。
git diff --ours <file>git diff --theirs <file>
git diff -p <file>
Git 會直接修改衝突檔案,格式如下:
<<<<<<< HEAD (或是你的分支名) 這是你目前分支的內容 (Ours) ======= 這是對方分支的內容 (Theirs) >>>>>>> branch_name
對於複雜的衝突,文字介面較難閱讀,建議調用專用的合併工具:
git mergetool
這會啟動如 Meld, P4Merge, 或 VS Code 等工具,以左右並排(Side-by-side)的方式比較三個版本:基準版本 (Base)、本地版本 (Local) 以及遠端版本 (Remote)。
手動編輯檔案並決定保留哪些程式碼後,必須執行以下步驟來完成合併:
<<<<, ====, >>>> 標記。git add <file> (將解決後的檔案加入暫存區)。git commit (完成合併提交)。.gitignore 檔案指定哪些檔案或目錄不加入版本控制。
建立 .gitignore:
# 忽略所有 log 檔
*.log
# 忽略 tmp 目錄
tmp/
# 忽略 build 目錄
build/
# 忽略 Visual Studio Code 設定
.vscode/
# 忽略 Python cache
__pycache__/
*.pyc
# 忽略 Windows 暫存檔
Thumbs.db
Desktop.ini
git status --ignored
顯示已被忽略的檔案:
git ls-files --others --ignored --exclude-standard
echo "*.log" >> .gitignore
但原本已加入版本控制的:
app.log
仍會被 Git 追蹤。
需先移除索引:
git rm --cached app.log
git commit -m "Stop tracking app.log"
整個目錄:
git rm -r --cached logs/
git commit -m "Stop tracking logs"
git rm -r --cached .
git add .
git commit -m "Refresh gitignore"
注意:
--cached
只移除 Git 索引,不會刪除實體檔案。
config.ini
*.log
*.tmp
*.bak
cache/
logs/
temp/
build/output/
*.txt
!keep.txt
例如:
logs/
!logs/README.md
git check-ignore -v filename
例如:
git check-ignore -v app.log
輸出:
.gitignore:5:*.log app.log
表示第 5 行規則造成忽略。
.git/info/exclude
加入:
local_config.ini
debug.log
只有自己生效,不會提交到 Repository。
~/.gitignore_global
內容:
Thumbs.db
Desktop.ini
*.tmp
*.bak
設定:
git config --global core.excludesfile ~/.gitignore_global
Windows:
git config --global core.excludesfile %USERPROFILE%\.gitignore_global
git add -f filename
例如:
git add -f app.log
__pycache__/
*.pyc
.venv/
.env
Node.js:
node_modules/
dist/
.env
Java:
target/
*.class
Visual Studio Code:
.vscode/
JetBrains IDE:
.idea/
Windows:
Thumbs.db
Desktop.ini
Linux:
*~
*.swp
GitHub 是一個基於雲端的版本控制和協作平台,主要用於軟體開發。它使用 Git 進行版本控制,讓開發者能夠管理專案的源代碼、跟蹤變更以及與他人協作。
GitHub 適合各類開發者,無論是單獨開發者、開源社區還是企業團隊。它能夠滿足小型專案到大型軟體專案的版本控制和協作需求。
git pull --rebasegit pull --rebase 是從遠端分支拉取最新變更,並將本地的更改重新應用到最新的遠端提交之上,而不是使用傳統的合併。
git pull --rebase
--rebase?--rebase 可以讓提交歷史保持簡潔,沒有多餘的合併提交。假設你在本地提交了一些更改,而遠端也有新的提交,使用 git pull --rebase 會:
如果在重放本地提交時發生衝突,Git 會要求手動解決衝突:
git add 暫存已解決的文件。git rebase --continue 繼續重放提交。git rebase --abort 回到原狀。git pull --rebase 是保持 Git 提交歷史整潔的重要工具,特別適合多人協作時,能夠避免產生冗餘的合併提交,並保持代碼庫的提交記錄線性。
若要讓其他開發者參與你的專案,你需要透過儲存庫的設定頁面將其加入為協作者(Collaborator)。以下是操作流程:
針對個人帳戶的儲存庫,受邀者通常預設擁有讀寫權限;若為組織(Organization)帳戶,則可進行更細緻的設定:
| 權限等級 | 說明 |
|---|---|
| Read | 僅能讀取與複製程式碼,適合純檢視者。 |
| Triage | 可管理 Issue 和 Pull Requests,但不能寫入程式碼。 |
| Write | 可直接推送 (Push) 程式碼至儲存庫。 |
| Maintain | 除了寫入外,還可管理儲存庫的部分設定。 |
| Admin | 擁有完全控制權,包括刪除儲存庫與管理其他協作者。 |
這是最常見且最直覺的刪除方式。請注意,儲存庫一旦刪除,除非有備份,否則無法復原。
使用者名稱/儲存庫名稱)。如果您有安裝 GitHub 的命令列工具 (gh),可以透過終端機快速刪除:
gh repo delete <使用者名稱>/<儲存庫名稱> --confirm
注意:--confirm 參數會直接跳過確認步驟,請謹慎使用。
GitHub 上的遠端儲存庫刪除後,您電腦上的本機資料夾依然會存在。若要徹底移除,請手動刪除該資料夾:
rm -rf <資料夾名稱>
rd /s /q <資料夾名稱>
---
| 操作方式 | 影響範圍 | 是否可復原 |
|---|---|---|
| 網頁介面刪除 | 僅刪除 GitHub 遠端伺服器上的資料。 | 否 (除非聯絡支援團隊或有 Fork) |
| rm -rf 指令 | 僅刪除您電腦本機上的檔案。 | 否 (除非有回收站) |
| 刪除 .git 資料夾 | 保留檔案,但將其轉回一般資料夾(失去版控功能)。 | 是 (重新 git init) |
Visual Studio 內建 Git 整合功能,但仍需在系統中安裝 Git 執行檔,才能進行版本控制操作(如 Commit、Push、Pull、Merge 等)。
git --version
git version 2.x.x,代表已安裝;若顯示「無法辨識 git 指令」,則需安裝 Git。開啟命令提示字元,設定您的使用者名稱與電子郵件:
git config --global user.name "你的名稱" git config --global user.email "你的[email protected]"
這些資訊會附加在每次提交(commit)中,用於識別開發者身份。
若使用 Visual Studio 2022 或更新版本,在安裝過程中可勾選 「Git for Windows」 選項。這樣 Visual Studio 會自動安裝與整合 Git,無需手動設定。
設定完成後,您可以直接在 Visual Studio 中完成以下操作:
Azure DevOps 是由 Microsoft 提供的一套整合式開發與協作平台,支援從程式碼管理、建置、自動化測試到部署的完整 DevOps 流程。它適用於多種程式語言與框架,可用於個人專案或大型企業團隊開發。
若您在使用 Azure DevOps Pipelines、Repos 或 Boards 時遇到權限限制問題,請先確認您的 Access Level 是否為 Basic 或以上等級。
CMake 是一個開源、跨平台的「元建置系統 (Meta-build System)」。它本身並不直接編譯程式碼,而是透過讀取設定檔(CMakeLists.txt),自動生成適合當前作業系統與開發環境的專案檔。例如,在 Windows 上它能生成 Visual Studio 解決方案,而在 Linux 上則生成 Makefile 或 Ninja 設定檔。
在不同作業系統上安裝 CMake 的方法如下:
brew install cmake。也可以下載 .dmg 檔手動安裝。sudo apt update && sudo apt install cmake。CMake 的設計目標是簡化複雜軟體專案的管理。它支援多層目錄結構、處理函式庫之間的相依性,並能自動搜尋系統中已安裝的程式庫。此外,它也提供測試 (CTest) 與打包 (CPack) 功能,涵蓋了從開發到發佈的完整生命週期。
| 步驟 | 指令範例 | 說明 |
|---|---|---|
| 1. 配置 (Configure) | cmake -S . -B build |
讀取設定並偵測系統環境。 |
| 2. 生成 (Generate) | (自動執行) | 產出 Makefile 或 VS 專案檔。 |
| 3. 建置 (Build) | cmake --build build |
呼叫編譯器進行實際的編譯。 |
| 4. 安裝 (Install) | cmake --install build |
將產出的執行檔移至指定路徑。 |
CMake 具備優秀的跨架構支援能力。在 Windows 環境下,開發者可以透過參數指定目標架構,例如使用 -A x64 或 -A ARM64。對於現代的 Apple Silicon,CMake 支援生成 Universal Binaries,讓程式能同時在 Intel 與 Apple M 系列處理器上執行。
傳統的 Makefile 難以維護且不具備跨平台通用性。CMake 讓開發者只需撰寫一套設定檔,就能讓全球開發者在其偏好的 IDE(如 VS Code, CLion, Xcode)中輕鬆開啟並編譯專案。這也是目前大多數主流開源 C++ 專案都採用 CMake 作為標準工具的原因。
程式部署(Application Deployment)是將開發完成的應用程式從開發環境移轉至測試或生產環境,讓使用者可以實際使用的過程。它確保系統能在不同環境中穩定、可重現地執行。
# 使用 GitHub Actions 部署範例
name: Deploy Web App
on:
push:
branches: [ "main" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 建立 Docker 映像
run: docker build -t myapp .
- name: 部署至伺服器
run: docker run -d -p 80:80 myapp
Docker(容器技術)是一個開放原始碼的平台,用於自動化應用程式的部署、擴展與管理。它利用「容器」(Container)將應用與其依賴封裝在一起,確保在不同環境中能一致執行。
docker pull [映像名稱]:下載映像檔docker run [映像名稱]:執行容器docker ps:查看執行中的容器docker stop [容器ID]:停止容器docker rm [容器ID]:刪除容器docker build -t [名稱] .:依據 Dockerfile 建立映像檔docker images:查看映像檔清單# 使用 Python 映像檔
FROM python:3.10
# 設定工作目錄
WORKDIR /app
# 複製程式檔案
COPY . /app
# 安裝依賴套件
RUN pip install -r requirements.txt
# 指定容器啟動時執行的指令
CMD ["python", "app.py"]
Docker Compose 是一個可定義多個容器應用的工具,使用 docker-compose.yml 檔案定義各服務。
version: '3'
services:
web:
build: .
ports:
- "8000:8000"
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: example
模組相依性分析是一種軟體工程技術,用於識別與追蹤應用程式執行時所依賴的外部檔案(如 DLL、庫、框架)。這對於確保軟體在不同環境下能正確執行至關重要。
| 目的 | 具體描述 |
|---|---|
| 故障排除 | 找出因 缺少組件 (Missing DLL) 或 版本不相符 (Version Mismatch) 導致的程式崩潰。 |
| 安全審核 | 識別程式是否載入了未經授權或具備安全性漏洞的第三方元件。 |
| 效能優化 | 評估過多相依模組對啟動時間與記憶體佔用的影響,移除不必要的引用。 |
| 移植性評估 | 確定軟體在未安裝特定開發環境(如 .NET 運行時或 VC++ 庫)的電腦上是否能運行。 |
Dependency Walker(通常稱為 Depends.exe)是一款用於分析 Windows 應用程式(如 .exe、.dll、.sys 等)檔案結構的免費工具。它主要用於掃描任何 32 位元或 64 位元 Windows 模組,並建立所有相關相依模組的分層樹狀圖。
| 場景 | 描述 |
|---|---|
| 系統錯誤排除 | 解決如 0xc000007b (並行設定錯誤) 或 找不到指定的模組 等系統報錯。 |
| 軟體部署 | 開發者確認安裝程式是否包含了所有必要的執行庫(如 VC++ Redistributable)。 | 檢查系統路徑中是否存在不同版本的同名 DLL 導致程式行為異常。 |
由於 Dependency Walker 已經多年未更新,對於現代 Windows 10/11 的一些特性(如 API Sets 或延遲載入的 DLL)支援度較差,經常會出現錯誤的紅色警告。在這種情況下,建議使用以下開源替代工具:
Dependencies 是一款開源的現代化相依性分析工具,旨在取代已停止更新多年的 Dependency Walker (Depends.exe)。它針對 Windows 10 與 Windows 11 的系統架構進行了全面優化,能夠準確解析現代軟體環境中的模組關聯。
api-ms-win-* 開頭的虛擬 DLL,不會產生錯誤的循環相依警告或遺失檔案錯誤。| 特性 | Dependency Walker (舊版) | Dependencies (現代版) |
|---|---|---|
| Windows 10/11 支援 | 差(頻繁誤報) | 優(原生支援) |
| API Sets 處理 | 無法識別(顯示為紅色遺失) | 正確映射至實體 DLL |
| 開發狀態 | 已停止更新 (2006) | 持續維護中 (GitHub) |
| 核心技術 | C++ / MFC | C# / WPF (部分底層 C++) |
Dependencies 是一個綠色免安裝軟體,通常可以從 GitHub 的 lucasg/Dependencies 儲存庫下載編譯好的 Release 版本。使用時只需將要分析的 .exe 或 .dll 檔案拖曳進視窗即可開始分析。
這兩款工具的設計哲學不同:Dependency Walker (Depends.exe) 包含一個動態分析器(Profiler),而現代的 Dependencies 則專注於靜態分析與 API Sets 解析。
如果你需要透過「執行程式」來排查只有在運行時才會出現的 DLL 錯誤(例如延遲載入失敗或路徑搜索問題),你可以繼續使用 Dependency Walker 的 Profile 功能:
| 優點 | 缺點 |
|---|---|
| 可捕捉 Dynamic Loading (LoadLibrary) 產生的錯誤。 | 在 Win10/11 上執行時,日誌會充斥大量虛假的 API-MS-WIN 錯誤資訊。 |
| 可確認實際載入的 DLL 檔案路徑(與搜尋路徑順序相關)。 | 如果程式因相依性太過混亂而完全無法啟動,Profiler 可能無法補獲有用資訊。 |
如果你在 Dependency Walker 的 Profiler 中因為太多虛假錯誤而看不出重點,建議改用以下組合:
NAME NOT FOUND 的紀錄,這能精確告訴你程式在哪些路徑下尋找 DLL 卻失敗了。由 GitHub 與 OpenAI 合作開發,是目前全球使用最廣泛的 AI 編碼助理。它能直接嵌入在 Visual Studio Code、JetBrains 等熱門開發環境中,透過分析註釋和上下文,即時建議程式碼行或整個函式。
這是一個基於 VS Code 架構開發的獨立 AI 程式碼編輯器。不同於單純的插件,Cursor 將 AI 深度整合進編輯器的底層。它能理解整個專案的結構(Context),並允許開發者直接與整個程式庫進行對話,執行全域性的重構或 Bug 修復。
雖然 Claude 是通用型 AI,但其 3.5 Sonnet 模型在程式碼邏輯、架構設計以及 Debug 能力上被公認為目前最頂尖的選擇之一。搭配 Artifacts 功能,開發者可以在對話框中直接預覽生成的網頁前端畫面或互動式組件。
Tabnine 重視隱私與安全性,適合對原始碼保密要求較高的企業。它支援完全的本地化部署(Self-hosting),確保程式碼不會上傳至雲端進行訓練,並提供精準的自動補全功能。
由 Amazon Web Services (AWS) 提供的開發助理。除了基本的程式碼生成,它特別強化了與 AWS 雲端服務的整合,能協助開發者快速編寫基礎設施即代碼(IaC)或處理安全性掃描與更新。
內建於 Replit 線上開發環境中的 AI 代理。它的特色在於自動化部署與環境建置,使用者只需描述應用程式的功能,Agent 就能自動建立檔案、安裝依賴套件、撰寫代碼並完成上線運行。
由 Vercel 推出的前端開發專用 AI。使用者透過自然語言描述介面需求,v0 會直接生成基於 React、Tailwind CSS 和 Shadcn UI 的前端組件程式碼,大幅縮短 UI 刻板的時間。
代理型 AI 編程助手(Agentic AI Coding Assistants)。這類工具的特點是能夠直接存取終端機、讀寫檔案、執行測試,並具備自主規劃任務的能力。
這類工具與 Claude Code 最為接近,直接在你的終端機中運作,適合習慣命令行操作的開發者。
這類工具將代理能力整合進編輯器中,提供更直觀的視覺化開發體驗。
這類工具通常具備更獨立的作業能力,能夠在網頁端或獨立環境中完成從需求分析到部署的過程。
| 工具名稱 | 主要介面 | 核心優勢 |
|---|---|---|
| Claude Code | CLI (終端機) | 由 Anthropic 原生開發,對 Claude 3.5 的指令遵循度極高。 |
| Aider | CLI (終端機) | 開源、支援多種模型、極強的 Git 自動化管理。 |
| Cursor | IDE (編輯器) | UI 整合度最高,適合不喜歡切換終端機的開發者。 |
| Cline | VS Code 插件 | 開源且完全透明,可自行串接各類 API Key。 |
Aider 是目前在 GitHub 上最受歡迎的開源 AI 配對編程專案。它的運作機制與 Claude Code 非常相似,都是直接在終端機中執行。Aider 完整開源了其核心架構與程式碼,並支援串接本地執行的免費開源模型。
Continue 是一個完全開源的 IDE 插件專案,旨在作為 VS Code Cursor 的開源替代方案。它允許開發者將程式碼自動補全(Autocomplete)與側邊欄對話框(Chat)的功能,完全建立在本地端的免費模型之上。
Open Interpreter 是一個開源的自然語言程式碼執行專案。與一般只負責寫代碼的 AI 不同,它是一個可以運行在終端機的 Agent。它可以像 Claude Code 一樣,在取得權限後直接在本地環境執行 Python、JavaScript 或 Shell 腳本,完成建立檔案、分析數據或操作系統等任務。
interpreter --local 直接串接本地的免費開源模型執行。| 模型名稱 | 授權與狀態 | 適合場景 |
|---|---|---|
| DeepSeek-Coder-V2 | 開源 / 免費商用 | 具備極強的程式碼邏輯推理與多語言支援,是目前本地編碼的首選。 |
| Qwen2.5-Coder | 開源 / 免費商用 | 針對程式碼生成、修改與 Debug 進行了深度優化,程式碼品質極高。 |
| CodeLlama | 開源 / 免費商用 | 由 Meta 基於 Llama 2 微調的程式專用模型,適合基礎代碼補全。 |
像 Cursor 或 Claude Code 這類商業軟體,其後端的專利上下文分析演算法與前端介面通常是閉源的。而上述的 GitHub 開源專案,則是公開了整個客戶端到服務端的原始碼,讓開發者可以自由修改底層架構,並完全切斷網路連線,落實 100% 的本地隱私編碼環境。
Claude Code 是由 Anthropic 開發的一款代理型命令行介面(CLI)工具。它直接運行在開發者的終端機中,能夠理解整個程式碼庫、執行終端指令、編輯檔案,並協助處理 Git 工作流,讓開發者透過自然語言就能完成複雜的編程任務。
Claude Code 需要 Node.js 18+ 環境,可透過以下方式安裝:
npm install -g @anthropic-ai/claude-code
claude 即可開始互動模式。
| 功能 | 操作方式 / 快捷鍵 |
|---|---|
| Plan Mode | Shift + Tab (Mac/Linux) 或 Alt + M (Windows)。在執行前先列出詳細計劃。 |
| Auto-accept | 自動接受所有變更,無需逐一確認。 |
| 選取檔案 | 輸入 @ 快速搜尋並加入特定檔案作為背景知識。 |
| 執行指令 | 使用 ! 前綴直接在會話中執行 Bash 指令(例如:!ls -la)。 |
/help:顯示所有可用指令與說明。/clear:清除當前對話歷史,重置上下文空間。/stats:顯示近期的 Token 使用量與費用統計。/doctor:診斷安裝狀態是否正常。/compact:手動壓縮對話內容以節省空間。針對頻繁的權限確認彈窗以及需要跨目錄存取的需求,可以透過啟動參數與內建指令進行優化設定。
Claude Code 預設會針對危險指令(如刪除檔案、執行系統指令)進行逐一確認。若要進入「自動化模式」以減少干擾,可使用以下參數:
/approve-all,這會對當前會話中的後續操作授予暫時的全面信任。
Claude Code 為了安全起見,預設會限制在啟動時所在的專案目錄。若要存取外部路徑,有以下三種方法:
claude . /path/to/another/project
@ 並貼上外部檔案的絕對路徑,將其內容加入上下文:請幫我參考 @/etc/nginx/nginx.conf 的設定來修改目前的專案。
ln -s /path/to/external_dir ./external_dir
為了方便以後快速啟動並帶入這些設定,建議在你的 .bashrc 或 .zshrc (Cygwin/Linux) 中加入別名:
alias c='claude --xxx . /home/user/common_library'
針對完全自動化啟動(免確認)以及存取外部目錄的需求,Claude Code 提供了專門的 CLI 參數來達成。這些功能通常被稱為「YOLO 模式」,適合在受信任的環境或沙盒中使用。
如果您不希望在執行 ls、mkdir 或編輯檔案時一直點擊確認,可以使用以下危險參數:
--dangerously-skip-permissions--allow-dangerously-skip-permissions--permission-mode bypassPermissions 使用。
預設情況下,Claude 只能讀寫啟動時所在的目錄。若要存取其他路徑,請使用 --add-dir:
claude --add-dir <路徑>
claude --add-dir ../another-project --add-dir /var/log如果您想一次到位,直接在多個目錄間進行無阻礙開發,可以組合使用:
claude --dangerously-skip-permissions --add-dir ../library --add-dir ~/shared_configs
| 參數名稱 | 功能描述 | 適用情境 |
|---|---|---|
--dangerously-skip-permissions |
跳過所有工具(Bash, Read, Write)的權限確認。 | 需要大量自動重構或在沙盒環境操作。 |
--add-dir <path> |
授權 Claude 存取目前目錄以外的特定路徑。 | 跨專案開發或需要引用外部說明文件。 |
--permission-mode acceptEdits |
自動接受檔案修改,但執行 Bash 指令仍會詢問。 | 相對安全的自動化模式。 |
--dangerously-skip-permissions 後,Claude 可能會執行未經你審核的 shell 指令,建議僅在受信任的專案環境中使用。--dangerously-skip-permissions 具有破壞性。如果 Claude 誤判指令(例如誤刪檔案),系統將不會有任何攔截。root 或 sudo 身份執行此參數,可能會因為安全機制被阻擋,建議在一般使用者權限下執行。Claude Code 會自動把每一次對話儲存在本機,所以即使關閉終端機,之後仍能接續之前的工作內容,不需要重新說明專案背景。--continue 與 --resume 就是用來載入這些已儲存對話的兩個指令旗標。
執行 claude --continue(簡寫 -c)會自動載入目前所在目錄下最近一次的對話紀錄,不會出現任何選單或詢問。如果該目錄下找不到任何先前的對話,畫面會顯示 No conversation found to continue 並結束程式。這個指令適合每天回到同一個專案、想要零摩擦接續工作的情境。
執行 claude --resume(簡寫 -r)不帶任何參數時,會開啟一個互動式的對話選擇清單,列出可恢復的歷史對話。每一筆紀錄會顯示對話名稱或摘要、距離上次活動的時間、訊息筆數,以及所屬的 git 分支。也可以在 --resume 後面直接帶入對話的 ID 或名稱,跳過選單直接載入指定的對話。在執行中的對話裡輸入指令 /resume,也會開啟同樣的互動式選單。
--continue 適合只想接續最近一次工作、不需要挑選的情境;--resume 則適合同時手上有多個進行中對話、需要明確指定要回到哪一個的情境。在自動化腳本或 CI 流程中,建議優先使用帶有明確 ID 的 --resume,因為 --continue 在非互動模式下有時會建立新的對話,而不是真正接續舊的。
啟動新對話時可以加上 -n 或 --name 旗標指定一個容易識別的名稱,例如 claude -n feature-user-auth。已經在進行中的對話也可以用指令 /rename 重新命名。有了名稱之後,就能在選單中快速辨識,或直接用名稱搭配 --resume 載入,例如 claude --resume feature-user-auth。
選擇器預設只顯示目前工作目錄下的對話。按下快速鍵可以擴大顯示範圍:擴大到同一個儲存庫的所有工作樹,或擴大到本機上的所有專案。選擇器也支援預覽對話內容、就地重新命名,以及直接貼上 pull request 連結來找到產生那個 PR 的對話。
在對話中輸入指令 /branch(先前名稱為 /fork)可以將目前對話複製成一個新的分支並切換進去,原本的對話維持不變,仍保留在選擇器中可供之後恢復。在 CLI 層面,搭配 --fork-session 旗標與 --resume 或 --continue 一起使用,也能達到同樣效果,也就是恢復對話時建立新的對話編號,而不是延續原本那一筆。需要注意的是,分支出去的新對話不會繼承原本對話中已核准的權限設定,需要重新確認。
每一個對話都會以本機檔案形式持續儲存,邊對話邊寫入磁碟,而不是只在結束時才儲存,因此即使程式中斷或當機,紀錄也不會遺失。對話的儲存與恢復是綁定在專案目錄上的,桌面應用程式、網頁版與編輯器擴充功能各自維護自己的對話紀錄,彼此不互通。
當對話過程中使用 gh pr create 建立了 pull request,該對話會自動與這個 PR 建立關聯。之後可以透過指令 claude --from-pr 加上 PR 編號或網址,直接回到當初建立該 PR 的對話,也可以把 PR 網址貼到 /resume 選擇器的搜尋欄位中找到它。
Claude Code 目前官方支援的切換方式是登出後重新登入,沒有內建的多帳戶管理指令。
在 Claude Code 對話提示字元內執行:
/logout
登出後重新啟動 Claude Code:
claude
啟動後會開啟瀏覽器,選擇或輸入另一個帳戶的憑證完成登入。若瀏覽器無法自動開啟(WSL2、SSH 環境),按 c 複製登入網址後手動貼入瀏覽器。
claude auth switch 的快速切換指令(此功能已有 GitHub Issue #35856 提出請求,尚未實作)。若使用 Anthropic Console 的 API Key,可直接透過環境變數切換,完全不需要瀏覽器登入,且不同 shell session 或專案可用不同 Key:
啟動時帶入:
ANTHROPIC_API_KEY=sk-ant-NEWKEY claude
或先設定環境變數再啟動:
# Bash / Cygwin
export ANTHROPIC_API_KEY=sk-ant-NEWKEY
claude
也可在專案目錄放 .env 檔,Claude Code 啟動時會自動讀取:
ANTHROPIC_API_KEY=sk-ant-NEWKEY
ANTHROPIC_API_KEY 又有已登入的訂閱帳戶,會以 API Key 為準。Claude Code 的憑證與設定存放於 ~/.claude/,透過 CLAUDE_CONFIG_DIR 環境變數可指定不同目錄,讓多個帳戶各自擁有獨立的設定資料夾:
# 在 ~/.bashrc 或 ~/.bash_profile 加入 alias
alias claude-personal='CLAUDE_CONFIG_DIR=$HOME/.claude-personal command claude'
alias claude-work='CLAUDE_CONFIG_DIR=$HOME/.claude-work command claude'
首次使用時各自執行一次登入即可,之後透過 alias 直接切換,無需重新登入:
claude-personal # 以個人帳戶啟動
claude-work # 以工作帳戶啟動
| 方式 | 適用情境 | 需要瀏覽器 |
|---|---|---|
/logout + 重新登入 | 偶爾切換,不介意流程中斷 | 是 |
ANTHROPIC_API_KEY 環境變數 | 使用 Console API Key,各專案對應不同帳單 | 否 |
CLAUDE_CONFIG_DIR alias | 訂閱帳戶,需要快速切換且不想重新登入 | 僅首次設定 |
| 優先層級 | 憑證來源 |
|---|---|
| 最高 | 企業 SSO gateway token(/login 連接自架 gateway) |
| 次高 | ANTHROPIC_API_KEY 環境變數 |
| 次之 | CLAUDE_CODE_OAUTH_TOKEN(CI 用長效 token) |
| 最低 | 訂閱 OAuth 憑證(/login 登入後儲存的 token) |
Cursor 是一款基於 VS Code 開發的 AI 代碼編輯器。它並非單純的插件,而是將 AI 能力深植於編輯器底層,使其能理解整個專案的上下文,並具備自動撰寫、重構與修復程式碼的能力。由於繼承了 VS Code,使用者可以直接匯入原有的所有設定與擴充功能。
在程式碼編輯器中直接呼叫 AI:
Cmd + K (Windows 為 Ctrl + K)。側邊欄聊天對話框:
Cmd + L 開啟聊天視窗。@Files、@Codebase 或 @Web 來指定 AI 參考的範圍。這是 Cursor 最強大的「代理人」模式:
Cmd + I 開啟 Composer。| 功能名稱 | 設定方式 | 說明 |
|---|---|---|
| 模型切換 | 側邊欄底部選單 | 可在 Claude 3.5 Sonnet、GPT-4o 或 Cursor Small 之間切換。 |
| Rules for AI | Settings > General | 自定義 AI 的回覆風格(例如:一律使用繁體中文、程式碼必須符合特定規範)。 |
| Index Project | Settings > Features | 確保開啟 Local Indexing,讓 AI 能精確檢索整個專案的檔案。 |
Cmd + K,讓 AI 幫您寫出複雜的 Shell 指令。GitHub Copilot 是由 GitHub(Microsoft)開發的 AI 程式碼助理,整合在編輯器中,能即時提供程式碼補全、生成、解釋與對話功能。它背後使用 OpenAI 的模型,適合各種程式語言,是目前最廣泛使用的 AI 編程工具之一。
| 方案 | 價格 | 說明 |
|---|---|---|
| 免費版 | $0 | 每月限量補全次數與對話次數 |
| 個人版 | $10/月 | 無限制補全與對話 |
| 企業版 | $19/月/人 | 加上組織管理、自訂模型等 |
Ctrl+Shift+X)GitHub Copilot| 動作 | 方式 |
|---|---|
| 接受補全建議 | 按 Tab |
| 略過建議 | 按 Esc |
| 開啟 Copilot Chat | 按 Ctrl+Alt+I |
| 對選取程式碼提問 | 選取後按右鍵,選 Copilot 選項 |
| 功能 | Copilot | Cursor |
|---|---|---|
| 安裝方式 | VS Code 外掛 | 獨立編輯器 |
| 價格 | $10/月 | $20/月 |
| 程式碼補全 | 強 | 強 |
| 整個專案理解 | 普通 | 較強 |
| 自訂 AI 模型 | 否 | 可選 Claude/GPT 等 |
| 學習成本 | 低(沿用 VS Code) | 中(需適應新介面) |
Aider 是一款基於終端機的 AI 配對編程工具,支援直接修改本地原始碼。安裝前請確保系統已具備 Python 3.8 以上版本與 Git。
python -m pip install aider-chat
啟動前需將 AI 服務商提供的金鑰設定為環境變數。Aider 預設支援 Anthropic 與 OpenAI 等多種模型:
export ANTHROPIC_API_KEY=your-keysetx ANTHROPIC_API_KEY your-key
設定完成後,在終端機輸入 aider 即可開啟對話介面。
Aider 僅會讀取與修改您指定加入上下文的檔案,以節省 Token 成本並提高準確度:
您可以直接使用自然語言要求 Aider 執行任務,例如:「幫我把這個 class 拆分成兩個檔案」或「修復現有的 bug」。執行修改後,Aider 會自動進行 Git Commit,並根據修改內容自動撰寫提交訊息。
| 指令 | 功能描述 |
|---|---|
| /undo | 撤銷 AI 最近一次執行的代碼修改與 Git 提交。 |
| /diff | 預覽目前 AI 所做的修改與原始版本的差異。 |
| /chat-mode | 切換對話模式(如 code 模式用於修改檔案,ask 模式僅用於問答)。 |
| /help | 顯示完整的內建指令列表。 |
| /exit | 退出 Aider 程式。 |
Aider 支援透過多種方式與本地執行的免費開源模型進行串接。最常見的方法是利用 Ollama、LM Studio 或 LocalAI 等工具建立一個相容於 OpenAI API 規範的本地伺服器,再讓 Aider 指向該伺服器位址即可。
這是目前最簡單且主流的本地化方案:
ollama pull deepseek-coder-v2(或選用 Qwen2.5-Coder 等針對程式優化的模型)。
--model 參數指定 Ollama 模型路徑。
aider --model ollama/deepseek-coder-v2
| 參數 | 功能描述 |
|---|---|
| --model | 指定模型名稱。本地模型通常格式為 ollama/<model_name>。 |
| --openai-api-base | 若使用 LM Studio,需手動指定本地 API 網址(如 http://localhost:1234/v1)。 |
| --no-stream | 若本地推論效能不足導致連線不穩,可關閉串流輸出以提高穩定性。 |
並非所有模型都適合搭配 Aider。以下是目前社群公認在程式開發任務中表現較佳的本地開源模型:
Aider 本身沒有內建「project」或「chat room」概念,但它會自動將對話歷程寫入檔案,可以用目錄隔離來達到類似效果。
Aider 預設會在當前目錄產生兩個檔案:
.aider.chat.history.md:完整的對話紀錄(Markdown 格式,人可讀).aider.input.history:你輸入過的指令歷史(供上下鍵回翻用)只要在同一個目錄下啟動 aider,就會自動接續上一次的對話歷程。
直接 cd 回原本的專案目錄,再執行 aider 即可。Aider 會自動載入該目錄的 .aider.chat.history.md,模型看得到之前的上下文。
cd /your/project
aider
每個專案放在不同目錄,各自擁有獨立的 .aider.chat.history.md,就等同於分開的 chat room:
~/projects/
project-a/ ← 有自己的 .aider.chat.history.md
project-b/ ← 有自己的 .aider.chat.history.md
若想把歷程存到特定位置,可用參數覆蓋預設路徑:
aider --chat-history-file ~/aider-logs/projectA.md
若不想寫入歷程檔,可加上:
aider --no-chat-history-file
| 需求 | 做法 |
|---|---|
| 恢復上次對話 | 回到同一目錄執行 aider |
| 分開多個 project | 每個 project 用不同目錄 |
| 自訂歷程位置 | --chat-history-file <路徑> |
| 不保留歷程 | --no-chat-history-file |
aider 本身沒有「登入帳戶」的概念,OpenRouter 帳戶的識別完全透過 API Key 決定。切換帳戶 = 替換 API Key。以下是各種設定方式。
aider --api-key openrouter=sk-or-v1-NEWKEY --model openrouter/anthropic/claude-sonnet-4-6
此參數等同於在當次執行期間設定環境變數 OPENROUTER_API_KEY,不影響系統設定,關掉終端機後就失效。
Bash / Zsh(Linux、macOS、Cygwin):
export OPENROUTER_API_KEY=sk-or-v1-NEWKEY
aider --model openrouter/anthropic/claude-sonnet-4-6
Windows CMD:
set OPENROUTER_API_KEY=sk-or-v1-NEWKEY
aider --model openrouter/anthropic/claude-sonnet-4-6
Windows PowerShell:
$env:OPENROUTER_API_KEY="sk-or-v1-NEWKEY"
aider --model openrouter/anthropic/claude-sonnet-4-6
在專案目錄下建立或編輯 .env 檔:
OPENROUTER_API_KEY=sk-or-v1-NEWKEY
aider 啟動時會自動讀取所在目錄的 .env,不同專案資料夾可放不同 key,達到每個專案對應不同帳戶的效果。
aider 的全域設定檔位於 ~/.aider.conf.yml(Linux/macOS)或 %USERPROFILE%\.aider.conf.yml(Windows):
openai-api-key: null
# OpenRouter 使用 api-key 區段傳入
api-key:
- openrouter=sk-or-v1-NEWKEY
| 優先層級 | 方式 | 生效範圍 |
|---|---|---|
| 最高 | --api-key openrouter=... 命令列參數 | 單次執行 |
| 次高 | export OPENROUTER_API_KEY=... 環境變數 | 本次 session |
| 次之 | 專案目錄的 .env | 該專案目錄 |
| 最低 | ~/.aider.conf.yml | 全域預設 |
OpenRouter 的每個 API Key 在後台是獨立命名的,可到 openrouter.ai/settings/keys 確認各 key 的用量,即可判斷 aider 目前在使用哪個帳戶的 key。
| 方式 | 指令 | 適用場景 |
|---|---|---|
| 啟動時帶入 | aider image.png |
已知要用哪張圖,啟動前就備妥 |
| 對話中加入 | /add image.png |
已在 aider 會話內,臨時補充圖片 |
| 貼上剪貼簿 | /paste |
截圖後直接貼,不用存檔 |
在命令列直接把圖片路徑與程式碼檔案一起傳入:
aider screenshot.png main.py
可同時帶多張圖:
aider ui_mockup.png error_screenshot.png app.py
已在 aider 會話內時,隨時用 /add 加入圖片:
/add /path/to/screenshot.png
/drop 移除。截圖後(例如 Windows 的 Win+Shift+S、或 Snipping Tool),直接在 aider 提示字元輸入:
/paste
aider 會把剪貼簿中的圖片讀入 chat,無需存成檔案。這是截取錯誤訊息、UI 截圖最方便的做法。
aider 接受常見圖片格式:png、jpg/jpeg、gif、webp。格式本身由底層模型決定是否支援,建議優先使用 png。
並非所有 model 都接受圖片輸入。以下為常見有效組合:
| Model | 啟動範例 |
|---|---|
| Claude 3.7 Sonnet | aider --model sonnet screenshot.png |
| GPT-4o | aider --model gpt-4o screenshot.png |
| GPT-4 Turbo | aider --model gpt-4-turbo screenshot.png |
/model 切換。傳圖的典型場景:截圖 UI 讓 aider 實作或修改對應元件;提供設計稿讓 aider 生成前端程式碼;截圖難以複製的終端機錯誤訊息讓 aider 診斷。
此錯誤 NotFoundError (404) 是由 OpenRouter 後端平台所拋出。錯誤訊息明確指出 No endpoints found for deepseek/deepseek-r1:free,這代表 OpenRouter 官方已經下架、重新命名或暫時關閉了該免費節點(deepseek-r1:free),導致 Aider 透過 LiteLLM 發送請求時找不到對應的 API 端點。
OpenRouter 的免費或付費模型識別碼經常變動。您可以嘗試將模型名稱更改為目前正常運作的標準版本或替代付費版本(付費版本通常極為便宜且穩定):
aider --model openrouter/deepseek/deepseek-r1
aider --model openrouter/deepseek/deepseek-chat
如果您不希望受到第三方 API 平台端點變動的影響,建議直接在自己的電腦上透過 Ollama 下載並執行開源模型,這也是最穩定的免費方案:
在您的終端機執行(推薦針對代碼優化的版本):
ollama run qwen2.5-coder
關閉上述模型後,直接啟動 Aider 並指定本地路徑:
aider --model ollama/qwen2.5-coder
有時 Aider 會快取舊的模型名稱,或是環境變數中的 AIDER_MODEL 設定錯誤。請確保您的啟動指令中明確加上了 --model 參數,以覆蓋掉任何可能出錯的預設值:
| 作業系統 | 檢查/清除快取指令 |
|---|---|
| macOS / Linux | unset AIDER_MODEL |
| Windows (CMD) | set AIDER_MODEL= |
可以透過設定檔來固定模型,這樣每次啟動 Aider 時就不需要手動輸入冗長的 --model 參數。Aider 預設會讀取名為 .aider.conf.yml 的 YAML 格式設定檔。
Aider 啟動時會依序尋找並載入以下路徑的設定檔(後者的設定會覆蓋前者):
~/.aider.conf.yml(Windows 為 C:\Users\您的用戶名\.aider.conf.yml)。
請建立或編輯 .aider.conf.yml,並在檔案中加入以下內容:
# 指定主聊天模型 model: openrouter/deepseek/deepseek-r1 # 如果有需要,也可以一併指定 API 金鑰存放的環境變數檔案 env-file: .env
為了配合 OpenRouter 的本地調用,通常會在專案根目錄下建立一個 .env 檔案來管理金鑰,這樣可以避免將敏感金鑰直接寫在設定檔中:
OPENROUTER_API_KEY=your_openrouter_api_key_here
如果您不想使用預設的隱藏檔案名稱,也可以將配置寫在任意名稱的 YAML 檔案中(例如 my_config.yml),並在啟動時透過 --config 參數指定載入:
aider --config my_config.yml
Tabnine 是一款專為軟體開發者設計的 AI 程式碼助理。與其他高度依賴雲端運算的工具不同,Tabnine 的核心優勢在於對隱私與安全性的嚴格掌控。它支援完全的本地化執行(On-premise / Local deployment),確保企業或個人的原始碼不會被上傳至外部伺服器,更承諾其訓練資料完全基於開源的授權代碼,避免侵權風險。
Tabnine 的收費策略經歷過數次調整,其目前的方案架構如下:
| 方案名稱 | 付費狀態 | 核心功能 |
|---|---|---|
| Starter / Free | 免費 | 僅提供基礎的短行代碼自動補全。不包含 AI 聊天對話區(Chat)與全函式生成。 |
| Pro | 付費訂閱 | 解鎖全行與全函式代碼補全、Tabnine Chat 功能,支援連接自定義的本地庫(Context)。 |
| Enterprise | 企業專案 | 支援完全本地化部署(Air-gapped / Local VPC)、自定義企業內部代碼模型訓練、最高層級的隱私保護。 |
雖然 Tabnine 保留了免費的 Starter 方案,但相比於市場上的競爭對手(如 GitHub Copilot、Cursor 或免費的本地模型),其免費版的功能非常受限。免費版不支援對話式 Chat 功能,只能在編輯器中觸發短小的代碼提示。如果您需要完整的全程式碼生成與問答體驗,通常需要升級至 Pro 方案,或透過申請免費試用(通常為 90 天)來體驗完整功能。
OpenRouter 是一個統一的 LLM API 閘道(gateway),讓開發者透過單一 OpenAI 相容端點存取來自 60 個以上供應商、超過 300 個模型,包含 Anthropic、OpenAI、Google、Meta、Mistral、DeepSeek、xAI 等主流廠商的模型。只需一組 API Key 與一個信用額度帳戶,即可在這些模型之間自由切換,無需分別申請各家帳號與管理多組 Key。目前全球已有 250 萬以上用戶使用,超過 25 萬個應用程式串接其 API。
https://openrouter.ai/api/v1)存取所有模型,完全相容 OpenAI SDK,只需更換 base URL 與 API Key在模型名稱後加上變體後綴可控制路由行為:
| 後綴 | 行為 |
|---|---|
:nitro | 優先選擇最低延遲的 provider |
:floor | 優先選擇最低成本的 provider |
| 無後綴 | 依 OpenRouter 預設策略平衡路由 |
無需信用卡,註冊後即可使用超過 26 個免費模型,包含 Qwen3 Coder、DeepSeek V4 Flash、Llama 3.3 70B、Google Gemma 4 31B、OpenAI GPT-OSS 120B 等。免費模型限速為每分鐘 20 次請求、每日 200 次請求。帳戶累積 $10 以上信用額度後,免費模型的每日上限會提升。
| 費用項目 | 說明 |
|---|---|
| Token 費率 | 直接轉傳 provider 官方定價,無額外 per-token 加成 |
| 平台費 | 購買信用額度時收取 5.5%(信用卡付款),最低 $0.80 |
| BYOK 費用 | 每月前 100 萬次請求免費;超過後收取 5% 費用 |
| 最低儲值 | $5 USD |
| 企業方案 | 依用量、預付額度、年度承諾議定,可享 OpenRouter 平台費折扣 |
與現有 OpenAI SDK 程式碼完全相容,只需修改兩個參數:
# Python 範例
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="sk-or-v1-YOUR_KEY",
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
aider --model openrouter/anthropic/claude-sonnet-4-6在 OpenRouter 帳戶設定中加入自有的 Anthropic、OpenAI 等 provider API Key 後,OpenRouter 會以該 Key 直接呼叫 provider,適合需要使用 provider 優惠時段折扣(如 DeepSeek 離峰 50–75% 折扣)或希望計費直接走自有帳戶的場景。
Tavily 是一個專為 AI 代理(AI Agents)與大型語言模型(LLM)打造的即時網路搜尋 API 平台。有別於傳統搜尋引擎以人類使用者為核心,Tavily 從底層設計就針對 AI 系統的需求進行優化:返回結構化、去雜訊、可直接送入 LLM 處理的網頁內容,讓 AI 代理能夠以即時網路資料作為推理依據,大幅降低幻覺(hallucination)風險。
| 端點 | 功能說明 |
|---|---|
/search | 語意搜尋,每次請求最多彙整 20 個來源,返回排名摘要與相關性分數 |
/extract | 從指定 URL 提取並清理頁面內容,轉為 LLM 可讀格式 |
/crawl | 依指令爬取整個網站,支援自訂爬取深度與範圍 |
/map | 生成網站的 URL 結構地圖,用於理解網站架構 |
/research | 深度研究端點,自動執行多輪搜尋、推理與去重,返回完整研究報告(私人測試中) |
Search API 提供四種深度模式,可在延遲與相關性之間取捨:
Tavily 的搜尋流程在單次 API 呼叫中完成以下步驟:自動解析查詢意圖 → 並行搜尋多個來源 → 使用自有 AI 模型評分與過濾 → 提取並清理內容 → 以標準 JSON 格式返回結果(含相關性分數與來源 URL)。其架構採用動態快取與代理原生索引(agent-native index),以確保在生產規模下維持低延遲。
Tavily 定位自身為 AI 代理與開放網路之間的防火牆,所有請求均通過安全驗證層,自動封鎖提示注入(prompt injection)攻擊、個人識別資訊(PII)洩漏,以及惡意來源內容,確保 AI 應用的資料安全。
Tavily 對模型廠商保持中立,支援 OpenAI、Anthropic、Llama、Mistral 等主流模型。它已深度整合至以下框架與平台:
| 方案 | 額度 / 費率 |
|---|---|
| 免費方案 | 每月 1,000 點,無需信用卡 |
| 隨用隨付 | 每點約 $0.008 USD |
| 月訂閱方案 | 每點 $0.005~$0.0075 USD(依使用量) |
基本搜尋消耗 1 點;進階搜尋消耗 2 點;Extract 每 5 個成功 URL 消耗 1 點;Map 每 10 頁消耗 1 點。
GPT Researcher 是 Tavily 的旗艦開源項目,採用 Apache 2.0 授權,靈感來自史丹佛大學的 STORM 論文,使用多代理架構進行深度網路研究,同時也是 Tavily API 能力的主要展示平台。
2026 年 2 月,Tavily 被 AI 基礎設施公司 Nebius 收購。此次收購引發部分開發者社群對平台未來路線圖、定價穩定性及資料處理政策的討論,也促使部分團隊開始評估 Exa.ai、Perplexity Sonar API、Brave Search API 等替代方案。
以下資料整合自 2026 年 6–7 月的多個公開 benchmark 來源(SWE-bench Verified、SWE-bench Pro、LiveCodeBench、Terminal-Bench 2.0),以目前實際可購買或可取得的 model 為準。benchmark 數字依賴測試用的 agent harness,同一 model 在不同 harness 可差距 17–22 個百分點,所有數字請作為參考方向而非精確排名。
| 廠牌 | Model | SWE-bench Verified | SWE-bench Pro(Scale 標準 harness) | Input / Output(/1M tokens) | Context | 開放權重 | Coding 強項 |
|---|---|---|---|---|---|---|---|
| Anthropic | Claude Opus 4.8 | 88.6% | 69.2%(廠商自測) | $5 / $25 | 1M | 閉源 | 多檔案 agentic、架構推理、codebase 導航、Claude Code 預設 model |
| OpenAI | GPT-5.5 | 88.7% | 58.6% | $5 / $30 | 1M | 閉源 | Terminal-Bench 2.0 第一(82%),shell / DevOps agent 工作流最強 |
| Gemini 3.1 Pro | ~85% | 46.1%(Scale) | $10 / $12 | 1M | 閉源 | 多模態、超長 context、LiveCodeBench 表現強 | |
| Anthropic | Claude Sonnet 4.6 | 79.6% | 51.9%(Opus 4.6 數據參考) | $3 / $15 | 200K | 閉源 | 日常 coding 助手、速度與品質平衡,API 高頻使用主力 |
| OpenAI | GPT-5.3 Codex | — | 59.1%(Scale 第一) | $1.75 / $14 | 400K | 閉源 | SWE-bench Pro Scale 標準排行第一,agentic coding 特化,比 GPT-5.5 便宜 |
| Gemini 3 Flash | ~78% | 34.6% | $0.50 / $3.00 | 1M | 閉源 | Gemini 3 Pro 的精簡版,LiveCodeBench 表現甚至優於 Pro;低成本高頻 coding | |
| xAI | Grok 4 | — | — | — | 2M | 閉源 | 2M context 是閉源最大;Grok Build agent 具備 Arena 多輸出競選模式,benchmark 尚未完整公布 |
| DeepSeek | DeepSeek V4 Pro | 80.6% | — | $1.60 / $3.38 | 1M | MIT | LiveCodeBench 93.5、Codeforces 3206,開源 coding benchmark 全面最高;可自架 |
| DeepSeek | DeepSeek V4 Flash | ~75% | — | $0.14 / $0.28 | 1M | MIT | 全列表最低成本,日常 coding 批次任務首選;品質約比 Pro 低 5–8% |
| Alibaba | Qwen3-Coder 480B | 70.6% | 38.7%(Scale) | $3.00 / $3.75(Max 版) | 1M | Apache 2.0 | 開源 agentic coding 最強之一,Qwen Code CLI 工具整合佳;480B MoE,35B activated |
| Zhipu AI | GLM-5.2 | 77.8% | — | $4.40 / output | 200K | MIT | SWE-bench Pro 62.1%,目前開源評測第一;Interleaved Thinking 模式降低 debug 幻覺 |
| MiniMax | MiniMax M3 | 80.5% | 59.0%(廠商自測) | $0.60 / $2.40 | 1M | 開放權重 | 首個同時具備 1M context、多模態、frontier coding 的開放權重 model;Anthropic SDK 相容,可直接替換 Claude Code |
| Moonshot | Kimi K2.7-Code | ~65%+ | — | $4.00 / output | 256K | Modified MIT | Coding agent 特化,1T 參數 / 32B activated,thinking token 比 K2.6 少 30%;Kimi Code Bench v2 62.0 |
| Meta / 社群 | Devstral 2 | ~72.2% | — | $0(自架) | 128K | Apache 2.0 | 開源 coding 模型,消費級 GPU 可跑;Mistral 與社群合作版本 |
| Alibaba | Qwen3-Coder-Next | 70.6% | — | $0(自架) | — | Apache 2.0 | 80B total / 3B activated,消費級硬體可跑,本地端 coding 成本為零 |
| 場景 | 推薦 | 理由 |
|---|---|---|
| 最高品質 agentic coding(不計成本) | Claude Opus 4.8 | SWE-bench Pro 廠商測試最高,多檔案推理與架構判斷力最強 |
| Shell / DevOps terminal agent | GPT-5.5 | Terminal-Bench 2.0 第一(82%) |
| SWE-bench Pro 標準 harness 第一 | GPT-5.3 Codex | Scale 獨立測試 59.1%,比 GPT-5.5 便宜一半 |
| 日常 coding + 成本平衡(閉源) | Claude Sonnet 4.6 / Gemini 3 Flash | 品質接近旗艦,費用降低 5–8 倍 |
| 開源最高品質(API 使用) | DeepSeek V4 Pro / GLM-5.2 | LiveCodeBench 全面最高;GLM-5.2 SWE Pro 開源第一 |
| 成本最低(開源 API) | DeepSeek V4 Flash | $0.28/M output,全列表最低,品質仍達中上 |
| 本地自架 / 零 API 費用 | Qwen3-Coder-Next / Devstral 2 | 消費級 GPU 可跑,Apache 2.0 授權 |
| 大 codebase 整個塞入 context | DeepSeek V4 Pro / MiniMax M3 / Gemini 3.1 Pro | 1M context + 成本可控 |
| 替換 Claude Code 後端(成本考量) | MiniMax M3 | Anthropic SDK drop-in 相容,約 Claude Opus 1/10 費用 |
軟體程式碼、設計文件、使用手冊等均屬著作權保護範疇。著作權保護自創作完成時自動成立,無需註冊,禁止未經授權的複製、修改、散布或商業利用。
若軟體涉及技術創新或獨特演算法,可申請專利保護。專利需經審查批准,保障發明人對技術方案的專有權利,但純粹的程式邏輯通常不受專利保護。
軟體名稱、圖示(Logo)、品牌識別可註冊為商標,用以保護品牌形象與市場區隔,防止他人冒用造成混淆。
未公開的程式設計細節、演算法、資料庫結構等屬營業秘密。公司應透過保密協議、資訊管理制度等方式保護。
如個資法、資通安全管理法等,要求軟體開發過程中妥善處理使用者個人資料、保障資訊安全,違規可能面臨行政或刑事責任。
軟體開發常涉及承攬契約、合作開發契約、軟體授權條款等,明確雙方權利義務、程式碼所有權、維護責任、保密義務等,避免法律爭議。
軟體上線運營涉及網路服務、電子支付、智慧財產權線上保護等,需遵守電子簽章法、電子交易法等規範。
軟體的程式碼屬於著作權保護的範疇,開發者自動擁有著作權,無需額外登記。著作權保護涵蓋原始碼、設計文件、使用手冊等。
若軟體涉及新穎性與技術創新,可能可申請專利保護。例如獨特的演算法或解決技術問題的方法,但純粹的程式碼邏輯通常不在專利保護範圍。
軟體名稱、圖示(Logo)可註冊為商標,用於區分不同來源的產品,避免市場混淆。
未公開的程式設計細節、資料庫結構、演算法可透過營業秘密方式保護。需依靠企業內部保密協議與管理措施維護。
軟體可透過不同授權模式發行,例如專有軟體、自由軟體、開源授權(如GPL、MIT)。不同授權模式影響使用者的修改與再散布權利。
未經授權使用他人程式碼、圖像或演算法可能構成侵權。開發過程應確保程式碼來源合法,避免法律糾紛。
智慧財產權具地域性,但可透過國際條約如伯恩公約、TRIPS協議在多國獲得一定程度的保護。
在 2026 年,選擇合適的軟體販售平台取決於您的軟體類型與目標市場。以下是主流平台的分類與特性:
| 需求目標 | 推薦平台 | 核心優勢 |
|---|---|---|
| 自動處理全球稅務 | Paddle / Lemon Squeezy | 省去各國稅務申報煩惱 |
| 追求最大流量 | Steam / App Store | 現成的海量用戶基數 |
| 快速建立品牌官網 | Shopify / SHOPLINE | 高度自定義的購物體驗 |
| 早期種子用戶獲取 | AppSumo | 透過促銷快速換取現金流 |
email: [email protected]