(一)Flutter學習之Dart變數和型別系統

Chiclaim發表於2019-07-23

前言

前些日子在公司嘗試著使用 Flutter 開發一些功能。要想寫好 Flutter 程式,首先 Dart 語言是基礎。

當然,在實際開發中,可能由於時間的關係,不用瞭解到 Dart 的方方面面才開始 Flutter 的開發,可以邊學邊用。

為了形成一個完整的體系,我想先系統的講解下 Dart 語言,然後在開始 Flutter 相關的介紹。

Dart 相對於 Java 來說有更強的變現力,甚至我覺得比 Kotlin 還要有表現力。Dart 不僅可以用來開發客戶端,還可以用來開發Web前端和後端開發。

所以呢,我覺得 Dart 還是一門值得學習的語言,下面就開始我們的 Dart 學習之旅吧

本文是 Dart 相關的第一篇,主要介紹 Dart 語言的變數的定義和型別體系

Hello World

按照慣例,學習一門語言都是從 Hello World 開始的。下面我們看下 Dart 的 Hello World 程式:

main(){

  var str = "Hello world!";

  print(str);
  
}
複製程式碼
  • 首先我們定義了頂級的 main 函式,這個程式的入口,如果沒有返回值可以省略函式的返回型別
  • 然後我們通過 var 關鍵字定義了一個字串,通過型別推導,str是一個字串型別的變數
  • 最後通過系統的 print 函式輸出 str 變數

變數的定義

上面我們提到了 Dart 是支援型別推導的,所以在定義變數的時候可以通過 var 關鍵字來定義

當然也可以顯示的指定 變數的型別,如:

// 通過 var 關鍵字定義變數,省略具體的型別
var str = "Hello world!";

// 顯式指定變數型別
String str = "Hello World!"
複製程式碼

在 Dart 中萬物皆物件,如 null、函式、數字 都是物件

所以我們的定義的變數如果沒有給初始值,那麼這個變數的預設是就是 null

int lineCount;
assert(lineCount == null);
複製程式碼

定義變數的時候,可以使用 final 關鍵字來修飾,如

final String name = "chiclaim";

name = "johnny"; // Error

複製程式碼

final定義的變數,在定義的時候就要初始化,並且只能賦值一次

定義變數的時候還可以使用 const 關鍵字來修飾:

const name = "chiclaim"

name = "johnny"; // Error

複製程式碼

const 關鍵字修飾變數,說明這個變數是 編譯時常量,如數字、字串字面量都是 編譯時常量

constfinal 區別是 final 表示這個變數只能被賦值一次,const 表示該變數不僅只能被賦值一次,並且該變數是一個常量

const 不僅可以修飾變數,還可以修飾變數的值,表示該值不能修改:

main(){

  // 通過 const 關鍵字修飾foo變數的值
  var foo = const [];
  // 由於 foo 的值被 const 修飾,所以不能修改裡面的值
  // 執行時錯誤,Unsupported operation: Cannot modify an unmodifiable list
  foo[0] = 0;
  // 可以賦值,因為 foo 變數沒有被 final 或 const 修飾
  foo = [1,2,3];
  // 由於foo變數被重新賦值,所以可以修改裡面的值
  foo[0] = 0;
  
  print(foo);

}
複製程式碼

Dart型別系統

Dart 型別系統如下所示:

  • numbers(數字如整型,浮點型等)
  • strings(字串)
  • booleans (布林值)
  • lists (集合框架和陣列)
  • sets (Set集合)
  • maps (Map集合)
  • runes (表達Unicode字元)
  • symbols (一般用不到)

numbers

不像 C/C++/Java 等語言,在 Dart 表示數字的型別只有兩個:int 和 double

  • int :表示沒有小數點的整型,最大值不大於64-bit(-2^63 ~ 2^63-1)底層依賴的系統最大值也不同,如果編譯後用於JavaScript,則最大值為 -2^53 ~ 2^53-1

  • double 表示雙精度的浮點型

int age = 17;

double weight = 65.5;

double waistline = 30; // 相當於 30.0

複製程式碼

字串和數字型別之間互轉:

// 字串轉化成整型
int num = int.parse("2");

// 字串轉化成浮點型
double pi = double.parse("3.14");

// 浮點型轉成字串
String piStr = 3.14.toString();

// 浮點型轉成用小數表示的字串(可以選擇保留幾位小數)
String piAsStringFix = 3.14159.toStringAsFixed(2);

複製程式碼

string 字串

字串的建立

一個 Dart 字串是個 UTF-16 碼元(code units)序列,可以使用單引號或雙引號來建立一個字串

// 雙引號建立一個字串字面量
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.";

複製程式碼

建立原生(raw)字串,在字串前面加上字首 r

// 一般 \n 是換行符,如果我們想把 \n 當做字串輸出時候,也可以使用建立原生字串的方式
var raw = r'In a raw string, not even \n gets special treatment.';
複製程式碼

我們還可以將表示式(${expression})嵌入字串中:

