Dart 作為一種靜態型別語言,在編譯時期就進行型別檢查,有助於減少執行時錯誤。變數型別可以明確指定,例如使用 intdouble 等關鍵字,或讓編譯器自動推斷。型別推斷可以簡化程式碼,但開發者仍需瞭解型別轉換規則。Dart 提供了 toInt()toDouble() 等方法進行顯式型別轉換,避免精度損失或型別錯誤。算術運算時,Dart 會根據運算元型別自動選擇結果型別,通常會提升到更廣泛的型別,例如 intdouble 運算結果為 double。理解 Dart 的型別系統和轉換規則對於編寫穩健的程式碼至關重要,能有效避免常見的型別錯誤。字串在 Dart 中以 UTF-16 編碼儲存,開發者需要了解 Unicode 和 UTF-16 的特性,才能正確處理各種字元和字串操作。

Dart 中的運算子和變數

在 Dart 中,運算子是用來對變數和值進行操作的。例如,++-- 運算子可以用來遞增或遞減變數的值。

var counter = 0;
counter++; // 1
counter--; // 0

此外,*=/= 運算子可以用來對變數的值進行乘法和除法操作。

double myValue = 10;
myValue *= 3; // same as myValue = myValue * 3;
// myValue = 30.0;
myValue /= 2; // same as myValue = myValue / 2;
// myValue = 15.0;

需要注意的是,Dart 中的除法運算會傳回一個 double 值,所以變數的型別必須是 double

挑戰

現在,讓我們來做一些挑戰來測試你對變數和常數的瞭解。

挑戰 1:變數 Dogs

宣告一個 int 型別的變數 dogs,並將其設為你所擁有的狗的數量。然後,假設你買了一隻新的小狗,遞增 dogs 變數的值。

挑戰 2:使其編譯

給定以下程式碼:

age = 16;
print(age);
age = 30;
print(age);

修改第一行程式碼,使其能夠編譯。 你使用了 varintfinal 還是 const

挑戰 3:計算答案

考慮以下程式碼:

const x = 46;

內容解密:

在這些挑戰中,我們需要使用 Dart 的運算子和變數來解決問題。首先,讓我們來解決挑戰 1。宣告一個 int 型別的變數 dogs,並將其設為你所擁有的狗的數量。假設你買了一隻新的小狗,遞增 dogs 變數的值。

int dogs = 0; // 宣告一個 int 型別的變數 dogs
dogs++; // 遞增 dogs 變數的值
print(dogs); // 輸出:1

接下來,讓我們來解決挑戰 2。修改第一行程式碼,使其能夠編譯。 我們需要宣告一個變數 age,並將其設為 16

int age = 16; // 宣告一個 int 型別的變數 age
print(age); // 輸出:16
age = 30; // 將 age 變數的值設為 30
print(age); // 輸出:30

最後,讓我們來解決挑戰 3。考慮以下程式碼:

const x = 46;

這是一個常數 x,其值為 46。我們可以使用這個常數來計算其他值。

const x = 46;
int y = x * 2; // 計算 y 的值
print(y); // 輸出:92

圖表翻譯:

以下是挑戰 1 的流程圖:

  flowchart TD
    A[宣告變數 dogs] --> B[設定 dogs 的值]
    B --> C[遞增 dogs 的值]
    C --> D[輸出 dogs 的值]

以下是挑戰 2 的流程圖:

  flowchart TD
    A[宣告變數 age] --> B[設定 age 的值]
    B --> C[輸出 age 的值]
    C --> D[修改 age 的值]
    D --> E[輸出 age 的值]

以下是挑戰 3 的流程圖:

  flowchart TD
    A[宣告常數 x] --> B[計算 y 的值]
    B --> C[輸出 y 的值]

Dart 基礎知識:表示式、變數和常數

在 Dart 中,表示式是用於計算值的程式碼片段。它可以是單個值、運算式或函式呼叫。變數和常數是用於儲存和命名資料的。

基本運算子

Dart 支援以下基本運算子:

  • 加法:+
  • 減法:-
  • 乘法:*
  • 除法:/
  • 整數除法:~/
  • 模運算(餘數):%

