第1天安裝並初體驗了一把Dart程式,本文按照Dart官網的“代碼實驗室”把Dart的基礎語法練習一遍,基礎語法特性很多,因此建議收藏本博客了 [本博客疑問:為什麼函數有了命名參數,還需要可選的位置參數?歡迎評論區討論!]…… ...
本博客原地址:https://ntopic.cn/p/2023092401/
Dart官網代碼實驗室:https://dart.dev/codelabs/dart-cheatsheet
特別說明:為了更進一步驗證Dart代碼特性,下麵示例的代碼並非與官方代碼完全一致(為了探究細節,預設比官方代碼要複雜一些)。
字元串插值:${}
基礎語法:字元串中,可以通過${}插入上下文中變數和變數運算值。
void main() {
// 1. 字元串插值
var a = 2;
var b = 3;
var c = 'Hello';
print('1. 字元串插值: ${c.toUpperCase()} Dart:a is ${a} and b is ${b}, so a>b is ${a > b}, a+b=${a + b}');
// 結果:1. 字元串插值: HELLO Dart:a is 2 and b is 3, so a>b is false, a+b=5
}
變數賦值:?和null
基礎語法:Dart是空安全(或者null安全)的語言,也就是說除非顯示聲明變數是可為null的,否則他們不能為null。預設情況下,變數預設是不能為null的。
void main() {
// 2. 變數賦值
// 非法聲明:int d = null; String s;
int d = 4;
int? e;
String f = 'Hello';
String? g;
print('2. 變數賦值: d: ${d}, e:${e}, f:${f}, g:${g}');
// 結果:2. 變數賦值: d: 4, e:null, f:Hello, g:null
}
空運算符:??和??=
基礎語法:用於處理可能會為空值的變數,??判斷是否為空,??=當為空時才運行賦值。
特別註意:??和??=這兩個運算符中間不能有空格。
void main() {
// 3. 空運算符
int? h;
h ??= 3;
h ??= 5; // 不生效,因為此時h非空
int i = 1 ?? 3; // 1非空,所以3不生效
int j = null ?? 4;
print('3. 空運算符: h=${h}, i=${i}, j=${j}');
// 結果:3. 空運算符: h=3, i=1, j=4
}
訪問空對象屬性:.?
基礎語法:為了正常訪問可能為空對象的屬性。在Java中,通過if條件判斷來訪問屬性,如:int a = (obj == null) ? 0 : obj.getA();
void main() {
// 4. 訪問空對象屬性
String? k;
String? l = 'Hello';
var m = k?.toLowerCase()?.toUpperCase();
var n = l?.toLowerCase()?.toUpperCase();
print('4. 訪問空對象屬性: m=${m}, n=${n}');
// 結果:4. 訪問空對象屬性: m=null, n=HELLO
}
集合類型:[]和{}
基礎語法:Dart存在內置的基礎集合類型,包括list, set和map等,可以指定元素類型。list元素可以重覆,set和map不允許重覆。
特別註意:這裡只是簡單用例,集合類型的其他用法,我在下次學習並通過博客分享。
void main() {
// 5. 集合類型
var aList = <String>['a', 'b', 'b'];
var aSet = <String>{'a', 'b', 'b'};
var aMap = <String, String>{'a': 'Hello', 'b': 'World', 'b': 'Dart'};
print('5. 集合類型: aList=${aList}, aSet=${aSet}, aMap=${aMap}');
// 結果:5. 集合類型: aList=[a, b, b], aSet={a, b}, aMap={a: Hello, b: Dart}
}
箭頭語法函數:=>
基礎語法:箭頭是定義函數的一種簡便方法,箭頭右邊的執行結果作為返回值。
void main() {
// 6. 箭頭語法函數
String joinWithCommas(List<int> values) => values.join(',');
final cList = <int>[2, 5, 7];
print('6. 箭頭語法函數: cList join=${joinWithCommas(cList)}');
// 結果:6. 箭頭語法函數: cList join=2,5,7
}
級聯和空判斷:..和?..
基礎語法:為了簡便對同一個對象連續執行多個方法,它每次執行均返回的是操作對象引用,而不是操作結果。
特別說明:常規情況下myObject.someMethod()返回的是方法的執行結果,但是級聯操作myObject..someMethod()返回的是myObject引用本身,那麼它就可以連續執行多個方法,如:myObject..someMethod()..otherMethod()等。
級聯聯合空判斷,可以在連續執行多個方法的時候,無需擔心操作對象為null,如以下代碼樣例:將 BigObject 的 anInt 屬性設為 1、aString 屬性設為 String!、aList 屬性設置為 [3.0]、然後調用 allDone()。
class BigObject {
int anInt = 0;
String aString = '';
List<double> aList = [];
bool _done = false;
void allDone() {
_done = true;
}
}
BigObject fillBigObject(BigObject obj) {
return obj?..anInt = 1
..aString = 'String!'
..aList = [3.0]
..allDone();
}
對象屬性訪問器:getters和setters
基礎語法:按照類的封裝原則,類屬性不能直接暴露給外部訪問或者設置,應該提供getters和setters方法。Dart提供了簡單的實現方式。Dart的類屬性沒有public/private等可見於修飾符,如果以下劃線_開頭,則為private,否則為public公共域。
代碼樣例:有一個購物車類,其中有一個私有的 List
class InvalidPriceException {}
class ShoppingCart {
// 下劃線開頭,私有屬性
List<double> _prices = [];
// total的getter方法,用戶返回總價格值
double get total => _prices.fold(0, (e,t) => e+t);
// 設置_prices的值,如果存在負數,則拋出異常
set prices(List<double> newPrices) {
if(newPrices.any((e) => e < 0)) {
throw InvalidPriceException();
}
_prices = newPrices;
}
}
函數可選位置入參:[]
基礎語法:函數的入參列表中,最後面的參數通過[]曝光起來,他們是可選的,即調用函數時可以不傳入參數。除非指定了預設值,否則可選入參默的認值均為null。
void main() {
// 8. 函數可選位置入參
int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {
return a + (b ?? 0) + (c ?? 0) + (d ?? 0) + (e ?? 0);
}
int sumUpToFive2(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
return a + b + c + d + e;
}
print('8. 函數可選位置入參: sumUpToFive(1,2)=${sumUpToFive(1, 2)}, sumUpToFive(1,2,3)=${sumUpToFive(1, 2, 3)}');
print('8. 函數可選位置入參: sumUpToFive2(1,1)=${sumUpToFive2(1, 1)}, sumUpToFive2(1,1,1)=${sumUpToFive2(1, 1, 1)}');
// 結果:
// 8. 函數可選位置入參: sumUpToFive(1,2)=3, sumUpToFive(1,2,3)=6
// 8. 函數可選位置入參: sumUpToFive2(1,1)=14, sumUpToFive2(1,1,1)=12
}
函數命名入參:{}
基本語法:與位置參數類似,命名參數使用{}包裹,它也是可選的,除非有預設值,否則它的值也是null。在調用命名參數函數時,命名參數必須通過參數名來定位,且它的順序是可隨意(這2點是與位置參數的最大區別)。
代碼樣例:有個MyDataObject類,有3個屬性和copyWith方法,方法的入參均可能為空,如果為空則使用原對象值,否則使用入參值:
class MyDataObject {
final int anInt;
final String aString;
final double aDouble;
MyDataObject({
this.anInt = 1,
this.aString = 'OLD',
this.aDouble = 2.0,
});
String toString() {
return 'MyDataObject: {anInt=$anInt, aString=$aString, aDouble=$aDouble}';
}
// 本方法的3個入參均為命名參數
MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {
return MyDataObject(
anInt: newInt ?? this.anInt,
aString: newString ?? this.aString,
aDouble: newDouble ?? this.aDouble,
);
}
}
void main() {
// 10. 函數命名入參
final myDataObject = MyDataObject();
final newDataObject = myDataObject.copyWith(newInt: 2, newString: 'NEW', newDouble: 4.0);
print('10. 函數命名入參: myDataObject=$myDataObject, newDataObject=$newDataObject');
// 結果:10. 函數命名入參: myDataObject=MyDataObject: {anInt=1, aString=OLD, aDouble=2.0}, newDataObject=MyDataObject: {anInt=2, aString=NEW, aDouble=4.0}
}
異常:try,on,catch,rethrow和finally
基礎語法:Dart可以拋出和捕獲異常,所有異常都是未檢測異常。函數或者方法無需聲明可能拋出的異常。Dart提供了Exception和Error兩種異常類型,但業務邏輯中,可以拋出任意非空的對象(如:throw 'abc')。通過rethrow關鍵字,可重新拋出異常。
代碼樣例:tryFunction不可靠方法,捕獲不同的異常並列印日誌。
typedef VoidFunction = void Function();
class ExceptionWithMessage {
final String message;
const ExceptionWithMessage(this.message);
}
// Call logException to log an exception, and doneLogging when finished.
abstract class Logger {
void logException(Type t, [String? msg]);
void doneLogging();
}
void tryFunction(VoidFunction untrustworthy, Logger logger) {
try {
untrustworthy();
} on ExceptionWithMessage catch(e) {
logger.logException(e.runtimeType, e.message);
} on Exception {
logger.logException(Exception);
} finally {
logger.doneLogging();
}
}
構造方法:this,required,位置參數和命名參數
基礎語法:在構造函數中,通過this關鍵字可以為成員變數快速賦值。構造函數的如此可以是位置參數,也可以是命名參數,如果參數是必選參數,則使用required關鍵字修飾,且該參數不能有預設值。
// 位置參數
class MyColor1 {
int red;
int green;
int blue;
// 主構造函數
MyColor1(this.red, this.green, this.blue);
// 命名構造函數:預設值初始化
MyColor1.origin()
: red = 0,
green = 0,
blue = 0;
MyColor1.origin2() : this(0, 0, 0);
}
final color10 = MyColor1(80, 80, 128);
final color11 = MyColor1.origin();
final color12 = MyColor1.origin2();
// 命名參數
class MyColor2 {
int red;
int green;
int blue;
// 主構造函數
MyColor2({required this.red, required this.green, required this.blue});
// 命名構造函數:預設值初始化
MyColor2.origin()
: red = 0,
green = 0,
blue = 0;
MyColor2.origin2() : this(red: 0, green: 0, blue: 0);
}
final color20 = MyColor2(
red: 80,
green: 80,
blue: 128,
);
final color21 = MyColor2.origin();
final color22 = MyColor2.origin2();
構造方法::初始化列表
基礎語法:在執行構造函數體之前,需要進行一些初始化操作,比如校驗參數合法性、初始化參數等。
代碼樣例:使用的初始化列表將 word 的前兩個字元分配給 letterOne 和 LetterTwo 屬性。
class FirstTwoLetters {
final String letterOne;
final String letterTwo;
// 初始化列表
FirstTwoLetters(String word)
: assert(word.length >= 2),
letterOne = word[0],
letterTwo = word[1];
String toString() {
return 'FirstTwoLetters: {letterOne=$letterOne, letterTwo=$letterTwo}';
}
}
void main() {
// 13. 構造方法:初始化列表
final firstTwoLetters = FirstTwoLetters('Dart');
print('13. 構造方法:初始化列表: firstTwoLetters=$firstTwoLetters');
// 結果:13. 構造方法:初始化列表: firstTwoLetters=FirstTwoLetters: {letterOne=D, letterTwo=a}
}
構造方法:factory工廠
基礎語法:父類根據入參,返回具體子類。
代碼樣例:一般父類方法提供一個無任何參數的構造函數。
class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) {
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
throw ArgumentError('Unrecognized $typeName');
}
}
構造方法::重定向
基本語法:構造方法中,通過:引用另外一個構造方法,可以是主構造函數,也可以是命名構造函數。
class Automobile {
String make;
String model;
int mpg;
// 類主構造函數
Automobile(this.make, this.model, this.mpg);
// 命名構造函數:重定向主構造函數
Automobile.hybrid(String make, String model) : this(make, model, 60);
// 命名構造函數:重定向命名構造函數
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
}
構造方法:final,const常量
基礎語法:如果類生成的對象永遠都不會更改,則可以讓這些對象成為編譯時常量。為此,請定義 const 構造方法並確保所有實例變數都是 final 的。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
// 類屬性必須用final修飾
final int x;
final int y;
// 構造函數更加const關鍵字
const ImmutablePoint(this.x, this.y);
}
最後
Dart學習第2天,根據官方文檔由淺入深學習,更多語法和技巧在後續研發中我在補充。
完整的測試用的實例代碼,部分代碼示例在小節中已經提供:
class MyDataObject {
final int anInt;
final String aString;
final double aDouble;
MyDataObject({
this.anInt = 1,
this.aString = 'OLD',
this.aDouble = 2.0,
});
String toString() {
return 'MyDataObject: {anInt=$anInt, aString=$aString, aDouble=$aDouble}';
}
// 本方法的3個入參均為命名參數
MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {
return MyDataObject(
anInt: newInt ?? this.anInt,
aString: newString ?? this.aString,
aDouble: newDouble ?? this.aDouble,
);
}
}
// 位置參數
class MyColor1 {
int red;
int green;
int blue;
// 主構造函數
MyColor1(this.red, this.green, this.blue);
// 命名構造函數:預設值初始化
MyColor1.origin()
: red = 0,
green = 0,
blue = 0;
MyColor1.origin2() : this(0, 0, 0);
}
final color10 = MyColor1(
80,
80,
128,
);
final color11 = MyColor1.origin();
final color12 = MyColor1.origin2();
// 命名參數
class MyColor2 {
int red;
int green;
int blue;
// 主構造函數
MyColor2({required this.red, required this.green, required this.blue});
// 命名構造函數:預設值初始化
MyColor2.origin()
: red = 0,
green = 0,
blue = 0;
MyColor2.origin2() : this(red: 0, green: 0, blue: 0);
}
final color20 = MyColor2(
red: 80,
green: 80,
blue: 128,
);
final color21 = MyColor2.origin();
final color22 = MyColor2.origin2();
class FirstTwoLetters {
final String letterOne;
final String letterTwo;
// 初始化列表
FirstTwoLetters(String word)
: assert(word.length >= 2),
letterOne = word[0],
letterTwo = word[1];
String toString() {
return 'FirstTwoLetters: {letterOne=$letterOne, letterTwo=$letterTwo}';
}
}
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
// 類屬性必須用final修飾
final int x;
final int y;
// 構造函數更加const關鍵字
const ImmutablePoint(this.x, this.y);
}
void main() {
// 1. 字元串插值
var a = 2;
var b = 3;
var c = 'Hello';
print(
'1. 字元串插值: ${c.toUpperCase()} Dart:a is ${a} and b is ${b}, so a>b is ${a > b}, a+b=${a + b}');
// 2. 變數賦值
// 非法聲明:int d = null; String s;
int d = 4;
int? e;
String f = 'Hello';
String? g;
print('2. 變數賦值: d: ${d}, e:${e}, f:${f}, g:${g}');
// 3. 空運算符
int? h;
h ??= 3;
h ??= 5; // 不生效,因為此時h非空
int i = 1 ?? 3; // 1非空,所以3不生效
int j = null ?? 4;
print('3. 空運算符: h=${h}, i=${i}, j=${j}');
// 4. 訪問空對象屬性
String? k;
String? l = 'Hello';
var m = k?.toLowerCase()?.toUpperCase();
var n = l?.toLowerCase()?.toUpperCase();
print('4. 訪問空對象屬性: m=${m}, n=${n}');
// 5. 集合類型
var aList = <String>['a', 'b', 'b'];
var aSet = <String>{'a', 'b', 'b'};
var aMap = <String, String>{'a': 'Hello', 'b': 'World', 'b': 'Dart'};
print('5. 集合類型: aList=${aList}, aSet=${aSet}, aMap=${aMap}');
// 6. 箭頭語法函數
String joinWithCommas(List<int> values) => values.join(',');
final cList = <int>[2, 5, 7];
print('6. 箭頭語法函數: cList join=${joinWithCommas(cList)}');
// 8. 函數可選位置入參
int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {
return a + (b ?? 0) + (c ?? 0) + (d ?? 0) + (e ?? 0);
}
int sumUpToFive2(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
return a + b + c + d + e;
}
print(
'8. 函數可選位置入參: sumUpToFive(1,2)=${sumUpToFive(1, 2)}, sumUpToFive(1,2,3)=${sumUpToFive(1, 2, 3)}');
print(
'8. 函數可選位置入參: sumUpToFive2(1,1)=${sumUpToFive2(1, 1)}, sumUpToFive2(1,1,1)=${sumUpToFive2(1, 1, 1)}');
// 10. 函數命名入參
final myDataObject = MyDataObject();
final newDataObject = myDataObject.copyWith(newInt: 2, newString: 'NEW', newDouble: 4.0);
print('10. 函數命名入參: myDataObject=$myDataObject, newDataObject=$newDataObject');
// 13. 構造方法:初始化列表
final firstTwoLetters = FirstTwoLetters('Dart');
print('13. 構造方法:初始化列表: firstTwoLetters=$firstTwoLetters');
}
本文作者:奔跑的蝸牛,轉載請註明原文鏈接:https://ntopic.cn