程式語言(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 中,可以使用條件判斷來檢查變數是否為空,以下提供幾種常見的方式:
-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 則會保留視窗。
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 開頭的變數預處理器指令 (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::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++/CLI 中,gcnew System::String(stdStr.c_str()) 會將 std::string(通常是 ANSI / UTF-8 編碼)直接轉成 .NET System::String,而 System::String 預期是 UTF-16。
如果 stdStr 內含非 ASCII 字元(例如中文),就會出現亂碼。
marshal_as(建議)需引用命名空間:
#include <msclr/marshal_cppstd.h>
using namespace msclr::interop;
std::string stdStr = "中文測試";
System::String^ netStr = marshal_as<System::String^>(stdStr);
✅ 此方法能自動依 UTF-8 / ANSI 正確轉成 .NET Unicode。
---#include <codecvt>
#include <locale>
std::string utf8Str = u8"中文測試";
std::wstring wideStr = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>{}.from_bytes(utf8Str);
System::String^ netStr = gcnew System::String(wideStr.c_str());
---
std::wstring若 C++ 端原本使用寬字元字串,直接使用即可:
std::wstring wstr = L"中文測試";
System::String^ netStr = gcnew System::String(wstr.c_str());
---
std::string 來源是 UTF-8(常見於現代系統)→ 用 marshal_as 或 codecvt。gcnew String(stdStr.c_str()),除非字串是純英文。在 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 提供更多的功能和安全性。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),且無建構子/解構子邏輯。
在 .NET(C++/CLI、C#、VB.NET 等語言)中,List<T> 是一個泛型集合類別,
可儲存任意型別的資料(T),並提供動態調整大小的功能。
它屬於 System::Collections::Generic 命名空間。
// 匯入命名空間
using namespace System;
using namespace System::Collections::Generic;
int main()
{
// 宣告一個 List 儲存 int
List<int>^ numbers = gcnew List<int>();
// 初始化時直接加入元素
List<String^>^ names = gcnew List<String^>({ "Alice", "Bob", "Charlie" });
}
List<int>^ nums = gcnew List<int>();
// 新增元素
nums->Add(10);
nums->Add(20);
nums->Add(30);
// 插入指定位置
nums->Insert(1, 15); // 在索引 1 插入 15
// 移除指定值
nums->Remove(20);
// 移除指定索引
nums->RemoveAt(0);
// 取得元素
int value = nums[1]; // 取得索引 1 的元素
// 檢查是否包含
if (nums->Contains(30))
Console::WriteLine("找到 30");
// 清空所有元素
nums->Clear();
// for 迴圈
for (int i = 0; i < nums->Count; i++)
{
Console::WriteLine("第 {0} 個元素: {1}", i, nums[i]);
}
// for each 迴圈
for each (int n in nums)
{
Console::WriteLine(n);
}
| 屬性 / 方法 | 說明 |
|---|---|
Count | 目前元素數量 |
Capacity | 內部容量(可自動成長) |
Add(item) | 加入一個元素 |
AddRange(collection) | 加入另一個集合的所有元素 |
Insert(index, item) | 在指定位置插入元素 |
Remove(item) | 移除指定值(找到第一個匹配項) |
RemoveAt(index) | 移除指定索引的元素 |
Clear() | 移除所有元素 |
Contains(item) | 檢查是否包含指定值 |
IndexOf(item) | 回傳元素索引(若不存在則為 -1) |
Sort() | 排序元素(適用於可比較型別) |
Reverse() | 反轉元素順序 |
10 15 30
#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: 無資料
在 .NET(C++/CLI、C#、VB.NET 等語言)中,Dictionary<TKey, TValue> 是一種泛型集合,
用於儲存「鍵值對 (key-value pair)」。每個 key 必須唯一,而 value 則可重複。
它屬於 System::Collections::Generic 命名空間。
// 匯入命名空間
using namespace System;
using namespace System::Collections::Generic;
int main()
{
// 建立一個 Dictionary,Key 為 int,Value 為 String
Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
// 直接初始化
users->Add(1, "Alice");
users->Add(2, "Bob");
users->Add(3, "Charlie");
return 0;
}
Dictionary<String^, int>^ ages = gcnew Dictionary<String^, int>();
// 新增元素
ages->Add("Tom", 25);
ages->Add("Jerry", 30);
// 修改值(透過索引器)
ages["Tom"] = 26;
// 新增或修改(若 key 不存在則自動新增)
ages["Spike"] = 40;
// 刪除元素
ages->Remove("Jerry");
// 存取元素
if (ages->ContainsKey("Tom"))
{
Console::WriteLine("Tom 的年齡是 {0}", ages["Tom"]);
}
// 嘗試取得值(避免例外)
int age;
if (ages->TryGetValue("Spike", age))
{
Console::WriteLine("Spike 的年齡是 {0}", age);
}
Dictionary<int, String^>^ users = gcnew Dictionary<int, String^>();
users->Add(1, "Alice");
users->Add(2, "Bob");
users->Add(3, "Charlie");
// 使用 KeyValuePair 迴圈
for each (KeyValuePair<int, String^> entry in users)
{
Console::WriteLine("Key = {0}, Value = {1}", entry.Key, entry.Value);
}
// 只取 Keys
for each (int key in users->Keys)
{
Console::WriteLine("Key: {0}", key);
}
// 只取 Values
for each (String^ name in users->Values)
{
Console::WriteLine("Name: {0}", name);
}
| 屬性 / 方法 | 說明 |
|---|---|
Add(key, value) | 新增一組鍵值對 |
Remove(key) | 移除指定的鍵及其對應的值 |
Clear() | 清空所有項目 |
ContainsKey(key) | 檢查是否存在指定的鍵 |
ContainsValue(value) | 檢查是否存在指定的值 |
TryGetValue(key, out value) | 安全地取得值,不會拋出例外 |
Count | 目前字典中的元素數量 |
Keys | 回傳所有鍵的集合 |
Values | 回傳所有值的集合 |
Key = 1, Value = Alice Key = 2, Value = Bob Key = 3, Value = Charlie Tom 的年齡是 26 Spike 的年齡是 40
在 C++ 中,模板 (Template) 是一種泛型編程的工具,允許我們在編寫函數或類別時,不指定具體的資料型別,從而使程式碼更具重用性。模板有助於在單一程式碼中處理不同型別的資料,避免重複的函數或類別定義。
函數模板允許我們撰寫可以處理不同型別的函數。函數模板的語法如下:
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 之前呼叫一次。在 .NET C++ (或更常見的 C++/CLI) 專案中,讀取和解析檔案中的 HTML 表格,通常需要藉助一個外部的 HTML 解析函式庫,因為標準的 .NET 框架和 C++ 本身沒有內建強大的 HTML DOM 解析功能。一個常見且高效的選擇是使用 C# 或其他 .NET 語言的函式庫,然後在 C++/CLI 中引用。
對於 C++/CLI 專案,最實用且推薦的方法是使用 Html Agility Pack (HAP),這是一個非常流行的 .NET HTML 解析器。雖然 HAP 是用 C# 編寫的,但它可以無縫地在任何 .NET 語言(包括 C++/CLI)中作為參考使用。
HtmlAgilityPack。以下是一個 C++/CLI 的程式碼片段,演示如何讀取一個本地 HTML 檔案並解析其中的第一個表格 (<table>) 的內容:
#using <System.dll>
#using <System.Xml.dll>
#using <HtmlAgilityPack.dll> // 確保已加入參考
using namespace System;
using namespace System::IO;
using namespace HtmlAgilityPack;
void ParseHtmlTable(String^ filePath)
{
// 檢查檔案是否存在
if (!File::Exists(filePath))
{
Console::WriteLine("錯誤:檔案不存在。");
return;
}
// 1. 載入 HTML 文件
HtmlDocument^ doc = gcnew HtmlDocument();
try
{
// 從檔案載入 HTML
doc->Load(filePath);
}
catch (Exception^ ex)
{
Console::WriteLine("載入檔案時發生錯誤:" + ex->Message);
return;
}
// 2. 選擇第一個 <table> 節點
HtmlNode^ table = doc->DocumentNode->SelectSingleNode("//table");
if (table != nullptr)
{
Console::WriteLine("找到 HTML 表格,開始解析...");
// 3. 選擇所有的 <tr> (表格列) 節點
HtmlNodeCollection^ rows = table->SelectNodes(".//tr");
if (rows != nullptr)
{
for each (HtmlNode^ row in rows)
{
// 4. 選擇 <td> 或 <th> (儲存格) 節點
// 使用 | 運算子選擇 <td> 或 <th>
HtmlNodeCollection^ cells = row->SelectNodes("td | th");
if (cells != nullptr)
{
String^ rowData = "";
for each (HtmlNode^ cell in cells)
{
// 獲取儲存格的內部文字並去除首尾空白
rowData += cell->InnerText->Trim() + "\t";
}
Console::WriteLine(rowData);
}
}
}
else
{
Console::WriteLine("表格中未找到 <tr> 標籤。");
}
}
else
{
Console::WriteLine("檔案中未找到 <table> 標籤。");
}
}
int main(array<String^>^ args)
{
// 請將 "your_html_file.html" 替換為您的實際檔案路徑
String^ htmlFilePath = "C:\\path\\to\\your_html_file.html";
ParseHtmlTable(htmlFilePath);
return 0;
}
doc->Load(filePath):將本地 HTML 檔案讀取到 HAP 的 DOM 結構中。SelectSingleNode("//table"):使用 XPath 表達式選取文件中的第一個 <table> 元素。SelectNodes(".//tr"):選取當前節點(<table>)下的所有 <tr> 元素。SelectNodes("td | th"):選取 <tr> 節點下的所有 <td> 或 <th> 元素。cell->InnerText:獲取儲存格內容的純文字,會自動去除 HTML 標籤。gcnew 和 ^: 這是 C++/CLI 用來建立和管理 .NET 物件(受 CLR 垃圾回收機制管理)的語法。HttpListener 類別允許您在 C++/CLI 應用程式中建立一個簡單的、自託管(Self-Hosted)的 HTTP 伺服器。
在 Visual Studio 中建立一個 C++/CLI 專案(例如 CLR Console Application),並確保已引用 System.dll。
以下是使用 HttpListener 建立一個簡單 API 伺服器並處理 GET 請求的 C++/CLI 程式碼。
#using <System.dll>
using namespace System;
using namespace System::Net;
using namespace System::Threading::Tasks;
using namespace System::Text;
// 處理傳入 HTTP 請求的函式
void HandleRequest(HttpListenerContext^ context)
{
HttpListenerRequest^ request = context->Request;
HttpListenerResponse^ response = context->Response;
// 預設回應設定
response->ContentType = "application/json; charset=utf-8";
String^ responseString = "";
response->StatusCode = 200; // 預設為 OK
// 檢查請求的路徑和方法
String^ url = request->RawUrl->ToLower();
if (url == "/api/status" && request->HttpMethod == "GET")
{
// 處理 GET /api/status 請求
responseString = "{\"status\": \"Server Running\", \"language\": \"C++/CLI\"}";
}
else
{
// 處理其他未定義的請求
response->StatusCode = 404; // Not Found
responseString = "{\"error\": \"404 Not Found\", \"path\": \"" + url + "\"}";
}
// 將字串回應轉換為位元組,並寫入輸出串流
array<Byte>^ buffer = Encoding::UTF8->GetBytes(responseString);
response->ContentLength64 = buffer->Length;
try
{
response->OutputStream->Write(buffer, 0, buffer->Length);
response->OutputStream->Close();
}
catch (Exception^)
{
// 忽略寫入錯誤,通常是客戶端斷開連線
}
}
// 啟動 HttpListener 的函式
void StartListener(String^ prefix)
{
HttpListener^ listener = gcnew HttpListener();
try
{
listener->Prefixes->Add(prefix);
listener->Start();
Console::WriteLine("C++/CLI API Server 啟動,正在監聽: {0}", prefix);
}
catch (Exception^ ex)
{
Console::WriteLine("啟動 HttpListener 發生錯誤。請確認權限或位址是否已被佔用。");
Console::WriteLine("錯誤訊息: {0}", ex->Message);
return;
}
// 異步迴圈,持續接收請求
while (listener->IsListening)
{
try
{
// 同步等待下一個請求
HttpListenerContext^ context = listener->GetContext();
// 使用 Task 異步處理請求,避免阻塞監聽迴圈
Task::Factory->StartNew(gcnew Action<HttpListenerContext^>(&HandleRequest), context);
}
catch (Exception^)
{
// 監聽器停止時會拋出例外
break;
}
}
if (listener->IsListening)
{
listener->Close();
}
}
int main(array<String^>^ args)
{
// 設定監聽的 URL 前綴
// 注意:在 Windows 中,可能需要管理員權限或使用 netsh 註冊 URL 命名空間。
String^ listeningPrefix = "http://localhost:8080/";
StartListener(listeningPrefix);
Console::WriteLine("按下任意鍵結束伺服器...");
Console::ReadKey(true);
return 0;
}
在 Windows 系統中,如果程式碼沒有以管理員權限運行,HttpListener 監聽特定埠時可能會拋出存取拒絕的例外。您可以透過以下命令註冊 URL 命名空間,以允許非管理員帳戶運行伺服器:
netsh http add urlacl url=http://+:8080/ user=Everyone
其中 http://+:8080/ 應替換為您程式碼中實際使用的前綴。
在 ASP.NET Core 中,建立 HTTP API 伺服器的標準專案類型是 Web API。最新的 .NET 版本(.NET 6 及更高版本)推薦使用 Minimal APIs 來快速且輕量地建立 API 端點。
Minimal APIs 使用最少的檔案和程式碼行,將啟動邏輯 (Startup) 和路由定義 (Routing) 合併到一個檔案 Program.cs 中。
要建立一個新的 Minimal API 專案,您可以使用 .NET CLI (命令列介面):
dotnet new web -n MinimalApiServer
cd MinimalApiServer
以下是 Minimal API 伺服器核心檔案 Program.cs 的完整內容。它定義了伺服器的啟動、中介軟體和兩個 API 端點 (一個 GET,一個 POST)。
// 1. 建立 WebApplication 實例 (取代了傳統的 Startup 類別)
var builder = WebApplication.CreateBuilder(args);
// 2. 註冊服務 (Service Configuration)
// 這裡可以加入資料庫連線、驗證服務等
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); // 啟用 Swagger/OpenAPI 支援
var app = builder.Build();
// 3. 配置 HTTP 請求管線 (Middleware Configuration)
// 開發環境下,啟用 Swagger UI 以供測試
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// 啟用 HTTPS 重新導向 (生產環境推薦)
// app.UseHttpsRedirection();
// 4. 定義 API 端點 (Endpoint Definition)
// 實體類別用於 POST 請求
public record Product(int Id, string Name, decimal Price);
// GET 請求端點:/api/hello
app.MapGet("/api/hello", () =>
{
return Results.Ok(new { message = "Hello from ASP.NET Core Minimal API!", timestamp = DateTime.Now });
});
// GET 請求端點:/api/products/{id}
app.MapGet("/api/products/{id}", (int id) =>
{
// 假設從資料庫查詢產品
if (id == 1)
{
return Results.Ok(new Product(1, "筆記型電腦", 1200.00m));
}
return Results.NotFound(new { error = $"Product ID {id} not found" });
});
// POST 請求端點:/api/products
app.MapPost("/api/products", (Product product) =>
{
// ASP.NET Core 會自動將 JSON 請求主體解序列化為 Product 實體
Console.WriteLine($"收到新產品: {product.Name} (ID: {product.Id})");
// 實際應用中,這裡會執行資料庫新增操作
// 回傳 201 Created 狀態碼
return Results.Created($"/api/products/{product.Id}", product);
});
// 5. 啟動應用程式,開始監聽 HTTP 請求
app.Run();
dotnet run。http://localhost:5000 或 https://localhost:7000 運行。
/api/hello (GET)。/swagger 路徑可以查看 Swagger UI,用於測試所有定義的 API 端點。app.UseSwagger()、app.UseHttpsRedirection() 等,這些組件按順序構成請求處理管線。每個請求都會流經這些中介軟體。app.MapPost 範例中,當用戶端發送 JSON 數據時,ASP.NET Core 會自動將其轉換(解序列化)為 C# 的 Product 記錄 (Record)。週邊控制程式是用來與電腦或主機連接的外部硬體裝置(例如:印表機、掃描器、馬達、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)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 提交歷史整潔的重要工具,特別適合多人協作時,能夠避免產生冗餘的合併提交,並保持代碼庫的提交記錄線性。
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 或以上等級。
程式部署(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
在 Ubuntu 系統上部署 .NET C++(C++/CLI 或以 .NET 為基底的 C++ 應用)需要根據專案類型分別處理。若使用純 C++/CLI(依賴 .NET Framework),將無法直接於 Linux 執行,需改為使用 .NET 6/7/8 的跨平台技術(如 C++/CLR → C# 或使用 C++/Native 搭配 .NET 互通)。
根據應用性質,部署可分為三種情境:
在 Ubuntu 中安裝 .NET SDK 與必要工具:
sudo apt update
sudo apt install -y dotnet-sdk-8.0
sudo apt install -y build-essential
(可依版本替換 dotnet-sdk-8.0 為 dotnet-sdk-7.0 或其他版本)
若使用 .NET C# 為主程式,並呼叫 C++ 函式庫:
# 建立主應用
dotnet new console -n MyApp
cd MyApp
# 建立 native 函式庫
mkdir native
cd native
nano add.cpp
add.cpp 範例:
extern "C" int add(int a, int b) {
return a + b;
}
編譯為共享函式庫:
g++ -fPIC -shared -o libadd.so add.cpp
於 Program.cs 加入:
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("libadd.so", EntryPoint="add")]
public static extern int Add(int a, int b);
static void Main()
{
Console.WriteLine("3 + 5 = " + Add(3, 5));
}
}
回到專案根目錄執行:
dotnet run
若輸出結果為 3 + 5 = 8,表示部署成功。
可將程式打包為可獨立執行的部署檔:
dotnet publish -c Release -r linux-x64 --self-contained true
生成的可執行檔位於 bin/Release/net8.0/linux-x64/publish/。
可使用 Docker 將應用容器化,以便在任何 Ubuntu 系統運行:
# Dockerfile 範例
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY ./publish .
COPY ./native/libadd.so /usr/lib/
ENTRYPOINT ["./MyApp"]
建構與執行容器:
docker build -t myapp .
docker run --rm myapp
.so 函式庫,Windows 使用 .dll。LD_LIBRARY_PATH。部署 .NET C++ 程式至 Ubuntu 的可行方式通常是採「C# + C++ 原生函式庫」架構。若原始程式依賴 C++/CLI,需重新設計以支援跨平台 .NET。最推薦的方式是使用 .NET 8 + 原生 C++ 模組搭配 Docker,達到穩定、可攜且一致的部署流程。
軟體程式碼、設計文件、使用手冊等均屬著作權保護範疇。著作權保護自創作完成時自動成立,無需註冊,禁止未經授權的複製、修改、散布或商業利用。
若軟體涉及技術創新或獨特演算法,可申請專利保護。專利需經審查批准,保障發明人對技術方案的專有權利,但純粹的程式邏輯通常不受專利保護。
軟體名稱、圖示(Logo)、品牌識別可註冊為商標,用以保護品牌形象與市場區隔,防止他人冒用造成混淆。
未公開的程式設計細節、演算法、資料庫結構等屬營業秘密。公司應透過保密協議、資訊管理制度等方式保護。
如個資法、資通安全管理法等,要求軟體開發過程中妥善處理使用者個人資料、保障資訊安全,違規可能面臨行政或刑事責任。
軟體開發常涉及承攬契約、合作開發契約、軟體授權條款等,明確雙方權利義務、程式碼所有權、維護責任、保密義務等,避免法律爭議。
軟體上線運營涉及網路服務、電子支付、智慧財產權線上保護等,需遵守電子簽章法、電子交易法等規範。
軟體的程式碼屬於著作權保護的範疇,開發者自動擁有著作權,無需額外登記。著作權保護涵蓋原始碼、設計文件、使用手冊等。
若軟體涉及新穎性與技術創新,可能可申請專利保護。例如獨特的演算法或解決技術問題的方法,但純粹的程式碼邏輯通常不在專利保護範圍。
軟體名稱、圖示(Logo)可註冊為商標,用於區分不同來源的產品,避免市場混淆。
未公開的程式設計細節、資料庫結構、演算法可透過營業秘密方式保護。需依靠企業內部保密協議與管理措施維護。
軟體可透過不同授權模式發行,例如專有軟體、自由軟體、開源授權(如GPL、MIT)。不同授權模式影響使用者的修改與再散布權利。
未經授權使用他人程式碼、圖像或演算法可能構成侵權。開發過程應確保程式碼來源合法,避免法律糾紛。
智慧財產權具地域性,但可透過國際條約如伯恩公約、TRIPS協議在多國獲得一定程度的保護。
email: [email protected]