程式語言(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()
方法,避免昂貴的反射掃描。Visual Studio 是由微軟開發的一個整合式開發環境(IDE),支援多種程式語言如 C++、C#、VB.NET、Python、JavaScript 等。適用於開發桌面應用程式、網站、雲端服務及行動應用程式。
Visual Studio 支援 Windows 作業系統,另有 Visual Studio for Mac,專為 macOS 設計。
[*.cs] indent_style = space indent_size = 43. 檔案儲存後,格式化會自動遵循這些規則。
您可以透過命令列工具檢查電腦上安裝的 .NET SDK 版本:
dotnet --list-sdks
若出現類似下列結果,表示已安裝的 SDK 版本:
8.0.100 [C:\Program Files\dotnet\sdk] 9.0.100-preview.3.24172.9 [C:\Program Files\dotnet\sdk]
如果未出現您想使用的版本,表示該版本尚未安裝。
dotnet
命令若未看到此選項,您也可以使用 Windows 的開始選單搜尋「Developer Command Prompt for VS」開啟。
Ctrl + `
dotnet
命令檢查或操作 SDK在 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
或 -f
target_dir="/path/to/your/dir"
subdirs=()
while IFS= read -r -d $'\0' dir; do
subdirs+=("$dir")
done < <(find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0)
find
:列出子目錄(不遞迴)-mindepth 1
:排除自身目錄-maxdepth 1
:只列出第一層-type d
:只列出目錄-print0
搭配 read -d $'\0'
:處理有空格的路徑for dir in "${subdirs[@]}"; do
echo "$dir"
done
subdirs=( "$target_dir"/*/ )
此寫法使用通配符匹配子目錄,但無法排除檔案或處理空格與特殊字元。
在處理包含空格、特殊符號(如空白、引號、換行)的檔名或路徑時,傳統使用 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` 無法持續讀取。
-print0
subdirs=()
find "$target_dir" -mindepth 1 -maxdepth 1 -type d -print0 | while IFS= read -r -d $'\0' dir; do
subdirs+=("$dir")
done
**注意**:若希望 `subdirs` 在主程式中可用,這方法不適合(因 `while ... |` 子 shell 無法回傳陣列)
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 編碼保存,避免出現編碼錯誤。
在 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++ 中,模板 (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 關鍵字通常只在設計需要密切配合的類別或函數時使用。
using System;
class Program {
static void Main() {
string name = "世界";
Console.WriteLine($"哈囉, {name}!");
}
}
(x, y) => x + y
var q = list.Where(x => x > 10);
await Task.Delay(1000);
CS8618 意思是:在建構函式結束時,非 Nullable
的屬性沒有被初始化,因此 C# 編譯器警告它可能會是 null
。
public class Product {
public string Name { get; set; } // 警告 CS8618
}
public class Product {
public string Name { get; set; }
public Product(string name) {
Name = name;
}
}
public class Product {
public string Name { get; set; } = string.Empty;
}
public class Product {
public string? Name { get; set; }
}
→ 適用於 Name
合理允許為 null
的情況。
required
修飾元(C# 11+ 支援)public class Product {
public required string Name { get; set; }
}
// 呼叫端必須初始化
var p = new Product { Name = "手機" }; // OK
null
:用 建構函式 或 required。null
:改成 string?
。string.Empty
)。float
與 System::String
相加,例如:
System::String^ tmpStr = "Value: " + someFloat;
當 someFloat
很大或很小時,可能會自動以科學記號(e.g. 1.23E+05)顯示。
System::String::Format
或 ToString
搭配格式字串,指定小數位數並避免科學記號。
using namespace System;
float value = 123456.789f;
// 方法1:String::Format
String^ tmpStr1 = String::Format("Value: {0:F2}", value); // F2 表示小數點後 2 位
Console::WriteLine(tmpStr1);
// 方法2:ToString 搭配格式
String^ tmpStr2 = "Value: " + value.ToString("F2");
Console::WriteLine(tmpStr2);
// 方法3:更多位數
String^ tmpStr3 = "Value: " + value.ToString("F6");
Console::WriteLine(tmpStr3);
F2
:固定小數點格式,小數點後 2 位F6
:固定小數點格式,小數點後 6 位F{位數}
在 .NET C++ 中,可以使用 System::IO
命名空間提供的功能來操作檔案與目錄。
取得目錄中最新的檔案,可以透過讀取所有檔案資訊並比較最後修改日期實現。
以下是一個完整的範例,展示如何取得指定目錄中最新的檔案:
#include "stdafx.h"
#include <iostream>
#include <cliext/vector>
#include <System.IO>
using namespace System;
using namespace System::IO;
using namespace cliext;
int main()
{
try
{
// 指定目錄路徑
String^ directoryPath = "C:\\Your\\Directory\\Path";
// 檢查目錄是否存在
if (!Directory::Exists(directoryPath))
{
Console::WriteLine("目錄不存在: {0}", directoryPath);
return -1;
}
// 取得目錄中的所有檔案
array^ files = Directory::GetFiles(directoryPath);
// 如果目錄中沒有檔案
if (files->Length == 0)
{
Console::WriteLine("目錄中沒有檔案。");
return 0;
}
// 找到最新的檔案
String^ newestFile = nullptr;
DateTime newestTime = DateTime::MinValue;
for each (String^ file in files)
{
// 取得檔案的最後修改時間
DateTime lastWriteTime = File::GetLastWriteTime(file);
// 比較時間並更新最新檔案資訊
if (lastWriteTime > newestTime)
{
newestTime = lastWriteTime;
newestFile = file;
}
}
// 輸出最新檔案資訊
Console::WriteLine("最新檔案: {0}", newestFile);
Console::WriteLine("最後修改時間: {0}", newestTime);
}
catch (Exception^ ex)
{
Console::WriteLine("發生錯誤: {0}", ex->Message);
}
return 0;
}
Directory::GetFiles
方法獲取指定目錄中的所有檔案路徑。File::GetLastWriteTime
方法取得每個檔案的最後修改時間。try-catch
區塊捕獲潛在的異常,例如目錄不存在或無法存取。System.Reflection
是 .NET 框架中的一個命名空間,提供了檢查和操作元數據的工具,使開發者可以在運行時動態檢查類型、方法、屬性等,並動態創建和操縱對象。
Assembly
:表示已加載的程序集,提供加載、探索和反射程序集的方法。Type
:表示一種類型,包括類別、結構、介面等,提供獲取類型信息的功能。MethodInfo
:表示方法資訊,允許開發者檢查方法的屬性並動態調用它們。PropertyInfo
:表示屬性資訊,提供對屬性元數據的存取。FieldInfo
:表示字段資訊,可用於訪問類型的字段。以下是一個使用 System.Reflection
的範例程式,展示如何動態檢查類型和方法,並調用方法。
// 定義一個簡單的範例類別
public class SampleClass {
public string SayHello(string name) {
return $"Hello, {name}!";
}
}
// 使用 Reflection 來動態調用方法
using System;
using System.Reflection;
class Program {
static void Main() {
// 創建 SampleClass 類別的實例
Type sampleType = typeof(SampleClass);
object sampleInstance = Activator.CreateInstance(sampleType);
// 獲取 SayHello 方法資訊
MethodInfo methodInfo = sampleType.GetMethod("SayHello");
// 動態調用 SayHello 方法
object result = methodInfo.Invoke(sampleInstance, new object[] { "World" });
Console.WriteLine(result); // 輸出: Hello, World!
}
}
在上述範例中,我們使用 Activator.CreateInstance
來創建類別的實例,並使用 MethodInfo.Invoke
來調用方法
SayHello
。
System::Management::ManagementClass
是 .NET Framework 中提供用於操作 Windows Management Instrumentation (WMI) 的類別之一。它允許開發者讀取、操作和管理系統資訊,例如硬體、作業系統、網路設定等。
System::Management
是位於 System.Management.dll
中的命名空間,使用此功能需先加入參考。
// C++/CLI 寫法
using namespace System;
using namespace System::Management;
int main() {
try {
ManagementClass^ mc = gcnew ManagementClass("Win32_OperatingSystem");
for each (ManagementObject^ mo in mc->GetInstances()) {
Console::WriteLine("OS 名稱: {0}", mo["Caption"]);
}
}
catch (Exception^ ex) {
Console::WriteLine("錯誤: {0}", ex->Message);
}
return 0;
}
System.Management.dll
的參考。ManagementException
。ManagementObject
ManagementObjectSearcher
ManagementBaseObject
net stop winmgmt winmgmt /resetrepository net start winmgmt
Get-WmiObject Win32_NetworkAdapterConfiguration方式二:WMI 測試工具 (wbemtest)
wbemtest
root\cimv2
並連線SELECT * FROM Win32_NetworkAdapterConfiguration
DISM /Online /Cleanup-Image /RestoreHealth
修復系統映像大多數現代 Chromebook 支援透過 Crostini 專案來運行 Linux 應用程式。
設定 > 進階 > 開發人員 > 啟用 Linux (Beta)
。sudo apt update
sudo apt install -y dotnet-sdk-7.0
無需在本地安裝任何軟體,即可在 Chromebook 上使用雲端平台運行 .NET 程式。
Chromebook 支援使用容器運行應用程式,您可以透過 Docker 啟動 .NET 環境。
docker pull mcr.microsoft.com/dotnet/runtime:7.0
從 .NET 6 開始,應用程式可以跨多平台運行,包括 Linux。將您的應用程式編譯為跨平台格式並部署至 Chromebook。
dotnet publish -c Release -r linux-x64 --self-contained
using System;
using System.Windows.Forms;
public class MyForm : Form {
public MyForm() {
Button btn = new Button();
btn.Text = "點我";
btn.Click += (s, e) => MessageBox.Show("你點了按鈕!");
Controls.Add(btn);
}
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
// MainWindow.xaml
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WPF 範例" Height="200" Width="300">
<StackPanel>
<Button Content="點我" Click="Button_Click"/>
</StackPanel>
</Window>
// MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("你點了按鈕!");
}
// MainPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
x:Class="MyApp.MainPage">
<VerticalStackLayout Spacing="25" Padding="30">
<Button Text="點我" Clicked="OnButtonClicked"/>
</VerticalStackLayout>
</ContentPage>
// MainPage.xaml.cs
private void OnButtonClicked(object sender, EventArgs e) {
await DisplayAlert("通知", "你點了按鈕!", "OK");
}
技術 | 用途 | 平台 |
---|---|---|
WinForms | 快速開發桌面應用 | Windows |
WPF | 複雜桌面應用、MVVM 模式 | Windows |
MAUI | 跨平台應用 | Windows、macOS、Android、iOS |
Blazor | Web 前端開發 | 跨平台(Web) |
以下範例展示了如何使用 Controls->GetEnumerator()
方法來逐一遍歷 .NET 表單中的所有子控制項並批量修改其屬性。
using System;
using System.Drawing;
using System.Windows.Forms;
public class FormExample : Form
{
public FormExample()
{
// 初始化一些控制項
Button button1 = new Button { Text = "Button 1", Location = new Point(10, 10) };
TextBox textBox1 = new TextBox { Location = new Point(10, 50) };
Controls.Add(button1);
Controls.Add(textBox1);
// 使用 GetEnumerator() 遍歷並修改控制項屬性
ModifyControls();
}
private void ModifyControls()
{
var enumerator = Controls.GetEnumerator();
while (enumerator.MoveNext())
{
Control control = (Control)enumerator.Current;
// 設定範例:將所有控制項的背景色設為淺藍
control.BackColor = Color.LightBlue;
// 若控制項為 TextBox,將其設為不可編輯
if (control is TextBox)
{
control.Enabled = false;
}
}
}
// 啟動應用程式
public static void Main()
{
Application.Run(new FormExample());
}
}
**GetEnumerator()**: 利用 `Controls.GetEnumerator()` 方法來取得控制項的列舉器,這樣可以遍歷所有子控制項。
**條件修改**: 在 `while` 迴圈中,對每個 `Control` 物件進行屬性修改,例如將背景色設為淺藍,並根據控制項類型進行特定修改,如將 `TextBox` 設為不可編輯。
**用途**: 這種方法在需要批量修改屬性時非常有效,比如調整 UI 風格或在特定情況下禁用多個控制項。
執行後,所有控制項的背景色會變成淺藍色,且所有 `TextBox` 控制項將被設為不可編輯。
當堆疊追蹤中出現以下錯誤訊息時,開發者可能需要檢查 Message
物件的內容:
at ....MainForm.Dispose(Boolean A_0) at System.Windows.Forms.Form.WmClose(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) ...
此時需要透過覆寫或檢測 WndProc
方法中的 Message m
來檢查其詳細資訊。
以下提供幾種方法來檢查或記錄 Message
物件的內容。
如果可以存取表單或控制項的原始碼,建議覆寫 WndProc
方法,直接記錄或檢查 Message m
的內容。
protected override void WndProc(ref Message m)
{
try
{
// 記錄 Message 的內容
Console.WriteLine($"Message Details: hWnd={m.HWnd}, Msg={m.Msg}, WParam={m.WParam}, LParam={m.LParam}");
base.WndProc(ref m);
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex}");
throw; // 保持原有例外行為
}
}
這段程式碼會在每次接收到訊息時記錄相關資訊,並允許開發者進一步分析。
如果錯誤發生在 Dispose
方法中,可以在方法內加入例外處理來檢查相關資訊。
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
// 釋放資源
}
base.Dispose(disposing);
}
catch (Exception ex)
{
Console.WriteLine($"Exception in Dispose: {ex}");
throw;
}
}
這樣可以確保在釋放資源時不會忽略重要的錯誤資訊。
如果無法確定錯誤發生的位置,可以透過全域例外處理來記錄堆疊追蹤和相關資訊。
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
Exception ex = (Exception)args.ExceptionObject;
Console.WriteLine($"Unhandled Exception: {ex}");
Environment.Exit(1);
};
可以使用 Visual Studio 將斷點設置在 WndProc
或 Dispose
方法中,並檢查 Message
物件的內容。
HWnd
:接收訊息的視窗句柄。Msg
:訊息的 ID。WParam
:附加訊息資訊(字參數)。LParam
:附加訊息資訊(長參數)。WndProc
或修改程式碼需小心,避免影響現有行為。Message
的座標或 ID 不固定,可考慮加入條件判斷來篩選需要的訊息。在 .NET C++/CLI 中,WmClose(Message& m)
是 System.Windows.Forms.Form
的內部保護方法,無法直接覆寫。不過,可以透過覆寫 WndProc
方法來攔截並處理 WM_CLOSE
訊息,以達到類似覆寫 WmClose
的效果。
#include <Windows.h>
#include <System.Windows.Forms.h>
using namespace System;
using namespace System::Windows::Forms;
public ref class CustomForm : public Form
{
protected:
// 模擬 WmClose 行為的覆寫
void WmClose(Message% m)
{
// 在這裡加入自定義的 WM_CLOSE 行為
if (MessageBox::Show("確定要關閉視窗嗎?", "確認", MessageBoxButtons::YesNo) == DialogResult::Yes)
{
// 繼續調用基底行為以進行正常關閉
this->Form::WndProc(m);
}
else
{
// 阻止關閉視窗
return;
}
}
// 覆寫 WndProc,攔截 WM_CLOSE 訊息並調用 WmClose
virtual void WndProc(Message% m) override
{
const int WM_CLOSE = 0x0010;
if (m.Msg == WM_CLOSE)
{
WmClose(m); // 調用自定義的 WmClose 方法
}
else
{
// 處理其他訊息
Form::WndProc(m);
}
}
};
[STAThread]
int main(array<String^>^ args)
{
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
CustomForm^ form = gcnew CustomForm();
form->Text = "覆寫 WmClose 行為範例";
Application::Run(form);
return 0;
}
模擬覆寫 WmClose
方法,在這裡自定義視窗關閉的行為。透過確認對話框詢問使用者是否要關閉視窗。
覆寫 WndProc
方法來攔截 WM_CLOSE
訊息,並將其委派給自定義的 WmClose
方法。
在沒有攔截 WM_CLOSE
的情況下,調用基底類別的 WndProc
方法處理其他訊息。
WmClose
方法僅是模擬覆寫,實際上是透過攔截訊息進行處理。Form::WndProc
以確保基底行為正常運作。InvokeRequired
是 System::Windows::Forms::Control
的屬性,用來判斷目前執行緒是否為控制項所屬的 UI 執行緒。當從背景執行緒存取 UI 控件時,應使用 Invoke
將操作封送回 UI 執行緒。
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
ref class ImageHelper {
public:
static void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
if (pPictureBox == nullptr)
return;
if (pPictureBox->InvokeRequired) {
// 使用 MethodInvoker 呼叫同一函式,但於 UI 執行緒執行
pPictureBox->Invoke(
gcnew MethodInvoker(gcnew Action
InvokeRequired
只能用於繼承 Control
的物件(如 PictureBox)。true
,代表目前執行緒不是 UI 執行緒,必須用 Invoke
。Tuple
封裝多參數並傳入靜態代理函式處理。try-catch
捕捉例外避免程式崩潰。
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
void SetImageSafe(PictureBox^ pPictureBox, Bitmap^ b) {
if (pPictureBox->InvokeRequired) {
pPictureBox->Invoke(gcnew MethodInvoker(
gcnew EventHandler(nullptr, &SetImageInvoker)
), gcnew array
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;
void SafeAssignImage(PictureBox^ pPictureBox, Bitmap^ b) {
if (pPictureBox->InvokeRequired) {
pPictureBox->Invoke(gcnew MethodInvoker(gcnew delegate {
try {
if (b != nullptr) {
if (pPictureBox->Image != nullptr)
delete pPictureBox->Image;
pPictureBox->Image = b;
}
} catch (System::Exception^ ex) {
Console::WriteLine("設定圖片失敗: " + ex->Message);
}
}));
} else {
try {
if (b != nullptr) {
if (pPictureBox->Image != nullptr)
delete pPictureBox->Image;
pPictureBox->Image = b;
}
} catch (System::Exception^ ex) {
Console::WriteLine("設定圖片失敗: " + ex->Message);
}
}
}
gcnew MethodInvoker(...)
必須是合法的委派,不支援 C++11 lambda(如 [=]())語法。BeginInvoke
搭配 Object^
陣列並解析。Blazor 是由微軟推出的前端框架,允許開發者使用 C# 和 Razor 語法來建立互動式 Web 應用程式,無需使用 JavaScript,即可在瀏覽器中執行 C# 程式碼。
Blazor 無縫整合 ASP.NET Core,可與現有的 .NET 應用程式結合,並支援依賴注入、路由、元件架構等功能。
對於以 .NET 為主要技術的開發者而言,Blazor 提供了與 Node.js 統一 JavaScript 語言相對應的解決方案,使前後端都能使用 C#,建立一致性更高的開發體驗。
.NET MAUI(Multi-platform App UI)是微軟推出的跨平台應用框架,可使用 C# 和 XAML 撰寫一次程式碼,部署到 Windows、Android、iOS 甚至 macOS。
可使用 .NET MAUI 的 Blazor Hybrid 模式,在原生應用中嵌入 Web UI,讓開發者用 Razor 組件開發跨平台使用者介面,仍可存取原生 API。
.NET MAUI 是目前最完整的 .NET 跨平台解決方案,可結合 Blazor 達成 Web、桌面與行動平台的統一應用開發,提供給 .NET 開發者一個語言與技術一致的全端體驗。
.NET MAUI 可透過 API 呼叫與 ASP.NET Core 建立的後端服務整合,形成完整的「前端 App + 後端 API」架構。
ASP.NET Core 解決「後端服務」與「Web 應用」問題,.NET MAUI 則專注在「用戶端跨平台 App」。兩者並不互斥,反而常搭配使用,構成完整解決方案。
以下是一個使用 .NET MAUI 建立的跨平台應用程式範例,這個應用在畫面上顯示一個按鈕,點擊後會更新文字計數。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp.MainPage">
<VerticalStackLayout Spacing="25" Padding="30">
<Label x:Name="counterLabel"
Text="你尚未點擊按鈕"
FontSize="24"
HorizontalOptions="Center" />
<Button Text="點我"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentPage>
namespace MauiApp;
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage()
{
InitializeComponent();
}
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
counterLabel.Text = $"你已經點擊了 {count} 次";
}
}
這個簡單的範例展示了 .NET MAUI 的跨平台能力,開發者只需撰寫一次 XAML 與 C# 程式碼,即可同時在多個平台運作。
MainPage.xaml
:定義 UI 介面(使用 XAML)MainPage.xaml.cs
:後端程式碼(C#)處理事件MauiProgram.cs
:應用程式啟動與服務註冊點Platforms
資料夾:各平台的原生設定(Android、iOS、Windows、MacCatalyst)使用 Visual Studio 建立 .NET MAUI 專案只需幾個步驟,且能一套程式碼同時部署到多個平台,是現代 C# 全端開發者的理想選擇。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout Spacing="25" Padding="30"
VerticalOptions="Center">
<Label
Text="歡迎使用 .NET MAUI!"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />
<Button
x:Name="counterBtn"
Text="點我!"
Clicked="OnCounterClicked"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentPage>
using System;
namespace MyMauiApp;
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage()
{
InitializeComponent();
}
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
counterBtn.Text = $"你已經點了 {count} 次";
}
}
Spacing
(間距)與 Padding
(內距)。x:Name
以供 C# 後端存取,Clicked
屬性指定點擊事件處理方法。InitializeComponent()
:初始化 XAML 中定義的 UI 結構。OnCounterClicked
:點擊事件的處理方法,每點一次按鈕就更新按鈕文字。counterBtn.Text
:透過 XAML 中指定的名稱來控制該按鈕的屬性。執行後,畫面中央會顯示一個歡迎訊息與一個按鈕。每次點擊按鈕,按鈕上的文字就會更新為「你已經點了 X 次」。
using Microsoft.Extensions.Logging;
namespace MyMauiApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp() // 指定應用程式的進入點
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
MauiApp
實例。App.xaml.cs
中的 App 類別),這會是應用程式的根組件。builder.Services.AddSingleton<MainPage>();這樣可讓 MainPage 使用依賴注入方式載入,例如在 App 類中使用
serviceProvider.GetService<MainPage>()
。
builder.Services.AddTransient<MyViewModel>(); builder.Services.AddSingleton<IMyService, MyService>();
MauiProgram.cs
就像 ASP.NET Core 的 Startup.cs
或 Program.cs
,是應用啟動與服務註冊的主要設定中心。開發大型 MAUI 應用時,擴充此檔案來加入 DI、日誌、組件設定等是非常常見的做法。
多媒體是指同時使用多種媒介(如文字、圖像、音頻、視頻和動畫)來傳達信息和內容的技術。它提供了一種豐富的方式來呈現和交流信息,並在教育、娛樂和廣告等領域得到了廣泛應用。
隨著技術的進步,多媒體技術也在不斷演變。從早期的靜態圖像和音頻到現在的高解析度視頻和虛擬實境(VR),多媒體的表達方式變得越來越豐富和多樣。
多媒體不僅提高了信息傳遞的效率和趣味性,還為用戶創造了更為沉浸式的體驗。未來,隨著技術的進一步發展,多媒體將在更多領域中發揮更大的作用。
OpenCV (Open Source Computer Vision Library) 是一個開源的計算機視覺與機器學習軟件庫,用於即時影像處理與分析。
# 讀取影像並顯示 import cv2 image = cv2.imread("image.jpg") cv2.imshow("Image", image) cv2.waitKey(0) cv2.destroyAllWindows()
Halcon 是由 MVTec 公司開發的一款強大的工業視覺軟體,專為影像處理和機器視覺應用而設計。
Shotcut 是一款免費且開源的影片編輯軟體,支援多種格式且具有許多強大的編輯工具。其特色包括:
適用平台:Windows、Mac、Linux
OpenShot 是一款易於上手的開源影片編輯工具,功能強大且支持多種格式。其主要特點包括:
適用平台:Windows、Mac、Linux
Blender 是一款知名的開源 3D 建模和動畫軟體,內建功能強大的影片編輯器,適合進行影片剪輯和特效製作。其功能包括:
適用平台:Windows、Mac、Linux
Kdenlive 是 Linux 上廣泛使用的開源影片編輯軟體,也支援 Windows。其主要功能包括:
適用平台:Windows、Mac、Linux
Lightworks 提供免費和付費版本,免費版本具備基本編輯功能。其特色包括:
適用平台:Windows、Mac、Linux
以上這些開源影片編輯軟體提供了強大的功能,適合不同層次的影片編輯需求,從簡單的家庭影片剪輯到專業級的影片製作都可以滿足。
軟體名稱 | 大約搜尋量 |
---|---|
OpenShot | 110,000 |
Kdenlive | 90,500 |
Shotcut | 49,500 |
Avidemux | 18,100 |
Losslesscut | 14,800 |
Blender VSE | 10,000 |
Natron | 6,600 |
Cinelerra | 5,400 |
Pitivi | 3,600 |
LiVES | 1,600 |
OpenShot 是一款免費且開源的影片編輯器,專案名稱為 OpenShot/openshot-qt
,主要基於 Python
和 Qt
開發。該專案旨在提供一個易於使用且功能豐富的影片編輯工具,適合不同水平的使用者。
OpenShot 使用 PyQt
作為圖形用戶界面,並結合 libopenshot
(C++ 實現) 來處理影片編輯的核心邏輯。此外,OpenShot 還利用了
FFmpeg
來支援多種格式的解碼與編碼。
OpenShot 適用於需要簡單操作、但功能強大的影片編輯需求的用戶。無論是業餘影片創作者還是教育用途,OpenShot 都提供了靈活的工具和插件,便於進行剪輯和創作。
OpenShot 專案擁有活躍的開源社群,使用者和開發者可以透過 GitHub 貢獻程式碼、報告問題或提交新功能建議。歡迎所有人參與,以協助提升 OpenShot 的功能與穩定性。
使用者可以透過 GitHub 頁面下載源碼,或從 OpenShot 官方網站下載可執行檔。詳細安裝指引和說明文件也可在 GitHub 上找到。
ffmpeg
、ffprobe
、ffplay
ffmpeg -i input.avi output.mp4
ffmpeg -ss 00:00:10 -i input.mp4 -t 5 output.mp4
ffmpeg -i input.mp4 -q:a 0 -map a output.mp3
ffmpeg -i input.mp4 -vf subtitles=sub.srt output.mp4
Unity 是一個功能強大的遊戲開發引擎和平臺,專門設計用於創建 2D 和 3D 遊戲、互動應用程式及虛擬現實 (VR) 和增強現實 (AR) 體驗。它提供簡單易用的介面和豐富的工具,適合初學者和專業開發人員使用。
Unity 是一個強大且靈活的開發引擎,為開發者提供了廣泛的應用場景和工具支持。無論是初學者還是專業開發人員,都可以利用 Unity 快速創建高質量的 2D、3D 遊戲及互動應用。
週邊控制程式是用來與電腦或主機連接的外部硬體裝置(例如:印表機、掃描器、馬達、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
、while
func
定義,支援參數標籤與多返回值class
和 struct
Swift 不僅適用於 Apple 生態系統,還可以用於伺服器端開發與跨平台工具。
相關學習與參考資源:
Objective-C 是一種以 C 為基礎的物件導向程式語言,最初由 NeXT 公司開發,後來被 Apple 廣泛用於 macOS 和 iOS 應用程式開發。
Objective-C 的語法結合了 C 和 Smalltalk 的特性,使用 @ 符號來標示語言擴展。
@interface
和 @implementation
[object method]
@property
和 @synthesize
Objective-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
:合併分支GitHub 是一個基於雲端的版本控制和協作平台,主要用於軟體開發。它使用 Git 進行版本控制,讓開發者能夠管理專案的源代碼、跟蹤變更以及與他人協作。
GitHub 適合各類開發者,無論是單獨開發者、開源社區還是企業團隊。它能夠滿足小型專案到大型軟體專案的版本控制和協作需求。
git pull --rebase
git pull --rebase
是從遠端分支拉取最新變更,並將本地的更改重新應用到最新的遠端提交之上,而不是使用傳統的合併。
git pull --rebase
--rebase
?--rebase
可以讓提交歷史保持簡潔,沒有多餘的合併提交。假設你在本地提交了一些更改,而遠端也有新的提交,使用 git pull --rebase
會:
如果在重放本地提交時發生衝突,Git 會要求手動解決衝突:
git add
暫存已解決的文件。git rebase --continue
繼續重放提交。git rebase --abort
回到原狀。git pull --rebase
是保持 Git 提交歷史整潔的重要工具,特別適合多人協作時,能夠避免產生冗餘的合併提交,並保持代碼庫的提交記錄線性。
email: [email protected]