Dart 的字串處理能力對於開發者來說至關重要,尤其在處理多語言文字和特殊符號時,理解 Unicode 的編碼機制以及 Dart 如何處理這些字元變得尤為重要。代理對和 Rune 的概念能幫助我們理解 Dart 如何表示超出基本多文種平面(BMP)的 Unicode 字元。更進一步,圖形叢集的概念則解決了多個 Unicode 字元組成單一顯示字元的問題,這在處理表情符號、旗幟等複雜字元時至關重要。使用 characters 套件能確保我們準確計算這些字元的數量,避免因為 UTF-16 編碼單元和實際顯示字元數量不一致而導致的錯誤。

代理對與Unicode碼點

代理對是一種使用兩個16位元的編碼單元來表示一個Unicode碼點的方法。例如,Unicode碼點127919可以使用代理對(55356, 57263)來表示。

const dart = ' ';
// [55356, 57263]

Rune與Unicode碼點

Dart提供了一種更方便的方式來處理Unicode碼點,稱為Rune。Rune是一個可以直接表示Unicode碼點的物件。

const dart = ' ';
print(dart.runes); // (127919)

Unicode圖形叢集

但是,Unicode圖形叢集(Grapheme Cluster)是一個更複雜的問題。圖形叢集是指多個Unicode碼點組合成一個單一的字元。例如,蒙古國旗可以使用兩個Unicode碼點(127474, 127475)來表示。

const flag = ' ';
print(flag.runes); // (127474, 127475)

家庭圖示的圖形叢集

另一個例子是家庭圖示,可以使用七個Unicode碼點(128104, 8205, 128105, 8205, 128103, 8205, 128102)來表示。

const family = ' ';
print(family.runes); // (128104, 8205, 128105, 8205, 128103, 8205, 128102)

字串長度與圖形叢集

計算字串長度時,需要考慮圖形叢集。使用length屬性可以得到UTF-16編碼單元的數量,但這可能不是預期的結果。

const family = ' ';
family.length; // 11
family.runes.length; // 7

使用Characters套件處理圖形叢集

Dart提供了一個名為characters的套件,可以用來處理圖形叢集。首先,需要在pubspec.yaml檔案中新增套件依賴。

dependencies:
  characters: ^1.2.1

然後,需要匯入套件並使用其功能。

import 'package:characters/characters.dart';

圖形叢集的應用

使用characters套件,可以正確地處理圖形叢集,並得到預期的結果。

const family = ' ';
print(family.characters.length); // 1

字串基礎

在 Dart 中,字串是一種基本的資料型別,使用雙引號或單引號來定義。下面是一個簡單的字串範例:

const family = '👪';
print(family.characters.length); // 1

這個範例使用 characters 屬性來取得字串的長度,結果為 1,因為 👪 是一個單一的 Unicode 字元。

單引號與雙引號

Dart 允許使用單引號或雙引號來定義字串,兩者都可以使用:

const str1 = 'Hello';
const str2 = "Hello";

雖然 Dart 沒有強制規定使用哪一種引號,但 Flutter 的風格建議使用單引號,因此本文也會遵循這個慣例。

連線字串

連線字串是指將兩個或多個字串合併成一個新的字串。在 Dart 中,可以使用 + 運運算元來連線字串:

const str1 = 'Hello';
const str2 = ' World';
const result = str1 + str2; // 'Hello World'

也可以使用 ${} 來插入變數:

const name = 'Ray';
const introduction = 'Hello, my name is $name'; // 'Hello, my name is Ray'

多行字串

Dart 支援多行字串,可以使用三個單引號或三個雙引號來定義:

const bigString = '''
You can have a string
that contains multiple
lines
by using triple quotes.
''';
print(bigString);

這個範例會輸出一個多行字串。

練習

  1. 建立兩個字串常數 firstNamelastName,分別初始化為您的名字和姓氏。
  2. 使用插值建立一個字串常數 fullName,包含 firstNamelastName,中間用空格分隔。
  3. 使用插值建立一個字串常數 myDetails,包含 fullName,以介紹自己。例如: “Hello, my name is Ray Wenderlich.”

內容解密:

  • characters 屬性可以取得字串的長度。
  • 使用 + 運運算元可以連線字串。
  • 使用 ${} 來插入變數。
  • 三個單引號或三個雙引號可以定義多行字串。

