# 变量和类型

# 变量

# 创建一个变量

因为在Dart语言中,一切皆对象,一个字符串,一个数字甚至null都是一个对象.因此,变量存储的是对象的引用.

var s = 'Hello Dart';
print(s);  // Hello Dart

虽然Dart是一门强类型的语言,但它具有类型推导的功能,即我们在定义的时候,不一定非要强制定义变量的具体类型.像上面的代码中,我们使用var来声明一个变量,Dart会自行帮我们判断出这是一个String类型的变量.上面的代码我们也可以直接指定一个具体的类型:

String s = 'Hello Dart';
print(s);

但我们再次给它赋值其他类型的时候,是会报错的.我们在上面的代码后面添加上如下代码,再次运行:

s = 23;
print(s);

会出现Error: A value of type 'int' can't be assigned to a variable of type 'String'.的错误提示.它告诉我们,不能将一个int整型赋值给一个类型为String的变量.但是下面的场景中,类型是可以改变的:

var s;
print(s);  // null
s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 12

当我们用var声明一个变量,但是不给它初始化,后续的赋值操作中,可以是多种类型的.并且,我们可以看到,未被初始化的变量,它的默认值为null. 一般我们建议,在声明的时候指定一个具体的类型.如果暂时还不知道类型的话,可以使用对象类型和动态类型.

# 对象类型 Object

Object s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 2

# 动态类型 dynamic

dynamic s = 'Hello Dart';
print(s);  // Hello Dart
s = 12;
print(s);  // 2

这两者虽然都是可以让变量接收不同的类型,但是它们是有本质的区别的.因为在Dart中,一切皆对象,所有的类型都是继承自Object,所以对象类型可以接收不同的类型.而dynamic则是让代码在编译阶段不执行类型检查.

# 创建一个常量

通常,创建一个常量,我们使用constfinal关键字,而不是var或者其他类型.

# const

const n = 1;
n = 2;

编译器会报错:Error: Can't assign to the const variable 'n'..告诉我们无法分配给一个常量

# final

final n = 1;
n = 2;

编译器会报同样的错误. 那么这两者有什么区别呢? const是编译时常量,即它的值在编译时就已经固定了,而final则在第一次使用时被初始化(当然,const算是隐式final类型).所以实例变量可以是final类型但不能是const类型,且必须在构造函数体执行之前初始化final实例变量.

# 类型

Dart语言支持多种内建类型

  • Number
  • String
  • Booleab
  • List
  • Set
  • Map
  • Rune

# Number

intdouble这两种类型,它们都是num的亚类型.其中 int 代表整数, double(也是采用 IEEE 754 标准)代表浮点数.num拥有各种基本运算方法: + , - , * , /,~/ , abs() , ceil() , floor()等.

# int

声明一个整数

var a = 1
int b = 1;

# double

声明一个浮点数

var a = 1.1;
int b = 1.1;

# 运算

/除法运算, %除法取余, ~/除法取整

int a = 7;
int b = 2;
print(a / b);  // 3.5
print(a % b);  // 1
print(a ~/ b);  // 3

# String

可以采用单引号' , 双引号 " , 三引号 ''' 来创建字符串,其中三引号实现多行字符串对象的创建.

String a = 'Hello Dart';
String b = "Hello Dart";
String c = 'Hello I\'m Dart';
String d = '''
  Hello World
  I'm Dart
''';

字符串通过 ${expression} 内嵌表达式,如果表达式是一个标识符,则{}可以省略

String language = 'Dart';
print('Hello ${language}');  // Hello Dart
print('Hello $language');  // Hello Dart

使用r前缀,创建原始raw字符串

String s = r'Hello \n world';
print(s);  // Hello \n world

# 类型转换

字符串与数字之间的类型转换

// String => int
String s = '1';
int a = int.parse(s);
assert(a == 1);

// String => double
String s2 = '1.2';
double b = double.parse(s2);
assert(b == 1.2);

