Dart语法

218 阅读7分钟

变量

1. 定义变量

Dart 中定义变量有两种方式,一种是静态类型语言常用的方式,即指定变量类型;另一种则是动态语言的常用方式,即不指定类型,由 Dart 自动类型判断。建议在编写代码时,尽可能指定变量类型,这样可以提升代码可读性与调试的便利性。

// 指定变量类型:
String name = 'DrM';

// 使用关键字 var ,不指定变量类型:
var name = 'DrM';

//
`int? aNullableAge;`

未初始化的变量默认值是 null。即使变量是数字类型默认值也是 null,因为在 Dart 中一切都是对象,数字类型也不例外。

var a; // 也可使用 dynamic a;
print(a);  // 输出值为 null
int b;
// Error: Non-nullable variable 'a' must be assigned before it can be used.

2.明确指定可为空的变量

可为空(dynamicvar 或者 ?声明)的变量,初始化可以为空;

int b;
// Error: Non-nullable variable 'a' must be assigned before it can be used.
int ?c;
print(c);  // 输出值为 null

3.动态改变变量类型

如想动态改变变量的数据类型,应当使用 dynamic 或 Object 来定义变量:

dynamic a = 'DrM';  // 也可使用 Object a = 'DrM';
a = 2022;
print(a);  // 输出结果为 2022

4. 定义常量

Dart 中定义常量也有两种方式,一种使用 final 关键字,另一种是使用 const 关键字。需要注意的是, final 定义的常量是运行时常量,而 const 常量则是编译时常量,也就是说 final 定义常量时,其值可以是一个变量,而 const 定义的常量,其值必须是一个字面常量值。

final a = DateTime.now();
print(a);  // 输出现在的时间

const a = DateTime.now();
print(a);  // 报错

内建类型

Dart 语言支持以下内建类型:

graph LR
内建类型 --> Number
Number --> int --> 整数
Number --> double --> 浮点数
内建类型 --> String --> 字符串
内建类型 --> Boolean --> 布尔类型
内建类型 --> List  --> 数组
内建类型 --> Map --> 字典或映射
内建类型 --> Set --> 集合

1. Number

// int 必须是整型:
int a = 123;

// 从 Dart 2.0 开始,double 既可以是浮点数也可以是整型:
double b = 12.34;
double c = 6;    // 相当于 double c = 6.0

2. String

// 用单引号或者双引号定义字符串:
String s1 = 'DrM'
String s2 = "DrM"

// 当字符串有引号时:
String s3 = 'I\'m DrM'
String s4 = "I'm DrM"

// 使用连续三个单引号或者三个双引号实现多行字符串对象的创建:
String s5 = '''
Hello Dart! 
Hello Flutter! 
Hello world!
''';
/*
类似于js的字符串模板``
let s5 = `
Hello Dart! 
Hello Flutter! 
Hello world!
`;
*/

// 使用 r 前缀,可以创建 “原始 raw” 字符串:
String s6 = r'Hello Dart \( ̄▽ ̄)/ Hello Flutter!'

// String -> int:必须是整型
int a = int.parse('123');
// 123

// String -> double:
double b = double.parse('12.34');
// 12.34

// int -> String:
String str1 = 123.toString();
//'123'

// double -> String:
String str2 = 12.3456.toString();  
//'12.3456'

// 字符串拼接,用“+”拼接,字面量字符串也可以直接写在一起:
String s1 = 'Hello Dart! ' 'Hello Flutter! ' 'Hello ImportV! ';
String s2 = 'Hello Dart! ' + 'Hello Flutter! ' + 'Hello ImportV! ';
//s1==s2

// 字符串可以通过 ${expression} 的方式内嵌表达式。如果表达式是一个标识符并且后方没有紧跟字面量,则{}可以省略:
String s1 = 'Hello Flutter!';
String s2 = 'Hello Dart! ' + s1 + 'Hello ImportV!';
String s3 = 'Hello Dart! ${s1} Hello ImportV!';
String s4 = 'Hello Dart! $s1 Hello ImportV!';
//s2==s3==s4