圖表翻譯:

  graph LR
    A[字串] --> B[連線]
    B --> C[插值]
    C --> D[多行字串]
    D --> E[輸出]

這個圖表展示了字串的基本操作,包括連線、插值和多行字串。

字串基礎

在 Dart 中,字串可以使用單引號或雙引號來定義。另外,還有一種多行字串的定義方式,使用三個單引號或三個雙引號。

// 單行字串
String singleLine = 'Hello, World!';

// 多行字串
String multiLine = '''這是一個多行字串
可以包含多行文字
並保留換行符''';

print(multiLine);

字串連線

Dart 提供了多種方式來連線字串。最簡單的方式是使用 + 運算子。

String hello = 'Hello, ';
String world = 'World!';

String result = hello + world;
print(result); // 輸出:Hello, World!

原始字串

如果你想要忽略字串中的特殊字元,可以使用原始字串。原始字串使用 r 字首來定義。

String rawString = r'My name \n is $name.';
print(rawString); // 輸出:My name \n is $name.

Unicode 字元

你可以使用 Unicode 程式碼來插入特定的字元。使用 \u 後面跟著四個十六進位制數字即可。

print('I \u2764 Dart\u0021'); // 輸出:

對於超過 FFFF 的 Unicode 程式碼,需要使用大括號 {} 包圍。

內容解密:

上述程式碼展示瞭如何在 Dart 中使用字串。首先,我們定義了一個單行字串和一個多行字串,然後展示瞭如何使用 + 運算子來連線字串。接下來,我們介紹了原始字串的概念,使用 r 字首來忽略特殊字元。最後,我們展示瞭如何使用 Unicode 程式碼來插入特定的字元。

圖表翻譯:

  graph LR
    A[字串定義] --> B[單行字串]
    A --> C[多行字串]
    B --> D[使用 + 連線]
    C --> E[原始字串]
    E --> F[忽略特殊字元]
    D --> G[Unicode 字元]
    G --> H[插入特定字元]

此圖表展示了 Dart 中字串的不同定義方式和操作,包括單行字串、多行字串、連線、原始字串和 Unicode 字元的使用。

控制流和布林值

在計算機程式設計中,控制流是指程式在不同情況下執行不同的動作。例如,一個計算器程式在使用者按下加號按鈕時執行加法運算,在按下減號按鈕時執行減法運算。

布林值

布林值(Boolean)是一種資料型別,僅能夠儲存兩種狀態:true(真)和false(假)。這種資料型別非常適合用於儲存是或否的答案。

bool isOpen = true;
bool isFlying = false;

在 Dart 中,布林值可以用於控制程式的流程。例如,當某個條件為 true 時,執行某段程式碼;當某個條件為 false 時,執行另一段程式碼。

比較運算

比較運算是用於比較兩個值是否相等或不相等。Dart 中的比較運算子包括:

  • ==:相等
  • !=:不相等
  • >:大於
  • <:小於
  • >=:大於或相等
  • <=:小於或相等
bool isEqual = 5 == 5; // true
bool isNotEqual = 5 != 3; // true
bool isGreater = 5 > 3; // true
bool isLess = 5 < 3; // false

邏輯運算

邏輯運算是用於組合多個條件。Dart 中的邏輯運算子包括:

  • &&:與(and)
  • ||:或(or)
  • !:非(not)
bool isValid = 5 > 3 && 5 < 10; // true
bool isInvalid = 5 > 3 || 5 < 0; // true
bool isNotValid = !(5 > 3); // false

控制流程

控制流程是用於控制程式的執行順序。Dart 中的控制流程語句包括:

  • if:如果條件為真,則執行某段程式碼
  • else:如果條件為假,則執行某段程式碼
  • switch:根據某個值執行不同的程式碼
int score = 85;
if (score >= 90) {
  print('優');
} else if (score >= 80) {
  print('良');
} else {
  print('差');
}

內容解密:

上述程式碼使用 ifelse 語句來控制程式的流程。如果 score 大於或等於 90,則印出「優」;如果 score 大於或等於 80,但小於 90,則印出「良」;否則印出「差」。

