Flutter/Dart第02天: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
热门相关:补天记 宠物小精灵之庭树 楚氏赘婿 嫡嫁千金 重生成偏执霍少的小仙女