Dart语言学习

Dart语言特点

Dart 是一种由 Google 开发的编程语言,最初在 2011 年发布。它被设计为一种客户端优化的语言,适用于构建快速、高效的应用程序,特别是在 Web 和移动应用开发领域。以下是 Dart 语言的一些主要特点:

  1. 面向对象
    Dart 是一种面向对象的语言,支持类和继承。它使用基于类的编程,并提供了丰富的内置类库。
  1. 强类型
    Dart 是一种强类型语言,支持静态类型检查。它也支持类型推断,可以使用 var 关键字让编译器自动推断变量的类型。

  2. 可空类型和非空类型
    Dart 支持可空类型和非空类型。默认情况下,类型是非空的,但可以使用 ? 使其可空。这有助于减少空指针异常。

  3. 异步编程
    Dart 提供了强大的异步编程支持,包括 FutureStream 类型,以及 asyncawait 关键字。这使得处理异步操作(如网络请求)变得简单和直观。

  4. 单线程模型
    Dart 运行在单线程模型上,但通过事件循环和异步编程模型,可以高效地处理并发任务。

  5. JIT 和 AOT 编译
    Dart 支持即时编译(JIT)和提前编译(AOT)。JIT 编译在开发过程中提供快速的开发周期和热重载功能,而 AOT 编译则生成高效的机器码,适用于生产环境。

  6. 跨平台
    Dart 可以编译为 JavaScript,使其适用于 Web 开发。同时,Dart 也是 Flutter 框架的主要编程语言,用于构建跨平台的移动应用(iOS 和 Android)、桌面应用(Windows、macOS、Linux)和 Web 应用。

  7. 热重载
    Dart 支持热重载功能,可以在开发过程中快速查看代码更改的效果,大大提高了开发效率。

  8. 丰富的标准库
    Dart 提供了丰富的标准库,涵盖了从基本数据结构到网络编程、文件 I/O 等多个领域。

  9. 扩展性
    Dart 支持扩展方法和自定义操作符,使得代码更具表达力和灵活性。

  10. 泛型
    Dart 支持泛型编程,可以在定义类、接口和函数时使用类型参数,提高代码的复用性和类型安全性。

  11. 工具链
    Dart 提供了强大的工具链,包括 Dart DevTools(用于调试和性能分析)、DartPad(在线 Dart 编辑器)等。

  12. 易于学习
    Dart 的语法借鉴了多种编程语言,特别是 Java 和 JavaScript,因此对于有这些语言经验的开发者来说,学习曲线相对平缓。

数据类型

Dart 是一种强类型语言,提供了多种内置数据类型来支持不同的编程需求。以下是对 Dart 中主要数据类型的详细讲解:

1. 基本数据类型