圖表翻譯:

  graph LR
    A[開始] --> B{score >= 90}
    B -->|true| C[印出「優」]
    B -->|false| D{score >= 80}
    D -->|true| E[印出「良」]
    D -->|false| F[印出「差」]

上述圖表展示了程式的控制流程。根據 score 的值,程式會執行不同的動作。

布林值與比較運運算元

布林值(Boolean)是一種基本的資料型態,代表真或假的邏輯值。在 Dart 中,你可以使用 truefalse 關鍵字來設定布林值的狀態。例如:

const bool yes = true;
const bool no = false;

由於 Dart 的型別推斷,你也可以省略型別注釋:

const yes = true;
const no = false;

比較運運算元

布林值常用於比較值。例如,你可能有兩個值,想要知道它們是否相等。這時候,你可以使用比較運運算元來進行比較。

測試相等

你可以使用相等運運算元 == 來測試兩個值是否相等。例如:

const doesOneEqualTwo = (1 == 2);

Dart 會推斷 doesOneEqualTwo 是一個布林值。由於 1 不等於 2,所以 doesOneEqualTwo 會是 false。你可以使用 print 函式來確認結果:

print(doesOneEqualTwo);

測試不相等

你也可以使用不相等運運算元 != 來測試兩個值是否不相等。例如:

const doesOneNotEqualTwo = (1 != 2);

這次,比較的結果是 true,因為 1 不等於 2。

邏輯運運算元

邏輯運運算元 ! 可以用來反轉布林值。例如:

const alsoTrue = !(1 == 2);

由於 1 不等於 2,所以 (1 == 2)false,然後 ! 運運算元將其反轉為 true

測試大於和小於

你可以使用大於運運算元 > 和小於運運算元 < 來比較兩個值。例如:

const isOneGreaterThanTwo = (1 > 2);
const isOneLessThanTwo = (1 < 2);

這些運運算元可以用來比較數值,並傳回布林值結果。

內容解密:

以上程式碼示範瞭如何在 Dart 中使用布林值和比較運運算元。比較運運算元可以用來測試兩個值是否相等、不相等、大於或小於。邏輯運運算元 ! 可以用來反轉布林值。這些運運算元是程式設計中非常重要的工具,可以用來控制程式的流程和邏輯。

圖表翻譯:

  flowchart TD
    A[開始] --> B[設定布林值]
    B --> C[比較運運算元]
    C --> D[測試相等]
    D --> E[測試不相等]
    E --> F[邏輯運運算元]
    F --> G[測試大於和小於]
    G --> H[傳回布林值結果]

此圖表示了布林值和比較運運算元的流程,從設定布林值開始,到比較運運算元、測試相等、測試不相等、邏輯運運算元,最後傳回布林值結果。

條件控制與邏輯運算

在程式設計中,條件控制和邏輯運算是非常重要的概念。它們允許我們根據不同的條件執行不同的動作或做出不同的決定。

條件運運算元

條件運運算元用於比較兩個值之間的關係。常見的條件運運算元包括:

  • ==:等於
  • !=:不等於
  • >:大於
  • <:小於
  • >=:大於或等於
  • <=:小於或等於

例如:

print(1 == 2); // false
print(1 != 2); // true
print(1 > 2); // false
print(1 < 2); // true
print(1 >= 2); // false
print(1 <= 2); // true

邏輯運算

邏輯運算用於組合多個條件。常見的邏輯運運算元包括:

  • &&:與(AND)
  • ||:或(OR)

與(AND)

與運運算元用於檢查兩個條件是否都為真。如果兩個條件都為真,則結果為真;否則,結果為假。

例如:

const isSunny = true;
const isFinished = true;
const willGoCycling = isSunny && isFinished;
print(willGoCycling); // true

或(OR)

或運運算元用於檢查兩個條件是否至少有一個為真。如果至少有一個條件為真,則結果為真;否則,結果為假。

例如:

const willTravelToAustralia = true;
const canFindPhoto = false;
const canDrawPlatypus = willTravelToAustralia || canFindPhoto;
print(canDrawPlatypus); // true

運運算元優先順序

當多個條件和邏輯運運算元一起使用時,需要注意運運算元優先順序。一般來說,條件運運算元優先於邏輯運運算元。

例如:

