Dart 语言基础
Dart 语言的基础学习
Dart 的变量与类型
具有可空类型的未初始化变量的初始值为 null,即使是具有数值类型的变量,初始值也为空,因为Dart 是类型安全的语言,并且所有类型都是对象类型,都继承自顶层类型 Object,因此一切变量的值都是类的实例(即对象),甚至数字、布尔值、函数和 null 也都是继承自 Object 的对象。
可以在声明变量时不初始化变量,但在使用之前需要为其赋值。顶级变量和类变量是延迟初始化的,它们会在第一次被使用时再初始化
空安全
- 当你为变量、参数或另一个相关组件指定类型时,可以控制该类型是否允许
null
。要让一个变量可以为空,你可以在类型声明的末尾添加?
- 必须在使用变量之前对其进行初始化。可空变量是默认初始化为
null
的。 Dart 不会为非可空类型设置初始值,它强制要求你设置初始值。 Dart 不允许你观察未初始化的变量。这可以防止你在接收者类型可以为null
但null
不支持的相关方法或属性的情况下使用它 - 你不能在可空类型的表达式上访问属性或调用方法。同样的例外情况适用于
null
支持的属性或方法,例如hashCode
或toString()
。
空安全将潜在的 运行时错误 转变为 编辑时 分析错误。当非空变量处于以下任一状态时,空安全会识别该变量:
- 未使用非空值进行初始化
- 赋值为
null
变量定义
Dart 中是类型安全的,因此不能像 js 一样使用 var a = 0; if (a) {...}
这样的判断,必须显式的判断 if(a == 0)
这样。
var
创建变量是由编译器来推断类型
var a = '123';
int、double、bool
int 定义一个整型变量、double 定义一个浮点型变量、bool 定义一个布尔变量(只有 true 和 false 两个对象),true 和 false 是编译时常量(在编译时就已确定的值)。
int a = 1;
double b = 1.1;
bool c = true;
Object、String、num
通过类声明变量
Object 定义一个可变类型的变量
Object a = 123;
a = '123';
String 定义一个字符串变量,可以使用模版嵌入表达式,如果表达式是一个标识符可以省略花括号
String name = 'Tom';
String a = 'Hello day ${1 + 1}!';
String b = 'Hello $name!';
print('Hello World! $a $b');
}
输出> Hello World! Hello day 2! Hello Tom!
通过成对的三个单引号惑三个双引号来定义多行字符串
var s3 = """This is a
multi-line string.""";
num 声明一个数字类型
num a = 1;
a += 1.1;
late
延迟初始化变量,可以先只声明,到使用前再初始化(使用时如果没有初始化还是会报错的),这样声明 dart 就不会去判断这个变量的是否空值。当一个 late
修饰的变量在声明时就指定了初始化方法,那么内容会在第一次使用变量时运行初始化。
late String description;
// temperature 变量未使用,readThermometer 函数也不会被调用
late String temperature = readThermometer();
void main() {
description = 'Feijoada!';
print(description);
}
const、final
如果你不打算更改一个变量,可以使用 final
或 const
修饰它,而不是使用 var
或作为类型附加。一个 final 变量只能设置一次,const 变量是编译时常量。(const 常量隐式包含了 final。)
const
关键字不仅仅可用于声明常量,你还可以使用它来创建常量 值(values),以及声明 创建(create) 常量值的构造函数。任何变量都可以拥有常量值。
在表达式一定是常量的上下文中,const
关键字是隐式的,不需要写,也不应该。(文档上这句话挺难理解的,因为对于 js 开发者来说是理所当然的事情,却被着重强调,看了例子才知道要表达的是什么=_=||)
// good
const primaryColors = [
Color('red', [255, 0, 0]),
Color('green', [0, 255, 0]),
Color('blue', [0, 0, 255]),
];
//bad
const primaryColors = const [
const Color('red', const [255, 0, 0]),
const Color('green', const [0, 255, 0]),
const Color('blue', const [0, 0, 255]),
];
如果变量的 值 没有被 final
或者 const
修饰,即使它以前被 const
修饰,你也可以修改这个变量:
// 指这样的变更
var a = const [];
a = [1,2,3];
虽然 final
定义的对象不能被修改,但对象的字段可以被更改。相比之下,const
定义的对象及其字段不能被更改:它们是 不可变的。额, const 创建的对象必须属性都是 final 的
class Person {
var name;
Person(this.name)
}
final person = Person('Tom')
person.name = 'Jerry';
print(person.name);
基本类型
- Numbers (
int
,double
) - Strings (
String
) - Booleans (
bool
) - Records (
(value1, value2)
) - Lists (
List
, also known as arrays) - Sets (
Set
) - Maps (
Map
) - Runes (
Runes
; often replaced by thecharacters
API) - Symbols (
Symbol
) - The value
null
(Null
)
Records
Records
(记录)是匿名的、不可变的聚合类型,与其他集合类型(List、Map
)一样,它允许您将多个对象捆绑到一个对象中。与其他集合类型不同的是Record
(记录)是固定大小的、异构的和类型化的。
单独的值是位置字段,带名字和冒号的是命名字段
var record = ('foo', a: 1, b: 'Tom', 'Hello World!');
print('${record.$1},${record.a},${record.b},${record.$2}');
(String, String, {int a, String b}) record1 = ('foo', a: 1, b: 'Tom', 'Hello World!');
print(record1);
(String, int) getRecord((String, String, {int a, String b}) recordx) {
return (recordx.$2, recordx.a);
}
print(getRecord(record1));
用在给函数传参和返回值的地方貌似还行,定义类型较为方便一些
Map、List、Set
Map
是一个键值对集合,其中的每个元素都是一个键值对。键和值可以是任何类型的对象,但键必须是唯一的。Dart 的 Map
是基于哈希表实现的,因此访问元素的速度很快。
List
是一个有序的元素集合,可以包含重复的元素。List
在 Dart 中是可变的,意味着你可以添加、删除或更改其中的元素。
Set
是一个无序的唯一元素集合,即集合中的元素不能重复。Set
在 Dart 中是基于哈希表实现的,因此添加、删除和查找元素的速度很快。
var map1 = {'name': 'Tom', 'sex': 'male'};
var map2 = Map<String, int>();
map2['name'] = 1;
map2['age'] = 23;
map2.forEach((k,v) => print('${k}: ${v}'));
print(map2 is Map); // true
var arr1 = <int>[1, 2, 3];
var arr2 = List<String>.of(['1','2','3']);
arr2.add('999');
arr2.forEach((v) => print('$v '));
print(arr1 is List<int>);
var set = {'apple', 'banana', 'cherry'};
var set1 = Set<String>();
set1.add('123');
set1.add('tome');
print(set1);
类型间的转换
// String -> int
var one = int.parse('1');
// String -> double
var onePointOne = double.parse('1.1');
// int -> String
String oneAsString = 1.toString();
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
泛型、别名
泛型是一种编程语言特性,它允许你定义函数、类、接口或数据结构,使其能够接受一个或多个类型参数。这些类型参数在定义时不指定具体的类型,而是在使用时指定。泛型提供了一种方式,使得代码可以操作多种数据类型,同时保持类型安全。
别名和 ts 中的 type 类似,不过多介绍了。
函数
函数是一段用来独立地完成某个功能的代码,它也是对象,类型是 Function。像 js 一样支持箭头函数表达式,且可作为另一函数的参数(PS:在 js 里回调很常见,这里为啥要强调这个能力)。
可选命名参数
// 命名参数:非必传参数,非必传有默认值,必传参数,必传参数也可为空值
void enableFlags({bool? bold, bool? hidden = false, required String a, required String? b}) {...}
enableFlags(a: true, b: false);
可选参数
void foo (String? a, int? b, [bool c]) {...}
foo('a', 1);
foo('a', 1, true);
匿名函数
常用于传参中,和 js 也比较像
list.forEach((item) => print(item);)
类
Dart 是面向对象的语言,每个对象都是一个类的实例,都继承自顶层类型 Object。Dart 中并没有 public
、protected
、private
这些关键字,我们只要在声明变量与方法时,在前面加上_
即可作为 private
方法使用。如果不加_
,则默认为 public
。不过,_
的限制范围并不是类访问级别的,而是库访问级别(在 Dart 中, 每一个 Dart 文件就是一个库)。
命名构造函数
class Point {
num x, y, z;
Point(this.x, this.y) : z = 0; // 初始化变量z
Point.bottom(num x) : this(x, 0); // 重定向构造函数
void printInfo() => print('($x,$y,$z)');
}
var p = Point.bottom(100);
p.printInfo(); // 输出(100,0,0)
复用
在面向对象的编程语言中,将其他类的变量与方法纳入本类中进行复用的方式一般有两种:继承父类和接口实现。Dart 还支持混入(Mixin),关键字使用 with
,通过混入,一个类里可以以非继承的方式使用其他类中的变量与方法。
class Point {
num x = 0, y = 0;
void printInfo() => print('($x,$y)');
}
//Vector继承自Point
class Vector extends Point{
num z = 0;
void printInfo() => print('($x,$y,$z)'); //覆写了printInfo实现
}
//Coordinate是对Point的接口实现
class Coordinate implements Point {
num x = 0, y = 0; //成员变量需要重新声明
void printInfo() => print('($x,$y)'); //成员函数需要重新声明实现
}
var xxx = Vector();
xxx
..x = 1
..y = 2
..z = 3; //级联运算符,等同于xxx.x=1; xxx.y=2;xxx.z=3;
xxx.printInfo(); //输出(1,2,3)
var yyy = Coordinate();
yyy
..x = 1
..y = 2; //级联运算符,等同于yyy.x=1; yyy.y=2;
yyy.printInfo(); //输出(1,2)
print (yyy is Point); //true
print(yyy is Coordinate); //true
// 混入
class Coordinate with Point {}
var yyy = Coordinate();
print (yyy is Point); //true
print(yyy is Coordinate); //true
一般来讲,单继承,多实现,混入是多继承
- 继承是子类需要复用父类的方法实现
- 实现接口是复用接口的参数,返回值,和方法名,但不复用方法的实现,在Dart中实现抽象类 更像在java中实现用interface修饰的接口
- 混入是多继承,当被混入的类有多个同名方法时,调用子类的该方法时,会调用with声明的最后一个拥有该方法的类中的该方法,同时混入中的父类不能继承
运算符
Dart 中用于简化处理变量实例缺失(即 null)的情况的几个额外运算符介绍:
- ?. 运算符:假设 Point 类有 printInfo() 方法,p 是 Point 的一个可能为 null 的实例。那么,p 调用成员方法的安全代码,可以简化为 p?.printInfo() ,表示 p 为 null 的时候跳过,避免抛出异常。(嗯, ts 中也这样用)
- ??= 运算符:如果 a 为 null,则给 a 赋值 value,否则跳过。这种用默认值兜底的赋值语句在 Dart 中我们可以用 a ??= value 表示。(类似 ts 中的 a || value)
- ?? 运算符:如果 a 不为 null,返回 a 的值,否则返回 b。在 Java 或者 C++ 中,我们需要通过三元表达式 (a != null)? a : b 来实现这种情况。而在 Dart 中,这类代码可以简化为 a ?? b。(类似 ts 中的 a && value)
覆写
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;
}
final x = Vector(3, 3);
final y = Vector(2, 2);
final z = Vector(1, 1);
print(x == (y + z)); // 输出true