num类型(子类int和double)

  1. 整型 (int)
    int 表示整数,可以是正数、负数或零。Dart 中的 int 类型在 Dart VM 中通常是 64 位有符号整数。

    1
    2
    int age = 25;
    int negativeNumber = -10;
  2. 浮点型 (double)
    double 表示双精度浮点数。

    1
    2
    double height = 5.9;
    double temperature = -20.5;
  • 常用方法

    1
    2
    3
    print(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
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
45
46
47
48
49
50
51
52
53
/// 1. 创建 Set
Set<int> uniqueNumbers = {1, 2, 3, 4, 5};
Set<String> uniqueNames = {'Alice', 'Bob', 'Charlie'};
// 使用 Set 构造函数创建 Set
Set<String> names = Set();
names.add('Alice');
names.add('Bob');
names.add('Charlie');

/// 2. 添加元素
Set<int> numbers = {1, 2, 3};
numbers.add(4); // 添加单个元素
numbers.addAll([5, 6, 7]); // 添加多个元素

/// 3. 移除元素
Set<int> numbers = {1, 2, 3, 4, 5};
numbers.remove(3); // 移除单个元素
numbers.removeAll([1, 5]); // 移除多个元素

/// 4. 检查元素
Set<String> names = {'Alice', 'Bob', 'Charlie'};
bool hasAlice = names.contains('Alice'); // true
bool hasDavid = names.contains('David'); // false

/// 5. 遍历 Set
Set<int> numbers = {1, 2, 3, 4, 5};
// 使用 for-in 循环遍历
for (var number in numbers) {
print(number);
}
// 使用 forEach 方法遍历
numbers.forEach((number) => print(number));

/// 6. 集合操作
Set<int> set1 = {1, 2, 3, 4};
Set<int> set2 = {3, 4, 5, 6};
// 并集
Set<int> unionSet = set1.union(set2); // {1, 2, 3, 4, 5, 6}
// 交集
Set<int> intersectionSet = set1.intersection(set2); // {3, 4}
// 差集
Set<int> differenceSet = set1.difference(set2); // {1, 2}

/// 7. 其他常用方法
//isEmpty 和 isNotEmpty:检查 Set 是否为空。
//length:获取 Set 中元素的数量。
//clear:清空 Set 中的所有元素。
Set<int> numbers = {1, 2, 3};
bool isEmpty = numbers.isEmpty; // false
bool isNotEmpty = numbers.isNotEmpty; // true
int length = numbers.length; // 3
numbers.clear(); // 清空 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
2
3
dynamic anything = 10;
anything = "Hello";
anything = true;

4. 对象类型 (Object)

Object 是 Dart 中所有类的基类。任何类型的值都可以赋值给 Object 类型的变量。

1
2
3
Object someValue = 10;
someValue = "Hello";
someValue = true;

5. 特殊类型 (null)

null 表示一个空值。在 Dart 中,所有类型都可以接受 null 值,除非它们被显式声明为非空类型。

1
2
int? nullableInt = null;
String? nullableString = null;

6. 枚举类型 (enum)

enum 是一种特殊的数据类型,用于定义一组命名的常量。

1
2
3
4
5
6
7
enum Color {
red,
green,
blue,
}

Color favoriteColor = Color.blue;

7. 函数类型 (Function)

函数类型表示一个函数。Dart 允许将函数作为参数传递或作为返回值返回。

1
2
3
4
5
6
7
typedef BinaryOperation = int Function(int, int);

int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;

BinaryOperation operation = add;
print(operation(2, 3)); // 输出 5

8. 泛型类型 (Generics)

泛型允许在定义类、接口和函数时使用类型参数,从而提高代码的复用性和类型安全性。

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
class Box<T> {
T value;
Box(this.value);
}

Box<int> intBox = Box(10);
Box<String> stringBox = Box("Hello");

//泛型类
class Cache<T> {
final Map<String, T> _cache = {};

void setItem(String key, T value) {
_cache[key] = value;
}

T? getItem(String key) {
return _cache[key];
}
}

//类型约束 T extends Person
class Member<T extends Person> {
final T _person;

Member(this._person);

String fixedName(){
return '${_person.name}';
}
}

9. 可空类型和非空类型

Dart 支持可空类型和非空类型。默认情况下,类型是非空的,但可以使用 ? 使其可空。

1
2
int nonNullableInt = 10;
int? nullableInt = null;

10. 类型推断

Dart 支持类型推断,可以使用 var 或 final 关键字让编译器自动推断变量的类型。

1
2
var name = "Alice"; // 推断为 String
final age = 25; // 推断为 int

函数/方法

在 Dart 中,函数(或方法)是执行特定任务的代码块。函数可以接受输入参数,执行一系列操作,并返回结果。Dart 支持多种函数类型和特性,包括命名函数、匿名函数、箭头函数、可选参数、命名参数等。以下是对 Dart 中函数的详细讲解:

1. 基本函数定义

一个基本的 Dart 函数定义包括返回类型、函数名、参数列表和函数体。

1
2
3
4
5
6
7
8
9
ReturnType functionName(ParameterType parameter1, ParameterType parameter2, ...) {
// 函数体
return returnValue; // 如果有返回类型
}

//示例
int add(int a, int b) {
return a + b;
}

2. 无返回值函数

如果函数没有返回值,可以使用 void 作为返回类型。

1
2
3
void printSum(int a, int b) {
print(a + b);
}

3. 匿名函数(Lambda 表达式)

匿名函数是没有名字的函数,通常用于回调或作为参数传递。

1
2
3
4
5
6
var add = (int a, int b) {
return a + b;
};

// 使用匿名函数
print(add(2, 3)); // 输出 5

4. 箭头函数

箭头函数(Arrow Function)是一种简化的匿名函数形式,适用于只有一条语句的函数。

1
2
3
4
5
var add = (int a, int b) => a + b;

// 使用箭头函数
print(add(2, 3)); // 输出 5

5. 可选参数

Dart 支持两种可选参数:位置可选参数和命名可选参数。

位置可选参数

位置可选参数使用 [] 包裹,并且必须在参数列表的末尾。

1
2
3
4
5
6
7
8
void printName(String firstName, [String? middleName, String? lastName]) {
print('$firstName ${middleName ?? ''} ${lastName ?? ''}');
}

// 调用函数
printName('John'); // 输出 John
printName('John', 'Doe'); // 输出 John Doe
printName('John', 'Doe', 'Smith'); // 输出 John Doe Smith

命名可选参数

命名可选参数使用 {} 包裹,并且可以按任意顺序传递。

1
2
3
4
5
6
7
8
9
void printName(String firstName, {String? middleName, String? lastName}) {
print('$firstName ${middleName ?? ''} ${lastName ?? ''}');
}

// 调用函数
printName('John'); // 输出 John
printName('John', lastName: 'Doe'); // 输出 John Doe
printName('John', middleName: 'Doe', lastName: 'Smith'); // 输出 John Doe Smith

6. 默认参数值

可以为可选参数设置默认值。

1
2
3
4
5
6
7
void printName(String firstName, {String middleName = '', String lastName = ''}) {
print('$firstName $middleName $lastName');
}

// 调用函数
printName('John'); // 输出 John
printName('John', lastName: 'Doe'); // 输出 John Doe

7. 函数作为参数

Dart 允许将函数作为参数传递给其他函数。

1
2
3
4
5
6
7
void performOperation(int a, int b, Function operation) {
print(operation(a, b));
}

// 调用函数
performOperation(2, 3, (int a, int b) => a + b); // 输出 5
performOperation(2, 3, (int a, int b) => a * b); // 输出 6

8. 闭包

闭包是一个函数对象,它可以访问其词法范围内的变量,即使函数在其原始范围之外使用。

1
2
3
4
5
6
7
8
9
10
11
Function makeAdder(int addBy) {
return (int i) => i + addBy;
}

void main() {
var add2 = makeAdder(2);
var add3 = makeAdder(3);

print(add2(5)); // 输出 7
print(add3(5)); // 输出 8
}

9. 高阶函数

高阶函数是接受一个或多个函数作为参数,或者返回一个函数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
List<int> map(List<int> items, int Function(int) f) {
var result = <int>[];
for (var item in items) {
result.add(f(item));
}
return result;
}

void main() {
var numbers = [1, 2, 3, 4, 5];
var doubled = map(numbers, (int x) => x * 2);
print(doubled); // 输出 [2, 4, 6, 8, 10]
}

10. 函数类型

Dart 允许使用函数类型来声明变量,以便存储和传递函数。

1
2
3
4
5
6
7
8
9
10
11
12
typedef int BinaryOperation(int a, int b);

int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;

void main() {
BinaryOperation operation = add;
print(operation(2, 3)); // 输出 5

operation = subtract;
print(operation(2, 3)); // 输出 -1
}

面向对象

Dart 是一种面向对象的编程语言,支持类和继承等面向对象的特性。面向对象编程(OOP)的核心思想是通过对象的方式来组织代码。以下是 Dart 中面向对象编程的详细讲解:

1. 类和对象

在 Dart 中,类是对象的蓝图。类定义了对象的属性和行为(方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// 定义类
class Person {
// 属性
String? name;
int? age;

// 构造函数
Person(this.name, this.age);

// 方法
void greet() {
print("Hello, my name is $name and I am $age years old.");
}
}

/// 创建对象
void main() {
Person person = Person('Alice', 30);
person.greet(); // 输出: Hello, my name is Alice and I am 30 years old.
}

2. 构造函数

构造函数用于创建类的实例。Dart 支持多种构造函数,包括命名构造函数和工厂构造函数。

默认构造函数

1
2
3
4
5
6
7
class Person {
String? name;
int? age;

// 默认构造函数
Person(this.name, this.age);
}

命名构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
String? name;
int? age;

Person(this.name, this.age);

// 命名构造函数
Person.withName(this.name) {
age = 0;
}
}

void main() {
Person person = Person.withName('Bob');
print(person.age); // 输出: 0
}

工厂构造函数

工厂构造函数用于返回实例的缓存,或者根据某些条件返回特定的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton {
static final Singleton _instance = Singleton._internal();

factory Singleton() {
return _instance;
}

Singleton._internal();
}

void main() {
Singleton s1 = Singleton();
Singleton s2 = Singleton();
print(identical(s1, s2)); // 输出: true
}

3. 继承

Dart 支持单继承,一个类可以继承自另一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
void eat() {
print("Animal is eating.");
}
}