const andTrue = 1 < 2 && 4 > 3;
const andFalse = 1 < 2 && 3 > 4;
const orTrue = 1 < 2 || 3 > 4;
const orFalse = 1 == 2 || 3 == 4;

複雜條件

可以使用多個條件和邏輯運運算元組合成複雜條件。

例如:

const complexCondition = 3 > 4 && 1 < 2 || 1 < 4;

這個複雜條件可以分解成以下步驟:

  1. 3 > 4:假
  2. 1 < 2:真
  3. 3 > 4 && 1 < 2:假(因為 3 > 4 為假)
  4. 1 < 4:真
  5. 假 || 真:真

因此,complexCondition 的結果為真。

條件判斷與邏輯運算

在 Dart 中,條件判斷和邏輯運算是控制程式流程的重要工具。讓我們來看看如何使用這些工具。

邏輯運算

Dart 中的邏輯運算包括 && (與)、|| (或) 和 ! (非)。這些運算可以用來組合條件判斷。

例如,下面的程式碼使用 &&|| 來判斷一個布林值:

bool result = false && true || true;
print(result); // true

在這個例子中,false && true 先被評估為 false,然後 false || true 被評估為 true

運算優先順序

Dart 中的運算優先順序是指運算的執行順序。當多個運算同時出現時,優先順序最高的運算先被執行。以下是 Dart 中的運算優先順序列表:

  • ! (非)
  • && (與)
  • || (或)

使用括號可以覆蓋運算優先順序。例如:

bool result = (3 > 4 && 1 < 2) || 1 < 4;
print(result); // true

在這個例子中,括號內的運算先被執行,然後結果被用來評估外面的運算。

字串比較

Dart 中可以使用 == 運算子來比較兩個字串是否相等。例如:

const guess = 'dog';
const guessEqualsCat = guess == 'cat';
print(guessEqualsCat); // false

在這個例子中,guessEqualsCat 是一個布林值,表示 guess 是否等於 'cat'

練習

  1. 建立一個名為 myAge 的常數,設定為你的年齡。然後,建立一個名為 isTeenager 的常數,使用布林邏輯來判斷你的年齡是否在 13 到 19 之間。
  2. 建立一個名為 maryAge 的常數,設定為 30。然後,建立一個名為 bothTeenagers 的常數,使用布林邏輯來判斷你和 Mary 是否都是青少年。
  3. 建立一個名為 reader 的字串常數,設定為你的名字。建立另一個名為 ray 的字串常數,設定為 'Ray Wenderlich'。建立一個名為 rayIsReader 的布林常數,使用字串比較來判斷 reader 是否等於 ray

If 陳述式

If 陳述式是控制程式流程的第一種方式。它允許程式在某個條件為真時執行某個動作。例如:

if (2 > 1) {
  print('Yes, 2 is greater than 1.');
}

這是一個簡單的 If 陳述式。條件是布林表示式,總是會被評估為真或假。如果條件為真,程式會執行 If 陳述式內的程式碼。

條件判斷的基礎

在 Dart 中,if 陳述式是一種基本的控制結構,允許您根據特定條件執行不同的程式碼。基本語法如下:

if (條件) {
  // 條件成立時執行的程式碼
}

這裡,條件 是一個布林值(Boolean),可以是 truefalse。如果條件為 true,則執行大括號內的程式碼。

Else 子句

您可以使用 else 子句來指定當條件不成立時要執行的程式碼。語法如下:

if (條件) {
  // 條件成立時執行的程式碼
} else {
  // 條件不成立時執行的程式碼
}

例如:

const animal = 'Fox';
if (animal == 'Cat' || animal == 'Dog') {
  print('Animal is a house pet.');
} else {
  print('Animal is not a house pet.');
}

在這個例子中,如果 animal 等於 'Cat''Dog',則執行第一個程式碼塊;否則,執行 else 子句中的程式碼。

Else-If 鏈

您可以使用 else if 來檢查多個條件。語法如下:

if (條件1) {
  // 條件1成立時執行的程式碼
} else if (條件2) {
  // 條件1不成立,但條件2成立時執行的程式碼
} else {
  // 條件1和條件2都不成立時執行的程式碼
}

例如:

const trafficLight = 'yellow';
if (trafficLight == 'red') {
  print('Stop');
} else if (trafficLight == 'yellow') {
  print('Slow down');
} else if (trafficLight == 'green') {
  print('Go');
} else {
  print('INVALID COLOR!');
}

