Dart基本語法二
一. 運算符
1.1. 除法、整除、取模運算
var num = 7;
print(num / 3); // 除法操作, 結果2.3333..
print(num ~/ 3); // 整除操作, 結果2;
print(num % 3); // 取模操作, 結果1;
1.2. ??= 賦值操作
- dart有一個很多語言都不具備的賦值運算符:
- 當變量爲null時,使用後面的內容進行賦值。
- 當變量有值時,使用自己原來的值。
1.3. 條件運算符:
- Dart中包含一直比較特殊的條件運算符:
expr1 ?? expr2
- 如果expr1是null,則返回expr2的結果;
- 如果expr1不是null,直接使用expr1的結果。
1.4. 級聯語法:…
var p = Person()
..name = "zad"
..eat()
..run();
print(p);
三. 類和對象
3.1. 類的定義
在Dart中,定義類用class關鍵字。
類通常有兩部分組成:成員(member)和方法(method)。
定義類的僞代碼如下:
class 類名 {
類型 成員名;
返回值類型 方法名(參數列表) {
方法體
}
}
-
這裏有一個注意點: 我們在方法中使用屬性(成員/實例變量)時,
並沒有加this
; -
Dart的開發風格中,在方法中通常使用屬性時,
會省略this
,但是有命名衝突
時,this不能省略
; -
注意:從Dart2開始,new關鍵字可以省略
// 1.創建類的對象
var p = new Person(); // 直接使用Person()也可以創建
3.2. 構造方法
3.2.1. 普通構造方法
我們知道, 當通過類創建一個對象時,會調用這個類的構造方法。
- 當類中
沒有明確指定構造方法
時,將默認擁有一個無參的構造方法
。 - 前面的Person中我們就是在調用這個構造方法.
我們也可以根據自己的需求,定義自己的構造方法:
-
注意一 : 當有了自己的構造方法時,默認的構造方法將會失效,不能使用
- 當然,你可能希望明確的寫一個默認的構造方法,但是會和我們自定義的構造方法衝突;
- 這是因爲Dart本身
不支持函數的重載
(名稱相同, 參數不同的方式)。
-
注意二 : 這裏我還實現了toString方法
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@override
String toString() {
return 'name=$name age=$age';
}
}
另外,在實現構造方法時,通常做的事情就是通過參數給屬性賦值
爲了簡化這一過程, Dart提供了一種更加簡潔的語法糖形式.
上面的構造方法可以優化成下面的寫法:
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 等同於
Person(this.name, this.age);
3.2.2. 命名構造方法
// 命名構造函數
Person.withNameAgeHeight(this.name, this.age, this.height);
Person.fromMap(Map<String, dynamic> map) {
this.name = map["name"];
this.age = map["age"];
this.height = map["height"];
}
3.2.3. 初始化列表
class Point {
final num x;
final num y;
final num distance;
// 錯誤寫法
// Point(this.x, this.y) {
// distance = sqrt(x * x + y * y);
// }
// 正確的寫法
Point(this.x, this.y): distance = sqrt(x * x + y * y);
}
- 另一種初始化的方式
class Person {
final String name;
final int age;
// 方式一和方式二的區別:
// 方式一: 應用更廣泛
// 方式二: 有侷限性, 不能使用表達式, 如三目運算符
// 方式一
// Person(this.name, {int age}): this.age = age ?? 10 { }
// 方式二
Person(this.name, {this.age = 10});
}
3.2.4. 重定向構造方法
- 在某些情況下, 我們希望在一個構造方法中去調用另外一個構造方法, 這個時候可以使用重定向構造方法:
- 在一個構造函數中,去調用另外一個構造函數(注意:是在冒號後面使用this調用)
class Person {
String name;
int age;
Person(this.name, this.age);
Person.fromName(String name) : this(name, 0);
}
3.2.5. 常量構造方法
- 如果將構造方法前加const進行修飾,那麼可以保證同一個參數,創建出來的對象是相同的
- 常量構造方法的所有屬性, 必須是final常量
main(List<String> args) {
var p1 = const Person('why');
var p2 = const Person('why');
print(identical(p1, p2)); // true
}
class Person {
final String name;
const Person(this.name);
}
-
注意一:擁有常量構造方法的類中,所有的成員變量必須是final修飾的.
-
注意二: 爲了可以通過常量構造方法,創建出相同的對象,不再使用 new關鍵字,而是使用const關鍵字
- 如果是將結果賦值給const修飾的標識符時,const可以省略.
3.2.6. 工廠構造方法
main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2)); // true
}
class Person {
String name;
// 用來做緩存, 保證同一個參數獲取的對象是一樣的
static final Map<String, Person> _cache = <String, Person>{};
factory Person(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final p = Person._internal(name);
_cache[name] = p;
return p;
}
}
Person._internal(this.name);
}
3.3. setter和getter
class Person {
String name;
// setter
set setName(String name) => this.name = name;
// set setName(String name) {
// this.name = name;
// }
// getter
String get getName => name;
// String get getName {
// return this.name;
// }
}
3.4. 類的繼承
- 父類中的所有成員變量和方法都會被繼承,,但是構造方法除外。
- 子類中可以調用父類的構造方法,對某些屬性進行初始化:
- 子類的構造方法在執行前,將隱含調用父類的
無參默認構造方法
(沒有參數且與類同名的構造方法)。 - 如果父類沒有
無參默認構造方法
,則子類的構造方法必須在初始化列表中通過super
顯式調用父類的某個構造方法。
- 子類的構造方法在執行前,將隱含調用父類的
class Animal {
int age;
Animal(this.age);
}
class Dog extends Animal {
String name;
// 要實現父類的構造方法
// Dog(String name, int age):name = name, super(age);
Dog(this.name, int age): super(age);
}
3.5. 抽象類
什麼是 抽象方法? 在Dart中沒有具體實現的方法(沒有方法體),就是抽象方法。
- 方法,必須存在於抽象類中。
- 抽象類是使用abstract聲明的類。
abstract class Shape {
getArea();
}
class Circle extends Shape {
double r;
Circle(this.r);
@override
getArea() {
return r * r * 3.14;
}
}
注意事項:
- 注意一: 抽象類不能實例化.
- 注意二: 抽象類中的抽象方法必須被子類實現, 抽象類中的已經被實現方法, 可以不被子類重寫.
3.6. 隱式接口
當我們定義了一個類的時候,同時就會產生一個和此類同名的接口,而且此接口包含了我們定義的類中所有的方法,以及它的成員變量。
abstract class Runner {
run();
}
abstract class Flyer {
fly();
}
class SuperMan implements Runner, Flyer {
@override
run() {
print('超人在奔跑');
}
@override
fly() {
print('超人在飛');
}
}
3.7. Mixin混入
在通過implements實現某個類時,類中所有的方法都 必須被重新實現
(無論這個類原來是否已經實現過該方法)。
但是某些情況下,一個類可能希望直接複用之前類的原有實現方案,怎麼做呢?
- 使用繼承嗎?但是Dart只支持單繼承,那麼意味着你只能複用一個類的實現。
Dart提供了另外一種方案: Mixin混入的方式
- 除了可以通過class定義類之外,也可以通過
mixin關鍵字
來定義一個類。 - 只是通過mixin定義的類用於被其他類混入使用,通過
with關鍵字
來進行混入。
main(List<String> args) {
var superMan = SuperMain();
superMan.run();
superMan.fly();
}
mixin Runner {
run() {
print('在奔跑');
}
}
mixin Flyer {
fly() {
print('在飛翔');
}
}
// implements的方式要求必須對其中的方法進行重新實現
// class SuperMan implements Runner, Flyer {}
class SuperMain with Runner, Flyer {
}
3.8. 類成員和方法
在Dart中我們使用static關鍵字來定義:
static String time;
static attendClass() {
print('去上課');
}
3.9. 枚舉類型
3.9.1. 枚舉的定義
enum Colors {
red,
green,
blue
}
3.9.2. 枚舉的屬性
枚舉類型中有兩個比較常見的屬性:
- index: 用於表示每個枚舉常量的索引, 從0開始.
- values: 包含每個枚舉值的List.
main(List<String> args) {
print(Colors.red.index);
print(Colors.green.index);
print(Colors.blue.index);
print(Colors.values);
}
枚舉類型的注意事項:
- 注意一: 您不能子類化、混合或實現枚舉。
- 注意二: 不能顯式實例化一個枚舉
四. 泛型
4.1. List和Map的泛型
List使用時的泛型寫法:
// 創建List的方式
var names1 = ['why', 'kobe', 'james', 111];
print(names1.runtimeType); // List<Object>
// 限制類型
var names2 = <String>['why', 'kobe', 'james', 111]; // 最後一個報錯
List<String> names3 = ['why', 'kobe', 'james', 111]; // 最後一個報錯
Map使用時的泛型寫法:
// 創建Map的方式
var infos1 = {1: 'one', 'name': 'why', 'age': 18};
print(infos1.runtimeType); // _InternalLinkedHashMap<Object, Object>
// 對類型進行顯示
Map<String, String> infos2 = {'name': 'why', 'age': 18}; // 18不能放在value中
var infos3 = <String, String>{'name': 'why', 'age': 18}; // 18不能放在value中
4.2. 類定義的泛型
如果我們需要定義一個類, 用於存儲位置信息Location, 但是並不確定使用者希望使用的是int類型,還是double類型, 甚至是一個字符串, 這個時候如何定義呢?
- 一種方案是使用Object類型, 但是在之後使用時, 非常不方便
- 另一種方案就是使用泛型.
Location類的定義: Object方式
main(List<String> args) {
Location l1 = Location(10, 20);
print(l1.x.runtimeType); // Object
}
class Location {
Object x;
Object y;
Location(this.x, this.y);
}
Location類的定義: 泛型方式
main(List<String> args) {
Location l2 = Location<int>(10, 20);
print(l2.x.runtimeType); // int
Location l3 = Location<String>('aaa', 'bbb');
print(l3.x.runtimeType); // String
}
}
class Location<T> {
T x;
T y;
Location(this.x, this.y);
}
如果我們希望類型只能是num類型, 怎麼做呢?
- 注意: int 和 double類型都繼承num類型
main(List<String> args) {
Location l2 = Location<int>(10, 20);
print(l2.x.runtimeType);
// 錯誤的寫法, 類型必須繼承自num
Location l3 = Location<String>('aaa', 'bbb');
print(l3.x.runtimeType);
}
class Location<T extends num> {
T x;
T y;
Location(this.x, this.y);
}
4.3. 泛型方法的定義
最初,Dart僅僅在類中支持泛型。後來一種稱爲泛型方法的新語法允許在方法和函數中使用類型參數。
main(List<String> args) {
var names = ['why', 'kobe'];
var first = getFirst(names);
print('$first ${first.runtimeType}'); // why String
}
T getFirst<T>(List<T> ts) {
return ts[0];
}
五. 庫的使用
- 在Dart中,你可以導入一個庫來使用它所提供的功能。
- 庫的使用可以使代碼的重用性得到提高,並且可以更好的組合代碼。
- Dart中任何一個dart文件都是一個庫,即使你沒有用
關鍵字library
聲明
5.1. 庫的導入
import語句用來導入一個庫,後面跟一個字符串形式的Uri來指定表示要引用的庫,語法如下:
import '庫所在的uri';
常見的庫URI有三種不同的形式
- 來自dart標準版,比如dart:io、dart:html、dart:math、dart:core(但是core可以省略,默認自動導入)
//dart:前綴表示Dart的標準庫,如dart:io、dart:html、dart:math
import 'dart:io';
- 使用相對路徑導入的庫,通常指自己項目中定義的其他dart文件
//當然,你也可以用相對路徑或絕對路徑的dart文件來引用
import 'lib/student/student.dart';
- Pub包管理工具管理的一些庫,包括自己的配置以及一些第三方的庫,通常使用前綴package
//Pub包管理系統中有很多功能強大、實用的庫,可以使用前綴 package:
import 'package:flutter/material.dart';
庫文件中內容的顯示和隱藏
如果希望只導入庫中某些內容
,或者刻意隱藏庫裏面某些內容
,可以使用show
和hide
關鍵字
- show關鍵字: 可以顯示某個成員(屏蔽其他)
- hide關鍵字: 可以隱藏某個成員(顯示其他)
import 'lib/student/student.dart' show Student, Person;
import 'lib/student/student.dart' hide Person;
庫中內容和當前文件中的名字衝突
當各個庫有命名衝突的時候,可以使用 as關鍵字
來使用命名空間
import 'lib/student/student.dart' as Stu;
Stu.Student s = new Stu.Student();
5.2. 庫的定義
library關鍵字
通常在定義庫時,我們可以使用library關鍵字給庫起一個名字。
但目前我發現,庫的名字並不影響導入,因爲import語句用的是字符串URI
library math;
part關鍵字
在之前我們使用student.dart作爲演練的時候,只是將該文件作爲一個庫。
在開發中,如果一個庫文件太大,將所有內容保存到一個文件夾是不太合理的,我們有可能希望將這個庫進行拆分,這個時候就可以使用part關鍵字了
不過官方已經不建議使用這種方式
了:
- https://dart.dev/guides/libraries/create-library-packages
mathUtils.dart
文件
part of "utils.dart";
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.dart
文件
part of "utils.dart";
String dateFormat(DateTime date) {
return "2020-12-12";
}
utils.dart
文件
part "mathUtils.dart";
part "dateUtils.dart";
test_libary.dart
文件
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
export關鍵字
官方不推薦使用part關鍵字
,那如果庫非常大,如何進行管理呢?
- 將每一個dart文件作爲庫文件,使用
export關鍵字
在某個庫文件中單獨導入
mathUtils.dart
文件
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.dart
文件
String dateFormat(DateTime date) {
return "2020-12-12";
}
utils.dart
文件
library utils;
export "mathUtils.dart";
export "dateUtils.dart";
test_libary.dart
文件
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
最後,也可以通過Pub管理自己的庫自己的庫,在項目開發中個人覺得不是非常有必要,所以暫時不講解這種方式。
external和@patch關鍵字
- external : 將方法的的聲明和實現分離
- @Patch: 補丁