class Dog extends Animal {
void bark() {
print("Dog is barking.");
}
}

void main() {
Dog dog = Dog();
dog.eat(); // 输出: Animal is eating.
dog.bark(); // 输出: Dog is barking.
}

4. 方法重写

子类可以重写父类的方法。使用 @override 注解可以明确表示重写操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
void eat() {
print("Animal is eating.");
}
}

class Dog extends Animal {
@override
void eat() {
print("Dog is eating.");
}
}

void main() {
Dog dog = Dog();
dog.eat(); // 输出: Dog is eating.
}

5. 接口和抽象类

Dart 中没有专门的接口关键字,任何类都可以作为接口。通过 implements 关键字可以实现接口。抽象类使用 abstract 关键字定义。

接口

1
2
3
4
5
6
7
8
9
10
11
class Flyer {
void fly(){
}
}

class Bird implements Flyer {
@override
void fly() {
print("Bird is flying.");
}
}

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Animal {
void makeSound();
}

class Dog extends Animal {
@override
void makeSound() {
print("Dog barks.");
}
}

void main() {
Dog dog = Dog();
dog.makeSound(); // 输出: Dog barks.
}

6. Mixin

Mixin 是一种在类中复用代码的方法。使用 with 关键字可以将 Mixin 应用于类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mixin Walker {
void walk() {
print("Walking...");
}
}