在這個例子中,程式會先檢查 trafficLight 是否等於 'red',如果不是,則檢查是否等於 'yellow',如果還不是,則檢查是否等於 'green'。如果都不是,則執行 else 子句中的程式碼。

變數範圍

if 陳述式還引入了一個新的概念:範圍(Scope)。範圍是指變數可以被存取的範圍。在 Dart 中,範圍是使用大括號 {} 來定義的。如果您在大括號內定義了一個變數,則該變數只能在該大括號內使用。

void main() {
  const local = 'Hello, main';
  if (2 > 1) {
    const insideIf = 'Hello, anybody?';
    print(local);
    print(insideIf);
  }
}

在這個例子中,localinsideIf 只能在 main 函式內使用。如果您試圖在 main 函式外使用它們,將會出現錯誤。

控制流程

控制流程是指程式執行的順序和流程。Dart 中的控制流程可以透過 if 陳述式、switch 陳述式和迴圈來實作。

變數範圍

在 Dart 中,變數的範圍是指變數可以被存取的區域。變數可以被定義在不同的範圍中,包括全域、區域和塊範圍。

  • 全域變數:定義在函式外部的變數,對於整個程式都是可見的。
  • 區域變數:定義在函式內部的變數,只能在函式內部被存取。
  • 塊範圍變數:定義在塊內部的變數,只能在塊內部被存取。

三元運運算元

三元運運算元是一種簡單的 if-else 陳述式,可以用來簡化程式碼。三元運運算元的語法如下:

(condition) ? valueIfTrue : valueIfFalse;

三元運運算元可以用來替代簡單的 if-else 陳述式,例如:

const score = 83;
String message;

if (score >= 60) {
  message = 'You passed';
} else {
  message = 'You failed';
}

可以簡化為:

const score = 83;
const message = (score >= 60) ? 'You passed' : 'You failed';

Switch 陳述式

Switch 陳述式是一種用來處理多個條件的控制流程陳述式。Switch 陳述式的語法如下:

switch (variable) {
  case value1:
    // code
    break;
  case value2:
    // code
    break;
  ...
  default:
    // code
}

Switch 陳述式可以用來替代多個 if-else 陳述式,例如:

int day = 2;
String dayName;

if (day == 1) {
  dayName = 'Monday';
} else if (day == 2) {
  dayName = 'Tuesday';
} else if (day == 3) {
  dayName = 'Wednesday';
} else {
  dayName = 'Unknown';
}

可以簡化為:

int day = 2;
String dayName;

switch (day) {
  case 1:
    dayName = 'Monday';
    break;
  case 2:
    dayName = 'Tuesday';
    break;
  case 3:
    dayName = 'Wednesday';
    break;
  default:
    dayName = 'Unknown';
}

練習

  1. 建立一個名為 myAge 的常數,並初始化它為你的年齡。寫一個 if 陳述式,若你的年齡在 13 到 19 之間,則輸出 “Teenager”,否則輸出 “Not a teenager”。
  2. 使用三元運運算元替代 if-else 陳述式,並將結果設為一個名為 answer 的變數。

從使用者體驗的最佳化角度,Dart 字串處理的機制,特別是圖形叢集的處理方式,對開發者和終端使用者都產生了實質影響。深入剖析 Dart 的字串表示方式,可以發現其從底層的 UTF-16 編碼到高階的 characters 套件,都體現了對 Unicode 正確性和使用者體驗的重視。分析 Dart 如何處理代理對、Rune 和圖形叢集,以及 characters 套件如何簡化字串長度計算,可以看出 Dart 致力於提供更直觀、更符合 Unicode 標準的字串操作方式。然而,開發者仍需注意 UTF-16 編碼單元和圖形叢集之間的差異,避免在字串操作中產生錯誤。對於需要處理複雜 Unicode 字元的應用程式,characters 套件的應用至關重要。玄貓認為,Dart 的字串處理機制,特別是 characters 套件的引入,代表了程式語言在處理 Unicode 字串方面的最佳實務,值得其他語言借鑒。隨著 Unicode 標準的持續演進,我們預見 Dart 也將持續最佳化其字串處理機制,以提供更便捷、更強大的功能。