常數和變數

常數是用 const 關鍵字宣告的,不能在執行時改變其值。變數可以使用 var 關鍵字宣告,如果其型別可以被推斷,則可以省略型別。

const y = 10;
var x = 5; // 可以省略型別

資料型別

Dart 是一種靜態型別語言,這意味著它在編譯時就會檢查變數的型別。

int x = 5; // 整數
double y = 10.5; // 浮點數

運算子和表示式

Dart 支援各種運算子,包括算術運算子、比較運算子和邏輯運算子。

const answer1 = (x * 100) + y;
const answer2 = (x * 100) + (y * 100);
const answer3 = (x * 100) + (y / 10);

挑戰:平均評分

宣告三個常數 rating1rating2rating3,並計算它們的平均值。

const rating1 = 4.5;
const rating2 = 4.8;
const rating3 = 4.2;
const averageRating = (rating1 + rating2 + rating3) / 3;

挑戰:二次方程

宣告三個常數 abc,並計算二次方程 ax^2 + bx + c = 0 的根。

const a = 1.0;
const b = -3.0;
const c = 2.0;
const root1 = (-b + sqrt(b * b - 4 * a * c)) / (2 * a);
const root2 = (-b - sqrt(b * b - 4 * a * c)) / (2 * a);

Dart 的函式

Dart 有許多內建函式,包括 minmaxsqrtsincos

import 'dart:math';

void main() {
  print(sqrt(16)); // 輸出:4.0
  print(sin(pi / 2)); // 輸出:1.0
}

程式碼注釋和檔案注釋

Dart 支援兩種型別的注釋:程式碼注釋和檔案注釋。程式碼注釋使用 ///* */ 來標記,檔案注釋使用 ////** */ 來標記。

// 這是一個程式碼注釋
/* 這是一個多行程式碼注釋 */

/// 這是一個檔案注釋
/**
 * 這是一個多行檔案注釋
 */

主要要點

  • 程式碼注釋使用 ///* */ 來標記。
  • 檔案注釋使用 ////** */ 來標記。
  • 可以使用 print 函式將輸出寫入除錯主控臺。
  • Dart 支援各種運算子,包括算術運算子、比較運算子和邏輯運算子。

Dart 中的資料型別

在 Dart 中,資料型別是告訴編譯器您如何使用某些資料的方式。資料型別有助於您分類所有在程式碼中使用的不同種類的資料。就像在現實生活中,名稱可以幫助您談論不同的事情,也可以幫助您認識何時某些東西不在正確的位置。

基本資料型別

Dart 中有幾種基本的資料型別,包括:

  • int:整數型別
  • double:浮點數型別
  • num:數值型別(包含整數和浮點數)
  • dynamic:動態型別
  • String:字串型別(用於文字資料)

自訂資料型別

除了基本資料型別外,Dart 還允許您建立自訂資料型別。例如,天氣應用程式可能需要一個 Weather 型別,而社交媒體應用程式可能需要一個 User 型別。您將在後面的章節中學習如何建立自訂資料型別。

物件型別

在 Dart 中,所有非可為 null 的資料型別都是 Object 型別的子類別。Object 型別定義了一些核心操作,例如測試相等性和描述自己。每個非可為 null 的資料型別都繼承了 Object 的基本功能。

型別推斷

Dart 中的型別推斷允許您在宣告變數時不需要明確指定資料型別。編譯器將根據變數的初始值推斷其資料型別。

明確宣告變數

您也可以在宣告變數時明確指定資料型別。這需要在變數名稱前新增資料型別。

int myInteger = 10;
double myDouble = 3.14;

宣告常數變數

如果您想要宣告一個不可變的變數,您可以在宣告變數時新增 constfinal 關鍵字。

const int myInteger = 10;
const double myDouble = 3.14;

或者

final int myInteger = 10;
final double myDouble = 3.14;

Dart 中的型別推斷和轉換

在 Dart 中,型別推斷是一個強大的功能,可以讓開發者不需要明確地指定變數或常數的型別。Dart 編譯器可以自動推斷出變數或常數的型別,從而減少程式碼的冗餘性。