class Human with Walker {
void speak() {
print("Speaking...");
}
}

void main() {
Human human = Human();
human.walk(); // 输出: Walking...
human.speak(); // 输出: Speaking...
}

7. 多态

多态允许对象在不同情境下表现出不同的行为。通过继承和接口实现多态。

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
class Animal {
void makeSound() {
print("Animal sound.");
}
}

class Dog extends Animal {
@override
void makeSound() {
print("Dog barks.");
}
}

class Cat extends Animal {
@override
void makeSound() {
print("Cat meows.");
}
}

void main() {
Animal dog = Dog();
Animal cat = Cat();

dog.makeSound(); // 输出: Dog barks.
cat.makeSound(); // 输出: Cat meows.
}

8. Getter 和 Setter

Getter 和 Setter 用于访问和修改私有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
String? _name;

Person(this._name);

// Getter
String? get name => _name;

// Setter
set name(String? newName) {
_name = newName;
}
}

void main() {
Person p = Person("Alice");
print(p.name); // 输出: Alice
p.name = "Bob";
print(p.name); // 输出: Bob
}

其他问题

final和const 区别

在 Dart 中,final 和 const 都用于声明不可变变量,但它们之间有一些重要的区别。理解这些区别对于编写高效和正确的 Dart 代码至关重要。

final 关键字

  • 运行时常量:final 变量的值在运行时确定,并且一旦赋值后就不能再改变。

  • 延迟初始化:final 变量可以在声明时不立即初始化,但必须在第一次使用前进行初始化。

  • 适用于对象:final 可以用于任何类型的变量,包括对象和集合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void 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
    8
    void main() {
    const int number = 10; // 必须在声明时赋值,且值必须是编译时常量
    // number = 20; // 错误:无法再次赋值

    const List<int> numbers = [1, 2, 3]; // 必须在声明时赋值,且列表内容必须是编译时常量
    // numbers.add(4); // 错误:无法修改常量列表
    // numbers = [5, 6, 7]; // 错误:无法重新赋值
    }

