目录
因为本人从事后端开发,本文只是用于记录自己学习Dart语言的笔记,可能有些地方不会很详细,请大家谅解。
1.入口方法
与其他语言没有什么差别
void main(){
print("hello Dert");
}
2.Dart变量
Dart中的变量用于存储数据,可以通过不同的关键字来声明。具体如下:
var声明变量:
最常用的方式,Dart会根据变量的初始值自动推断其类型。
例如:
var name = 'Bob';
在这里,变量name
的类型被推断为String
。
指定变量类型
也可以显式地声明变量的类型,比如
String name = 'Bob';
这样可以让代码更加清晰
Object或dynamic:
如果变量可能会有多种类型的值可以将其类型声明为
Object
或者使用dynamic
关键字。例如:
Object name = 'Bob';
这样name
就可以引用任何类型的对象。
final和const关键字:
final
关键字用于声明一个只能被赋值一次的变量,
const
关键字用于声明一个常量,这意味着它的值在编译时就确定了。例如:
final age = 30;
const pi = 3.14159;
。
在Dart中,所有的变量都是通过引用来存储值的,而不是直接存储值本身。这意味着当你创建一个变量并给它赋值时,实际上是将变量与内存中的一个对象关联起来。
3.命名规范
类型命名:使用UpperCamelCase风格来命名类型,例如MyClass。
库和源文件名:在库、package、文件夹和源文件中使用lowercase_with_underscores方式命名,例如my_library.dart。
其他标识符:对于非类型的标识符,包括变量、函数、属性等,应使用lowerCamelCase风格命名,例如myVariable。
常量命名:推荐使用lowerCamelCase风格来命名常量。
缩写词处理:对于缩写单词,如果字符较多,应将其视为普通单词处理,例如HttpsFtp。而对于两个字母的缩写,可以根据可读性决定是全部大写还是大写驼峰,例如IO或Io。
4.数据类型
void main() {
// String:字符串类型,用于存储文本数据。例如:
String name = 'zhiHao';
String message = 'Hello, ${name}!'; // 使用插值表达式
print(message); // 输出: Hello, zhiHao!
// int:整数类型,用于存储整数值。例如:
int age = 30;
print(age); // 输出: 30
// double:双精度浮点数类型,用于存储小数值。例如:
double pi = 3.14159;
print(pi); // 输出: 3.14159
// double 可以用来存储int类型
double intDb = 3;
print(intDb); // 输出:3
// bool:布尔类型,用于表示真或假。例如:
bool isTrue = true;
bool isFalse = false;
print(isTrue); // 输出: true
print(isFalse); // 输出: false
// List:列表类型,用于存储一组有序的元素。例如:
List<int> numbers = [1, 2, 3, 4, 5];
print(numbers); // 输出: [1, 2, 3, 4, 5]
var strList = List.filled(1, ''); // filled:生成固定长度集合,不能使用add方法,入参为(数组大小,默认值)
// strList.add('张三'); 错误写法
strList[0] = '张三';
print(strList); // 输出: [张三]
// Map:映射类型,用于存储键值对的集合。例如:
Map<String, int> ages = {
'Alice': 25,
'Bob': 30,
'Charlie': 35,
};
print(ages['Alice']); // 输出: 25
print(ages); // 输出: {Alice: 25, Bob: 30, Charlie: 35}
var agesMap = new Map();
agesMap['Alice'] = '25';
print(agesMap); // 输出: {Alice: 25}
// Set:集合类型,用于存储一组无序且不重复的元素。例如:
Set<String> names = {'John', 'Jane', 'Jack'};
print(names); // 输出: {John, Jane, Jack}
// dynamic:动态类型,可以存储任何类型的值。例如:
dynamic data = 'Hello';
data = 42;
data = true;
print(data); // 输出: true
}
5.基本运算
Dart支持多种运算符,包括算术运算符、关系运算符、逻辑运算符、位运算符等。以下是一些常见运算符的示例:
-
算术运算符:
- 加法(+):
int sum = 1 + 2;
- 减法(-):
int difference = 5 - 3;
- 乘法(*):
int product = 7 * 4;
- 除法(/):
double division = 10 / 3;
- 取模(%):
int remainder = 11 % 3;
- 加法(+):
-
关系运算符:
- 等于(==):
bool isEqual = (5 == 5);
- 不等于(!=):
bool isNotEqual = (5 != 6);
- 大于(>):
bool isGreater = (7 > 4);
- 小于(<):
bool isLess = (3 < 8);
- 大于等于(>=):
bool isGreaterOrEqual = (5 >= 5);
- 小于等于(<=):
bool isLessOrEqual = (6 <= 9);
- 等于(==):
-
逻辑运算符:
- 与(&&):
bool isTrue = (true && false);
- 或(||):
bool isTrue = (true || false);
- 非(!):
bool isFalse = (!true);
- 与(&&):
-
位运算符:
- 按位与(&):
int result = (5 & 3);
- 按位或(|):
int result = (5 | 3);
- 按位异或(^):
int result = (5 ^ 3);
- 按位取反(~):
int result = (~5);
- 左移(<<):
int result = (5 << 2);
- 右移(>>):
int result = (5 >> 2);
- 按位与(&):
5.1 赋值运算
void main() {
// 单个变量赋值:
int x = 5;
print(x); // 输出:5
// 多个变量同时赋值:
int a, b, c;
a = b = c = 10;
print(a); // 输出:10
print(b); // 输出:10
print(c); // 输出:10
// 加法赋值(+=):
int sum = 10;
sum += 5;
print(sum); // 输出:15
// 减法赋值(-=):
int difference = 10;
difference -= 3;
print(difference); // 输出:7
// 乘法赋值(*=):
int product = 5;
product *= 2;
print(product); // 输出:10
// 除法赋值(/=):
double division = 10;
division /= 3;
print(division); // 输出:3.3333333333333335
// 取模赋值(%=):
int remainder = 11;
remainder %= 3;
print(remainder); // 输出:2
// 判断为空之后赋值
dynamic nullTestNum;
nullTestNum ??= 5;
print(nullTestNum); // 输出:5
}
5.2 条件表达式
void main() {
// if-else语句:
String weather = 'sunny';
if (weather == 'sunny') {
print('It is a sunny day!');
} else {
print('It is not a sunny day.');
}
// 三元运算符:
int age = 20;
String message = (age >= 18) ? 'You are an adult.' : 'You are a minor.';
print(message); // 输出:You are an adult.
// 逻辑运算符:
bool isAdult = true;
bool hasLicense = false;
if (isAdult && hasLicense) {
print('You can drive.');
} else {
print('You cannot drive.');
}
// switch语句:
int dayOfWeek = 3;
switch (dayOfWeek) {
case 1:
print('Monday');
break;
case 2:
print('Tuesday');
break;
case 3:
print('Wednesday');
break;
default:
print('Invalid day of the week.');
}
}
5.3 类型转换
// 将int类型转换为double类型:
void main() {
int num = 10;
double result = num.toDouble();
print(result); // 输出:10.0
}
// 将double类型转换为int类型:
void main() {
double num = 10.5;
int result = num.toInt();
print(result); // 输出:10
}
// 将String类型转换为int类型:
void main() {
String str = '123';
int result = int.parse(str);
print(result); // 输出:123
}
// 将String类型转换为double类型:
void main() {
String str = '123.45';
double result = double.parse(str);
print(result); // 输出:123.45
}
// 将int类型转换为String类型:
void main() {
int num = 123;
String result = num.toString();
print(result); // 输出:"123"
}
// 将double类型转换为String类型:
void main() {
double num = 123.45;
String result = num.toString();
print(result); // 输出:"123.45"
}
5.4 循环
void main() {
// 普通for循环, continue跳出本次循环,break结束循环
for (var i = 0; i < 3; i++) {
if (i == 1) {
continue;
}
if (i == 2) {
break;
}
print(i); // 输出0
}
// 增强for循环
List<int> numList = [0, 1, 2, 3];
numList.forEach((element) {
print(element);
});
for (var element in numList) {
print(element);
}
// while循环
var i = 0;
while (i < 5) {
i++;
print(i);
}
// do while循环
var j = 5;
do {
j--;
print(j);
} while (j < 0);
}
5.5 常用函数
/**
* 1.添加required修饰后,参数为必填参数
* 2.由于无需按照顺序传参,{}补分需指定字段名称
*/
void main() {
sayHello(); // 输出 hello Dart
printInfo("zhangsan"); // 输出 name:张三, City: shenzhen
printInfo("zhangsan", 23); // 输出 Name: zhangsan Age:23. City: shenzhen
printInfo1("zhangsan", city: 'shenzhen');
// 箭头函数
int add(int a, int b) => a + b;
print(add(1, 2));
// 静态方法
MyClass.myStaticMethod();
}
void sayHello() {
print("hello Dart");
}
// 在参数中用中括号[]age、city为可选参数,调用时可传可不传,age无默认值,city有默认值,
void printInfo(String name, [int? age, String city = 'shenzhen']) {
print('Name: $name');
if (age != null) {
print('Age: $age');
}
if (city != null) {
print('City: $city');
}
}
// 在{ }外的参数必传,而{ }里的参数为可选参数,即可传可不传,可以不按照{ }里的参数顺序传参
void printInfo1(String name, {int? age, String? city}) {
print('Name: $name');
if (age != null) {
print('Age: $age');
}
if (city != null) {
print('City: $city');
}
}
// 静态方法
class MyClass {
static void myStaticMethod() {
print('This is a static method');
}
}
异步函数:
// 异步函数
Future<String> fetchData() async {
// 暂停2秒
await Future.delayed(Duration(seconds: 2));
return 'Data fetched';
}
void main() async {
String data = await fetchData();
print(data); // 输出 Data fetched
}
匿名函数
void main() {
fun();
fun1("张三");
}
// 无参匿名函数
var fun = () {
print("Hello"); // 输出:Hello
};
// 带参匿名函数
var fun1 = (String name) {
print("Hello$name"); // 输出:Hello 张三
};
自执行方法
// 通常用于立即执行一些初始化代码或设置,而不是作为常规的函数调用。
void main() {
(() {
print('Hello, World!');
})();
}
6.类
6.1 属性
在Dart中,类的属性不一定要加?
(空安全)标记。
Dart语言在2.12版本之后引入了空安全(sound null safety)特性,这意味着所有的变量都有了非空类型,除非显式地声明为可空类型。在这个版本之前,Dart中的变量默认是可以为null的,因此不需要特别的标记。但是为了提高代码的健壮性和清晰度,建议在定义属性时明确其是否可为null。
以下是关于Dart类属性的一些要点:
- 非空属性:如果一个属性被声明为非空类型,如
int
、String
等,那么它必须在构造函数或初始化列表中被赋值,否则会报错。 - 可空属性:如果一个属性被声明为可空类型,如
int?
、String?
等,那么它可以在构造函数中被赋值,也可以稍后在类的其它方法中赋值。使用late
关键字可以延迟初始化这些属性。 - 私有属性:在Dart中,可以通过在属性名前加上下划线
_
来表示私有属性。但是,要实现真正的私有性,需要将包含私有属性的类放在单独的文件中。 - getter和setter:Dart提供了getter和setter方法来访问和修改属性的值。对于final修饰的属性,只有getter方法;而对于可变属性,则同时有getter和setter方法。
- this关键字:在某些情况下,如构造函数中或当参数名与类成员属性同名时,需要使用
this.
来区分。
综上所述,Dart类的属性不一定要有?
标记,这取决于你是否想要允许该属性为null以及你的Dart版本是否支持空安全特性。
6.1.1 私有属性和方法
在Dart中,私有属性和方法的实现方式是通过在其名称前加上下划线_
来定义的。这样做的目的是为了让开发者知道这些属性和方法应该被视为私有的,并且不应该被外部访问。然而,需要注意的是,这只是一种约定,实际上Dart并不会强制执行这种私有性。在Dart中,要创建私有属性或方法,只需在属性或方法名前加一个下划线 _
即可。
class MyClass {
int _privateVariable = 0; // 私有变量
void _privateMethod() { // 私有方法
// ...
}
}
总结来说,Dart语言没有类似Java的public、private和protected等访问修饰符,而是通过一种约定来定义私有属性和方法。在实际应用中,为了确保真正的私有性,可以将含有私有属性或方法的类单独放在一个文件中。
6.1.2 static 关键字
static
关键字用于实现类级别的变量和函数。
首先,静态成员是类的所有实例共享的,这意味着无论创建多少个类的实例,静态成员只有一份副本。这与实例变量不同,实例变量是每个实例各自拥有的。
其次,静态成员可以通过类名直接访问,而不需要创建类的实例。例如,如果有一个名为Person
的类,其中包含一个静态变量name
,那么可以通过Person.name
来访问这个变量。
此外,静态方法不能访问非静态成员,因为静态方法不依赖于类的任何实例。而非静态方法可以访问静态成员,因为它们可以通过类的实例来调用。
总的来说,static
关键字在Dart中用于定义类级别的属性和方法,这些静态成员是所有实例共享的,并且可以通过类名直接访问。
6.2 构造函数
6.2.1 默认构造函数
默认构造函数是类的构造函数之一,用于初始化对象的状态。默认构造函数没有参数,并且在创建对象时自动调用。
其中有一个坑:如果属性为 非空,不可以使用「构造函数体初始化」这种形式的无参构造方法去构造这个类,
class ClassName {
// 属性定义
int attribute1;
String attribute2;
// 命名参数初始化:如果属性为 非null,需要使用这种构造函数
ClassName() : attribute1 = 0, attribute2 = 'Hello, World!';
// 构造参数体初始化:如果属性可以为null,可以使用这种构造函数
ClassName(){
attribute1 = 0;
attribute2 = 'Hello, World!';
}
}
6.2.2 命名构造函数
通过使用命名构造函数,我们可以更清晰地表达创建对象的意图,并提供更多灵活性来处理不同的初始化场景。在实际开发中,根据具体需求选择合适的构造函数可以提高代码的可读性和可维护性。
class Person {
String name;
int age;
// 命名构造函数
Person.named({required String name, required int age})
: name = name,
age = age;
}
6.2.3 常量构造函数
如果类生成的对象不会改变,可以通过常量构造函数使这些对象成为编译时常量。常量构造函数是一种特殊的构造函数,它允许创建编译时常量,以下是关于常量构造函数的要点:
- const关键字:常量构造函数需要使用
const
关键字进行修饰。- final成员变量:使用常量构造函数的类,其成员变量必须都是
final
的,这意味着它们是只读的,一旦赋值后就不能更改。- 创建常量实例:要构建一个常量实例,必须使用定义的常量构造函数,并且在创建对象时前面也要加上
const
关键字。- 工厂构造函数:自Dart 2.0起,常量构造函数可以有一个对应的工厂构造函数,用于在运行时返回相同的常量实例。
- 初始化列表:在常量构造函数中,可以在参数列表之后使用初始化列表来初始化成员变量。
- 编译时常量:通过常量构造函数创建的对象可以作为编译时常量,这意味着它们在编译时就被确定下来,而不是在运行时。
总的来说,常量构造函数在Dart中用于创建不可变的对象,这些对象在编译时就已经确定,因此可以提高程序的性能。在设计类时,如果类的实例一旦创建就不应该被修改,那么可以考虑使用常量构造函数。
class ImmutablePoint {
// 属性必须通过 final 声明
final num x;
final num y;
// 常量构造函数,必须通过 const 声明。常量构造函数,不能有body
const ImmutablePoint(this.x, this.y);
}
6.2.4 工厂构造函数
在下面的示例中,Car
类有一个工厂构造函数Car.named
。这个构造函数首先检查是否已经存在相同型号的实例,如果存在,则直接返回该实例;否则,创建一个新的实例并返回。
在main
函数中,使用工厂构造函数Car.named
创建了两个相同型号的实例car1
和car2
。由于工厂构造函数的特性,这两个实例实际上是同一个对象,因此car1 == car2
的结果为true
。
通过使用工厂构造函数,可以实现更复杂的实例创建逻辑,例如单例模式或缓存已有实例等。这有助于提高代码的灵活性和性能。
class Car {
String model;
int year;
// 默认构造函数
Car({required this.model, required this.year});
// 工厂构造函数
factory Car.named({required String model, required int year}) {
// 检查是否已经存在相同型号的实例
for (var car in cars) {
if (car.model == model) {
return car; // 如果存在,直接返回该实例
}
}
// 如果没有找到相同型号的实例,则创建一个新的实例并返回
var newCar = Car(model: model, year: year);
cars.add(newCar);
return newCar;
}
static List<Car> cars = []; // 用于存储已创建的实例
}
void main() {
var car1 = Car.named(model: 'Toyota', year: 2020);
var car2 = Car.named(model: 'Toyota', year: 2020);
print(car1 == car2); // 输出:true
}
6.3 抽象类
Dart语言中的抽象类是一种特殊的类,它不能被实例化,只能作为基类被其他类继承。
抽象类通常包含一种或多种抽象方法,这些方法没有具体的实现,只有声明。子类继承抽象类后,必须实现这些抽象方法,否则子类也需要声明为抽象类。
抽象类的定义使用abstract
关键字,而抽象方法的定义使用@
符号加上override
注解。
以下是一个简单的示例:
abstract class Animal {
void makeSound(); // 抽象方法
}
class Dog extends Animal {
@override
void makeSound() {
print('Woof!');
}
}
void main() {
var dog = Dog();
dog.makeSound(); // 输出:Woof!
}
在上面的示例中,Animal
是一个抽象类,它有一个抽象方法makeSound
。Dog
类继承了Animal
类,并实现了makeSound
方法。在main
函数中,我们创建了一个Dog
对象,并调用了它的makeSound
方法。
需要注意的是,抽象类可以包含实现具体的方法,但抽象方法必须在子类中实现。此外,抽象类也可以包含实例变量和静态变量。
总的来说,Dart中的抽象类和抽象方法主要用于定义标准接口和共享代码,通过继承机制来实现多态性和代码复用。
6.4 继承
在Dart中,继承是面向对象编程的一个重要特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。以下是关于Dart继承的一些详细说明:
-
使用
extends
关键字:在Dart中,要创建一个子类,需要在类声明后使用extends
关键字,然后跟上父类的名称。例如,class Dog extends Animal
表示Dog类继承了Animal类。 -
构造函数:子类的构造函数需要调用
super
关键字来执行父类的构造函数。这样可以确保父类的初始化代码被正确执行。如果父类有多个构造函数,需要在super
后面跟上相应的参数。class Animal { String name; Animal(this.name); } // 如果子类继承了父类,对应的构造方法需要带上父类的属性,并且实现父类的构造方法, class Dog extends Animal { String breed; Dog(String name, this.breed) : super(name); }
-
方法覆盖(Override):子类可以重写(覆盖)父类的方法。这意味着,如果子类有一个与父类相同名称的方法,那么在子类对象上调用该方法时,将执行子类中的版本。要在子类中重写父类的方法,需要在子类的方法前加上
@override
注解。 -
属性覆盖:与方法类似,子类也可以覆盖父类的属性。如果子类定义了一个与父类相同的属性,那么在子类对象上访问该属性时,将返回子类中的值。
-
抽象类和抽象方法:在Dart中,可以使用
abstract
关键字来定义抽象类。抽象类不能被实例化,只能被其他类继承。抽象类可以包含抽象方法,抽象方法没有具体的实现,需要在子类中进行实现。 -
接口继承:在Dart中,虽然没有显式的接口概念,但可以通过抽象类来实现接口。一个类可以实现多个接口,只需要在类声明时使用
with
关键字,然后跟上接口名称即可。 -
mixin:在Dart中,可以使用
mixin
关键字来定义混合类型。Mixin是一种特殊类型的类,它可以包含方法和属性,但不能被实例化。Mixin的主要目的是通过组合来复用代码。要使用mixin,需要在类声明时使用with
关键字,然后跟上mixin名称。mixin SalaryCalculator { double calculateSalary(double baseSalary) { // 假设有10%的提成 double commission = baseSalary * 0.1; return baseSalary + commission; } } mixin VacationCalculator { int calculateVacationDays(int yearsOfService) { // 假设每工作一年可以获得5天的假期 return yearsOfService * 5; } } class Person { String name; int age; Person(this.name, this.age); } class Employee extends Person with SalaryCalculator, VacationCalculator { double baseSalary; int yearsOfService; Employee(String name, int age, this.baseSalary, this.yearsOfService) : super(name, age); void printInfo() { print('Name: $name'); print('Age: $age'); print('Base Salary: $baseSalary'); print('Years of Service: $yearsOfService'); print('Total Salary: ${calculateSalary(baseSalary)}'); print('Vacation Days: ${calculateVacationDays(yearsOfService)}'); } } void main() { var employee = Employee('Tom', 30, 5000, 5); employee.printInfo(); }
-
限制:在Dart中,一个类只能继承自一个父类,即不支持多重继承。但是,可以通过mixin来实现类似的功能。
总之,Dart的继承机制提供了一种强大的方式来组织和复用代码,同时也遵循了面向对象编程的基本原则。通过合理地使用继承,可以使代码更加清晰、易于维护和扩展。
6.3 fromJson和jsonDecode
Dart中的JSON处理主要涉及到两个内置的库:dart:convert
和dart:core
。其中,dart:convert
库提供了jsonDecode()
和jsonEncode()
方法,用于将JSON字符串与Map之间进行转换。而dart:core
库则提供了decode()
和encode()
方法,用于处理编码和解码的问题。
其次,为了方便地将JSON数据转换为特定领域的模型类,通常会在模型类中定义fromJson()
和toJson()
方法。fromJson()
方法负责将JSON数据转换为模型类的实例,而toJson()
方法则将模型类的实例转换回JSON数据。
最后,在fromJson()
方法中,通常会进行一些额外的操作,如数据验证、类型转换和空值检查,以确保数据的有效性和安全性。此外,对于复杂的JSON结构,可能需要使用嵌套的模型类来表示。
示例:
import 'dart:convert';
class Person {
String? name;
int? age;
Person.fromJson(Map<String, dynamic> data) {
name = data['name'];
age = data['age'];
}
}
void main() {
String jsonString = '{"name": "John", "age": 30}';
Map<String, dynamic> jsonData = jsonDecode(jsonString);
Person person = Person.fromJson(jsonData);
print('Name: ${person.name}, Age: ${person.age}');
}