const name = "chiclaim";
// 將表示式${expression}嵌入字串中
var info = "My name is ${name}";
// 如果表示式是一個識別符號,可以省略花括號
var info2 = "My name is $name";
複製程式碼

字串的拼接:

// 1. 通過 + 號來拼接字串
var s2 = 'The + operator ' + 'works, as well.';

// 2. 毗鄰的字串字面量
var s1 = 'String '
    'concatenation'
    " works even over line breaks.";

// 3. 通過多行字串(使用三引號)
var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

複製程式碼

字串常用的函式

1. 字串查詢相關
  • contains(str) 字串中是否包含某個字串
  • startsWith(str) 字串是否以某個字串開頭
  • endsWith(str) 字串是否以某個字串結尾
  • indexOf(str) 字串在目標字串中的位置
// Check whether a string contains another string.
assert('Never odd or even'.contains('odd'));

// Does a string start with another string?
assert('Never odd or even'.startsWith('Never'));

// Does a string end with another string?
assert('Never odd or even'.endsWith('even'));

// Find the location of a string inside a string.
assert('Never odd or even'.indexOf('odd') == 6);
複製程式碼
2. 字串提取
  • substring(startIndex,endIndex) 字串擷取
  • split(Pattern) 字串分割
  • length 字串長度
  • [index] 通過索引獲取字串中UTF-16編碼單元
  • codeUnits 返回字串的UTF-16編碼單元的不可修改的List
// 字串擷取
assert('Never odd or even'.substring(6, 9) == 'odd');

// 使用正規表示式分割字串,返回字串集合
var parts = 'structured web apps'.split(' ');

// 通過索引獲取字串的UTF-16編碼單元
assert('Never odd or even'[0] == 'N');

// 迴圈列印字串中的字串
for (var char in 'hello'.split('')) {
    print(char);
}

// 獲取字串中的所有UTF-16碼元
var codeUnitList ='Never odd or even'.codeUnits.toList();

// N 的 ASCII 編碼的十進位制就是 78
assert(codeUnitList[0] == 78);
複製程式碼
3. 大小寫轉換
  • toUpperCase 將字串全部轉成大寫
  • toLowerCase 將字串全部轉成小寫
// 轉成大寫
assert('structured web apps'.toUpperCase() == 'STRUCTURED WEB APPS');

// 轉成小寫
assert('STRUCTURED WEB APPS'.toLowerCase() ==  'structured web apps');
複製程式碼
4. 字串的頭尾空格的移除和空字串
  • trim 去除頭和尾的空格
  • isEmpty 字串是否為空
  • isNotEmpty 字串是否不為空
// 去除頭尾的空格
print('  hello  '.trim() == 'hello');

// 字串是否為空
print(''.isEmpty);

// 空格不是空字串
print('  '.isNotEmpty);

// 空格trim後,就變成空字串
print('  '.trim().isNotEmpty);
複製程式碼
5. 字串部分替換
  • replaceAll

字串是不可變的,替換函式不是修改原字串,而是會產生一個新的字串

var greetingTemplate = 'Hello, NAME!';
var greeting =  greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');

// greetingTemplate didn't change.
assert(greeting != greetingTemplate);
複製程式碼
6. 字串構建

通過程式設計的方式生成一個字串,可以使用 StringBuffer 來實現,直到呼叫 StringBuffer.toString() 函式才會建立字串

var sb = StringBuffer();

// 通過 .. 實現鏈式呼叫
sb
  ..write('Use a StringBuffer for ')
  // 寫入一個字串列表,以空格分割
  ..writeAll(['efficient', 'string', 'creation'], ' ')
  ..write('.');

// 建立字串
var fullString = sb.toString();

assert(fullString ==
    'Use a StringBuffer for efficient string creation.');
複製程式碼

Boolean

在 Dart 中通過 bool 關鍵字來描述布林,布林有兩個值:true和false。 true 和 false 都是編譯器常量

bool isSuccess = true;
bool isFailed = false;
複製程式碼

List

List不僅用來表示List集合(有序的資料集合),也用來表示陣列

下面來看下如何建立一個List 字面量

var list = [1, 2, 3]; // 型別推匯出list是 List<int>

複製程式碼

介紹 const 關鍵字的時候,也用 const 修飾 List 的值 如:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // Error.
複製程式碼

Dart2.3 提供了展開操作符(... 和 ...?) 讓開發者更加方便的將多個值插入到集合中

var list = [1, 2, 3];
// 展開操作符(...) spread operator
var list2 = [0, ...list];


var list;
// 展開操作符(...?) null-aware spread operator
var list2 = [0, ...?list];

複製程式碼

Dart2.3 除了提供了展開操作符,還有 collection ifcollection for

// collection if
var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

// collection for
var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
複製程式碼

collection if/for 在開發Flutter的時候很有用,可以簡化很多程式碼

Set

Set 表示一個無序的集合,下面看下如何建立一個 Set 字面量

// halogens is Set<String>
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
複製程式碼

建立一個空 Set :

// 通過var和泛型推匯出Set型別
var names = <String>{};