区别总结

  1. 初始化时机:
    final:可以在运行时初始化。
    const:必须在编译时初始化。
  2. 可变性:
    final:变量本身不可变,但其引用的对象可能是可变的(例如 final List 可以添加或删除元素)。
    const:变量及其引用的对象都是不可变的。
  3. 适用范围:
    final:适用于任何类型的变量。
    const:适用于编译时常量,特别是用于创建不可变的对象和集合。

使用场景

  • final:当你需要在运行时确定一个值,并且希望该值在初始化后不可变时使用。

  • const:当你有一个在编译时已知的常量值,并且希望该值及其引用的对象都是不可变时使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void 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
    3
    Object name = 'Dart';
    print(name.toUpperCase()); // 错误: Object 类型没有定义 toUpperCase 方法。
    print((name as String).toUpperCase()); // 正确: 使用类型转换。

2. dynamic

  • dynamic 类型的变量可以被赋予任何类型的值,不需要进行显式类型转换即可调用任何方法,因为实际的方法调用是在运行时解析的。

  • 使用 dynamic 会关闭类型检查,这意味着直到运行时才会知道是否正确调用了一个方法或属性,如果调用了不存在的方法或属性,会抛出运行时异常。

  • dynamic 类型的使用降低了代码的类型安全性,但增加了灵活性。在无法预知变量类型或需要动态行为时可能会用到它。

    1
    2
    3
    4
    dynamic name = 'Dart';
    print(name.toUpperCase()); // 正确: 运行时调用,不会在编译时报错。
    name = 123;
    print(name.toUpperCase()); // 运行时错误: 'int' 没有 'toUpperCase' 方法。

3. var

  • var 关键字用于声明变量而不显式指定类型。变量的类型由编译器根据赋值来推断,并且在变量首次赋值后确定。

  • 一旦变量的类型被推断出来后,它就固定下来了,之后尝试将不同类型的值赋给该变量会导致编译时错误。

  • 使用 var 可以让编译器自动推断变量类型,保持代码的简洁性,同时不牺牲类型安全。

    1
    2
    3
    var name = 'Dart'; // 类型被推断为 String。
    print(name.toUpperCase()); // 正确: String 类型有 toUpperCase 方法。
    name = 123; // 错误: 不能将 int 值赋给 String 类型的变量。

总结

  • Object 是一个确切的类型,要求类型安全,是所有 Dart 类的基类。
  • dynamic 禁用类型安全的检查,使得在编程时更加灵活但在运行时可能出错。
  • var 让编译器基于初始赋值来推断变量的类型,一旦类型确定,就无法改变。

mixin的作用

  • 复用代码:mixin 提供了一种复用代码的方式,可以在多个类之间共享相同的行为。
  • 避免多重继承的问题:Dart 不支持多重继承,但 mixin 提供了一种替代方式,可以让类拥有多重行为。
  • 灵活性:你可以将多个 mixin 混入一个类中,从而组合出多种行为。
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
mixin MyMixin {
// 可以定义属性
String get description => "This is a mixin";

// 可以定义方法
void mixinMethod() {
print('Method from MyMixin');
}
}

class MyBaseClass {
void baseMethod() {
print('Method from MyBaseClass');
}
}

class MyClass extends MyBaseClass with MyMixin {
void myMethod() {
print('Method from MyClass');
}
}

void main() {
var myObject = MyClass();
myObject.baseMethod(); // Method from MyBaseClass
myObject.mixinMethod(); // Method from MyMixin
print(myObject.description); // This is a mixin
myObject.myMethod(); // Method from MyClass
}

Dart中静态成员

在 Dart 中,静态成员(包括静态变量和静态方法)是与类而不是与类的实例相关联的。这意味着静态成员可以在类级别上访问,而不需要创建类的实例。静态成员通常用于表示与类本身相关的共享状态或行为。

1. 静态变量

静态变量是类级别的变量,在类加载时初始化。静态变量可以用于存储在多个实例之间共享的数据。