// String toStringAsFixed(int fractionDigits):
//该方法是将Number转为String的一个方法,接受int类型作为参数来控制小数点后的精度
//官网说只能在double类型上使用该方法,但经过测试int类型也可以
1.toStringAsFixed(3); // 1.000 
4321.12345678.toStringAsFixed(3); // 4321.123 
4321.12345678.toStringAsFixed(5); // 4321.12346 
123456789012345.toStringAsFixed(3); // 123456789012345.000 
10000000000000000.toStringAsFixed(4); // 10000000000000000.0000 
5.25.toStringAsFixed(0); // 5

常见方法

//indexOf 同List
//replaceFirst
'0.0001'.replaceFirst('0', ''); // .0001
//replaceAll
'0.0001'.replaceFirst('0', ''); // .1

3. Boolean

// 布尔类型默认值为null:
bool b = true;

4. List

具有长度的可索引对象集合

// 定义列表方法一:
List lt1 = [1234];

// 定义列表方法二:
List a = [];
a.add(1);
a.add(2);
a.add(3);

// 获取列表属性:
List a = ['Dart''Flutter''ImportV'];
print(a.length);  // 获取列表长度
print(a.isEmpty);  // 判断列表是否为空,如果是,输出 true
print(a.isNotEmpty);  // 判断列表是否为空,如果不是,输出 true
print(a.reversed);  // 将列表逆序输出(翻转列表)

// 列表的下标索引是从0开始的:
List a = [1234];
print(a[0]); // 1

// 列表转字符串:
List a = ['Dart''Flutter''Android'];
String b = a.join('->');  // 此处 b 的字面量为 Dart->Flutter->Android

// 字符串转列表:
String a = 'Dart->Flutter->Android';
List b = a.split('->');  // 此处 b 的字面量为 [Dart, Flutter, Android]

// 指定列表类型:
List a = <String>[];
a.add('Flutter');
a.add(1);  //报错

// 在列表字面量前添加 const 关键字,定义一个不可改变的列表:
List a = const [1234];
a[0] = 2;  // 报错

常见方法

1.增加

List a = ['Dart''Flutter'];
a.add('Android');  // 拼接一个元素
a.addAll(['Android''iOS']);  // 拼接多个元素
a.insert(2'Android');  // 插入一个元素,2 为插入位置的索引值
a.insertAll(2, ['Android''iOS']);  // 插入多个元素

2.删除

List a = ['Dart''Flutter'];
/*remove(value) 从此列表中删除第一次出现的,如果在列表中,则返回 true ,否则返回 false。
不在列表中,则该方法无效。*/
print(a.remove('Dart')); //true 

//removeAt(int index) 从此列表中删除位置处的对象并返回删除的对象,必须在范围内0 ≤ index < length
print(a.removeAt(0)); //Dart 

//removeLast() 删除并返回此列表中的最后一个对象。
print(a.removeLast()); //Flutter 

//void removeRange(int start,int end) 删除`start`包含到`end`排除范围内的对象,0 <= start <= end <= length
a.removeRange(); //没有返回值

//void removeWhere(bool Function) 从此列表中删除所有满足的对象。
a.removeWhere(item=>item=='Flutter'); //没有返回值

//void clear() 从此列表中删除所有对象;列表的长度变为零。
a.clear(); //没有返回值

3.替换

List a = ['Dart''Flutter'];
//void replaceRange (int start,int end,Iterable<E> replacement)
a.replaceRange(0,1,['DrM']); //没有返回值
print(a); //[DrM, Flutter]

4.查找

List a = ['Dart''Flutter'];
//indexOf(E element, int start = 0)在索引start到列表末尾之间搜索,返回查找到的第一个索引
print(a.indexOf('Dart')); //0
print(a.indexOf('Dart',1)); //-1

//indexWhere(bool Function, int start = 0)返回列表中满足条件的第一个索引
a.indexWhere((item) => item.startsWith('D'));       // 0
a.indexWhere((item) => item.startsWith('D'), 1);    // -1

5. Set

Set 是没有顺序且元素不能重复的集合,因此不能通过索引去获取值。

// 定义集合方法一:
Set a = {'Dart''Flutter''ImportV'};

// 定义集合方法二
Set a = {};
a.add('Dart');
a.add('Flutter');
a.add('ImportV');

// 创建空集合:
Set a = {};  //  方法一
var a = <String>{};  // 方法二
var a = {};  // 这样会创建一个空的 Map,而不是 Set

