# Dart教程 - 2 基础语法

# 2.1 数据类型

Dart是一种强类型语言,以下是Dart中支持的数据类型:

  • 数值型:int(整数)、double(浮点数)
  • 布尔型:bool
  • 字符串:String
  • 列表:List
  • 集合:Set
  • 映射:Map

下面先介绍一下 数字字符串布尔值,其他的后面再说。

# 1 数字(num)

和数学中的一致,有整数和小数。

例如:2,123,-12,12.13,-10.24

# 2 字符串

字符串(string),又称文本,是由任意数量的字符如中文、英文、各类符号、数字等组成。

在Dart中,支持单引号、双引号、三引号,和Python是一模一样的。

例如:

'123'      // 这个是字符串,不是数字
"abc"
"""
你好,China
"""
1
2
3
4
5

# 3 布尔(bool)

值只有2个,即 truefalse,也就是真和假。

List、Set、Map 后面介绍集合的时候再说。

# 2.2 注释

我们在学习任何语言,都会有注释,注释的作用就是向别人解释我们编写的代码的含义和逻辑,使代码有更好的可读性,注释不是程序,是不会被执行的

注释一般分类两类,单行注释多行注释

# 1 单行注释

单行注释以 // 开头,// 号右边为注释内容。

例如:

// 我是单行注释,打印Hello World
print("Hello World!")
1
2

注意:// 号和注释内容一般建议以一个空格隔开,这是代码规范,建议大家遵守。

单行注释一般用于对一行或一小部分代码进行解释。

# 2 多行注释

多行注释是以**/* */**括起来,中间的内容为注释内容,注释内容可以换行。

/*
我是多行注释,
我来哔哔两句
*/
print("哔~~")
1
2
3
4
5

多行注释一般对:类、方法进行解释,类和方法后面我们再学习。

# 2.3 变量和常量

变量是在程序运行的时候存储数据用的,可以想象变量为一个盒子。

整数、浮点数(小数)、字符串都可以放在变量中。

# 1 定义变量

void main(List<String> args) {
  String name = "zhangsan"; // 定义一个变量,用来存储姓名
  int age = 18;
  double height = 1.78;

  name = "李四"; 						// 修改变量的值
  age = 19;									

  print(name);
  print(age);
  print(height);
}
1
2
3
4
5
6
7
8
9
10
11
12

分别使用 String 来定义字符串、int 来定义整数、double 来定义浮点数。

# 2 使用var定义变量

在 dart 中可以使用 var 来替代具体类型的声明,会自动推导变量的类型。

例如:

