Flutter 陈航 06-Dart 基础语法
目录
06 | 基础语法与类型变量
Dart 的变量与类型
Dart 以 main
函数作为执行的入口。
- 可以用
var
或者具体的类型来声明一个变量 - 当使用
var
定义变量时,表示类型是交由编译器推断决定的 - 未初始化的变量的值都是
null
Dart 是类型安全的语言,所有类型都是对象类型,都继承自顶层类型 Object
,函数
和 null
也都是继承自 Object 的对象。
num、bool 与 String
Dart 的数值类型 num
只有两种子类:
- 64 位的
int
,代表整数类型 - 64 位的
double
,代表浮点数类型
main() {
var age = 30;
double size = 5.678;
int hex = 0xFF;
double e = 1.2e3;
print("$age - $size - ${size.round()} - $hex - $e"); // 30 - 5.678 - 6 - 255 - 1200.0
}
- 只有两个对象具有 bool 类型:
true
和false
,它们都是编译时常量 - 构造字符串字面量时既能使用
单引号
也能使用双引号
- 可以在字符串中使用
${express}
嵌入变量或表达式,Dart 会调用内嵌对象的toString()
方法获取字符串 - 可以通过
三个单引号
或三个双引号
的方式声明多行字符串
List 与 Map
main() {
var arr1 = ["Tom", "Andy", "Jack"]; // List<String>
var arr2 = List.of([1, 2, 3]); // List<int>
arr1.add("BQT");
arr2.insert(0, 30);
print(arr1); // [Tom, Andy, Jack, BQT]
arr2.forEach((v) => print(v));
var map1 = {"name": "Tom", 'sex': 'male'}; // Map<String, String>
var map2 = new Map();
map1['name'] = 'BQT';
map2['sex'] = '男';
print(map1); // {name: BQT, sex: male}
map2.forEach((k, v) => print('$k: $v'));
}
Dart 会自动根据上下文对集合类型
进行推断,所以后续往容器内添加的元素也必须遵照这一类型。
建议在声明时显式地把类型标记出来,解除类型不匹配带来的安全隐患或 Bug。
main() {
var arr1 = <String>["Tom", "Andy", "Jack"];
var arr2 = List<int>.of([1, 2, 3]);
var map1 = <String, String>{"name": "Tom", 'sex': 'male'};
var map2 = new Map<String, String>();
print(arr1.runtimeType); // List<String>
print(map1.runtimeType); // _Map<String, String>
print(arr2 is List<int>); // true
print(map2 is Map<String, String>); // true
}
常量定义
const
:编译时常量。表示变量在编译时即能确定的值final
:运行时常量。表示变量可以在运行时动态确定的值,而一旦确定后就不可再变
import 'dart:math';
main() {
const int size = 3; // 编译期常量,可以加修饰符 int,但不可以加 var
final int count = Random().nextInt(10); // 运行时常量,不能使用 const 定义
}
07 | 函数、类与运算符
函数 Function
在 Dart 中,所有类型都是对象类型,函数也是对象,它的类型叫作 Function
。
main() {
bool isZero(int number) => number == 0;
void printInfo(int number, Function check) => print("$number is Zero: ${check(number)}");
Function function = isZero;
printInfo(10, isZero); // 10 is Zero: false
printInfo(0, function); // 0 is Zero: true
}
可选命名参数和可选参数
Dart 认为重载会导致混乱,因此从设计之初就不支持重载,而是提供了可选命名参数和可选参数。
- 可选命名参数:给参数增加
{}
,必须显式声明参数名,参数位置
无所谓 -- 可理解为 map - 可忽略的参数:给参数增加
[]
,这些参数可以忽略,但必须按顺序
摆放 -- 可理解为 链表(LinkedList)
可选命名参数非常重要,后续会高频使用
在使用这两种方式定义函数时,我们还可以在参数未传递时设置默认值。
main() {
// 可选命名参数
void f1({int? age, String? name = "xxx"}) => print("$age,$name");
f1(age: 30, name: "BQT"); // 30,BQT
f1(name: "BQT", age: 30); // 30,BQT
f1(name: "BQT"); // null,BQT
f1(); // null,xxx
// 可忽略的参数
void f2(int? age, [String? name, bool meal = true]) => print("$age,$name,$meal");
f2(30, "BQT", false); // 30,BQT,false
f2(30, "BQT"); // 30,BQT,true
f2(30); // 30,null,true
}
类与构造函数
Dart 中没有 public、protected、private,在声明变量与方法时,在前面加上 _
即可作为 private 方法使用,不加则默认为 public。
注意,
_
的限制范围并不是类访问级别的,而是库(package
)访问级别。
class Point {
num x, y;
static num factor = 0;
Point(this.x, this.y); //构造函数语法糖,等同于在函数体内执行 this.x = x; this.y = y;
void printInfo() => print('($x, $y)');
static void printF() => print('$factor');
}
main() {
Point.factor = 8;
Point.printF();
Point point = new Point(100, 200); // 关键字 new 可以省略
point.printInfo();
}
命名构造函数
除了可选命名参数
和可选参数
之外,Dart 还支持命名构造函数,使得类的实例化过程语义更清晰。
Dart 还支持初始化列表,在构造函数的函数体执行之前,可以给实例变量赋值,或者重定向至另一个构造函数。
class Point {
num x, y, z;
Point(this.x, this.y, [this.z = 8]); // 可选参数
Point.bottom(num x) : this(x, x); // 命名构造函数 + 重定向至另一个构造函数
Point.def() : x = 1, y = 1, z = 1; // 命名构造函数 + 初始化列表
void printInfo() => print('($x,$y,$z)');
}
main() {
Point(6, 6).printInfo(); // (6,6,8)
Point(6, 6, 6).printInfo(); // (6,6,6)
Point.bottom(5).printInfo(); // (5,5,8)
Point.def().printInfo(); // (1,1,1)
}
继承与接口实现
在 Dart 中,可以对同一个父类进行继承或接口实现:
- 继承父类意味着,子类由父类派生,会获取父类的成员变量和方法实现,可以根据需要覆写构造函数及父类方法
- 接口实现意味着,子类获取到的仅仅是接口的成员变量符号和方法符号,需要必须重新实现成员变量和接口方法
class Point {
num x = 1, y = 1;
void printInfo() => print('$x,$y');
}
// 继承
class Vector extends Point {
@override
void printInfo() => print('$x-$y'); // 覆写了父类实现
}
// 接口实现,注意,这里会将 class Point 当做接口
class Coordinate implements Point {
@override
num x = 2, y = 2; // 所有成员变量【必须】重新声明【并实现】
@override
void printInfo() => print('$x--$y'); // 所有成员函数也必须重新声明并实现
}
main() {
Point().printInfo(); // 1,1
Vector()
..x = 1
..y = 2 // 级联运算符,等同于 xxx.x=1; xxx.y=2;
..printInfo(); // 1-2
Coordinate yyy = Coordinate()
..x = 1
..y = 2
..printInfo(); // 1--2
}
混入 Mixin
Dart 还提供了 Mixin
来实现类的复用。Mixin 可以被视为具有实现方法的接口,不仅可以解决 Dart 缺少对多重继承的支持问题,还能够避免由于多重继承可能导致的歧义(菱形问题)。
菱形问题:当 B 和 C 继承自 A,并且 A 中有一个方法在 B 和 C 中都已经覆写,此时如果 D 同时继承 B 和 C 时就会产生歧义,因为无法确认 D 继承的方法是来自 B 还是 C。
通过混入,可以以非继承的方式使用其他类中的变量与方法。Flutter 很多地方都用到了混入。
注意:声明构造函数的类无法被别的类混入。The class 'XXX' can't be used as a mixin because it declares a constructor. (Documentation)
使用案例
mixin class A { // 必须是 a mixin class or a mixin
num x = 1;
void printInfo() => print('A_$x');
}
class B {
num x = 2;
void printInfo() => print('B_$x');
}
class MixinP extends B with A {} // 使用混入
main() {
MixinP yyy = MixinP()..printInfo(); // A_1
print((yyy is A) && (yyy is B)); //true
}
extends with implements
顺序: extends
before with
before implements
class F extends A implements B, C {}
class G extends A with B, C {} // 可以同时混入多个类
class H with B, C implements C, D {} // 可同时 with / implements 同一个 class
class E extends A with B, C implements C, D {} // extends > with > implements
运算符
空运算符
Dart 设计了几个用于简化处理变量实例为 null 情况的运算符。
?.
:a?.xx()
,表示 a 为 null 的时候跳过,避免抛出异常??=
:a ??= value
,如果 a 为 null,则给 a 赋值 value,否则跳过 -- 注意,这里会修改 a 的值??
:a ?? b
,如果 a 不为 null,则返回 a,否则返回 b
main() {
var a = null;
print(a ??= 1); // 1
print(a); // 1
var b = null;
var c = b ??= 2;
print(b); // 2
print(c); // 2
}
自定义或覆写运算符
Dart 提供了类似 C++ 的运算符覆写机制,使得我们可以覆写或者自定义运算符。
operator
是 Dart 的关键字,与运算符一起使用,表示一个类成员运算符函数。
class Vector {
num x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y); // 自定义 + 运算符,实现向量相加
bool operator ==(dynamic v) => x == v.x && y == v.y; // 覆写 == 运算符,判断相等。注意,这里使用的是 dynamic
String info() => "($x,$y)";
}
main() {
var x = Vector(3, 3);
var y = Vector(2, 2) + Vector(1, 1);
print(y.info()); // (3,3)
print(Vector(3, 3) == y); // true
}
在覆写相等运算符 == 时,为何需要传入
dynamic
而不能传入 Vector 呢?
答:因为operator==
是继承自 Object 类,这个类的参数声明就是dynamic
08 | 综合案例
class Meta {
double price;
String name;
Meta(this.name, this.price); // 成员变量初始化
}
class Item extends Meta {
Item(name, double price) : super(name, price);
Item operator +(Item item) => Item("BQT", price + item.price); // 运算符重载
}
abstract mixin class PrintHelper {
void printInfo() => print(getInfo());
String getInfo();
}
// 以非继承的方式复用另一个类的成员变量及函数
class ShoppingCart extends Meta with PrintHelper {
DateTime date;
String? code;
late List<Item> bookings; // add an init expression, or init in constructor, or mark it 'late'
double get price => bookings.reduce((v, e) => v + e).price; // Item 重写了 + 运算符
ShoppingCart({name}) : this.withCode(name: name, code: null); // 重定向
ShoppingCart.withCode({name, this.code})
: date = DateTime.now(),
super(name, 0); // 调用父类初始化方法
@override
getInfo() => '''
购物车信息:
-----------------------------
用户名: $name
优惠码: ${code ?? "code 为空"}
总价: $price
Date: ${date.getDate()}
-----------------------------
''';
}
// 扩展方法 https://blog.csdn.net/weixin_43836055/article/details/126073256
extension DateTimeExt on DateTime {
String getDate() => "$year.$month.$day $hour:$minute:$second";
}
void main() {
ShoppingCart.withCode(name: '张三', code: '123456')
..bookings = [Item('苹果', 6.0), Item('鸭梨', 3.0)]
..printInfo();
ShoppingCart(name: '李四')
..bookings = [Item('香蕉', 8.0), Item('西瓜', 5.0)]
..printInfo();
}
2022-12-4
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/16949985.html