// 获取集合中元素的个数:
Set a = {'Dart''Flutter''ImportV'};
print(a.length);  // 输出为 3

// 创建一个不可改变的集合:
Set a = const {'Dart''Flutter''ImportV'};
a.add('Android');  // 报错

// 列表转集合:
List a = ['Dart''Flutter''Android'];
print(a.toSet());

// 集合转列表:
Set a = {'Dart''Flutter''Android'};
print(a.toList());

因为Set是不能重复的集合,所以可以用Set去重;

List a = [1,2,3,1,4,2].toSet().toList();
print(a);   // [1, 2, 3, 4]

6. Map

Map(映射)是用来关联 keys 和 values 的对象。 keys 和 values 可以是任何类型的对象。在一个 Map 对象中一个 key 只能出现一次。

// 定义映射方法一:
Map a = {
// key     value
  'first''Dart',
  'second''Flutter',
  'third''Android'
  };

// 定义映射方法二:
Map a = {};
a['first'] = 'Dart';
a['second'] = 'Flutter';
a['third'] = 'Android';

// 获取映射长度:
print(a.length);

// 获取指定 value 值:
print(a['first']);

// 在映射字面量前添加 const 关键字,定义一个不可改变的映射:
Map a = const {'first''Dart''second''Flutter''third''Android'};
a['third'] = 'iOS'  //报错

常见方法

1.新增一个键值对

  Map<String, int> map8 = Map();
  map8['a8'] = 1;
  print(map8); //{a8: 1}

2.修改一个键值对

  Map<String, int> map9 = {'a9': 1, 'b9': 2};
  map9['a9'] = 9;
  print(map9); //{a9: 9, b9: 2}  

3.update(K key, V update(V value), {V ifAbsent()}) 根据指定的Key对应的value做出修改,同时Map本身也会被修改

  Map<String, int> map10 = {'a10': 1, 'b10': 2, 'c10': 3};
  var resultMap10 = map10.update('b10', (value) => value * 2);
  print(resultMap10); //4
  print(map10); //{a10: 1, b10: 4, c10: 3}
  
  var resultMap101 = map10.update('c', (value) => (value * 2),
      ifAbsent: () => (10)); //如果key不存在,但是有ifAbsent参数,返回idAbsent函数的值,并添加到map中
  print('$resultMap101,${resultMap101.runtimeType}'); //10,int
  print(map10); //{a10: 1, b10: 4, c10: 3, c: 10}

4.remove() 删除一个key

  Map<String, int> map12 = {'a12': 2, "b12": 1};
  map12.remove('a12');
  print(map12); //{b12: 1}
  map12.remove('c12'); //删除一个不存在的key,毫无影响,无报错无警告
  print(map12); //{b12: 1}  

5.removeWhere(bool predicate(K key, V value)) 根据函数条件批量删除key

 Map<String, int> map13 = {'a13': 3, 'b13': 4, 'c13': 1};
 map13.removeWhere((key, value) => value > 3);
 print(map13); //{a13: 3, c13: 1}  

6.containsKey() 是否包含某个key contrainsValue()是否包含某个value

   Map<String ,int> map14 = {'a14':1};
   bool resultMap14 = map14.containsKey('a11'); //false
   bool resultMap141 = map14.containsValue(1); //true

7.forEach(void f(K key, V value)) 遍历Map ,遍历时不可add或者remove

  Map<String, int> map15 = {'a15': 1, 'b15': 2, 'c': 3, 'd': 4, 'e': 5};
  map15.forEach((key, value) {
    print('$key,$value');
    /*
    a15,1
    b15,2
     c,3
     d,4
     e,5
    */
  });
   map15.forEach((key, value) {
    map15['a15'] = 8;
   });
   print(map15); //{a15: 8, b15: 2, c: 3, d: 4, e: 5}
   
   map15.forEach((key, value) {
    map15.addAll({'ccc':22});
   });
   //Uncaught Error: Concurrent modification during iteration: Instance of 'JsLinkedHashMap<String, int>'.

8.map() 遍历每个键值对 根据参数函数,对keyvalue做出修改,转换成其他泛型Map

   Map<String,int> map16 = {'a16':7,"b16":5,'c16':4};
   Map<int,String> map17 = map16.map((key, value) {
       return MapEntry(value, key);
   });
   print(map17);