void main(List<String> args) {
  var name = "zhangsan"; // 定义一个变量,用来表示姓名
  var age = 18;
  var height = 1.78;

  name = "李四"; // 修改变量的值
  // name = 123; // 错误, name是字符串类型的,无法赋值其他类型数据
  age = 19;

  print(name);
  print(age);
  print(height);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

需要注意,var name="zhangsan"; 已经确定了变量name的类型为字符串,无法使用 name = 123; 赋值其他类型数据。

# 3 常量

变量的值是可以变的,就像上面我们定义了变量,后面可以修改它的值。

常量就是值不能改变的。

在 Dart 中,使用 finalconst 关键字来定义常量。

两者的区别在于:

  • final 关键字定义的常量是在第一次使用时初始化的,初始化后不可改变;
  • const 关键字定义的常量在编译时就已经被初始化了,永远不会改变。

举个栗子:

main() {
  const PI = 3.141592653; 						// const定义常量,程序运行之前就知道了值
  final PI2 = 3.141592653;						// 使用const定义的常量也可以使用final来定义
  
  final nowTime = DateTime.now(); 		// final定义常量,需要等到程序第一次运行的时候才能知道值
  
  //const nowTime = DateTime.now(); 	// const是不可以赋值为DateTime.now(),因为编译期DateTime.now()的值不是确定的
}
1
2
3
4
5
6
7
8

使用const定义的常量也可以使用final来定义,但是使用 const 定义,这有助于优化性能和节省内存。

另外需要注意的是,常量和变量的类型可以显式指定,也可以通过类型推断来自动推断。例如:

// 使用 const 关键字定义常量,并显式指定类型为 int
const int a = 10;

// 使用 const 关键字定义常量,并自动推断类型为 int
const b = 20;
1
2
3
4
5

# 4 dynamic

在使用var定义变量的时候,我们无法给一个string类型的变量赋值其他类型数据,但是dynamic类型的变量可以在运行时动态地改变其类型,而不需要在编译时指定类型。

dynamic是一种特殊的类型,它可以表示任何类型的值。

下面是一个使用dynamic类型的简单示例:

dynamic variable = "Hello World";
print(variable); // 输出:Hello World

variable = 42;
print(variable); // 输出:42

variable = true;
print(variable); // 输出:true
1
2
3
4
5
6
7
8

在上面的示例中,我们定义了一个名为 variable 的变量,并将其初始化为字符串 "Hello World"。然后,我们将其值更改为整数42,最后再将其值更改为布尔值true。这些更改都是在运行时进行的,而不需要在编译时指定类型。

请注意,由于dynamic类型可以表示任何类型的值,因此它可能会导致一些意想不到的错误,尤其是在代码变得复杂时。因此,建议尽可能使用更具体的类型来声明变量。

# 2.4 类型类型与转换

# 1 获取数据类型

通过 变量.runtimeType 可以获取变量当前的类型。

举个栗子:

void main() {
  String name = "doubi";
  print(name.runtimeType); 	// String

  var age = 18;
  print(age.runtimeType); 	// int

  dynamic dyn = 1.78;
  print(dyn.runtimeType); 	// double

  dyn = "hello";
  print(dyn.runtimeType); 	// String
}
1
2
3
4
5
6
7
8
9
10
11
12
13

使用 dynamic 定义的变量,数据类型是可以变化的。

# 2 数据类型判断

通过 is 关键字,可以判断当前变量的类型。

举个栗子:

import 'dart:ffi';

void main() {
  var age = 18;
  print(age is int); 		// true
  print(age is String); // false

  dynamic dyn = 1.78;
  print(dyn is double); // true
  print(dyn is String); // false

  dyn = "hello";
  print(dyn is String); // true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

is结果返回的是一个 bool 值。

# 3 int 与 double 转换

int 转换为 double

int i = 3;
double d = i.toDouble();
print(d);			// 输出:3
1
2
3

一般好像用不到 int 转 double

double 转 int

double d = 3.14;
int i = d.toInt();
print(i); // 输出:3
1
2
3

小数点后忽略,向下取整。

可以使用round()方法,在取整时,四舍五入:

double d = 3.6;
int i = d.round();
print(i); 			// 输出:4
1
2
3

# 4 String与数字相互转换

数字转换为String

使用 toString() 方法将数字转换为字符串。

int number = 42;
String str = number.toString();
print(str);  // 输出:42
1
2
3

String转换为数字

可以使用 parse() 函数将字符串转换为数字。

String str = '42';

int num1 = int.parse(str);
print(num1); // 输出:42

double num2 = double.parse(str);
print(num2); // 输出:42.0
1
2
3
4
5
6
7

需要注意的是,如果字符串不能被解析为数字,则会抛出一个异常,导致程序终止运行。

因此,建议在使用 parse() 函数时使用 try-catch 语句来处理可能的异常。(异常处理后面再学习)

我们可以使用 tryParse() 函数来将字符串转换为数字,不会抛出异常。

void main() {

  var str = 'hello';
  var number = int.tryParse(str);
  print(number);						// 输出:null

}
1
2
3
4
5
6
7

转换不成功,返回null,不会抛出异常。

需要注意 tryParse(参数) 方法的参数不能为null,转换会报错。

# 5 dynamic 转其他类型

将dynamic转换为int、double或String,直接赋值即可。

dynamic variable = 42;
int i = variable;
print(i);
1
2
3

当然类型相同才能复制,否则会报错。

# 2.5 标识符

什么是标识符?

标识符就是名字,例如变量的名字、方法的名字、类的名字。

起名字肯定会有限制,肯定不能 张Three 这样起名字,所以标识符也有限制。

# 1 标识符命名规则

标识符需要遵守一下规则:

  1. 只能是英文、数字、下划线,其他任何内容都不允许;
  2. 不能使用数字开头,可以英文或下划线开头;
a					# 可以
a_b				# 可以
_a				# 可以
a1				# 可以
a_b_a			# 可以

1					# 错误
1_				# 错误
1_a				# 错误
1
2
3
4
5
6
7
8
9
  1. 大小写敏感,大小写不同是不同的标识符;
name = "zhangsan"   
Name = "lisi"				# 和 name 是不同的标识符
1
2
  1. 不能使用关键字,关键字就是Dart中保留的一些单词,有特殊的用途,不能被用作标识符;

Dart 中有如下关键字:

abstract   		as        		assert    		async     		await     
break      		case      		catch     		class     		const     
continue   		covariant 		default   		deferred  		do        
dynamic    		else      		enum      		export    		extends   
extension  		external  		factory   		false     		final     
finally    		for       		Function  		get       		hide      
if         		implements 		import    		in        		interface 
is         		late      		library   		mixin     		new       
null       		on        		operator  		out       		part      
rethrow    		return    		set       		show      		static    
super      		switch    		sync      		this      		throw     
true       		try       		typedef   		var       		void      
while      		with      		yield     
1
2
3
4
5
6
7
8
9
10
11
12
13

这么多怎么记?不用记!不用记!不用记!

后面每个关键字都会学到,自然知道每个关键字是做什么用的,不用记!

# 2 变量命名规范

使用上面说的规则,我们可以定义变量名了。

但是为了优雅、统一、规范,我们在定义变量名时,还应该遵守以下规范,虽然你不遵守,没人开枪打死你,但是建议你遵守。

  1. 见名知意

看见一个变量名,就知道这个变量名是干嘛的。

a = "zhangsan"								// 看到a,鬼知道a是干嘛的
name = "zhangsan"							// 看到name,就知道这是个名字,简单明了
personName = "zhangsan"		    // 在确保明了的前提下,尽量减少长度,这个有点不够简洁
1
2
3
  1. 类名使用首字母大写的驼峰规则
class BankAccount {}
class ShoppingCartItem {}
1
2
  1. 变量使用首字母小写的驼峰规则
String name;
String accountNumber;
1
2

# 2.6 字符串扩展

# 1 字符串定义方式

在上面我们使用双引号 " 来定义字符串,但其实字符串有3种不同的定义方式:

方式一:双引号定义法

var text1 = "我是一个字符串"
1

方式二:单引号定义法

var text2 = '我是一个字符串'
1

方式三:三引号定义法

使用三引号定义字符串,还可以换行:

var text3 = """我是一个字符串,
我还可以换行"""
1
2

# 2 转义字符

如果我们的字符串中包含引号,该怎么处理呢?

如果字符串中包含单引号,可以使用双引号定义法:

String str = "Hello 'Dart'";
1

如果字符串中包含双引号,可以使用单引号定义法:

String str = 'Hello "Dart"';
1

还有一个通用的方法,就是使用转义符 \ ,在引号前面加上 \ 表示后面的字符是普通字符串:

String str = "Hello \"Dart\"";
String str = "Hello \'Dart\'";
1
2

同样,如果你想在字符串中输出 \t ,你可能会这样写:

String str = "Hello \t \"Dart\"";
print(str);
1
2

但运行完成结果却没有 \t ,因为 \t 是制表符,同样 \n 是换行符,如果想在字符串中输出 \t\n 等特殊字符,也是需要对 斜杠 \ 进行转义:

String str = "Hello \\t \"Dart\"";
print(str);
1
2

# 3 字符串拼接

# 方式一:使用插值表达式

在Dart中,可以使用字符串插值(${} )的方式进行字符串格式化和拼接。

举个栗子:

void main() {
  
  String name = 'doubi';
  int age = 18;

  // 使用 ${} 进行字符串插值
  String message = '我的名字是${name}, 今年${age}岁, 明年${age + 1}岁。';
  print(message); 		// 输出:我的名字是doubi, 今年18岁, 明年19岁。
  
}
1
2
3
4
5
6
7
8
9
10

可以看到在 ${} 括号内填写变量和执行任意的 Dart 表达式。

如果直接拼接变量,还可以直接使用 $

String name = 'doubi';
int age = 18;

String message1 = '我的名字是$name, 今年$age岁, 明年${age + 1}岁。';
1
2
3
4

# 方式二:使用加号拼接

如果是字符串或字符串变量之间的拼接,直接使用 加号+ 拼接即可,例如:

String name = 'zhangsan';
print("我的名字是" + name + ", 我是法外狂徒");
1
2

如果是拼接其他类型,需要先转换为字符串,然后再拼接,有点麻烦:

String name = 'zhangsan';
int age = 18;
  
// 可以使用 + 符号进行字符串拼接
String message = '我的名字是' + name + ", 今年" + age.toString() + '岁,明年' + (age + 1).toString() + '岁。';
print(message); 				// 我的名字是zhangsan, 今年18岁,明年19岁。
1
2
3
4
5
6

需要调用 .toString() 方法将数字、boolean、对象等类型转换为字符串,然后再拼接。

# 4 判断字符串非空

可以使用 .isEmpty.isNotEmpty 来判断字符串是否为空字符串。

空字符串就是没有内容的字符串。

举个栗子:

String str = "";
print(str.isEmpty);				// 输出: true
print(str.isNotEmpty);		// 输出: false
1
2
3

# 5 浮点数格式化

在输出浮点数的时候,我们可以指定输出的浮点数的精度。

举个栗子:

double number = 1234.56789;

String message = '浮点数是:${number.toStringAsFixed(2)}';
print(message); // 输出:浮点数是:1234.57
1
2
3
4

使用 toStringAsFixed() 方法将浮点数格式化为固定位数的小数,结果会四舍五入。

# 2.7 运算符

下面介绍 Dart 中常见的运算符:算数运算符赋值运算符关系运算符

# 1 算数运算符

算数运算符是干嘛的?

就是进行数学运算的,就是小学学的加、减、乘、除等。

Dart 中常见的算数运算符如下:

运算符 描述 示例
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取余 a % b
~/ 整除 a ~/ b

举个例子:

main() {
  int a = 10;
  int b = 3;

  print(a + b); 		// 输出 13
  print(a - b); 		// 输出 7
  print(a * b); 		// 输出 30
  print(a / b); 		// 输出 3.3333333333333335
  print(a % b); 		// 输出 1
  print(a ~/ b); 		// 输出 3
}
1
2
3
4
5
6
7
8
9
10
11

# 2 赋值运算符

赋值运算符就是用来给变量赋值的。

# 赋值运算符
运算符 描述 举例
= 赋值运算符 就是把 = 右边的值赋值给左边的变量,之前用过了。

例如:

int a = 2;
int b = 3;
int c = a + b;
1
2
3

除了赋值运算符,下面还有复合赋值运算符。

# 复合赋值运算符

复合赋值运算符就是经过计算后,将值赋给前面的变量。

运算符 描述 举例
+= 加法赋值运算符 c += a 等效于 c = c + a
-= 减法赋值运算符 c -= a 等效于 c = c - a
*= 乘法赋值运算符 c *= a 等效于 c = c * a
/= 除法赋值运算符 c /= a 等效于 c = c / a
%= 取模赋值运算符 c %= a 等效于 c = c % a
~/= 整除赋值运算符 c ~/= a 等效于 c = c ~/ a

举个栗子:

void main() {
  int a = 5;
  int b = 3;
  a += b; // a = a + b
  print(a); // 8

  a = 5;
  a -= b; // a = a - b
  print(a); // 2

  a = 5;
  a *= b; // a = a * b
  print(a); // 15
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

复合赋值运算符感觉这么写可读性还变差了,有什么好处呢?

  1. 更加简练;
  2. 效率更高。

# 3 自增自减运算符

Dart中的自增自减运算符包括++--。它们可以用于对变量进行加1或减1操作。

举个栗子:

void main() {
  int a = 5;
  a++; // a自增1
  print(a); // 输出 6
  a--; // a自减1
  print(a); // 输出 5
}
1
2
3
4
5
6
7

需要注意的是,自增自减运算符可以放在变量的前面或后面,这会影响到表达式的值。

如果运算符放在变量的前面,表示先加减,后使用;如果运算符放在变量的后面,表示先试用,后加减。

举个栗子:

void main() {
  int a = 5;
  int b = a++; 		// 先使用a的值还是5,后加减,a加1,a值为0
  print(b); 			// 5
  print(a); 			// 6

  int c = 5;
  int d = ++c; 		// 先加减c+1变为6,后使用,d的值为6
  print(d); 			// 6
  print(c); 			// 6
}
1
2
3
4
5
6
7
8
9
10
11

-- 操作符也是一样的。

# 4 关系运算符

关系运算符也就是比较运算符,主要有以下几种:

运算符 > < >= <= == !=
含义 大于 小于 大于等于 小于等于 等于 不等于

举个栗子:

void main() {
  int a = 10;
  int b = 3;
  print(a == b); // false
  print(a != b); // true
  print(a > b); // true
  print(a < b); // false
  print(a >= b); // true
  print(a <= b); // false
  
  String str = '3';
  print(str == b);	// false,字符串和数字类型是不相等的
}
1
2
3
4
5
6
7
8
9
10
11
12
13

关系运算符得到的结果是 bool 类型,在后面可以通过使用关系运算符来进行条件的判断。例如身高大于120cm就要买儿童票等场景。

# 5 逻辑运算符

主要有以下三种:

运算符 && || !
含义 与,两边条件同时满足 或,两边条件满足一个即可 非,取反值,真为假,假为真

举个栗子:

void main() {
  bool a = true;
  bool b = false;
  print(a && b); 		// false
  print(a || b); 		// true
  print(!a); 				// false
}
1
2
3
4
5
6
7

可以看到逻辑运算符可以用来判断多个bool类型的条件:

void main() {
  int a = 3;
  int b = 12;

  print(a > 5 && b > 10); 		// false
  print(a > 5 || b > 10); 		// true
  print(!(a > 5)); 						// true
}
1
2
3
4
5
6
7
8

!(a > 5) 是将 a > 5 的结果取反。 a > 5 结果为false,取反则为true。

# 逻辑运算符的优先级

优先级:! > && > ||

举个栗子:

void main() {
  bool result = !(1 < 2) && 3 == 3 || 1 == 3 && 2 <= 3;
  print(result); 				// 打印:false
}
1
2
3
4

可以将逻辑运算的结果赋值给一个变量的。当一个逻辑运算语句中同时包含与、或 、 非运算的时候,会按照优先级进行运算。

所以上面先运算 !(1 < 2),然后运算 !(1 < 2) && 3 == 3,然后运算 1 == 3 and 2 <= 3,最后运算||两边的结果。

# 逻辑短路

逻辑短路就是在进行逻辑运算的时候,如果已经能够得到最后的值,就不会再继续进行判断了。

只有 与运算符 && 和 或运算符 || 存在逻辑短路。

举个栗子:

void main() {
  int num = 10;
  bool b = num > 20 && num < 40;
  print(b);
}
1
2
3
4
5

在执行上面代码的时候,num > 20 已经不成立了,而 && 运算需要两边都满足,所以后面的条件成不成立都不会影响最终逻辑表达式的结果,所以 && 后面的 num < 40 就不会再继续判断了,这就是逻辑短路。

同样 || 运算符:

void main() {
  int num = 10;
  bool b = num < 20 || num > 40;
  print(b);
}
1
2
3
4
5

在执行上面代码的时候,num < 20 已经成立了,|| 运算只要一边满足即可,所以整个逻辑表达式肯定是成立的,所以 || 后面的 num > 40 就不会继续判断了。