// int => String
int n = 1;
String c = n.toString();
assert(c == '1');

// double => String
double m = 1.2345;
String d = m.toStringAsFixed(2);
assert(d == '1.23');

其中assert是断言,在生产环境会被忽略,在开发环境下assert(condition)会在非true的情况下抛出异常.

# Boolean

使用 bool 类型表示布尔值,有字面量 truefalse ,都是编译时常量.Dart的类型安全意味着不能使用if(nonbooleanValue)assert(nonbooleanValue) ,而应该明确检查.

String s = '';
assert(s.isEmpty);

int n = 0;
assert(n == 0);

var o = null;
assert(o == null);

var t = 0 / 0;
assert(t.isNaN);

在JavaScript中,因为存在隐式转换,所以我们可以有如下操作

let arr = []
if(arr){
  console.log('1')
}else{
  console.log('2')
}

结果会输出1,但是在Dart里面这样就是行不通的,if(flag)括号里面的flag必须是个很明确的bool值.

# List

就是Dart中的数组类型,用List来定义.

List list = [1,2];
print(list);  // [1, 2]
list.add(3);
print(list);  // [1, 2, 3]
list.addAll([4,5]);
print(list);  // [1, 2, 3, 4, 5]
print(list.length);  // 5
list[2] = 22;
print(list);  // [1, 2, 22, 4, 5]

也可以约束List中的每一个元素的类型

List<int> list = [1,2];
list.add('2');  //Error: The argument type 'String' can't be assigned to the parameter type 'int'.

这里我们尝试将一个错误类型的值添加进去,则会提示错误.但其实如果我们将代码改成如下:

var list = [1,2];
list.add('2');

也还是会报同样的错误提示.这是因为我们之前讲到过的,有默认的类型推导,判断出list的类型为LIst<int>,而现在添加的却是一个String类型的字符串.

# Set

是一个元素唯一的无序集合

var set = {'zhangsan','lisi'};  // 创建一个set

var set = {'zhangsan','lisi'}; 这里会有个默认的类型推导,判断出set的类型为 Set<String> ,如果尝试将错误类型添加进去,则会提示错误.

要创建一个空集,使用前面带有类型参数的{} 或将{}赋值给Set类型的变量

var set = <String>{};
Set<String> set2 = {}; 
var o = {};  //创建的是一个Map,而不是一个Set

使用add()addAll()添加元素,使用 .length获取Set中元素的个数

var set = <String>{};
var set2 = {'list'};
set.add('zhangsan');
set.addAll(set2);
print(set);  // {zhangsan, list}
print(set.length);  // 2

在Set字面量之前添加const,可以创建一个编译时Set常量,此后便不再可以更改set的内容.

final set = const {
  'zhangsan'
};

# Map

关联keys和values的对象,keys和values可以是任何类型的对象

var map = {
  'name':'zhangsan',
  'location':'China'
};

这里会有个默认的类型推导,判断出map的类型为 Map<String,String> ,如果尝试将错误类型添加进去,则会提示错误.

使用Map构造函数创建,Dart一切皆是对象(都是类的实例),所以变量可以使用构造函数进行初始化

var map = Map();
map['name'] = 'zhangsan';
map['age'] = 18;
print(map);  // {name: zhangsan, age: 18}

在获取values值时,如不存在的返回null:

print(map['name']);
print(map['gender']);  // null

使用 .length获取键值对数量

print(map.length);  // 2

# Rune

用来表示字符串中的UTF-32编码字符.由于Dart字符串是UTF-16编码单元,因此要在字符串中表示32位的Unicode需要特殊语法支持: \uXXXX用4个16进制数来表示,对于非4个数值的情况,用大括号把编码值括起来即可.

print('\u2665');  // ♥
print('\u{1f44d}');  // 👍
Runes input = new Runes('\u2665 \u{1f47b} \u{1f44d}');
print(new String.fromCharCodes(input));  // ♥ 👻 👍