flutter--Dart基礎語法(一)
一、前言
Flutter 是 Google 開源的 UI 工具包,幫助開發者通過一套程式碼庫高效構建多平臺精美應用,Flutter 開源、免費,擁有寬鬆的開源協議,支援移動、Web、桌面和嵌入式平臺。
Flutter是使用Dart語言開發的跨平臺移動UI框架,通過自建繪製引擎,能高效能、高保真地進行Android和IOS開發。Flutter採用Dart語言進行開發,而並非Java,Javascript這類熱門語言,這是Flutter團隊對當前熱門的10多種語言慎重評估後的選擇。因為Dart囊括了多數程式語言的優點,它更符合Flutter構建介面的方式。
本文主要就是簡單梳理一下Dart語言的一些基礎知識和語法。關於程式語言的基本語法無外乎那麼些內容,註釋、變數、資料型別、運算子、流程控制、函式、類、異常、檔案、非同步、常用庫等內容,相信大部分讀者都是有一定程式設計基礎的,所以本文就簡單地進行一個梳理,不做詳細的講解。大家也可以參考 Dart程式語言中文網。
二、Dart的基本語法
Dart基本語法是指編寫dart程式碼最基本的一些內容、規範,主要包括註釋、變數、資料型別和運算子等內容。
2.1 註釋
Dart 支援單行註釋、多行註釋和文件註釋。
- 單行註釋:單行註釋以
//
開始。 所有在//
和改行結尾之間的內容被編譯器忽略。void main() { // TODO: refactor into an AbstractLlamaGreetingFactory? print('Welcome to my Llama farm!'); }
- 多行註釋:多行註釋以
/*
開始, 以*/
/*
和*/
之間的內容被編譯器忽略 (不會忽略文件註釋)。 多行註釋可以巢狀。void main() { /* * This is a lot of work. Consider raising chickens. Llama larry = Llama(); larry.feed(); larry.exercise(); larry.clean(); */ }
- 文件註釋:文件註釋可以是多行註釋,也可以是單行註釋, 文件註釋以
///
或者/**
開始。 在連續行上使用///
[Food]
會成為一個連結, 指向 Food 類的 API 文件。/// A domesticated South American camelid (Lama glama). /// /// 自從西班牙時代以來, /// 安第斯文化就將駱駝當做肉食類和運輸類動物。 class Llama { String name; /// 餵養駱駝 [Food]. /// /// 典型的美洲駝每週吃一捆乾草。 void feed(Food food) { // ... } /// 使用 [activity] 訓練駱駝 /// [timeLimit] 分鐘。 void exercise(Activity activity, int timeLimit) { // ... } }
2.2 變數
任何儲存在變數中的都是一個 物件 , 並且所有的物件都是對應一個 類 的例項。 無論是數字,函式和 null
都是物件。所有物件繼承自Object 類。儘管 Dart 是強型別的,但是 Dart 可以推斷型別,所以型別註釋是可選的。 如果要明確說明不需要任何型別, 需要使用特殊型別 dynamic
。
2.2.1 建立變數
var name = 'Bob';
變數僅儲存物件引用,這裡的變數是 name
儲存了一個 String
型別的物件引用。 “Bob” 是這個 String
型別物件的值。
name
變數的型別被推斷為 String
。 但是也可以通過指定型別的方式,來改變變數型別。 如果物件不限定為單個型別,可以指定為 物件型別
或 動態型別
。
//指定為動態型別 dynamic name = 'Bob'; //顯示指定為字串型別 String name = 'Bob';
2.2.2 預設值
未初始化的變數預設值是 null
。即使變數是數字 型別預設值也是 null,因為在 Dart 中一切都是物件,數字型別 也不例外。
int lineCount; assert(lineCount == null); //結果為true
提示: 在生產環境程式碼中
assert()
函式會被忽略,不會被呼叫。 在開發過程中,assert(condition)
會在非true
的條件下丟擲異常。
2.3 常量 Final 和 Const
使用過程中從來不會被修改的值,我們成為常量,可以使用 final
或 const
, 而不是 var
或者其他型別。 Final 變數的值只能被設定一次; Const 變數在編譯時就已經固定 (Const 變數 是隱式 Final 的型別.) 。最高階 final 變數或類變數在第一次使用時被初始化。
提示: 例項變數可以是
final
型別但不能是const
型別。 必須在建構函式體執行之前初始化 final 例項變數 —— 在變數宣告中,引數建構函式中或建構函式的初始化列表中進行初始化。
2.2.1 常量的建立
final name = 'Bob'; // Without a type annotation final String nickname = 'Bobby'; //final 不能被修改: name = 'Alice'; // Error: 一個 final 變數只能被設定一次。
如果需要在編譯時就固定變數的值,可以使用 const
型別變數。 如果 Const 變數是類級別的,需要標記為 static const
。 在這些地方可以使用在編譯時就已經固定不變的值,字面量的數字和字串, 固定的變數,或者是用於計算的固定數字:
const bar = 1000000; // 壓力單位 (dynes/cm2) const double atm = 1.01325 * bar; // 標準氣壓 // Const 關鍵字不僅可以用於宣告常量變數。 還可以用來建立常量值,以及宣告建立常量值的建構函式。 任何變數都可以擁有常量值。 var foo = const []; final bar = const []; const baz = []; // 宣告 const 的初始化表示式中 const 可以被省略。 比如上面的 baz。 Equivalent to `const []` //Const 變數的值不可以修改: baz = [42]; // Error: 常量變數不能賦值修改。 //非 Final , 非 const 的變數是可以被修改的,即使這些變數 曾經引用過 const 值。 foo = [1, 2, 3]; // 曾經引用過 const [] 常量值。
2.2.2 final和const的相同點
1.宣告時必須要賦值
2.只能在初始化賦值一次,之後不能重新賦值
3.後面都不能接var關鍵字
4.型別宣告可以忽略,類似 var,可以根據初始化的值推斷出變數型別
2.2.3 final和const的區別
1、final變數的初始值可以在編譯時確定,也可以在執行時確定,cosnt變數的初始值只能是編譯時確定的值,比如當前時間
2.const變數的不可變性是巢狀的,final不是
const a = {'c': 1}; a['c'] = 2; // 執行結果 Unsupported operation: Cannot set value in unmodifiable Map
3.記憶體中的建立:相同的值,final變數會重複建立,const會引用同一份值
const a = {'c': 1}; const b = {'c': 1}; print(a == b);//true final c = {'c': 1}; final d = {'c': 1}; print(c == d);//false
2.4 資料型別
Dart 語言支援以下內建型別:
- Number:數值型別
- String:字串型別
- Boolean:布林型別
- List (也被稱為 Array):列表或陣列型別
- Map:字典型別
- Set:集合型別
- Rune (用於在字串中表示 Unicode 字元):
- Symbol:符號型別
這些型別都可以被初始化為字面量。 例如, 'this is a string'
是一個字串的字面量, true
是一個布林的字面量。因為在 Dart 所有的變數終究是一個物件(一個類的例項), 所以變數可以使用 構造涵數 進行初始化。 一些內建型別擁有自己的建構函式。 例如, 通過 Map()
來構造一個 map 變數。
2.4.1 Number
Dart 語言的 Number 有兩種型別:
- int:整數值不大於64位, 具體取決於平臺。 在 Dart VM 上, 值的範圍從 -263到 263 - 1. Dart 被編譯為 JavaScript 時,使用 JavaScript numbers, 值的範圍從 -253 到 253 - 1.
- double:64位(雙精度)浮點數,依據 IEEE 754 標準。
int
和 double
都是 num
. 的亞型別。 num 型別包括基本運算 +, -, /, 和 *, 以及 abs()
, ceil()
, 和 floor()
, 等函式方法。 (按位運算子,例如»,定義在 int 類中。) 如果 num 及其亞型別找不到你想要的方法, 嘗試查詢使用 dart:math 庫。
// 整數型別不包含小數點。 下面是定義整數型別字面量的例子: var x = 1; var hex = 0xDEADBEEF;
// 如果一個數字包含小數點,那麼就是小數型別。 下面是定義小數型別字面量的例子: var y = 1.1; var exponents = 1.42e5;
// 從 Dart 2.1 開始,必要的時候 int 字面量會自動轉換成 double 型別。 double z = 1; // 相當於 double z = 1.0. //版本提示: 在dart 2.1 之前,在 double 上下文中使用 int 字面量是錯誤的。 //以下是將字串轉換為數字的方法,反之亦然: // String -> int var one = int.parse('1'); assert(one == 1); // String -> double var onePointOne = double.parse('1.1'); assert(onePointOne == 1.1); // int -> String String oneAsString = 1.toString(); assert(oneAsString == '1'); // double -> String String piAsString = 3.14159.toStringAsFixed(2); assert(piAsString == '3.14');
//int 特有的傳統按位運算操作,移位(<<, >>),按位與(&)以及 按位或(|)。 例如: assert((3 << 1) == 6); // 0011 << 1 == 0110 assert((3 >> 1) == 1); // 0011 >> 1 == 0001 assert((3 | 4) == 7); // 0011 | 0100 == 0111
//數字型別字面量是編譯時常量。 在算術表示式中,只要參與計算的因子是編譯時常量, 那麼算術表示式的結果也是編譯時常量。 const msPerSecond = 1000; const secondsUntilRetry = 5; const msUntilRetry = secondsUntilRetry * msPerSecond;
2.4.2 String
Dart 字串是一組 UTF-16 單元序列。 字串通過單引號或者雙引號建立。
var s1 = 'Single quotes work well for string literals.'; var s2 = "Double quotes work just as well."; var s3 = 'It\'s easy to escape the string delimiter.'; var s4 = "It's even easier to use the other delimiter.";
字串可以通過 ${
expression
}
的方式內嵌表示式。 如果表示式是一個識別符號,則 {} 可以省略。 在 Dart 中通過呼叫就物件的 toString()
方法來得到物件相應的字串。
var s = 'string interpolation'; assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' + 'which is very handy.'); assert('That deserves all caps. ' + '${s.toUpperCase()} is very handy!' == 'That deserves all caps. ' + 'STRING INTERPOLATION is very handy!');
提示:
==
運算子用來測試兩個物件是否相等。 在字串中,如果兩個字串包含了相同的編碼序列,那麼這兩個字串相等。
此外,還有字串的拼接和多行字串等用法
// 用 + 運算子來把多個字串連線為一個,也可以把多個字面量字串寫在一起來實現字串連線: var s1 = 'String ' 'concatenation' " works even over line breaks."; assert(s1 == 'String concatenation works even over ' 'line breaks.'); var s2 = 'The + operator ' + 'works, as well.'; assert(s2 == 'The + operator works, as well.');
// 使用連續三個單引號或者三個雙引號實現多行字串物件的建立: var s1 = ''' You can create multi-line strings like this one. '''; var s2 = """This is also a multi-line string.""";
// 使用 r 字首,可以建立 “原始 raw” 字串: var s = r"In a raw string, even \n isn't special.";
**一個編譯時常量的字面量字串中,如果存在插值表示式,表示式內容也是編譯時常量, 那麼該字串依舊是編譯時常量。 插入的常量值型別可以是 null,數值,字串或布林值
// const 型別資料 const aConstNum = 0; const aConstBool = true; const aConstString = 'a constant string'; // 非 const 型別資料 var aNum = 0; var aBool = true; var aString = 'a string'; const aConstList = [1, 2, 3]; const validConstString = '$aConstNum $aConstBool $aConstString'; //const 型別資料 // const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 型別資料,error Not a constant expression.
2.4.3 Boolean
Dart 使用 bool
型別表示布林值。 Dart 只有字面量 true、
false
是布林型別, 這兩個物件都是編譯時常量。
Dart 的型別安全意味著不能使用 if (nonbooleanValue)
或者 assert (nonbooleanValue)
。 而是應該像下面這樣,明確的進行值檢查:
// 檢查空字串。 var fullName = ''; assert(fullName.isEmpty); // 檢查 0 值。 var hitPoints = 0; assert(hitPoints <= 0); // 檢查 null 值。 var unicorn; assert(unicorn == null); // 檢查 NaN 。 var iMeantToDoThis = 0 / 0; assert(iMeantToDoThis.isNaN);
2.4.4 List
幾乎每種程式語言中最常見的集合可能是 array 或有序的物件集合。 在 Dart 中的 Array 就是 List 物件, 通常稱之為 List 。 下面是一個 Dart List 的示例:
var list = [1, 2, 3];
提示: Dart 推斷 list
的型別為 List<int>
。 如果嘗試將非整數物件新增到此 List 中, 則分析器或執行時會引發錯誤。
Lists 的下標索引從 0 開始,第一個元素的索引是 0。 list.length - 1 是最後一個元素的索引。
var list = [1, 2, 3]; assert(list.length == 3); assert(list[1] == 2); list[1] = 1; assert(list[1] == 1); //在 List 字面量之前新增 const 關鍵字,可以定義 List 型別的編譯時常量 var constantList = const [1, 2, 3]; // constantList[1] = 1; // 取消註釋會引起錯誤。
List 型別包含了很多 List 的操作函式。 更多資訊參考 泛型 和 集合.
2.4.5 Set
在 Dart 中 Set 是一個元素唯一且無序的集合。 Dart 為 Set 提供了 Set 字面量和 Set 型別。
版本提示: 雖然 Set 型別 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。
下面是通過字面量建立 Set 的一個簡單示例:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
Note: Dart 推斷 halogens
型別為 Set<String>
。如果嘗試為它新增一個 錯誤型別的值,分析器或執行時會丟擲錯誤。
要建立一個空集,使用前面帶有型別引數的 {}
,或者將 {}
賦值給 Set
型別的變數:
var names = <String>{}; // Set<String> names = {}; // 這樣也是可以的。 // var names = {}; // 這樣會建立一個 Map ,而不是 Set 。
是 Set 還是 Map ? Map 字面量語法同 Set 字面量語法非常相似。 因為先有的 Map 字母量語法,所以 {}
預設是 Map
型別。 如果忘記在 {}
上註釋型別或賦值到一個未宣告型別的變數上, 那麼 Dart 會建立一個型別為 Map<dynamic, dynamic>
的物件。
// 使用 add() 或 addAll() 為已有的 Set 新增元素: var elements = <String>{}; elements.add('fluorine'); elements.addAll(halogens); // 使用 .length 來獲取 Set 中元素的個數: var elements = <String>{}; elements.add('fluorine'); elements.addAll(halogens); assert(elements.length == 5); // 在 Set 字面量前增加 const ,來建立一個編譯時 Set 常量: final constantSet = const { 'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine', }; // constantSet.add('helium'); // Uncommenting this causes an error.
更多關於 Set 的內容,參閱 Generic 及 Set。
2.4.6 Map
通常來說, Map 是用來關聯 keys 和 values 的物件。 keys 和 values 可以是任何型別的物件。在一個 Map 物件中一個 key 只能出現一次。 但是 value 可以出現多次。 Dart 中 Map 通過 Map 字面量 和 Map 型別來實現。下面是使用 Map 字面量的兩個簡單例子:
var gifts = { // Key: Value 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' }; var nobleGases = { 2: 'helium', 10: 'neon', 18: 'argon', };
提示: Dart 會將 gifts
的型別推斷為 Map<String, String>
, nobleGases
的型別推斷為 Map<int, String>
。 如果嘗試在上面的 map 中新增錯誤型別,那麼分析器或者執行時會引發錯誤。
以上 Map 物件也可以使用 Map 建構函式建立:
var gifts = Map(); gifts['first'] = 'partridge'; gifts['second'] = 'turtledoves'; gifts['fifth'] = 'golden rings'; var nobleGases = Map(); nobleGases[2] = 'helium'; nobleGases[10] = 'neon'; nobleGases[18] = 'argon';
提示: 這裡為什麼只有
Map()
,而不是使用new Map()
。 因為在 Dart 2 中,new
關鍵字是可選的。
// 新增 key-value 對到已有的 Map 中: var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; // Add a key-value pair // 從一個 Map 中獲取一個 value: var gifts = {'first': 'partridge'}; assert(gifts['first'] == 'partridge'); // 如果 Map 中不包含所要查詢的 key,那麼 Map 返回 null: var gifts = {'first': 'partridge'}; assert(gifts['fifth'] == null); // 使用 .length 函式獲取當前 Map 中的 key-value 對數量: var gifts = {'first': 'partridge'}; gifts['fourth'] = 'calling birds'; assert(gifts.length == 2); // 建立 Map 型別執行時常量,要在 Map 字面量前加上關鍵字 const。 final constantMap = const { 2: 'helium', 10: 'neon', 18: 'argon', }; // constantMap[2] = 'Helium'; // 取消註釋會引起錯誤。
更名多關於 Map 的內容,參考 Generics and Maps.
2.4.7 Rune
在 Dart 中, Rune 用來表示字串中的 UTF-32 編碼字元。
Unicode 定義了一個全球的書寫系統編碼, 系統中使用的所有字母,數字和符號都對應唯一的數值編碼。 由於 Dart 字串是一系列 UTF-16 編碼單元, 因此要在字串中表示32位 Unicode 值需要特殊語法支援。
表示 Unicode 編碼的常用方法是, \uXXXX
, 這裡 XXXX 是一個4位的16進位制數。 例如,心形符號 (♥) 是 \u2665
。 對於特殊的非 4 個數值的情況, 把編碼值放到大括號中即可。 例如,emoji 的笑臉 (�) 是 \u{1f600}
。
String 類有一些屬性可以獲得 rune 資料。 屬性 codeUnitAt
和 codeUnit
返回16位編碼資料。 屬性 runes
獲取字串中的 Rune 。
下面是示例演示了 Rune 、 16-bit code units、 和 32-bit code points 之間的關係。
main() { var clapping = '\u{1f44f}'; print(clapping); print(clapping.codeUnits); print(clapping.runes.toList()); Runes input = new Runes( '\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}'); print(new String.fromCharCodes(input)); }
提示: 謹慎使用 list 方式操作 Rune 。 這種方法很容易引發崩潰, 具體原因取決於特定的語言,字符集和操作。
2.4.8 Symbol
一個 Symbol 物件表示 Dart 程式中宣告的運算子或者識別符號。 你也許永遠都不需要使用 Symbol ,但要按名稱引用識別符號的 API 時, Symbol 就非常有用了。 因為程式碼壓縮後會改變識別符號的名稱,但不會改變識別符號的符號。 通過字面量 Symbol ,也就是識別符號前面新增一個 #
號,來獲取識別符號的 Symbol 。
#radix #bar
Symbol 字面量是編譯時常量。
2.5 運算子
下表是 Dart中定義的運算子,描述的運算子優先順序近似於Dart 解析器實際行為。
描述 | 運算子 |
---|---|
字首運算子 | expr++ expr-- () [] . ?. |
字尾運算子 | -expr !expr ~expr ++expr --expr |
倍數運算子 | * / % ~/ |
加減運算子 | + - |
移位運算子 | << >> >>> |
位與 | & |
位異或 | ^ |
位或 | | |
關係運算符和測試運算子 | >= > <= < as is is! |
相等判斷 | == != |
邏輯與 | && |
邏輯或 | || |
判空運算子 | ?? |
條件運算子 | expr1 ? expr2 : expr3 |
級聯運算子 | .. |
賦值運算子 | = *= /= += -= &= ^= etc. |
建立表示式的時候會用到運算子。 下面是一些運算子表示式的例項:
a++ a + b a = b a == b c ? a : b a is T
在 運算子表 中, 每一行的運算子優先順序,由上到下依次排列,第一行優先順序最高,最後一行優先順序最低。 例如 %
運算子優先順序高於 ==
, 而 ==
高於 &&
。 根據優先順序規則,那麼意味著以下兩行程式碼執行的方式相同:
// 括號可以提高可讀性。 if ((n % i == 0) && (d % i == 0)) ... // 可讀性差,但是是等效的。 if (n % i == 0 && d % i == 0) ...
警告: 對於有兩個運算元的運算子,運算子的功能由左邊的運算元決定。 例如, 如果有兩個運算元 Vector 和 Point, aVector + aPoint
使用的是 Vector 中定義的 + 運算子。
下面就對dart中的運算子進行常規意義的分類簡單列舉一下:
- 算術運算子:+、-、*、/、~/(整除,結果為整數)、%(求餘運算)、++(自增)、--(自減)。(++、--分別有字首和字尾兩種表達形式,大家在學習的過程中要注意他們之間的區別)
assert(2 + 3 == 5); assert(2 - 3 == -1); assert(2 * 3 == 6); assert(5 / 2 == 2.5); // 結果是雙浮點型 assert(5 ~/ 2 == 2); // 結果是整型 assert(5 % 2 == 1); // 餘數 assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
- 關係運算符:==、!=、>、<、>=、<=,都是常規符號,不做其他解釋,大家要注意的是關係運算符的表示式結果為boolean型別。
assert(2 == 2); assert(2 != 3); assert(3 > 2); assert(2 < 3); assert(3 >= 3); assert(2 <= 3);
- 邏輯運算子:&&(邏輯與,短路運算)、||(邏輯或,短路運算)、!。
//短路運算,即當左邊的表示式結果能確定最終結果時,右邊的表示式不再進行運算 var a = 10; var b = a > 9 || a++ > 10; //a>9成立,又是或運算,所以b的結果為true,右邊的 a++ > 10不會進行計算,所以a的值不會加1 print(a); //10
- 賦值運算子:=、+=、-=、*=、/=。。。等一系列的擴充套件賦值運算子
var a = 2; // 使用 = 複製 a *= 3; // 複製並做乘法運算: a = a * 3 assert(a == 6);
- 位運算子:&(按位與運算)、|(按位或運算)、^(按位異或運算)、<<(按位左移)、>>(按位右移)。(所有的位運算都是以二進位制形式進行的)
final value = 0x22; final bitmask = 0x0f; assert((value & bitmask) == 0x02); // AND assert((value & ~bitmask) == 0x20); // AND NOT assert((value | bitmask) == 0x2f); // OR assert((value ^ bitmask) == 0x2d); // XOR assert((value << 4) == 0x220); // Shift left assert((value >> 4) == 0x02); // Shift right
- 條件運算子:
- condition ? expr1 : expr2 如果條件為 true, 執行 expr1 (並返回它的值), 否則, 執行並返回 expr2 的值。
- expr1 ?? expr2 如果 expr1 是 non-null, 返回 expr1 的值; 否則, 執行並返回 expr2 的值。
- 級聯運算子(..):可以實現對同一個對像進行一系列的操作。 除了呼叫函式, 還可以訪問同一物件上的欄位屬性。 這通常可以節省建立臨時變數的步驟, 同時編寫出更流暢的程式碼。嚴格的來講, “兩個點” 的級聯語法不是一個運算子。 它只是一個 Dart 的特殊語法。
// 第一句呼叫函式 querySelector() , 返回獲取到的物件。 獲取的物件依次執行級聯運算子後面的程式碼, 程式碼執行後的返回值會被忽略。 querySelector('#confirm') // 獲取物件。 ..text = 'Confirm' // 呼叫成員變數。 ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!')); // 上面的程式碼等價於: var button = querySelector('#confirm'); button.text = 'Confirm'; button.classes.add('important'); button.onClick.listen((e) => window.alert('Confirmed!')); // 級聯運算子可以巢狀,例如: final addressBook = (AddressBookBuilder() ..name = 'jenny' ..email = '[email protected]' ..phone = (PhoneNumberBuilder() ..number = '415-555-0100' ..label = 'home') .build()) .build(); // 在返回物件的函式中謹慎使用級聯操作符。 例如,下面的程式碼是錯誤的: var sb = StringBuffer(); sb.write('foo') ..write('bar'); // Error: 'void' 沒喲定義 'write' 函式。 // sb.write() 函式呼叫返回 void, 不能在 void 物件上建立級聯操作。
- 型別判定運算子:as()、is(判定是否是指定型別或該型別子類的物件)、is! (跟is相反)。
- 使用
as
運算子將物件強制轉換為特定型別。 通常,可以認為是is
型別判定後,被判定物件呼叫函式的一種縮寫形式。 請考慮以下程式碼:
- 使用
if (emp is Person) { // 型別判斷 // emp.firstName = 'Bob'; // 下面這種寫法一般是沒問題的,進行型別強轉 (emp as Person).firstName = 'Bob'; }
&n