3.Dart语法

6 阅读9分钟

1.main函数

main(List<String> args) {
  // List<String> args -> 列表<String> - 泛型
  print("Hello World");
}

  • Dart语言的入口也是main函数,并且必须显示的进行定义;

  • Dart的入口函数main是没有返回值的;

  • 传递给main的命令行参数,是通过List完成的,从字面值就可以理解List是Dart中的集合类型,其中的每一个String都表示传递给main的一个参数;

  • 定义字符串的时候,可以使用单引号或双引号;

  • 每行语句必须使用分号结尾,很多语言并不需要分号,比如Swift、JavaScript;

2.变量和常量的声明

声明常量 const 和final

  // final声明常量
  final height = 1.88;
  // height = 2.00;

  //const声明常量
  const address = "广州市";
  // address = "北京市";

那么const 和final 的区别是什么呢?

const必须赋值 常量值(编译期间需要有一个确定的值),final可以通过计算/函数获取一个值(运行期间来确定一个值,其实用法不用太纠结,一般就用final就行,final用的更多,只有在声明常量构造函数等特殊时间用下const

  const date1 = DateTime.now(); 写法错误
  final date2 = DateTime.now();
class Person {
  final String name;
  const Person(this.name);
}
    
main(List<String> args) {
  // 在Dart2.0之后, new可以省略
  const p1 = const Person("why");
  const p2 = const Person("why");
  const p3 = const Person("lilei");

  print(identical(p1, p2));
  print(identical(p2, p3));
}

上面的代码注意几点:

  • 在Dart2.0之后, new可以省略
  • 前面的打印一个是true,一个是false
  • identical 是判断两个对象是不是同一个对象

声明变量 类型推导var 和直接声明类型

下面是直接声明,好处是类型直接可以看明白

 String name = "why";
 int age = 20;
 double height = 1.88;
 bool isStudent = true;
 List<String> names = ["why", "lilei", "hanmeimei"];
 Map<String, int> ages = {"why": 20, "lilei": 18, "hanmeimei": 19};
 Set<String> namesSet = {"why", "lilei", "hanmeimei"};

下面是var,是类型推导,虽然是声明变量,但是第一次声明了一种类型后,第二次赋值的时间你不能给他另外一种类型。

// var声明变量
var age = 20;
// age = "abc"; 这句代码会报错,因为类型一旦确定,就不能再修改了
age = 30;

dynamic和Object

前面我们说var第一次声明了一种类型,比方说string,第二次赋值你只能再给他一个string,如果你想给他一个int,他就会报错,而dynamic可以允许你这么干,不过我们一般不这么干,因为类型变来变去会让人混乱,不太好。

dynamic name = 'coderwhy';
print(name.runtimeType); // String
name = 18;
print(name.runtimeType); // int

在dart中类型判断可以用runtimeType来获取 Object,在dart中所有对象都继承于Object,这个就是一个最底层的基类

题外问题identical 和 ==

默认行为下这俩是一样的,如果不重写 == 的话,dart中也有运算符重载,identical对于对象来说本质是比较对象是否相等

3.数据类型

3.1 数字类型 (Numbers)

在 Dart 中,你不需要过多关心数值是否有符号或精度深度,最常用的就是 intdouble

注意intdouble 可表示的范围并不是固定的,它取决于运行 Dart 的平台(例如在 移动端 VM 上和在 Web 编译器下有所不同)。

1. 整数类型 int

int age = 18;
int hexAge = 0x12; // 支持十六进制
print(age);    // 18
print(hexAge); // 18

2. 浮点类型 double

double height = 1.88;
print(height);

3. 字符串与数字的转化

  • 字符串转数字:使用 parse() 方法。
  • 数字转字符串:使用 toString()toStringAsFixed()(保留小数)。
// 1. 字符串转数字
var one = int.parse('111');
var two = double.parse('12.22');
print('${one} ${one.runtimeType}');    // 111 int
print('${two} ${two.runtimeType}');    // 12.22 double

// 2. 数字转字符串
var num1 = 123;
var num2 = 123.456;
var num1Str = num1.toString();
var num2Str = num2.toString();
var num2StrD = num2.toStringAsFixed(2); // 四舍五入保留两位小数

print('${num1Str} ${num1Str.runtimeType}');   // 123 String
print('${num2Str} ${num2Str.runtimeType}');   // 123.456 String
print('${num2StrD} ${num2StrD.runtimeType}'); // 123.46 String

3.2 布尔类型 (Booleans)

Dart 使用 bool 类型来表示布尔值,取值为 truefalse

重要:Dart 是类型安全的,不支持“非 0 即真”或“非空即真”,这句话怎么理解呢?就是说如果需要逻辑真假判断的时间,判断的表达式或者条件必须是一个明确的真假,而不能你给一个模糊的,比方说你不能说一个非空字符串就是真的,必须要调下不为空的方法,如果判断条件是3,那也不行,你要写成 >0类似这种,就是说表达式的结果必须严格是一个bool值

var isFlag = true;
print('$isFlag ${isFlag.runtimeType}');

// 错误示例
var message = 'Hello Dart';
if (message) { // 编译报错:这里必须是一个布尔表达式
  print(message);
}

3.3 字符串类型 (Strings)

Dart 字符串是 UTF-16 编码单元序列。单行和多行这里基本和swift中的一模一样

1. 定义字符串

可以使用单引号 ' 或双引号 "

var s1 = 'Hello World';
var s2 = "Hello Dart";
var s3 = 'Hello\'Fullter'; // 内部有单引号时可以用反斜杠转义
var s4 = "Hello'Fullter";  // 或者内外使用不同的引号

2. 多行字符串

使用三个单引号 ''' 或三个双引号 """

var message1 = '''
哈哈哈
呵呵呵
嘿嘿嘿''';

3. 字符串拼接 (插值)

使用 ${expression}。如果表达式只是一个变量(标识符),可以省略 {},这句话可以理解为,只能是一个变量,不能调变量调方法,或者变量.

var name = 'coderwhy';
var age = 18;
print('my name is ${name}, age is $age');

3.4 集合类型 (Collections)

Dart 内置了三种最常用的集合:List (列表)、Set (集合)、Map (映射)。

3.4.1 集合的定义方式

类型描述定义示例
List有序、可重复var list = [1, 2, 3];
Set无序、不重复var set = {'a', 'b'};
Map键值对 (Key-Value)var map = {'key': 'value'};
// List 定义
var letters = ['a', 'b', 'c']; // 类型推导
List<int> numbers = [1, 2, 3]; // 明确指定

// Set 定义
var lettersSet = {'a', 'b'};
Set<int> numbersSet = {1, 2, 3};

// Map 定义
var infoMap1 = {'name': 'why', 'age': 18};
Map<String, Object> infoMap2 = {'height': 1.88, 'address': '北京市'};

3.4.2 常用操作

1. 公共属性

  • length:获取集合长度。

2. List/Set 的增删改查

numbers.add(5);      // 添加
numbers.remove(1);   // 删除指定元素
numbers.contains(2); // 是否包含
numbers.removeAt(3); // List 特有:根据索引删除

3. Map 的特有操作

print(infoMap1['name']); // 根据key获取value
print(infoMap1.entries); // 获取所有条目
print(infoMap1.keys);    // 获取所有key
print(infoMap1.values);  // 获取所有value
print(infoMap1.containsKey('age')); // 是否包含某个key
infoMap1.remove('age');  // 根据key删除

4. 函数的使用

4.1 函数的基本定义

Dart 是一种真正的面向对象语言,所以函数也是对象,具有类型 Function。这意味着函数可以作为变量定义,或者作为其他函数的参数、返回值。

1. 定义方式

返回值 函数名称(参数列表) {
  函数体
  return 返回值
}

示例代码:

main(List<String> args) {
  print(sum(20, 30));
}

// 返回值的类型是可以省略的(开发中不推荐)
int sum(int num1, int num2) {
  return num1 + num2;
}

2. 箭头函数

如果函数中只有一个表达式,可以使用箭头语法,这个和js中的箭头函数不太一样,js箭头函数是为了解决函数二义性的。 示例代码:

void bar() => print("bar函数被调用");

4.2 函数的参数与重载

1. 函数重载问题

什么是函数重载? 函数重载就是方法名相同,只是参数不同。 在 Dart 中是没有函数重载的。

2. 可选参数

由于没有重载,Dart 通过可选参数来实现各种调用需求。

注意:只有可选参数才可以有默认值,必须参数(必选参数)不能有默认值且必须传参。

1) 位置可选参数 [参数类型 参数名 = 默认值]

  • 原则:实参和形参在匹配时,是根据位置进行匹配的。因为有顺序,所以是用[],list有顺序,也是用[]
  • 如果不设置默认值:在空安全环境下,必须使用可空类型(类型后加 ?),此时默认值为 null

示例代码

// 位置可选参数: [int age, double height]
void sayHello2(String name, [int age = 10, double height = 2]) {
  print('name=$name age=$age height=$height');
}

// 不设置默认值的写法 (必须使用可空类型 ?)
void sayHello2Nullable(String name, [int? age, double? height]) {
  // 因为只是打印输出,不需要进行非空判断,如果是使用,必须进行非空判断
  print('name=$name age=$age height=$height');
}

main() {
  sayHello2("why");          // name=why age=10 height=2.0
  sayHello2("why", 18);      // name=why age=18 height=2.0
  
  sayHello2Nullable("why");  // name=why age=null height=null
}

2) 命名可选参数 {参数类型 参数名 = 默认值}

  • 调用时:通过 参数名: 值 的形式,顺序可以随意,因为和顺序没关系,和map类似,所以用{}
  • 如果不设置默认值:同样需要使用可空类型(类型后加 ?),默认值为 null

示例代码:

// 命名可选参数有默认值
void sayHello3(String name, {int age = 10, double height = 3.14}) {
  print('name=$name age=$age height=$height');
}

// 命名可选参数无默认值 (使用可空类型 ?)
void sayHello3Nullable(String name, {int? age, double? height}) {
  // 因为只是打印输出,不需要进行非空判断,如果是使用,必须进行非空判断
  print('name=$name age=$age height=$height');
}

main() {
  sayHello3("why", height: 1.88); // age 使用默认值 10
  sayHello3("kobe", age: 30, height: 1.98); // 所有参数都传入
  sayHello3Nullable("why");       // age=null height=null
  sayHello3Nullable("james", age: 38); // height=null
}

4.3 函数是一等公民

动作阶段(传统语言):函数像是一个**“遥控器按钮”**。你只能去“按”它(调用),它才会产生一个动作(执行代码)。你没法把“按按钮”这个动作揣在兜里带走,也没法把它递给另一个遥控器。

物品阶段(Dart / 一等公民):函数从“动作”进化成了一个**“实体光盘”或者“U盘”**。 可以存起来:你可以把光盘放进抽屉(赋值给变量)。 可以送人:你可以把光盘递给朋友(作为参数传给另一个函数)。 可以制造:一个工厂可以生产一张光盘作为产品卖给你(作为函数返回值)。

1. 直接传递定义的函数

示例代码

main(List<String> args) {
  // 1.直接找到另外一个定义的函数传进去
  test(bar);
}

void test(Function foo) {
  foo();
}

void bar() {
  print("bar函数被调用");
}

2. 函数类型别名 typedef

当函数的参数类型变得复杂时,使用 typedef 封装以提高可读性,就是起别名,容易看,不要那么长,太丑陋。

示例代码

typedef Calculate = int Function(int num1, int num2);

void test(Calculate calc) {
  print(calc(20, 30));
}

main() {
  // 传入匿名函数
  test((num1, num2) {
    return num1 + num2;
  });
}

3. 函数作为返回值

示例代码:

Calculate demo() {
  return (num1, num2) {
    return num1 * num2;
  };
}

main() {
  var demo1 = demo();
  print(demo1(20, 30)); // 600
}

4.4 匿名函数、作用域与闭包

1. 匿名函数,类似于js中箭头函数

定义格式:(参数列表) {函数体};

示例代码:

var movies = ['盗梦空间', '星际穿越', '少年派', '大话西游'];

// 使用 forEach 配合匿名函数
movies.forEach((item) {
  print(item);
});

// 箭头形式的匿名函数
movies.forEach((item) => print(item));

2. 词法作用域

作用域是由代码的结构 ({}) 决定的。

示例代码:

var name = 'global';
main() {
  void foo() {
    print(name); // 会一层层向外查找,直到找到 global
  }
  foo();
}

3. 词法闭包

闭包可以访问其词法范围内的变量,即使函数在其他地方被使用。

示例代码:

makeAdder(num addBy) {
  return (num i) {
    return i + addBy; // 捕获了词法范围内的 addBy
  };
}

main() {
  var adder2 = makeAdder(2);
  print(adder2(10)); // 12
  
  var adder5 = makeAdder(5);
  print(adder5(10)); // 15
}

4.5 返回值规则

所有函数都返回一个值。

如果没有手动指定返回值,则语句会隐式附加 return null; 到函数体末尾。

示例代码:

main() {
  print(foo()); // 输出 null
}

foo() {
  print('foo function');
}

五. 运算符

5.1 数字运算 (除法、整除、取模)

Dart除了别的语言的除法,还有取整除法。

var num = 7;
print(num / 3);  // 除法操作: 结果 2.3333... (double)
print(num ~/ 3); // 整除操作: 结果 2 (int)
print(num % 3);  // 取模操作: 结果 1 (int)

5.2 ??= 赋值操作

  • 当原来的变量为 null 时,那么将值赋值给这个变量;当原来的变量有值时,那么 ??= 不执行,这个记住就行,也很简单。

示例代码:

main(List<String> args) {
  var name = null;
  name ??= "lilei";
  print(name); // 输出: lilei (原先为 null,被赋值)

  var name2 = 'coderwhy';
  name2 ??= 'james';
  print(name2); // 输出: coderwhy (原先已有值,不执行赋值)
}

5.3 条件运算符 ??

格式:expr1 ?? expr2

  • ?? 前面的数据有值,那么就使用 ?? 前面的数据;如果前面的数据为 null,那么就使用后面的值,这个和三目运算符差不多

示例代码:

var name = null;
var temp = name ?? "lilei";
print(temp); // lilei

5.4 级联语法 ..

级联语法允许你对同一个对象进行连续的一系列操作(赋值、调用方法),而不需要重复写对象名,类似于链式调用。

示例代码:

class Person {
  String name = "";

  void run() {
    print("running");
  }

  void eat() {
    print("eating");
  }
}

main(List<String> args) {
  // 普通写法
  // var p = Person();
  // p.name = "why";
  // p.run();
  // p.eat();

  // 级联语法写法
  var p = Person()
            ..name = "why"
            ..eat()
            ..run();
}

六. 流程控制

Dart 的流程控制与大部分主流语言相似,但有其特有的强制规范。

6.1 ifelse

  • 核心注意点:不支持“非空即真”或者“非0即真”,必须有明确的 bool 类型。

6.2 循环操作

1. 基础 for 循环

示例代码:

// 1. 基础 for 循环
for (var i = 0; i < 10; i++) {
  print(i);
}

// 2. 遍历数组 (普通方式)
var names = ["why", "cba", "cba"];
for (var i = 0; i < names.length; i++) {
  print(names[i]);
}

2. for-in 遍历

遍历 ListSet 类型最简洁的方式,dart中的for in 针对于Iterable 接口都可以用for in,类似于js中的for of

var names = ["why", "cba", "cba"];
for (var name in names) {
  print(name);
}

6.3 switch-case

  • 支持整数、字符串或编译时常量。
  • 注意:在 Dart 中,每一个 case 语句默认情况下必须以一个 break 结尾。
main(List<String> args) {
  var direction = 'east';
  switch (direction) {
    case 'east':
      print('东面');
      break;
    case 'south':
      print('南面');
      break;
    default:
      print('其他方向');
  }
}