讓編譯器推斷型別

以下是使用型別推斷的例子:

const myInteger = 10;
const myDouble = 3.14;

Dart 編譯器可以自動推斷出 myInteger 是一個 int,而 myDouble 是一個 double

檢查推斷型別

在 VS Code 中,可以使用滑鼠懸停在變數或常數上來檢查其推斷型別。例如,懸停在 myInteger 上會顯示其型別為 int

型別轉換

有時,需要將資料從一個型別轉換為另一個型別。以下是使用 is 關鍵字來檢查變數的型別的例子:

num myNumber = 3.14;
print(myNumber is double); // true
print(myNumber is int); // false

也可以使用 runtimeType 屬性來檢查變數的型別:

print(myNumber.runtimeType); // double

強制型別轉換

以下是使用強制型別轉換的例子:

int myInt = 10;
double myDouble = myInt.toDouble();
print(myDouble); // 10.0

在這個例子中,myInt 被轉換為 double 型別。

Dart 中的型別和運算

在 Dart 中,型別是非常重要的概念。Dart 是一種靜態型別語言,這意味著它在編譯時就會檢查型別是否正確。這有助於防止型別相關的錯誤和提高程式碼的可靠性。

型別轉換

在 Dart 中,型別轉換是明確的。也就是說,如果你想要將一個值從一個型別轉換到另一個型別,你需要明確地說明這一點。例如,假設你有一個整數變數 integer 和一個雙精度浮點數變數 decimal

var integer = 100;
var decimal = 12.5;

如果你試圖將 decimal 的值賦給 integer,Dart 會報錯:

integer = decimal; // A value of type 'double' can't be assigned to a variable of type 'int'.

這是因為 Dart 不允許隱式型別轉換。相反,你需要明確地使用 toInt() 方法將 decimal 轉換為整數:

integer = decimal.toInt();

混合型別的運算

當你使用混合型別的運算子(如 *)時,Dart 會根據運算元的型別自動選擇結果的型別。例如:

const hourlyRate = 19.5;
const hoursWorked = 10;

const totalCost = hourlyRate * hoursWorked;

在這個例子中,hourlyRate 是一個雙精度浮點數,而 hoursWorked 是一個整數。Dart 會將 totalCost 的型別設為雙精度浮點數,以避免精度損失。

如果你想要將結果轉換為整數,你需要明確地使用 toInt() 方法:

final totalCost = (hourlyRate * hoursWorked).toInt();

注意,toInt() 是一個執行時方法,所以你不能使用 const 關鍵字來宣告 totalCost。相反,你需要使用 final 關鍵字。

確保某個型別

有時你可能想要定義一個常數或變數,並確保它保持某個型別,即使賦給它的值是不同型別的。例如:

const wantADouble = 3;

在這個例子中,Dart 會推斷 wantADouble 的型別為整數。但是,如果你想要將常數設為雙精度浮點數,你可以使用 toDouble() 方法:

final actuallyDouble = 3.toDouble();

型別階層