9.addAll() 两个Map合并,类型需要一致 ,且如果key相同,则会覆盖value

  Map<String,int> map18 = {'a18':1,'b18':7,'a19':2};
  Map<String,int> map19 = {'a19':9};
  map18.addAll(map19);
  print(map18); //{a18: 1, b18: 7, a19: 9}   

10.addEntries(key,value) 两个Map合并,类型需要一致 ,且如果key相同,则会覆盖value

  Map<String,int> map20 = {'a20':2,'b20':3};
  Map<String,int> map21 = {'a21':5,'b21':9};
  map20.addEntries(map21.entries);
  print(map20); //{a20: 2, b20: 3, a21: 5, b21: 9}  

11.putIfAbsent() 存在key则返回value,查不到则返回值 不修改Map

 Map<String,int> map22 = {'a22':3,'b22':4};
 var resultMap22 = map22.putIfAbsent('a22', () => 2); //存在key则返回value,查不到则返回 2 不修改Map
 print('$resultMap22,$map22');//3,{a22: 3, b22: 4} 
 
 var resultMap221 = map2.putIfAbsent('a2', () => 1);
 print('$resultMap221,$map22'); //1,{a22: 3, b22: 4} //存在key则返回value,查不到则返回 1 不修改Map 

12.清除所有键值对

 Map<String,int> map25 = {'a25':2,'b25':3};
 map25.clear();
 print('$map25,${map25.runtimeType}'); //{},_InternalLinkedHashMap<String, int>

运算符

下面是 Dart 定义的运算符:

