Dart语言学习
Dart语言特点
Dart 是一种由 Google 开发的编程语言,最初在 2011 年发布。它被设计为一种客户端优化的语言,适用于构建快速、高效的应用程序,特别是在 Web 和移动应用开发领域。以下是 Dart 语言的一些主要特点:
- 面向对象
Dart 是一种面向对象的语言,支持类和继承。它使用基于类的编程,并提供了丰富的内置类库。
强类型
Dart 是一种强类型语言,支持静态类型检查。它也支持类型推断,可以使用var
关键字让编译器自动推断变量的类型。可空类型和非空类型
Dart 支持可空类型和非空类型。默认情况下,类型是非空的,但可以使用?
使其可空。这有助于减少空指针异常。异步编程
Dart 提供了强大的异步编程支持,包括Future
和Stream
类型,以及async
和await
关键字。这使得处理异步操作(如网络请求)变得简单和直观。单线程模型
Dart 运行在单线程模型上,但通过事件循环和异步编程模型,可以高效地处理并发任务。JIT 和 AOT 编译
Dart 支持即时编译(JIT)和提前编译(AOT)。JIT 编译在开发过程中提供快速的开发周期和热重载功能,而 AOT 编译则生成高效的机器码,适用于生产环境。跨平台
Dart 可以编译为 JavaScript,使其适用于 Web 开发。同时,Dart 也是 Flutter 框架的主要编程语言,用于构建跨平台的移动应用(iOS 和 Android)、桌面应用(Windows、macOS、Linux)和 Web 应用。热重载
Dart 支持热重载功能,可以在开发过程中快速查看代码更改的效果,大大提高了开发效率。丰富的标准库
Dart 提供了丰富的标准库,涵盖了从基本数据结构到网络编程、文件 I/O 等多个领域。扩展性
Dart 支持扩展方法和自定义操作符,使得代码更具表达力和灵活性。泛型
Dart 支持泛型编程,可以在定义类、接口和函数时使用类型参数,提高代码的复用性和类型安全性。工具链
Dart 提供了强大的工具链,包括 Dart DevTools(用于调试和性能分析)、DartPad(在线 Dart 编辑器)等。易于学习
Dart 的语法借鉴了多种编程语言,特别是 Java 和 JavaScript,因此对于有这些语言经验的开发者来说,学习曲线相对平缓。
数据类型
Dart 是一种强类型语言,提供了多种内置数据类型来支持不同的编程需求。以下是对 Dart 中主要数据类型的详细讲解:
1. 基本数据类型
num类型(子类int和double)
整型 (int)
int 表示整数,可以是正数、负数或零。Dart 中的 int 类型在 Dart VM 中通常是 64 位有符号整数。1
2int age = 25;
int negativeNumber = -10;浮点型 (double)
double 表示双精度浮点数。1
2double height = 5.9;
double temperature = -20.5;
常用方法
1
2
3print(negativeNumber.abs()); //绝对值
print(height.toInt()); //转换成int
print(age.toDouble()); //转换成浮点
布尔型 (bool)
bool 表示布尔值,只有两个值:true 和 false。
1
2
3
4
5
6
7
///强类型检查
_boolType() {
bool success = true, fail = false;
print('success $success fail:$fail');
print(success || fail);
print(success && fail);
}
字符串 (String)
String 表示文本数据。Dart 字符串是 UTF-16 编码的序列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
///定义
String name = "Alice";
String greeting = 'Hello, $name!'; // 支持插值
String multiLine = '''
This is a
multi-line
string.
''';
String str1 = "双引号", str2 = '单引号';
String str3 = "str1: $str1 str2:$str2";
String str6 = "ABC的风格";
print(str3);
///常用方法
print(str6.substring(0,3)); //截取
print(str6.indexOf('ABC')); //查找位置
str6.startsWith('A');
str6.contains('B');
str6.split('A');
str6.replaceAll('BC', '');
2. 集合类型
列表 (List)
List 是有序的集合,可以包含重复的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
///定义:
List list = [];
//初始化时添加元素(动态类型)
List<int> numbers = [1, 2, 3, 4, 5];
List<String> names = ['Alice', 'Bob', 'Charlie'];
List list2 = [1, 2, 3, '集合'];
//添加泛型:
List<String> list3 = ['A', 'B', 'C'];
print(list2);
print(list3);
//生产元素方式生成 length: 3
List list4 = List.generate(3, (index) => index * 2);
print(list4);
//遍历方式
for (int i = 0; i < list4.length; i++) {
print(list4[i]);
}
for (var i in list4) {
print(i);
}
list4.forEach((element) {
print(element);
});
//常用方法
list3.add('D');
list2.addAll(list3);
list4.remove('value');
list4.insert(1, 'element');
list4.sublist(1);
list4.indexOf(2);
集合 (Set)
Set 是无序的集合,不允许重复的元素。
1 | /// 1. 创建 Set |
映射 (Map)
Map 是键值对的集合,键和值可以是任何类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//key和value相关联的对象
//key和valuekey是任何类型
//key是唯一的,如果key重复,后面添加的key会被替换
//定义:
Map<String, int> ages = {
'Alice': 25,
'Bob': 30,
'Charlie': 35,
};
//初始化
Map names = {"xiaoming":'小明','xiaomi':'小米'};
print(names);
Map ages = {};
ages['xiaoming'] = 16;
ages['xiaohong'] = 18;
print(ages);
//遍历
ages.forEach((key, value) {
print('key: $key value:$value');
});
//通过遍历一个map 来生成另外一个map
Map ages2 = ages.map((key, value){
return MapEntry(key, value + 1);
});
print(ages2);
for(var key in ages2.keys){
print('key: $key value: ${ages2[key]}');
}
Map<String,int> ages3 = {};
ages3['age1'] = 1;
ages3['age2'] = 2;
print(ages3);
//常用方法
ages3.keys;
ages3.values;
ages3.remove('age1');
ages3.containsKey('age1');
3. 动态类型 (dynamic)
dynamic 类型可以表示任何类型的值。使用 dynamic 类型时,编译器不会进行类型检查。
1 | dynamic anything = 10; |
4. 对象类型 (Object)
Object 是 Dart 中所有类的基类。任何类型的值都可以赋值给 Object 类型的变量。
1 | Object someValue = 10; |
5. 特殊类型 (null)
null 表示一个空值。在 Dart 中,所有类型都可以接受 null 值,除非它们被显式声明为非空类型。
1 | int? nullableInt = null; |
6. 枚举类型 (enum)
enum 是一种特殊的数据类型,用于定义一组命名的常量。
1 | enum Color { |
7. 函数类型 (Function)
函数类型表示一个函数。Dart 允许将函数作为参数传递或作为返回值返回。
1 | typedef BinaryOperation = int Function(int, int); |
8. 泛型类型 (Generics)
泛型允许在定义类、接口和函数时使用类型参数,从而提高代码的复用性和类型安全性。
1 | class Box<T> { |
9. 可空类型和非空类型
Dart 支持可空类型和非空类型。默认情况下,类型是非空的,但可以使用 ? 使其可空。
1 | int nonNullableInt = 10; |
10. 类型推断
Dart 支持类型推断,可以使用 var 或 final 关键字让编译器自动推断变量的类型。
1 | var name = "Alice"; // 推断为 String |
函数/方法
在 Dart 中,函数(或方法)是执行特定任务的代码块。函数可以接受输入参数,执行一系列操作,并返回结果。Dart 支持多种函数类型和特性,包括命名函数、匿名函数、箭头函数、可选参数、命名参数等。以下是对 Dart 中函数的详细讲解:
1. 基本函数定义
一个基本的 Dart 函数定义包括返回类型、函数名、参数列表和函数体。
1 | ReturnType functionName(ParameterType parameter1, ParameterType parameter2, ...) { |
2. 无返回值函数
如果函数没有返回值,可以使用 void 作为返回类型。
1 | void printSum(int a, int b) { |
3. 匿名函数(Lambda 表达式)
匿名函数是没有名字的函数,通常用于回调或作为参数传递。
1 | var add = (int a, int b) { |
4. 箭头函数
箭头函数(Arrow Function)是一种简化的匿名函数形式,适用于只有一条语句的函数。
1 | var add = (int a, int b) => a + b; |
5. 可选参数
Dart 支持两种可选参数:位置可选参数和命名可选参数。
位置可选参数
位置可选参数使用 [] 包裹,并且必须在参数列表的末尾。
1 | void printName(String firstName, [String? middleName, String? lastName]) { |
命名可选参数
命名可选参数使用 {} 包裹,并且可以按任意顺序传递。
1 | void printName(String firstName, {String? middleName, String? lastName}) { |
6. 默认参数值
可以为可选参数设置默认值。
1 | void printName(String firstName, {String middleName = '', String lastName = ''}) { |
7. 函数作为参数
Dart 允许将函数作为参数传递给其他函数。
1 | void performOperation(int a, int b, Function operation) { |
8. 闭包
闭包是一个函数对象,它可以访问其词法范围内的变量,即使函数在其原始范围之外使用。
1 | Function makeAdder(int addBy) { |
9. 高阶函数
高阶函数是接受一个或多个函数作为参数,或者返回一个函数的函数。
1 | List<int> map(List<int> items, int Function(int) f) { |
10. 函数类型
Dart 允许使用函数类型来声明变量,以便存储和传递函数。
1 | typedef int BinaryOperation(int a, int b); |
面向对象
Dart 是一种面向对象的编程语言,支持类和继承等面向对象的特性。面向对象编程(OOP)的核心思想是通过对象的方式来组织代码。以下是 Dart 中面向对象编程的详细讲解:
1. 类和对象
在 Dart 中,类是对象的蓝图。类定义了对象的属性和行为(方法)。
1 | /// 定义类 |
2. 构造函数
构造函数用于创建类的实例。Dart 支持多种构造函数,包括命名构造函数和工厂构造函数。
默认构造函数
1 | class Person { |
命名构造函数
1 | class Person { |
工厂构造函数
工厂构造函数用于返回实例的缓存,或者根据某些条件返回特定的实例。
1 | class Singleton { |
3. 继承
Dart 支持单继承,一个类可以继承自另一个类。
1 | class Animal { |
4. 方法重写
子类可以重写父类的方法。使用 @override 注解可以明确表示重写操作。
1 | class Animal { |
5. 接口和抽象类
Dart 中没有专门的接口关键字,任何类都可以作为接口。通过 implements 关键字可以实现接口。抽象类使用 abstract 关键字定义。
接口
1 | class Flyer { |
抽象类
1 | abstract class Animal { |
6. Mixin
Mixin 是一种在类中复用代码的方法。使用 with 关键字可以将 Mixin 应用于类。
1 | mixin Walker { |
7. 多态
多态允许对象在不同情境下表现出不同的行为。通过继承和接口实现多态。
1 | class Animal { |
8. Getter 和 Setter
Getter 和 Setter 用于访问和修改私有属性。
1 | class Person { |
其他问题
final和const 区别
在 Dart 中,final 和 const 都用于声明不可变变量,但它们之间有一些重要的区别。理解这些区别对于编写高效和正确的 Dart 代码至关重要。
final 关键字
运行时常量:final 变量的值在运行时确定,并且一旦赋值后就不能再改变。
延迟初始化:final 变量可以在声明时不立即初始化,但必须在第一次使用前进行初始化。
适用于对象:final 可以用于任何类型的变量,包括对象和集合。
1
2
3
4
5
6
7
8
9void main() {
final int number;
number = 10; // 可以在运行时赋值
// number = 20; // 错误:无法再次赋值
final List<int> numbers = [1, 2, 3];
numbers.add(4); // 可以修改列表内容,但不能重新赋值给 numbers
// numbers = [5, 6, 7]; // 错误:无法重新赋值
}
const 关键字
编译时常量:const 变量的值在编译时确定,并且一旦赋值后就不能再改变。
立即初始化:const 变量必须在声明时立即初始化,并且初始化表达式必须是编译时常量。
适用于常量对象:const 可以用于任何类型的变量,但通常用于创建不可变的对象和集合。
1
2
3
4
5
6
7
8void main() {
const int number = 10; // 必须在声明时赋值,且值必须是编译时常量
// number = 20; // 错误:无法再次赋值
const List<int> numbers = [1, 2, 3]; // 必须在声明时赋值,且列表内容必须是编译时常量
// numbers.add(4); // 错误:无法修改常量列表
// numbers = [5, 6, 7]; // 错误:无法重新赋值
}
区别总结
- 初始化时机:
final:可以在运行时初始化。
const:必须在编译时初始化。 - 可变性:
final:变量本身不可变,但其引用的对象可能是可变的(例如 final List 可以添加或删除元素)。
const:变量及其引用的对象都是不可变的。 - 适用范围:
final:适用于任何类型的变量。
const:适用于编译时常量,特别是用于创建不可变的对象和集合。
使用场景
final:当你需要在运行时确定一个值,并且希望该值在初始化后不可变时使用。
const:当你有一个在编译时已知的常量值,并且希望该值及其引用的对象都是不可变时使用。
1
2
3
4
5
6
7
8
9
10void main() {
final DateTime now = DateTime.now(); // 运行时确定的时间
// now = DateTime.now(); // 错误:无法重新赋值
const int daysInWeek = 7; // 编译时常量
// daysInWeek = 8; // 错误:无法重新赋值
const List<String> weekdays = ['Mon', 'Tue', 'Wed']; // 编译时常量列表
// weekdays.add('Thu'); // 错误:无法修改常量列表
}
Object、dynamic 和 var 讲解
在 Dart 中,Object、dynamic 和 var 是三个在定义变量时经常遇到的关键字,它们在某些方面有相似之处,但也有重要的不同。理解它们之间的区别对于编写类型安全且高效的 Dart 代码非常重要。
1. Object
Object 是 Dart 所有类的基类。如果一个变量的类型为 Object,它意味着这个变量可以是任何 Dart 对象的实例。
开始于 Dart 2.0,Object 与 Object? 有所不同,前者不允许 null 值,而后者则允许 null。
使用 Object 时,编译时类型检查会生效,你需要在调用任何方法或属性之前进行显式类型转换(强制类型转换)。
1
2
3Object name = 'Dart';
print(name.toUpperCase()); // 错误: Object 类型没有定义 toUpperCase 方法。
print((name as String).toUpperCase()); // 正确: 使用类型转换。
2. dynamic
dynamic 类型的变量可以被赋予任何类型的值,不需要进行显式类型转换即可调用任何方法,因为实际的方法调用是在运行时解析的。
使用 dynamic 会关闭类型检查,这意味着直到运行时才会知道是否正确调用了一个方法或属性,如果调用了不存在的方法或属性,会抛出运行时异常。
dynamic 类型的使用降低了代码的类型安全性,但增加了灵活性。在无法预知变量类型或需要动态行为时可能会用到它。
1
2
3
4dynamic name = 'Dart';
print(name.toUpperCase()); // 正确: 运行时调用,不会在编译时报错。
name = 123;
print(name.toUpperCase()); // 运行时错误: 'int' 没有 'toUpperCase' 方法。
3. var
var 关键字用于声明变量而不显式指定类型。变量的类型由编译器根据赋值来推断,并且在变量首次赋值后确定。
一旦变量的类型被推断出来后,它就固定下来了,之后尝试将不同类型的值赋给该变量会导致编译时错误。
使用 var 可以让编译器自动推断变量类型,保持代码的简洁性,同时不牺牲类型安全。
1
2
3var name = 'Dart'; // 类型被推断为 String。
print(name.toUpperCase()); // 正确: String 类型有 toUpperCase 方法。
name = 123; // 错误: 不能将 int 值赋给 String 类型的变量。
总结
- Object 是一个确切的类型,要求类型安全,是所有 Dart 类的基类。
- dynamic 禁用类型安全的检查,使得在编程时更加灵活但在运行时可能出错。
- var 让编译器基于初始赋值来推断变量的类型,一旦类型确定,就无法改变。
mixin的作用
- 复用代码:mixin 提供了一种复用代码的方式,可以在多个类之间共享相同的行为。
- 避免多重继承的问题:Dart 不支持多重继承,但 mixin 提供了一种替代方式,可以让类拥有多重行为。
- 灵活性:你可以将多个 mixin 混入一个类中,从而组合出多种行为。
1 | mixin MyMixin { |
Dart中静态成员
在 Dart 中,静态成员(包括静态变量和静态方法)是与类而不是与类的实例相关联的。这意味着静态成员可以在类级别上访问,而不需要创建类的实例。静态成员通常用于表示与类本身相关的共享状态或行为。
1. 静态变量
静态变量是类级别的变量,在类加载时初始化。静态变量可以用于存储在多个实例之间共享的数据。
定义和使用静态变量
1 | class Circle { |
2. 静态方法
静态方法是类级别的方法,可以在类本身上调用,而不是在类的实例上调用。静态方法无法访问实例变量或实例方法,但可以访问静态变量和其他静态方法。
定义和使用静态方法
1 | class Calculator { |
3. 静态成员的使用场景
静态成员的使用场景包括但不限于以下几个方面:
- 常量值:例如数学常数(如 π)或配置设置。
- 实用函数:例如不依赖于实例状态的工具函数。
- 计数器或共享状态:例如记录创建的实例数量。
4. 静态块
Dart 中没有专门的静态初始化块,但可以通过定义静态变量时进行初始化来实现类似的效果。
1 | class Config { |
5. 访问控制
静态成员也遵循 Dart 的访问控制规则。可以使用 private 关键字(以 _ 开头的命名方式)使静态成员仅在定义它们的库中可见。
1 | class Internal { |
Dart中的编程小技巧总结
在 Dart 编程中,有一些实用的小技巧可以帮助你编写更高效、优雅和可维护的代码。以下是几个常见的 Dart 编程小技巧总结:
1. 使用 Null-aware 操作符
Dart 提供了多个 null 安全操作符,可以帮助简化处理 null 值的代码:
?? 操作符:用于提供默认值。
1
2String? name;
String displayName = name ?? 'Guest';??= 操作符:如果变量为 null,则赋值。
1
2String? name;
name ??= 'Guest';?. 操作符:用于安全地调用可能为 null 的对象的方法或属性。
1
2String? name;
print(name?.toUpperCase());
2. 使用集合操作符简化列表和集合的操作
Dart 提供了方便的集合操作符来简化列表和集合的操作:
扩展操作符 … 和 …?:
1
2
3List<int> list1 = [1, 2, 3];
List<int> list2 = [4, 5, 6];
List<int> combinedList = [...list1, ...list2]; // [1, 2, 3, 4, 5, 6]集合字面量和条件元素:
1
2bool addExtra = true;
List<int> numbers = [1, 2, 3, if (addExtra) 4]; // [1, 2, 3, 4]
3. 使用级联操作符 (..)
级联操作符可以让你在同一个对象上进行多次操作,而不需要重复引用对象:
1
2
3
4
5
6
7
8
9
10
11
12
13class Builder {
int? value;
void increment() => value = (value ?? 0) + 1;
void reset() => value = 0;
}
void main() {
var builder = Builder()
..increment()
..increment()
..reset();
print(builder.value); // 0
}
4. 使用常量构造函数
尽可能使用常量构造函数 (const) 来定义不可变对象,这样可以提高性能并减少内存开销:
1
2
3
4
5
6
7
8
9
10
11
12class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
const point1 = Point(1, 2);
const point2 = Point(1, 2);
void main() {
print(identical(point1, point2)); // true
}
5. 使用扩展方法
扩展方法可以为现有类添加新的方法,而无需修改类本身:
1
2
3
4
5
6
7
8
9
10extension NumberParsing on String {
int? toIntOrNull() {
return int.tryParse(this);
}
}
void main() {
print('123'.toIntOrNull()); // 123
print('abc'.toIntOrNull()); // null
}
6. 使用 as 和 is 进行类型检查和转换
类型检查 is:
1
2
3
4dynamic value = 'hello';
if (value is String) {
print('The value is a string');
}类型转换 as
1
2
3dynamic value = 'hello';
String str = value as String;
print(str.toUpperCase()); // HELLO
7. 使用解构赋值
虽然 Dart 没有直接支持解构赋值,但你可以使用 List 和 Map 来实现类似的效果:
列表解构:
1
2
3
4
5List<int> numbers = [1, 2, 3];
var [a, b, c] = numbers;
print(a); // 1
print(b); // 2
print(c); // 3映射解构:
1
2
3
4Map<String, int> map = {'x': 1, 'y': 2};
var {'x': x, 'y': y} = map;
print(x); // 1
print(y); // 2
8. 使用 assert 进行调试
在开发过程中,可以使用 assert 来确保某些条件为真。如果条件为假,程序会在调试模式下抛出异常:
1
2
3
4
5void main(List<String> args) {
int age = 15;
assert(age >= 18, 'Age must be at least 18.');
print('Age is $age');
}
9. 使用 async 和 await 进行异步编程
Dart 支持异步编程,使用 async 和 await 可以让你的代码更加简洁和易读:
1
2
3
4
5
6
7
8
9
10
11
12Future<void> fetchData() async {
var data = await getDataFromServer();
print(data);
}
Future<String> getDataFromServer() async {
return Future.delayed(Duration(seconds: 2), () => "Server Data");
}
void main() {
fetchData();
}
10. 使用 late 延迟初始化
使用 late 关键字来延迟对象的初始化,直到第一次使用它:
1
2
3
4
5
6
7
8
9
10
11
12
13class Example {
late String value = _computeValue();
String _computeValue() {
print('Computing value...');
return 'Hello, late!';
}
}
void main() {
var example = Example();
print(example.value); // Computing value... Hello, late!
}