Dart 中的型別階層如下:

  • Object
  • num(包括 intdouble
  • int
  • double
  • String

這個階層結構有助於你理解不同型別之間的關係和如何進行型別轉換。

Dart 中的型別轉換和運算

在 Dart 中,型別轉換是一種常見的操作,尤其是在處理不同型別的資料時。這節課將介紹 Dart 中的型別轉換,包括隱式轉換和顯式轉換。

隱式轉換

Dart 中的隱式轉換是指在指定時,系統會自動將一個型別的值轉換為另一個型別。例如:

int age = 30;
double averageAge = age; // 隱式轉換

在這個例子中,age 是一個 int 型別的變數,而 averageAge 是一個 double 型別的變數。當我們將 age 指定給 averageAge 時,Dart 會自動將 age 轉換為 double 型別。

顯式轉換

Dart 中的顯式轉換是指使用 as 關鍵字或 toDouble() 方法等來顯式地將一個型別的值轉換為另一個型別。例如:

num someNumber = 3;
final someInt = someNumber as int; // 顯式轉換
print(someInt.isEven); // true

在這個例子中,someNumber 是一個 num 型別的變數,而 someInt 是一個 int 型別的變數。使用 as 關鍵字,我們可以將 someNumber 轉換為 int 型別。

型別轉換的注意事項

在 Dart 中,型別轉換需要注意以下幾點:

  • 只能將超型別轉換為子型別,不能將子型別轉換為超型別。
  • 不能將一個型別轉換為其兄弟型別(例如,int 不能轉換為 double)。
  • 如果轉換失敗,會丟擲 _CastError 例外。

練習題

  1. 建立一個名為 age1 的常數,並將其設定為 42。建立另一個名為 age2 的常數,並將其設定為 21。檢查這兩個常數的型別是否正確地被推斷為 int
  2. 建立一個名為 averageAge 的常數,並將其設定為 age1age2 的平均值,使用 (age1 + age2) / 2 運算。將滑鼠懸停在 averageAge 上以檢查其型別。然後檢查 averageAge 的結果。為什麼它是一個 double,而其元件都是 int

內容解密:

在這個練習題中,我們需要建立兩個常數 age1age2,並將它們設定為 42 和 21。然後,我們需要建立另一個常數 averageAge,並將其設定為 age1age2 的平均值。

const int age1 = 42;
const int age2 = 21;
const double averageAge = (age1 + age2) / 2;

當我們將滑鼠懸停在 averageAge 上時,我們會發現其型別是 double。這是因為在 Dart 中,當我們使用 / 運算子時,系統會自動將結果轉換為 double,即使其元件都是 int

圖表翻譯:

  graph LR
    A[age1] --> B[age2]
    B --> C[(age1 + age2) / 2]
    C --> D[averageAge]
    D --> E[double]

這個圖表展示了 age1age2 如何被用來計算 averageAge,以及 averageAge 的型別如何被推斷為 double

物件和動態型別

Dart 語言是為瞭解決 JavaScript 中的一些問題而誕生的。JavaScript 是一種動態型別語言,這意味著變數的型別可以在執行時改變。以下是一個 JavaScript 的例子:

var myVariable = 42;
myVariable = "hello";

在 JavaScript 中,第一行是數字,第二行是字串。這種在執行時改變型別的做法是完全有效的,但它也很容易導致程式錯誤。例如,你可能錯誤地以為 myVariable 還是一個數字,所以你寫了以下程式碼:

var answer = myVariable * 3; // 執行時錯誤

哎呀!這是一個錯誤,因為 myVariable 其實是一個字串,電腦不知道如何處理 “hello” 乘以 3 的運算。這不僅是一個錯誤,你甚至在執行程式碼之前都不知道會發生錯誤。

Dart 語言可以完全避免這種錯誤,因為它是一種可選靜態型別語言。這意味著你可以選擇以動態型別或靜態型別的方式使用 Dart。如果你試圖在 Dart 中做以下事情:

var myVariable = 42;
myVariable = 'hello'; // 編譯時錯誤

Dart 編譯器會立即告訴你這是一個錯誤。這使得型別錯誤很容易被發現。

正如你在第 2 章 “表示式、變數和常數” 中所見,Dart 的創造者們確實包含了一個動態型別供那些希望以動態型別方式編寫程式的人使用。

dynamic myVariable = 42;
myVariable = 'hello'; // 沒問題

事實上,如果你使用 var 並且不初始化變數,這是預設行為:

var myVariable; //預設為 dynamic
myVariable = 42; // 沒問題
myVariable = 'hello'; // 沒問題

雖然 dynamic 是內建的,但它更像是對系統的讓步,而不是鼓勵使用它。你仍應該在你的程式碼中使用靜態型別,因為它可以防止你犯下愚蠢的錯誤。

如果你需要明確指出任何型別都是允許的,你應該考慮使用 Object? 型別。

Object? myVariable = 42;
myVariable = 'hello'; // 沒問題

在執行時,Object?dynamic 的行為幾乎相同。然而,當你明確將變數宣告為 Object? 時,你是在告訴每個人,你故意將變數泛化,並且如果他們想對變數進行任何具體操作,他們需要在執行時檢查其型別。使用 dynamic 則更像是說你不知道型別是什麼,你是在告訴別人,他們可以對這個變數做任何事情,但如果他們的程式碼崩潰了,那是他們自己的問題。

注意:你可能想知道 Object? 末尾的問號是什麼意思。 這意味著型別可以包括 null 值。你將在第 11 章 “可空性” 中學習更多關於可空性的知識。

現在,讓我們進行一些練習來加強我們對型別和運算的理解。

挑戰 1:老師的評分

你是一名老師,在你的課堂上,出勤率佔 20% 的成績,作業佔 30%,考試佔 50%。你的學生在出勤率上得了 90 分,在作業上得了 80 分,在考試上得了 94 分。計算她的成績為一個整數百分比,向下舍入。

挑戰 2:什麼型別?

什麼是 value 的型別?

const value = 10 / 2;

字串與Unicode

在Dart中,字串是一種基本的資料型別,代表著一系列的字元。字元可以是字母、數字、符號等。在計算機中,字元是以數字的形式儲存和處理的。

字元集

計算機使用字元集將字元對映到數字上。字元集是一種兩向對映關係,允許計算機將字元轉換為數字,也允許將數字轉換回字符。最常用的字元集是Unicode,它定義了幾乎所有計算機使用的字元對映。

Unicode

Unicode是一種國際標準,定義了字元的對映關係。它允許計算機之間使用相同的字元集,從而實作字元的正確傳輸和顯示。Unicode不僅支援拉丁字元,也支援世界各地的字元,包括簡體中文、繁體中文、日文、韓文等。

字串在Dart中的表示

在Dart中,字串可以使用引號(" “)或撇號(’ ‘)來表示。例如:

String str1 = "Hello, World!";
String str2 = 'Hello, World!';

Unicode字元在Dart中的表示

在Dart中,Unicode字元可以使用\u\x來表示。例如:

String str1 = "\u0041"; // A
String str2 = "\x41"; // A

Dart中的字串操作

Dart提供了許多字串操作方法,包括連線、分割、替換等。例如:

String str1 = "Hello, ";
String str2 = "World!";
String result = str1 + str2; // Hello, World!
內容解密:

在Dart中,字串可以使用引號或撇號來表示。Unicode字元可以使用\u\x來表示。Dart提供了許多字串操作方法,包括連線、分割、替換等。這些方法可以用於處理和操作字串。

圖表翻譯:

  graph LR
    A[字串] --> B[Unicode]
    B --> C[字元集]
    C --> D[字元對映]
    D --> E[數字]
    E --> F[計算機]
    F --> G[顯示]
    G --> H[字串操作]
    H --> I[連線]
    I --> J[分割]
    J --> K[替換]

這個圖表展示了字串、Unicode、字元集、字元對映、數字、計算機、顯示、字串操作等之間的關係。

字串處理與Unicode

在Dart中,字串是以UTF-16編碼儲存的。UTF-16是一種使用16位元的編碼方案,可以表示大部分常見的字元。但是,當需要表示超過65,536個字元時,UTF-16就需要使用特殊的編碼方式,稱為代理對(surrogate pair)。

從程式語言設計的演進來看,Dart 在變數和運算子的設計上,平衡了效能與型別安全的需求。透過型別推斷和可選的靜態型別,Dart 提供了兼顧開發效率和程式碼穩健性的解決方案。然而,開發者仍需留意型別轉換的規則和潛在的型別錯誤,尤其在處理 dynamic 型別和數值運算時,更需謹慎。 Dart 的字串以 UTF-16 編碼,能有效處理多語言文字,但也需要注意代理對的使用,避免在字串操作中產生錯誤。對於注重程式碼品質和長期維護性的專案,建議充分利用 Dart 的靜態型別特性,並遵循最佳實務,以降低程式碼出錯的風險。玄貓認為,Dart 的型別系統設計,在兼顧開發彈性與執行效能方面,展現了其語言設計的成熟度,值得更多開發者深入學習和應用。