// 顯式指定是Set型別
Set<String> names = {}; 

需要注意的是下面是建立了一個Map
var names = {}; 

複製程式碼

類似地,展開操作符collection if/for 也可以用到 Set 中

Map

Map 是一個 鍵值對 集合,下面來看下如何建立 Map 字面量:

// gifts is Map<String, String>
var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

// nobleGases is Map<int, String>
var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};
複製程式碼

需要注意的時候,Key只能出現一次,否則會被後面的替換掉

通過中括號 [] 來新增、修改、獲取Map中的元素:

var gifts = Map();
//  新增元素
gifts['first'] = 'partridge';

var gifts = {'first': 'partridge'};
// 獲取通過Key獲取Map中的元素
assert(gifts['first'] == 'partridge');

var gifts = {'first': 'partridge'};
// 修改Map中的元素
gifts['fourth'] = 'calling birds';

複製程式碼

類似地,展開操作符collection if/for 也可以用到 Set 中

關於 Dart 的 展開操作符 以及 collection if/for 更多內容可以檢視:

(二)Flutter學習之Dart展開操作符 和 Control Flow Collections

Rune

在 Dart 中,Rune型別是一個字串的UTF-32的碼點(code points),我們在介紹 String 型別的時候,提到了 code units

那麼在介紹 Rune 之前,我們先來看下 code points 和 code units 是什麼?

Unicode 編碼為世界上所有的字母、數字、符號定義了對應的數字來表示

  • 碼點:Unicode是屬於編碼字符集的範圍。它所做的事情就是將我們需要表示的字元表中的每個字元對映成一個數字,這個數字被稱為相應字元的碼點(code points)
  • 碼元:就是碼點多少個位元組能儲存,比如 UTF-8 碼元就是 8 bit 也就是 1 byte,UTF-16 碼元就是 2 byte

因為 Dart 字串是 UTF-16 碼元的序列,描述 32-bit Unitcode 的字串需要特殊的語法

通常地, Unicode使用 \uXXXX 描述碼點 ,這裡的 XXXX 是一個4位十六進位制的值,例如描述心形的符號使用 \u2665 表示

如果多於或者少於 4 個十六進位制,則需要加上花括號,例如笑的表情使用 \u{1f600}

String 類有幾個屬性可以提取字串的 rune 資訊 (例如屬性 runes),codeUnitAtcodeUnit 屬性返回 16 bit的 碼元(code units)

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));
}
複製程式碼

Object 和 dynamic

在 Dart 所有的類都是繼承自 Object,和Java類似。dynamic 和 Object 允許所有的值賦值給它:

dynamic name;
Object email;

main() {
  name = "chiclaim";
  email = "chiclaim@gmail.com";
}
複製程式碼

但是他們是兩個完全不同的概念。你甚至可以把 dynamic 不要當做一個型別看待,dynamic 顧名思義就是動態的意思,它只是告訴編譯器不要做型別檢查

dynamic name;
Object email;

main() {
  // 將 String 賦值給 name
  name = "chiclaim";
  // 列印 name 的長度
  print(name.length);
  
  // 將 int 賦值給name
  name = 1;
  
  // 編譯器並不會報錯(編譯時不做型別檢查),執行時才會報錯
  // NoSuchMethodError: Class 'int' has no instance getter 'length'.
  print(name.length);
  
  email = "chiclaim@gmail.com";
  // 編譯器報錯,因為 Object 沒有 length 屬性
  print(email.length);
}

複製程式碼

通過上面的程式碼示例,相信你對 dynamic 和 Object 的區別有了比較清楚的理解

Dart 在官網的最佳實踐中建議開發者,如果你想表達意思是任何物件都可以,使用 Object 代替 dynamic

如果你允許型別推導失敗,一般推到失敗,編譯器會預設給dynamic,但是建議顯式地寫上 dynamic ,因為程式碼的閱讀者不知道你是忘記了寫型別還是:

// 建議
dynamic mergeJson(dynamic original, dynamic changes){
}

// 不建議
mergeJson(original, changes){
}

複製程式碼

Dart幾個重要的概念

  • 萬物接物件:null、函式、數字
  • Dart有型別推導功能,定義變數是可以不指定變數型別,如果不想指定型別,可以使用 dynamic 型別
  • Dart支援泛型,如 List<Int>、List<dynamic>
  • Dart支援 top-level 函式,也支援成員函式和靜態函式,同時也支援函式的巢狀
  • Dart沒有 public、private、protected 關鍵字來控制訪問許可權,Dart 通過下劃線來控制訪問許可權,如果以下劃線開頭,則表示只能在 library 內可見

Reference

關於 Dart 變數和型別系統 就講到這裡, 更多的關於 Android 學習資料可以檢視我的GitHub: github.com/chiclaim/An…

dart.dev/guides/lang… github.com/acmerfight/…


聯絡我

下面是我的公眾號,乾貨文章不錯過,有需要的可以關注下,有任何問題可以聯絡我:

公眾號:  chiclaim

相關文章