您现在的位置是:网站首页> Flutter
Dart基本语法
- Flutter
- 2024-11-26
- 953人已阅读
Dart基本语法
***与C语言语法很类似而又有扩充***
等待wait sleep(Duration(seccond:5))
语言走马观花笔记
Dart使用new和不用new是一样的
函数参数
一般参数:
命名参数:使用大括号{}括起来的参数
位置参数:使用中括号[]括起来的参数
void funcname(String param1,{double param2,bool param3,...});
void printUserInfo(String name,[String from='中国',int age]);
包使用
Flutter/Dart import导入文件关键字总结
导入文件import后面的路径有哪几种?各代表什么意思?关键字有那些?文件导入的顺序是什么?
import 'dart:xxx'; 引入Dart标准库
import 'xxx/xxx.dart';引入绝对路径的Dart文件
import 'package:xxx/xxx.dart'; 引入Pub仓库pub.dev(或者pub.flutter-io.cn)中的第三方库
import 'package:project/xxx/xxx.dart';引入自定义的dart文件
import 'xxx' show compute1,compute2 只导入compute1,compute2
import 'xxx' hide compute3 除了compute都引入
import 'xxx' as compute4 将库重命名,当有名字冲突时
library compute5; 定义库名称
part of compute6; 表示文件属于某个库
文件导入顺序(从上到下依次)
依赖
推荐使用相对路径导入依赖,如项目结果如下
my_package
|__lib
| |__src
| |__utiles.dart
|__api.dart
想要在api.dart中导入untils.dart,可以按如下方式导入
import 'src/utiles.dart'
下面的方式不推荐,因为一旦my_pakcjkage改名后,对应的所有需要导入的地方都需要修改,二相对路劲就没有这个问题
import 'package:my_package/src/utiles.dart'
dart sdk 内的库
flutter内的库
第三方库
自己的库(文件)
相对路径引用
e.g.
import 'dart:io';
import 'package:material/material.dart';
import 'package:dio/dio.dart';
import 'package:project/common/uitls.dart';
import 'xxx/xxx/xxx/xxx.dart';
变量类型
在 Dart 中,var和具体类型声明变量主要有以下区别:
一、语法和声明方式
使用var声明变量
语法简洁,无需明确指定具体的数据类型。
例如:var name = 'John';,这里name的类型由初始化的值'John'推断为String类型。
使用具体类型声明变量
明确指定变量的数据类型。
例如:String name = 'John';,直接表明name是String类型的变量。
二、类型推断和灵活性
var的类型推断
var让 Dart 根据初始化的值自动推断变量的类型。一旦推断出类型,变量的类型就固定了。
例如:var number = 10;,这里number被推断为int类型。如果之后尝试将一个非int类型的值赋给number,会导致编译错误。
优点:在一些情况下可以简化代码,特别是当类型不那么明显或者可能会发生变化的场景下,可以先快速声明变量,让编译器自动推断类型。
缺点:可能会使代码的可读性降低,尤其是在复杂的代码中,读者可能需要查看初始化的值才能确定变量的类型。
具体类型的明确性
使用具体类型声明变量可以让代码更加清晰和易于理解。读者可以在代码中直接看到变量的类型,无需推断。
例如:int age = 25;,明确表示age是一个整数类型的变量。
优点:提高代码的可读性和可维护性,有助于在开发过程中避免类型错误。可以更好地利用 Dart 的类型系统进行静态分析和错误检测。
缺点:在某些情况下可能会显得比较冗长,特别是当需要声明多个变量时。并且,如果类型选择不当,可能会导致在后续代码中需要进行类型转换。
三、可变性和常量声明
var与可变性
使用var声明的变量可以是可变的,也可以是不可变的,具体取决于初始化的值。
如果初始化的值是不可变的(如一个常量或不可变的集合),那么变量也是不可变的。如果初始化的值是可变的(如一个普通的列表或对象),那么变量也是可变的。
例如:var numbers = [1, 2, 3];,这里numbers是一个可变的列表,可以添加、删除或修改元素。var pi = 3.14;,这里pi是不可变的,因为3.14是一个常量值。
具体类型与可变性
使用具体类型声明变量时,可以通过添加final或const关键字来声明不可变的变量。
例如:final int count = 10;,这里count是一个不可变的整数变量。const List<String> names = ['John', 'Jane'];,这里names是一个不可变的字符串列表。
总的来说,var在某些情况下可以提供简洁的语法和灵活的类型推断,但具体类型声明可以提高代码的可读性和可维护性。在选择使用var还是具体类型时,可以根据具体的编程场景和需求来决定。如果需要明确变量的类型以提高代码的可读性和可维护性,或者需要声明不可变的变量,那么使用具体类型声明可能更合适。如果想要简洁的语法并且不介意让编译器自动推断类型,那么var是一个不错的选择。
dart的数据类型有哪些?详细介绍
在 Dart 中,主要的数据类型如下:
一、数值类型
int(整数类型):
用于表示整数值,例如 -10、0、42 等。
可以进行算术运算,如加、减、乘、除等。
示例:int age = 30;
double(双精度浮点类型):
用于表示带有小数部分的数值。
适用于需要更高精度的数值计算。
示例:double price = 9.99;
二、字符串类型(String)
特点:
用于表示文本数据。
可以使用单引号 ' 或双引号 " 来创建字符串。
支持插值表达式,允许在字符串中嵌入变量和表达式。例如:String name = 'John'; print('Hello, $name!');
常用方法:
length:获取字符串的长度。
contains:判断字符串是否包含指定的子字符串。
substring:提取字符串的一部分。
toUpperCase 和 toLowerCase:将字符串转换为大写或小写。
三、布尔类型(bool)
取值:
只有两个可能的值:true 和 false。
用于表示条件判断和逻辑运算。
示例:bool isActive = true;
四、列表类型(List)
特点:
也称为数组,用于存储一组有序的元素。
可以包含不同类型的元素,但通常建议保持元素类型的一致性以提高代码的可读性和可维护性。
声明方式:
使用方括号 [] 来创建列表,并可以在其中添加初始元素。例如:List<int> numbers = [1, 2, 3];
也可以使用 List 的构造函数来创建列表。例如:List<String> names = List<String>();
常用方法:
add:向列表中添加一个元素。
remove:从列表中移除一个元素。
length:获取列表的长度。
indexOf:查找指定元素在列表中的索引。
五、集合类型(Set)
特点:
用于存储一组无序的、唯一的元素。
不允许包含重复的元素。
声明方式:
使用花括号 {} 来创建集合,并可以在其中添加初始元素。例如:Set<String> fruits = {'apple', 'banana', 'orange'};
也可以使用 Set 的构造函数来创建集合。例如:Set<int> numbers = Set<int>();
常用方法:
add:向集合中添加一个元素。
remove:从集合中移除一个元素。
length:获取集合的大小。
六、映射类型(Map)
特点:
也称为字典,用于存储键值对。
键必须是唯一的,而值可以重复。
声明方式:
使用花括号 {} 来创建映射,并在其中指定键值对。例如:Map<String, int> scores = {'John': 80, 'Jane': 90};
也可以使用 Map 的构造函数来创建映射。例如:Map<String, dynamic> user = Map<String, dynamic>();
常用方法:
putIfAbsent:如果键不存在,则添加一个键值对。
remove:根据键移除一个键值对。
keys:获取映射中的所有键。
values:获取映射中的所有值。
七、动态类型(dynamic)和对象类型(Object)
dynamic:
表示动态类型,可以存储任何类型的值。
在运行时可以动态地改变其类型。
使用时需要小心,因为缺少静态类型检查可能会导致运行时错误。
Object:
是所有 Dart 对象的基类。
可以存储任何对象,但同样缺少具体的类型信息,可能会影响代码的可读性和可维护性。
总之,Dart 提供了丰富的数据类型来满足不同的编程需求。合理选择数据类型可以提高代码的效率、可读性和可维护性。
当你为变量、参数或另一个相关组件指定类型时,可以控制该类型是否允许 null 。要让一个变量可以为空,你可以在类型声明的末尾添加 ? 。
String? name // Nullable type. Can be `null` or string.
String name // Non-nullable type. Cannot be `null` but can be string.
八、Record类型
Record类型
var record = ('first', a: 2, b: true, 'last');
(int, int) swap((int, int) record) {
var (a, b) = record;
return (b, a);
}
(String, int) record;
// Initialize it with a record expression:
record = ('A string', 123);
({int a, bool b}) record;
// Initialize it with a record expression:
record = (a: 123, b: true);
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);
print(point == color); // Prints 'true'.
content_copy
({int x, int y, int z}) point = (x: 1, y: 2, z: 3);
({int r, int g, int b}) color = (r: 1, g: 2, b: 3);
print(point == color); // Prints 'false'. Lint: Equals on unrelated types.
枚举
enum Color { red, green, blue }
基本语法
=> 粗箭头运算符 代表的是
{
return *****;
}
命名参数,使用{参数1,参数2,..}
void textStyle(String content,{double fontSize,bool bold})
textStyle("123",fontSize:12,bold:true);
可选参数 实用[]
void hello(int A,[int b,int C]);
dart 语法糖 ?.
它的意思是左边如果为空返回 null,否则返回右边的值。
A?.B
如果 A 等于 null,那么 A?.B 为 null
如果 A 不等于 null,那么 A?.B 等价于 A.B
dart 语法糖 ??
它的意思是左边如果为空返回右边的值,否则不处理。
A??B
如果 A 等于 null,那么 A??B 为 B
如果 A 不等于 null,那么 A??B 为 A
int a = null;
等价于
int? a;
级联运算符(..)
//通常是在创建对象时)的一种简单方法,而不是获得对该对象的引用并逐个更改属性
Person p = Person();
p.x = 0;
p.y = 1;
Person p = Person()
..x = 0
..y = 1;
算术运算符(~/)
//除以并返回结果的底数(整数部分)
int a = 3;
int b = 7;
int c = b ~/ a;
print(c);//2
类型检查(is和is!)和强制类型转换(as)
main() {
int number = 100;
double distance = 200.5;
num age = 18;
print(number is num);//true
print(distance is! int);//true
print(age as int);//18
}
集合(List、Set、Map)
1.List初始化方式
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];//直接使用[]形式初始化
var colorList = <String> ['red', 'yellow', 'blue', 'green'];
}
List常用的函数
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
colorList.add('white');//和Kotlin类似通过add添加一个新的元素
print(colorList[2]);//可以类似Kotlin一样,直接使用数组下标形式访问元素
print(colorList.length);//获取集合的长度,这个Kotlin不一样,Kotlin中使用的是size
colorList.insert(1, 'black');//在集合指定index位置插入指定的元素
colorList.removeAt(2);//移除集合指定的index=2的元素,第3个元素
colorList.clear();//清除所有元素
print(colorList.sublist(1,3));//截取子集合
print(colorList.getRange(1, 3));//获取集合中某个范围元素
print(colorList.join('<--->'));//类似Kotlin中的joinToString方法,输出: red<--->yellow<--->blue<--->green
print(colorList.isEmpty);
print(colorList.contains('green'));
}
List的遍历方式
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
//for-i遍历
for(var i = 0; i < colorList.length; i++) {//可以使用var或int
print(colorList[i]);
}
//forEach遍历
colorList.forEach((color) => print(color));//forEach的参数为Function. =>使用了箭头函数
//for-in遍历
for(var color in colorList) {
print(color);
}
//while+iterator迭代器遍历,类似Java中的iteator
while(colorList.iterator.moveNext()) {
print(colorList.iterator.current);
}
}
2.集合Set
集合Set和列表List的区别在于 集合中的元素是不能重复 的。所以添加重复的元素时会返回false,表示添加不成功.
Set初始化方式
main() {
Set<String> colorSet= {'red', 'yellow', 'blue', 'green'};//直接使用{}形式初始化
var colorList = <String> {'red', 'yellow', 'blue', 'green'};
}
集合中的交、并、补集,在Kotlin并没有直接给到计算集合交、并、补的API
main() {
var colorSet1 = {'red', 'yellow', 'blue', 'green'};
var colorSet2 = {'black', 'yellow', 'blue', 'green', 'white'};
print(colorSet1.intersection(colorSet2));//交集-->输出: {'yellow', 'blue', 'green'}
print(colorSet1.union(colorSet2));//并集--->输出: {'black', 'red', 'yellow', 'blue', 'green', 'white'}
print(colorSet1.difference(colorSet2));//补集--->输出: {'red'}
}
Set的遍历方式(和List一样)
main() {
Set<String> colorSet = {'red', 'yellow', 'blue', 'green'};
//for-i遍历
for (var i = 0; i < colorSet.length; i++) {
//可以使用var或int
print(colorSet[i]);
}
//forEach遍历
colorSet.forEach((color) => print(color)); //forEach的参数为Function. =>使用了箭头函数
//for-in遍历
for (var color in colorSet) {
print(color);
}
//while+iterator迭代器遍历,类似Java中的iteator
while (colorSet.iterator.moveNext()) {
print(colorSet.iterator.current);
}
}
3、集合Map
集合Map和Kotlin类似,key-value形式存储,并且 Map对象的中key是不能重复的
Map初始化方式
main() {
Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};//使用{key:value}形式初始化
var colorMap = <String, int>{'white': 0xffffffff, 'black':0xff000000};
}
Map中常用的函数
main() {
Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};
print(colorMap.containsKey('green'));//false
print(colorMap.containsValue(0xff000000));//true
print(colorMap.keys.toList());//['white','black']
print(colorMap.values.toList());//[0xffffffff, 0xff000000]
colorMap['white'] = 0xfffff000;//修改指定key的元素
colorMap.remove('black');//移除指定key的元素
}
Map的遍历方式
main() {
Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};
//for-each key-value
colorMap.forEach((key, value) => print('color is $key, color value is $value'));
}
Map.fromIterables将List集合转化成Map
main() {
List<String> colorKeys = ['white', 'black'];
List<int> colorValues = [0xffffffff, 0xff000000];
Map<String, int> colorMap = Map.fromIterables(colorKeys, colorValues);
}
4、集合常用的操作符
dart对于集合操作的也非常符合现代语言的特点,含有丰富的集合操作符API,可以让你处理结构化的数据更加简单。
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
//forEach箭头函数遍历
colorList.forEach((color) => {print(color)});
colorList.forEach((color) => print(color)); //箭头函数遍历,如果箭头函数内部只有一个表达式可以省略大括号
//map函数的使用
print(colorList.map((color) => '$color_font').join(","));
//every函数的使用,判断里面的元素是否都满足条件,返回值为true/false
print(colorList.every((color) => color == 'red'));
//sort函数的使用
List<int> numbers = [0, 3, 1, 2, 7, 12, 2, 4];
numbers.sort((num1, num2) => num1 - num2); //升序排序
numbers.sort((num1, num2) => num2 - num1); //降序排序
print(numbers);
//where函数使用,相当于Kotlin中的filter操作符,返回符合条件元素的集合
print(numbers.where((num) => num > 6));
//firstWhere函数的使用,相当于Kotlin中的find操作符,返回符合条件的第一个元素,如果没找到返回null
print(numbers.firstWhere((num) => num == 5, orElse: () => -1)); //注意: 如果没有找到,执行orElse代码块,可返回一个指定的默认值
//singleWhere函数的使用,返回符合条件的第一个元素,如果没找到返回null,但是前提是集合中只有一个符合条件的元素, 否则就会抛出异常
print(numbers.singleWhere((num) => num == 4, orElse: () => -1)); //注意: 如果没有找到,执行orElse代码块,可返回一个指定的默认值
//take(n)、skip(n)函数的使用,take(n)表示取当前集合前n个元素, skip(n)表示跳过前n个元素,然后取剩余所有的元素
print(numbers.take(5).skip(2));
//List.from函数的使用,从给定集合中创建一个新的集合,相当于clone一个集合
print(List.from(numbers));
//expand函数的使用, 将集合一个元素扩展成多个元素或者将多个元素组成二维数组展开成平铺一个一位数组
var pair = [
[1, 2],
[3, 4]
];
print('flatten list: ${pair.expand((pair) => pair)}');
var inputs = [1, 2, 3];
print('duplicated list: ${inputs.expand((number) =>[
number,
number,
number
])}');
函数类型与高阶函数
在dart函数也是一种类型Function,可以作为函数参数传递,也可以作为返回值
main() {
Function square = (a) {
return a * a;
};
Function square2 = (a) {
return a * a * a;
};
add(3, 4, square, square2)
}
num add(num a, num b, [Function op, Function op2]) {
//函数作为参数传递
return op(a) + op2(b);
}
Dart 中一切都是对象,这意味着如果我们定义一个数字,在初始化它之前如果我们使用了它,假如没有某种检查机制,则不会报错,比如:
test() {
int i;
print(i*8);
}
在 Dart 引入空安全之前,上面代码在执行前不会报错,但会触发一个运行时错误,原因是 i 的值为 null 。但现在有了空安全,则定义变量时我们可以指定变量是可空还是不可空。
int i = 8; //默认为不可空,必须在定义时初始化。
int? j; // 定义为可空类型,对于可空变量,我们在使用前必须判空。
// 如果我们预期变量不能为空,但在定义时不能确定其初始值,则可以加上late关键字,
// 表示会稍后初始化,但是在正式使用它之前必须得保证初始化过了,否则会报错
late int k;
k=9;
如果一个变量我们定义为可空类型,在某些情况下即使我们给它赋值过了,但是预处理器仍然有可能识别不出,这时我们就要显式(通过在变量后面加一个”!“符号)告诉预处理器它已经不是null了,比如:
class Test{
int? i;
Function? fun;
say(){
if(i!=null) {
print(i! * 8); //因为已经判过空,所以能走到这 i 必不为null,如果没有显式申明,则 IDE 会报错
}
if(fun!=null){
fun!(); // 同上
}
}
}
所有类型都是对象,没赋值前都是null
var i=10;
String A="1234";
const r=5; //不能重复赋值,在编译前就要赋值
final s="222222";//不能重复赋值
double z=1;
var one=int.parse('1');
String oneString=one.toString();
String piAsString=3.14159.toStringAsFixed(2);
字符串可以是单引号也可以是双引号
assert(piAsString=='3.14');
插字符串
var s='5';
var sss='${s}hello";
字符串多行用三个单引号或三个双引号
var s1='''2121224helo
eeeeee''';
var s2="""fdshfkkf
fdfdf"""
s2.runtimeType;//运行时类型
var list=[1,2,3];
assert(list.length==3);
assert(list[1]==2);
list[1]=1;
var gifs={
'first':'fff',
'ggg':'ffff'
}
var nob={
1:'1',
2:'2',
3:'3'
}
gifts=Map();
gifts["fff"]=1111;
var _nobleGases={
2:'111',
10:'1111',
18:'333'
}
bool isNble(int atomicNumber){
return _nobleGases[atomicNumber]!=null;
}
isNble(atomicNumber){
return _nobleGases[atomicNumber]!=null;
}
bool isNble(int atomicNumber)=>
_nobleGases[atomicNumber]!=null;
void enableFlags({bool bold,bool hidden}){} //命名参数
enableFlags(blod:true,hidden:false);
String Say(String from,String msg,[String Device]){ //可选位置参数
if(Device!=){
}
//代默认参数
void enableFlagsDefault({bool bold=false,bool hidden=false}){
}
enableFlagsDefault(bold:true);
String sayDefault(String from,String msg,[String device='car',String mood])
{
}
void printElement(int element){
print(element);
}
var l1=[1,2,3];
l1.forEach(printElement);
l1.forEach((item){
print('${l1.indexOf(item)}:$item');
});
l1.forEach((item)=>print('${l1.indexOf(item)}:$item'));
返回函数
Function makeAdder(num addBy){
return (num i)=>addBy+1;
}
var add2=makeAdder(2);
var add4=makeAdder(4);
assert(add2(3)==5);
assert(add4(3)==7);
//函数不返回任何值时候默认是null
foo(){}
void hello(){}
assert(foo()==null);
List<dynamic>l=[1,'a',2,'b'];
if(l[1] is String){
l[1].toUpperCase();
}
b ??=2 //如果b是null 那么赋值2
var sb=StringBuffer();
sb.write('1);
sb.write('1)..write('bar');//..函数连续调用
语句
for(var i=0;i<10;i++){
}
var collection=[0,1,2];
for(var x in collection)
{
print(x);
}
var iDone=false;
while(!isDone)
{
print('do something');
isDone=true;
}
do(
}
while(!isDone);
while(true)
{
break;
}
dart的注解
在 Dart 中,注解(Annotation)用于为代码添加元数据,以便在编译时或运行时进行额外的处理。以下是一些常见的 Dart 注解及介绍和实例:
一、@override
介绍:
用于标记子类中重写父类方法的方法。
帮助编译器检查是否正确地重写了父类的方法。
实例:
class Animal {
void makeSound() {
print('Animal makes a sound');
}
}
class Dog extends Animal {
@override
void makeSound() {
print('Dog barks');
}
}
二、@required
介绍:
在方法参数前使用,表示该参数是必需的。
通常用于构造函数或方法中,以确保调用者提供该参数。
实例:
class Person {
final String name;
Person({@required this.name});
}
void main() {
var person = Person(name: 'John');
}
三、@deprecated
介绍:
用于标记已过时的代码元素,如类、方法、字段等。
向开发者发出警告,表明该元素可能在未来的版本中被移除。
实例:
@deprecated
void oldMethod() {
print('This method is deprecated.');
}
void main() {
oldMethod();
}
四、@proxy
介绍:
用于创建代理类,可以拦截对目标对象的方法调用。
通常用于实现 AOP(面向切面编程)等功能。
实例(较为复杂,这里仅为简单示例示意):
class TargetClass {
void method() {
print('Target method called.');
}
}
class ProxyClass implements TargetClass {
final TargetClass target;
ProxyClass(this.target);
@override
void method() {
print('Before calling target method.');
target.method();
print('After calling target method.');
}
}
五、@immutable
介绍:
用于标记类是不可变的。
表示该类的实例一旦创建,其状态就不能被修改。
实例:
@immutable
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
var point = const Point(10, 20);
}
这些只是 Dart 中一些常见的注解,Dart 还支持自定义注解,可以通过创建类并使用 @Target 和 @Retention 等元注解来定义特定的注解行为。
//异常处理
Exception是核心错误 ,Error是应用代码异常
throw rethrow 再出抛出异常
finally 必须执行的代码
try
{
}
catch(e)
{
rethrow;
}
try
{
}
on ErrorType catch(e) //on 是捕获指定类型的错误
{
}
catch(e,s) //s为栈在代码哪儿
{
}
类
class Point{
num x,y,r;
num distanceFromOrigin;
Point(this.x,this.y):distanceFromOrigin=sqrt(x*x+y*y);
num get right=>r;
set right(num value)=>r=value;
}
//抽象类
abstract class Door{
void doSomething();
}
class A extends B
{
A()
{
}
@override
void turnOn()
{
super.turnOn();
}
}
静态变量和函数
static String A;
static bool isMobile(){
return true;
}
通过函数购置类如:
class Point{
num x,y
Point.fromJson(Map<string,dynamic>json)
{
x=json["x"];
y=json["y"];
}
调用构建:
Point pt=Point.fromJson({"x":1,"y":2});
}
类派生与with使用
*with的最后那个是最高级别,页就是说同一个函数最后一个为准*
class AB extends P with A,B{
}
class BA extends P with B,A{
}
class P
{
String getMessage()=>'P';
}
class A{
String getMessage()=>'A';
}
class B{
String getMessage()=>'B';
}
void main()
{
String result='';
AB ab=AB();
result+=ab.getMessage();
BA ba=BA();
result+=ba.getMessage();
print(result);
}
执行结果'BA'
类前置转换as
泛型
abstract clsss Cache<T>{
T getbyKey(String key);
void setBykey(string key,T value);
}
方法泛型
T first<T>(List<T>s){
T tmp=s[0];
return tmp;
}
dart混入
在 Dart 中,混入(Mixins)是一种重用代码的机制,它允许一个类从多个其他类中获取功能,而无需使用传统的多重继承。
一、混入的特点
代码复用:可以将一些通用的功能提取到一个单独的类中,然后通过混入将这些功能添加到其他类中,避免了代码重复。
灵活性:可以根据需要选择要混入的类,从而实现更加灵活的代码结构。
避免多重继承的复杂性:Dart 不支持多重继承,但混入提供了一种类似的功能,同时避免了多重继承可能带来的复杂性和二义性问题。
二、混入的使用方法
定义一个混入类:
混入类通常是一个普通的类,但它使用 mixin 关键字进行声明。
混入类可以包含方法、属性和构造函数,但不能被实例化。
在其他类中使用混入:
使用 with 关键字将一个或多个混入类添加到一个类的声明中。
这样,该类就可以访问混入类中的方法和属性。
三、实例
以下是一个使用混入的例子:
// 定义一个混入类
mixin Flyable {
void fly() {
print('I can fly!');
}
}
// 定义一个混入类
mixin Swimable {
void swim() {
print('I can swim!');
}
}
// 定义一个类,使用混入
class Duck with Flyable, Swimable {
// 类的其他方法和属性...
}
void main() {
var duck = Duck();
duck.fly();
duck.swim();
}
在这个例子中:
定义了两个混入类 Flyable 和 Swimable,分别包含了 fly 和 swim 方法。
定义了一个类 Duck,使用 with 关键字将 Flyable 和 Swimable 混入到 Duck 类中。
这样,Duck 类就具有了 fly 和 swim 方法,可以在实例化后调用这些方法。
混入是一种强大的代码复用机制,可以帮助你更好地组织和管理代码。但在使用混入时,要注意避免混入类之间的冲突,并确保混入的功能与类的需求相匹配。
库:
内置库的URI前缀是dart: ,由包管理工具提供的库前缀为package:
导入多个库之间出现表示冲突,可以使用as指定引用前缀来避免
使用show和hide来导入或隐藏指定的标识符
使用deferred as来延迟加载库
import ‘dart:math' as m
m.sqrt(2);
import 'dart:math' show sqrt
import 'dart:math' hide sqrt
import 'dart:math' deferred as m //延迟加载
await m.loadLibrary(); //加载库
m.sqrt();
异步
函数里要使用await 那么调用函数必须加async
如
Future<String> lookUpVersion() async=>'1.0.01';
Future checkVersion() async{
var version=await lookUpVersion();
print(version);
}
void main() async{
var version;
try
{
version=await lookUpVersion();
}
catch (e)
{
print(e);
return;
}
print(version);
...
}
dart中的生成器函数
在 dart 中有生成器函数的语法,在很多其他的语言中也有,比如 js c#
这个语法看上去和 async await 语法很像
使用的关键字是 async* sync* yield yield*
官方对于这个语法的说明可以参考这个连接generators
其实async await也是一种生成器语法
生成器语法就是你返回的类型通常情况下和 return 的类型可能不一致
比如你return 1,但是返回值上却需要写Future<int>
sync*
在 dart 中可以使用这个便利的生成一个迭代器
如下所示
这两种写法是一样的,但是第一个写法会简洁很多
main(List<String> arguments) {
print(genList());
print(genList2());
}
Iterable<int> genList({int max = 10}) sync* {
var i = 0;
while (i < max) {
yield i;
i++;
}
}
Iterable<int> genList2({int max = 10}) {
var list = <int>[];
var i = 0;
while (i < max) {
list.add(i);
i++;
}
return list.map((i) => i);
}
async*
这个返回值是一个 Stream
main(List<String> arguments) {
print(genList());
print(genList2());
genStream().listen((data) {
print("stream1 : $data");
});
genStream2().listen((data) {
print("stream2 : $data");
});
}
Stream<int> genStream({int max = 10}) async* {
int i = 0;
while (i < max) {
yield i;
await Future.delayed(Duration(milliseconds: 300));
i++;
}
}
Stream<int> genStream2({int max = 10}) {
StreamController<int> controller = StreamController();
Future<void>.delayed(Duration.zero).then((_) async {
int i = 0;
while (i < max) {
controller.add(i);
await Future.delayed(Duration(milliseconds: 300));
i++;
}
controller.close();
});
return controller.stream;
}
两种写法达到了一样的效果,但是生成器函数代码会更加简洁一些
执行结果如下
yield*
在生成器函数中还有一个关键字 yield*
这个关键字是结合递归使用的,可以配合sync* 也可以配合async*
结合 sync*
main(List<String> arguments) {
var r = naturalsDownFrom(10);
print(r); //(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
r = naturalsDownWithNormal(10);
print(r); //(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
}
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Iterable<int> naturalsDownWithNormal(int n) {
var list = <int>[];
if (n > 0) {
list.add(n);
var r = naturalsDownWithNormal(n - 1);
list.addAll(r);
}
return list.map((v) => v);
}
结合 async*
main(List<String> arguments){
naturalsStreamDownFrom(10).listen((data) {
print("data = $data");
});
}
Stream<int> naturalsStreamDownFrom(int n) async* {
if (n > 0) {
yield n;
yield* naturalsStreamDownFrom(n - 1);
}
}
输出结果
data = 10
data = 9
data = 8
data = 7
data = 6
data = 5
data = 4
data = 3
data = 2
data = 1
常规写法分开写
main(List<String> arguments) {
naturalsStreamDownWithNormal(10).listen((data) {
print("data2 = $data");
});
}
Stream<int> naturalsStreamDownWithNormal(int n) {
var controller = StreamController<int>();
if (n > 0) {
controller.add(n);
naturalsStreamDownWithNormal(n - 1).listen((data) {
controller.add(data);
});
}
return controller.stream;
}
data2 = 10
data2 = 9
data2 = 8
data2 = 7
data2 = 6
data2 = 5
data2 = 4
data2 = 3
data2 = 2
data2 = 1
这里常规的写法也比较复杂,而且还有 controller 不关闭的可能
还需要注意一下 streamController 的关闭
需要修改如下
Stream<int> naturalsStreamDownWithNormal(int n) {
var controller = StreamController<int>();
if (n > 0) {
controller.add(n);
naturalsStreamDownWithNormal(n - 1).listen((data) {
controller.add(data);
}, onDone: () {
print("close controller = $n");
controller.close();
});
} else {
controller.close();
}
return controller.stream;
}
这里加了一个 print 输出
close controller = 1
data2 = 10
close controller = 2
data2 = 9
close controller = 3
data2 = 8
close controller = 4
data2 = 7
close controller = 5
data2 = 6
close controller = 6
data2 = 5
close controller = 7
data2 = 4
close controller = 8
data2 = 3
close controller = 9
data2 = 2
close controller = 10
data2 = 1
可以调用类
使用call方法
class hello{
call(String a,String b,String c)=>'$a,$b,$c';
}
void main() async{
var str=hello();
}
}
类型定义
Function comp;
Sort(int f(Object a,Object b)){
comp=f;
}
//定义函数
typedef FF=int Function(Object a,Object b);
元数据 @开头
@deprecated 将要废弃的函数 @override
公有和私有变量函数
在Flutter中,没有像其他一些编程语言那样严格的公有函数和私有函数的概念。但是,Flutter遵循了一些命名约定来表示函数的可见性和预期用途。
在Flutter中,函数的可见性主要通过以下约定来控制:
以下划线 (_) 开头的函数名称被认为是私有的,只能在当前库中访问。这种函数通常被称为库私有函数。
没有以下划线开头的函数或变量名称被认为是公有的,可以在其他库中导入和使用。
这种约定是基于Dart语言的库和可见性规则。在Dart中,以下划线开头的标识符只在当前库中可见,而没有下划线开头的标识符在其他库中也可以访问。
虽然这种约定不像其他语言中的访问修饰符那样严格,但它提供了一种在Flutter项目中组织和控制函数可见性的方式。
举个例子:
// 公有函数
void publicFunction() {
// ...
}
// 私有函数(库私有)
void _privateFunction() {
// ...
}
// 公有变量 int publicVariable = 10; // 私有变量(库私有) int _privateVariable = 20;
implements 和 extends的区别
在 Dart 编程语言中,implements 和 extends 是两个用于建立类之间关系的关键字,但它们有着不同的用途和含义。
extends 关键字:
用于类的继承,表示一个类是另一个类的子类。
子类继承父类的所有属性和方法,并且可以重写父类的方法。
一个类只能继承一个父类,形成单继承关系。
示例:
class Animal {
void eat() {
print('Animal is eating.');
}
}
class Dog extends Animal {
void bark() {
print('Dog is barking.');
}
}
在上述示例中,Dog 类通过 extends 关键字继承了 Animal 类,因此 Dog 类拥有 Animal 类的 eat() 方法,同时也定义了自己的 bark() 方法。
implements 关键字:
用于接口的实现,表示一个类实现了一个或多个接口。
接口定义了一组方法的签名,类必须提供这些方法的具体实现。
一个类可以实现多个接口,用逗号分隔。
示例:
abstract class Flyable {
void fly();
}
abstract class Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
void fly() {
print('Duck is flying.');
}
void swim() {
print('Duck is swimming.');
}
}
在上述示例中,Duck 类通过 implements 关键字实现了 Flyable 和 Swimmable 两个接口,并提供了这些接口中定义的方法的具体实现。
区别:
extends 用于类的继承,表示 "是一个" 的关系。子类继承父类的属性和方法,并且可以添加自己的特定功能。
implements 用于接口的实现,表示 "具有某种能力" 的关系。类必须实现接口中定义的所有方法,以满足接口的契约。
一个类只能继承一个父类,但可以实现多个接口。
继承是一种 "是一个" 的关系,而接口实现是一种 "具有某种能力" 的关系。
总之,extends 用于类的继承,表示类之间的层次结构和共享的属性和行为;而 implements 用于接口的实现,表示类具有某些特定的能力或满足某些契约。它们在面向对象编程中扮演着不同但同样重要的角色。
Dart 异步处理
***返回Future,async函数其实是类似创建了一个线程来执行,要不消耗CPU同步得到结果可以用wait ,也可以用then注册回调来异步获得结果***
Future创建的几个方法
Future();
Future.micotask();
Future.sync();
Future.value();
Future.delayed();
Future.error();
其中sync是同步方法,任务会立即执行
Future.sync(()
{
});
使用then注册回调函数
Future fu=Future.value('Future的值为30');
fu.then((res){
print(res);
});
链式调用
Future((){
print('async task')
}).then((res){
print('async task complete');
}).then((res){
print('async task after');
});
catchError写在then之后捕获异常
Future((){
print('async task')
}).then((res){
print('async task complete');
}).catchError((e){
print(e);
});
Future用静态方法wait等待多个任务全部完成后回调
print('main start');
Future task1=Future((){
print('task1');
return 1;
});
Future task2=Future((){
print('task2');
return 2;
});
Future task3=Future((){
print('task3');
return 3;
});
//使用wait方法等待三个任务完成后回调
Future future=Future.wait([task1,task2,task3]);
future.then((response){
print(response);
print('main stop');
})
最后输出结果
main start
main stop
task1
task2
task3
future:[1,2,3]
使用关键字async wait
doTask() async
{
//等待器执行完成,耗时2秒
await sleep(const Duration(second:2));
return "执行了耗时操作";
}
test() async
{
var r=wait doTask();
print(r);
}
void main()
{
print('main start');
test();
print('main end');
}
输出
main start
main end
执行了耗时操作
Stream多次异步操作
stream创建方式
Stream<T>.periodic(间隔时间,回调函数)
代码
createStream() async
{
Stream<int> stream=Stream<int>.periodic(Duration(second:1),callback);
await for(var i in stream)
{
print(i);
}
}
int callback(int value)
{
return value;
}
输出
flutter:0
flutter:1
flutter:2
...
Stream<T>.fromFuture
代码
createStream() async
{
print("开始");
Future<String>future[Future((){
return "异步任务";
});
Stream<String> stream=Stream<int>.fromFuture(future);
await for(var s in stream)
{
print(s);
}
print("结束");
}
打印结果
flutter:开始
flutter:异步任务
flutter:结束
Stream<T>.fromFutures
代码
createStream() async
{
print("开始");
Future<String>future1=Future((){
sleep(Duration(second:5));
return "异步任务1";
});
Future<String>future2=Future((){
//sleep(Duration(second:5));
return "异步任务2";
});
Future<String>future3=Future((){
//sleep(Duration(second:5));
return "异步任务3";
});
Stream<String> stream=Stream<int>.fromFutures([future1,future2,future3]);
await for(var s in stream)
{
print(s);
}
print("结束");
}
}
打印结果
flutter:开始
flutter:异步任务1
flutter:异步任务2
flutter:异步任务3
flutter:结束
Stream的方法
Stream<T>.take(int)指定发送时间次数,对应Stream.periodic方法
Stream<int> stream=Stream<int>.periodic(Duration(second:1),callback);
stream=stream.tak(10);//只发生10次
await for(var i in stream)
{
print(i);
}
}
int callback(int value)
{
return value;
}
Stream<T>.takeWhile加触发完结条件
stream=stream.takeWhile((data){
return data<8;//满足这个条件
});
Stream<T>.skip(int )加跳过几个事件
Stream<T>.skipWhile((data){return data<8} )加跳过事件条件
Stream<T>.toList()返回Future<List<T>>对象
Stream<T>.listen()与forEach类似,其中几个参数说明
onData 接收到数据处理,必须要实现的方法
onError 流发生错误时的处理
onDone 流读取完后调取
cancelOnError 发生错误时是否立马终结
stream.listen((data){
print(data);
},onError:(error){
print("流发生错误");
},onDone:(){
print("流已完毕");
}
Stream<T>.forEach和listen类似,只是他只监听了onData
stream.forEach((data){
print(data);
});
Stream<T>.length统计事件总数
Stream<T>.where,过滤一些不想要的数据
stream=stream.where((data)=>data<6);
await for( var i in stream){
}
开发上一般使用StreamControl控制Stream
自己编写一个耗时函数
Future<UserInfo> hello(){
//想象这个是一个耗时的数据库操作
return Future(()=>"Large Latte");
//也可以写成
return Future.value("Large Latte");
}
void main() {
fetchUserOrder().then((result){print(result)})
print('Fetching user order...');
}
可以写耗时函数为
Future<String> login(String name,String password){
//登录
}
Future<User> fetchUserInfo(String token){
//获取用户信息
}
Future saveUserInfo(User user){
// 缓存用户信息
}
用Future大概可以这样写:
login('name','password').then((token) => fetchUserInfo(token))
.then((user) => saveUserInfo(user));
换成async 和await 则可以这样:
void doLogin() async {
try {
var token = await login('name','password');
var user = await fetchUserInfo(token);
await saveUserInfo(user);
} catch (err) {
print('Caught error: $err');
}
}
送多一颗语法糖给你:
Future<String> getUserInfo() async {
return 'aaa';
}
等价于:
Future<String> getUserInfo() async {
return Future.value('aaa');
}
Future((){
耗时操作
});
延迟量秒后执行
Future.delayed(new Duration(second:2),(){
return "hello world";
}).then((data){
});
Future.then
Future.whenComplete
Future.wait([
Future.delayed(new Duration(second:2),(){
return "hello";
}),
Future.delayed(new Duration(second:4),(){
return "hello";
})
]).then((results){
printf(results[0]+results(1)); 匿名自定义回调函数
}).catchError((e){
print(e);
});
所有结果都是一个自定义回调函数
Stream可以可以接收多个异步操作的结果
Stream.fromFutures([
//1秒后返回结果
Future.delayed(new Duration(seconds:1),(){
return "hello1";
}),
//2秒后抛出异常
Future.delayed(new Duration(seconds:2),(){
throw AssertionError("Error");
}),
//三秒后输出结果
Future.delayed(new Duration(seconds:3),(){
return "hello2";
})]).listen((data){
printf(data);
},onError:(e){
print(e.message);
},onDone:(){
});
输出为:
hello 1
Error
hello 3
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.get(url, headers: headers));
Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.post(url, headers: headers, body: body, encoding: encoding));
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
var client = Client();
try {
return await fn(client);
} finally {
client.close();
}
}
//红色部分为回调函数
概念
Future是 Dart 中用于处理异步操作的核心类。它表示一个在未来某个时间点可能完成(或者可能产生错误)的操作。可以把它想象成一个承诺(promise),在未来会返回一个值或者一个错误。
异步操作在 Dart 中很常见,比如读取文件、网络请求等。这些操作通常需要花费一些时间来完成,而Future允许程序在等待这些操作完成的同时可以去做其他事情,而不是一直阻塞等待。
状态
一个Future有三种状态:未完成(uncompleted)、已完成并带有值(completed with a value)、已完成并带有错误(completed with an error)。
当一个Future被创建时,它最初处于未完成状态。随着异步操作的进行,它会转变为另外两种状态之一。一旦Future进入完成状态(无论是成功还是失败),它的状态就不能再改变。
创建 Future 的方式
使用Future构造函数:
可以直接使用Future构造函数来创建一个Future对象。例如:
import 'dart:async';
void main() {
Future<void> myFuture = Future(() {
print('This is an asynchronous operation.');
});
myFuture.then((_) {
print('The future is completed.');
});
}
在这个例子中,Future构造函数接受一个回调函数作为参数。这个回调函数就是异步操作的具体内容。当Future被调度执行时,这个回调函数会被调用。这里的myFuture是一个Future<void>类型,因为回调函数没有返回值。then方法用于在Future成功完成后执行一个操作。
使用async和await关键字(实际上也是基于Future):
当函数内部有异步操作时,可以将函数标记为async。在async函数内部,可以使用await来暂停函数的执行,直到一个Future完成并返回结果。例如:
import 'dart:async';
Future<int> addNumbers() async {
await Future.delayed(Duration(seconds: 2));
return 2 + 3;
}
void main() {
addNumbers().then((result) {
print('The result is $result');
});
}
在addNumbers函数中,首先使用Future.delayed来模拟一个延迟 2 秒的异步操作(这可以类比网络延迟等情况)。然后返回两个数的和。在main函数中,调用addNumbers函数会返回一个Future<int>,通过then方法来处理返回的结果。
处理 Future 的结果
then方法:
用于在Future成功完成后获取并处理返回的值。它接受一个回调函数作为参数,这个回调函数的参数就是Future返回的值。例如:
import 'dart:async';
Future<String> getMessage() {
return Future<String>(() {
return "Hello, World!";
});
}
void main() {
getMessage().then((message) {
print(message);
});
}
这里getMessage函数返回一个Future<String>,在main函数中,通过then方法获取并打印了这个Future返回的字符串。
catchError方法:
用于处理Future执行过程中出现的错误。例如:
import 'dart:async';
Future<int> divideNumbers() {
return Future<int>(() {
throw FormatException('Cannot divide by zero');
});
}
void main() {
divideNumbers().catchError((error) {
print('An error occurred: $error');
});
}
在divideNumbers函数中,通过抛出一个异常来模拟出错的情况。在main函数中,使用catchError方法捕获并打印了这个错误。
whenComplete方法:
无论Future是成功完成还是出现错误,whenComplete方法中的回调函数都会被执行。例如:
import 'dart:async';
Future<int> multiplyNumbers() {
return Future<int>(() {
return 4 * 5;
});
}
void main() {
multiplyNumbers().whenComplete(() {
print('The future operation is finished.');
}).then((result) {
print('The result is $result');
});
}
在这里,先通过whenComplete方法在Future操作完成后(无论是成功还是失败,不过这里没有失败的情况)打印一条消息,然后通过then方法获取并打印结果
dart序列化类
在 Dart 中,可以使用 dart:convert 库中的 json 编码器和解码器将类序列化为 JSON 格式,并将其写入文件。然后,可以从文件中读取 JSON 数据,并将其反序列化为类的实例。
下面是一个详细的例子,演示了如何将类序列化到文件并从文件中反序列化:
import 'dart:convert';
import 'dart:io';
class Person {
String name;
int age;
Person(this.name, this.age);
// 将 Person 对象转换为 Map
Map<String, dynamic> toMap() {
return {
'name': name,
'age': age,
};
}
// 从 Map 中创建 Person 对象
factory Person.fromMap(Map<String, dynamic> map) {
return Person(
map['name'],
map['age'],
);
}
// 将 Person 对象转换为 JSON 字符串
String toJson() => json.encode(toMap());
// 从 JSON 字符串中创建 Person 对象
factory Person.fromJson(String source) => Person.fromMap(json.decode(source));
}
void main() async {
// 创建 Person 对象
Person person = Person('John Doe', 25);
// 将 Person 对象序列化为 JSON 字符串
String jsonString = person.toJson();
// 将 JSON 字符串写入文件
File file = File('person.json');
await file.writeAsString(jsonString);
// 从文件中读取 JSON 字符串
String fileContent = await file.readAsString();
// 将 JSON 字符串反序列化为 Person 对象
Person deserializedPerson = Person.fromJson(fileContent);
// 打印反序列化后的 Person 对象的属性
print('Name: ${deserializedPerson.name}');
print('Age: ${deserializedPerson.age}');
}
在上面的例子中:
我们定义了一个 Person 类,包含 name 和 age 两个属性。
在 Person 类中,我们实现了 toMap() 方法,将 Person 对象转换为 Map。
我们还实现了一个工厂构造函数 Person.fromMap(),用于从 Map 中创建 Person 对象。
我们实现了 toJson() 方法,将 Person 对象转换为 JSON 字符串。
我们实现了另一个工厂构造函数 Person.fromJson(),用于从 JSON 字符串中创建 Person 对象。
在 main() 函数中,我们创建了一个 Person 对象,并将其序列化为 JSON 字符串。
我们将 JSON 字符串写入文件 person.json。
然后,我们从文件中读取 JSON 字符串。
我们将读取的 JSON 字符串反序列化为 Person 对象。
最后,我们打印反序列化后的 Person 对象的属性。
通过这个例子,你可以看到如何将类序列化为 JSON 格式并写入文件,以及如何从文件中读取 JSON 数据并将其反序列化为类的实例
Flutter可以创建线程吗
在 Flutter 中,你可以使用 Dart 语言提供的异步编程特性,如 Future 和 Isolate,来实现并发和多线程。
Future: Future 允许你编写异步代码,但它并不会创建新的线程。相反,它在 Dart 的事件循环中运行,允许你在不阻塞 UI 的情况下执行异步操作。你可以使用 async 和 await 关键字来简化异步编程。
Isolate: Isolate 是 Dart 中真正的多线程解决方案。每个 Isolate 都在自己的线程中运行,有自己的内存堆,确保了不同 Isolate 之间的状态隔离。Isolate 之间通过消息传递进行通信。
下面是一个使用 Isolate 的简单示例:
import 'dart:async';
import 'dart:isolate';
void main() async {
// 创建一个 ReceivePort 用于接收来自新 Isolate 的消息
ReceivePort receivePort = ReceivePort();
// 创建一个新的 Isolate
await Isolate.spawn(isolateFunction, receivePort.sendPort);
// 监听来自新 Isolate 的消息
receivePort.listen((message) {
print('Received message from isolate: $message');
});
}
// 在新的 Isolate 中运行的函数
void isolateFunction(SendPort sendPort) {
// 向主 Isolate 发送消息
sendPort.send('Hello from the new isolate!');
}
在这个例子中,我们创建了一个新的 Isolate,并在其中运行 isolateFunction。主 Isolate 和新 Isolate 通过 SendPort 和 ReceivePort 进行通信。
需要注意的是,尽管 Flutter 支持多线程,但大多数情况下,你应该首先考虑使用 Future 和异步编程。只有在执行计算密集型任务或需要并行执行多个任务时,才应该使用 Isolate。过度使用 Isolate 可能会使你的代码复杂化,并引入不必要的开销。