定义和使用静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Circle {
// 静态变量
static double pi = 3.14159;

// 实例变量
double radius;

Circle(this.radius);

// 实例方法
double area() {
return pi * radius * radius; // 使用静态变量
}
}

void main() {
// 静态变量通过类名访问
print(Circle.pi); // 输出: 3.14159

// 创建实例
Circle c1 = Circle(5);
print(c1.area()); // 输出: 78.53975
}

2. 静态方法

静态方法是类级别的方法,可以在类本身上调用,而不是在类的实例上调用。静态方法无法访问实例变量或实例方法,但可以访问静态变量和其他静态方法。

定义和使用静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Calculator {
// 静态方法
static int add(int a, int b) {
return a + b;
}

static int subtract(int a, int b) {
return a - b;
}
}

void main() {
// 静态方法通过类名调用
print(Calculator.add(5, 3)); // 输出: 8
print(Calculator.subtract(5, 3)); // 输出: 2
}

3. 静态成员的使用场景

静态成员的使用场景包括但不限于以下几个方面:

  • 常量值:例如数学常数(如 π)或配置设置。
  • 实用函数:例如不依赖于实例状态的工具函数。
  • 计数器或共享状态:例如记录创建的实例数量。

4. 静态块

Dart 中没有专门的静态初始化块,但可以通过定义静态变量时进行初始化来实现类似的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Config {
static String? appName;
static int? maxConnections;

// 静态初始化
static void initialize() {
appName = "MyApp";
maxConnections = 100;
}
}

void main() {
// 在使用静态变量之前进行初始化
Config.initialize();
print(Config.appName); // 输出: MyApp
print(Config.maxConnections); // 输出: 100
}

5. 访问控制

静态成员也遵循 Dart 的访问控制规则。可以使用 private 关键字(以 _ 开头的命名方式)使静态成员仅在定义它们的库中可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Internal {
// 私有静态变量
static int _counter = 0;

// 私有静态方法
static void _increment() {
_counter++;
}

// 公共静态方法
static void incrementCounter() {
_increment();
print(_counter);
}
}

void main() {
// 无法直接访问私有静态成员
// Internal._counter; // 错误
// Internal._increment(); // 错误

// 通过公共静态方法访问
Internal.incrementCounter(); // 输出: 1
}

Dart中的编程小技巧总结

在 Dart 编程中,有一些实用的小技巧可以帮助你编写更高效、优雅和可维护的代码。以下是几个常见的 Dart 编程小技巧总结:

1. 使用 Null-aware 操作符

Dart 提供了多个 null 安全操作符,可以帮助简化处理 null 值的代码:

  • ?? 操作符:用于提供默认值。

    1
    2
    String? name;
    String displayName = name ?? 'Guest';
  • ??= 操作符:如果变量为 null,则赋值。

    1
    2
    String? name;
    name ??= 'Guest';
  • ?. 操作符:用于安全地调用可能为 null 的对象的方法或属性。

    1
    2
    String? name;
    print(name?.toUpperCase());

2. 使用集合操作符简化列表和集合的操作

Dart 提供了方便的集合操作符来简化列表和集合的操作:

  • 扩展操作符 … 和 …?:

    1
    2
    3
    List<int> list1 = [1, 2, 3];
    List<int> list2 = [4, 5, 6];
    List<int> combinedList = [...list1, ...list2]; // [1, 2, 3, 4, 5, 6]
  • 集合字面量和条件元素:

    1
    2
    bool 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
13
class 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
12
class 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
10
extension 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
    4
    dynamic value = 'hello';
    if (value is String) {
    print('The value is a string');
    }
  • 类型转换 as

    1
    2
    3
    dynamic value = 'hello';
    String str = value as String;
    print(str.toUpperCase()); // HELLO

7. 使用解构赋值

虽然 Dart 没有直接支持解构赋值,但你可以使用 List 和 Map 来实现类似的效果:

  • 列表解构:

    1
    2
    3
    4
    5
    List<int> numbers = [1, 2, 3];
    var [a, b, c] = numbers;
    print(a); // 1
    print(b); // 2
    print(c); // 3
  • 映射解构:

    1
    2
    3
    4
    Map<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
5
void 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
12
Future<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
13
class 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!
}