graph LR
运算符 --> 算数运算符
算数运算符 --> A["+ - * /  加减乘除"]
算数运算符 --> B["~/  除法,返回整数值"]
算数运算符 --> C["%  返回除法余数值"]
运算符 --> 关系运算符
关系运算符 --> 2A["== !="]
关系运算符 --> 2B["> <"]
关系运算符 --> 2C[">= <="]
运算符 --> 类型判断运算符
类型判断运算符 -->8A["as 用于类型转换"] 
类型判断运算符 -->8B["is 如果对象是指定的类型就返回true"]
类型判断运算符 -->8C["is! 如果对象不是指定的类型就返回true"]
运算符 --> 赋值运算符 --> 3A["= -= /= %= >>= ^= += *= ~/= <<= &= |="]
运算符 --> 逻辑运算符 --> 4A["&& || !  与或非"]
运算符 --> 位运算符
位运算符 --> 5A["& | ^  按位与 按位或 按位异或"]
位运算符 --> 5B["~ << >>  按位取反 左移动 右移动"]
运算符 --> 条件表达式
条件表达式 --> 6A["condition ? expr1 : expr2  三目运算"]
条件表达式 --> 6B["expr1 ?? expr2"]
运算符 --> 级联运算符 --> 7A["级联运算符(..)严格来讲不是一个运算符﹐而是一个Dart的特殊语法
"]

1. 算数运算符

除了基本的算术运算外,Dart 还支持前缀和后缀、自增和自减运算符:

var a, b;
a = 0;

b = ++a;  // a自加1后赋值给b
b = a++;  // a赋值给b后自加1
b = --a;  // a自减1后赋值给b
b = a--;  // a赋值给b后自减1

2. 类型判断运算符

dart也是通过 is 关键字来对类型进行检查以及使用 as 关键字对类型进行强制转换,如果判断不是某个类型dart中使用 is!

int number = 123;
double distance = 123.4;
num age = 12;
print(number is num);//true
print(distance is! int);//true
print(age as int);//12
print(distance as int);
//Uncaught Error: TypeError: 123.4: type 'JSNumNotInt' is not a subtype of type 'int'

3. 赋值运算符

下面示例使用几个赋值和复合赋值运算符,其他使用方法类似:

a = 123;  // 使用 = 直接为变量赋值。

b ??= 456;  // 使用 ??= 运算符时,只有当 b 值为 null 时才会被赋值

a *= 3;  // 赋值并做乘法运算,相当于 a = a * 3

4. 逻辑运算符

下面是关于逻辑表达式的示例:

if (!list && (count == 0 || count == 3)) {
  // ...Do something...
}

5. 位运算符

位运算符把数字转为二进制来运算:

int a = 12
int b = 6

// 按位与,参与运算的两个值,如果两个相应二进位都为 1,则该位结果为 1,否则为 0
print(a & b);  // 结果为 4

// 按位或,只要对应的两个二进位有一个为 1,结果位就为 1
print(a | b);  // 结果为 14

// 按位异或,当两个对应的二进位相异时,结果位为 1,否则为 0
print(a ^ b);  // 结果为 10

// 按位取反,对数据的每一个二进位,把 1 变为 0,把 0 变为 1
print(~a);  // 结果我为 -13

// 左移,把 << 左边的数的各二进位左移若干位,由 << 右边的数指定移动位数,高位丢弃,低位补 0
print(a << 2);  // 结果为 48,相当于乘以 2 的 2 次方

// 右移,把 << 左边的数的各二进位右移若干位,由 << 右边的数指定移动位数
print(a >> 2);  // 结果为 3,相当于除以 2 的 2 次方

6. 条件表达式

Dart有两个运算符,有时可以替换 if-else 表达式, 让表达式更简洁,称为条件表达式:

condition ? expr1 : expr2

如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值。

expr1 ?? expr2

如果 expr1 不是 null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值。

如果条件判断是根据布尔值, 考虑使用第一种,如果是根据是否为 null,考虑使用第二种。

var a = 'Dart';
var b = 'Flutter';
var c = a ?? b.toUpperCase();  // 此处 c 的字面量为 Dart

7. 级联运算符

级联运算符 .. 可以实现对同一个对像进行一系列的操作。 除了调用函数外, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。

querySelector('#confirm')   // 获取对象。
  ..text = 'Confirm'    // 调用成员变量。
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一句调用函数 querySelector() ,返回获取到的对象。 获取的对象依次执行级联运算符后面的代码, 代码执行后的返回值会被忽略。

上面的代码等价于:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

级联运算符是可以嵌套的。但要注意,在返回对象的函数中谨慎使用级联操作符。 例如,下面的代码是错误的:

var sb = StringBuffer();
sb.write('foo')
  ..write('bar');   // sb.write() 函数调用返回 void, 不能在 void 对象上创建级联操作。

流程控制语句

Dart 中的流程控制语句与 Java 相似。

1. if - else 分支

和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型。

int a = 1;
if (a < 0) {
  a++;
} else if (a > 0) {
  a--;
}

2. switch - case 语句

在 Dart 中 switch 语句使用 == 比较整数、字符串、编译时常量或者枚举类型。 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。

在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句。 除  break 以外,还有可以使用 continue, throw 或者 return。当没有 case 语句匹配时,执行 default 代码:

var a = 'yes';
switch (a) {
  case 'yes':
    print('yes');
    break;
  case 'no':
    print('no');
    break;
  default:
    print('fault');
}

3. for 循环

进行迭代操作,可以使用标准 for 语句。 例如:

// 计算 5 的阶乘
int result = 1;
for (int i = 1; i < 6; i++) {
  result = result * i;
}

闭包在 Dart 的 for 循环中会捕获循环的初始索引值, 来避免 JavaScript 中常见的陷阱。下面的代码输出的是 0 和 1,但是在 JavaScript 中会连续输出两个 2 :

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

4. while 和 do while 循环

while 循环是在执行前判断执行条件,do-while 循环是在执行后判断执行条件:

// 计算 5 的阶乘
int i = 1;
int result = 1;
// 使用 while 循环
while (i < 6) {
  result = result * i;
  i++;
}
// 使用 do - while 循环
do {
  result = result * i;
  i++;
} while (i < 6);

5. break 和 continue

使用 break 停止程序循环:

for (int i = 1; i < 5; i++) {
  if (i == 3) {
    break;
  }
  print(i);  // 输出结果为 1 2(i = 3 时循环结束)
}

使用 continue 跳转到下一次循环:

for (int i = 1; i < 5; i++) {
  if (i == 3) {
    continue;
  }
  print(i);  // 输出结果为 1 2 4(i = 3 时跳到下一次循环)
}

6. 其他循环

使用 for...in... 循环:

// 遍历数组
List a = ['Dart''Flutter''Android'];
for (var item in a) {
  print(item);
}

使用 forEach 循环:

// 遍历数组
List a = ['Dart''Flutter''Android'];
a.forEach((var item) {
  print(item);
});