一个基本的Dart程序
下面的代码中使用了很多Dart最基本的特性:
// 定义一个函数
printInteger(int aNumber) {
print('The number is $aNumber.');
// 输出到控制台。
}
// 和多数语言一样,main()是程序开始运行的入口。
main() {
var number = 32;
// 声明和初始化一个变量。
printInteger(number);
// 调用上面定义的函数。
}
重要概念
- 所有你能够赋值给一个变量的都是一个对象,而每个对象都是类的一个实例。甚至是数字、函数还有null值都是对象。所有对象都源于一个公共父类Object。
- 虽然Dart是强类型语言,但是类型声明是可选项(即不是必须的),因为Dart可以自己去推断类型。在上面的代码中,变量number就被推断为int类型。当你想明确说没有类型时,可以使用特殊类dynamic。
- Dart支持泛型,像List<int>,一个整数列表。
- Dart支持顶层函数,如main(),以及与类绑定的函数(静态方法),还有与对象绑定的函数(实例方法)。甚至可以在函数中创建函数(嵌套函数或局部函数)。
- 同样,Dart支持顶级变量、静态变量和实例变量。实例变量即对象的属性。
- 和Java不同的是,Dart没有public、protected和private的关键字。如果一个变量名以下划线“_”开头,说明它是私有变量。
- 变量名要以字母或者下划线开头。
关键字
待补充。
变量
以下是声明变量并初始化的示例:
var name = 'yzune';
以上的声明没有明确指定变量是什么类型的,由Dart去推断。当然也可以显式地指定变量类型,像下面这样子:
String name = 'yzune';
默认值
未初始化的变量初始值为null,哪怕对于int类型的变量也是如此。这点跟Java不太一样。
final和const
如果你不打算修改一个变量,那么可以在声明的时候使用final或const。一个final变量只能被赋值一次;一个const变量等同于编译时常量(声明为const也意味着是final变量)。
注意:类的属性可以是final,但不可以是const。
声明final变量的示例:
final name = 'yzune';
// 可以不需要指明类型。
// name = 'yzune';
// 但上面这种就不行,因为没有final的关键字。
final String nickname = 'yz';
// 也可以去指明类型。
使用const可以声明一些常量。如果是在定义一个类的成员变量时使用const,应该使用static const进行声明。声明const示例:
const c = 300000000;
// 光速,单位为m/s。
const light_year = c * 365 * 24 * 60 * 60;
// 光年,单位为m。
内置类型
Dart语言对以下类型有着特别的支持(就是内置了的意思):
- numbers
- strings
- booleans
- lists(arrays)
- maps
- runes(用于在字符串中表示Unicode字符)
- symbols
你可以用字面值来初始化任何以上这些特殊类型的对象。比如,'this is a string'就是一个字符串类型字面值,true就是一个布尔类型字面值。
因为Dart中的每个变量都指向一个对象,也就是一个类的实例,所以你通常可以使用类的构造函数来初始化变量。一些内置类型有自己的构造函数。比如,可以使用Map类型的构造函数Map()来创建Map对象:new Map()。
Numbers
Dart中有两种数字类型:int和double。这两种类型都是num类的子类,num类包含了基本的运算,比如四则运算,还有像abs()、ceil()和floor()这些方法。按位运算是在int类中定义的。如果num类和它的子类中没有你想使用的方法,可以引入dart:math库,里面有更多的数学运算方法,比如三角函数。
int
整数值不大于64位,具体取决于平台。在Dart虚拟机上,int的范围为- 2^(63) ~ 2^(63) - 1。如果是将Dart编译为JavaScript,则使用JavaScript对应的类型,范围是- 2^(53) ~ 2^(53) - 1。
double
64位(双精度)浮点数,符合IEEE 754标准。
字符串类型和数字类型相互转换
// String -> int
var one = int.parse('1');
// String -> double
var onePointOne = double.parse('1.1');
// int -> String
String oneAsString = 1.toString();
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
// piAsString的值会是'3.14',在转换时指定了保留两位小数。
Strings
你可以使用单引号或者双引号来创建字符串。
你可以在字符串中通过使用 ${表达式} 来置入一个表达式的值。如果这个表达式本身就是一个变量,那么可以不用添加{}。为了得到表达式的字符串结果,Dart会调用返回对象的toString()方法。
var s = 'string';
assert('Dart的$s还能这样用。' == 'Dart的string还能这样用。');
assert('Dart的${s.toUpperCase()}还能这样用。' == 'Dart的STRING还能这样用。');
注意:==操作符用来测试两个对象是否相等。如果两个字符串包含相同的字符序列,那它们就是相等的。这个跟Java也不一样。
连接字符串的方式有两种,一种是将要连接的字符串相邻并排;另一种是使用+来连接。
var s1 = '字符串' '可以这样'
'来连接。';
var s2 = '也可以' + '这样子来连接。';
若要表示多行字符串,跟Python相似,可以使用成对的三个单引号或成对的三个双引号。
var s = '''
这是
多行
字符串
''';
让转义字符不发生转义,即显示原始字符串,在引号前加上前缀r即可。
var s = r'这是原始字符串,\n不会被显示为换行符。';
Booleans
为了表示布尔值,Dart有bool类型。bool类型只有两个对象:布尔字面值true和false,它们都是编译时常量。
Dart的类型安全意味着你不能使用类似if (非布尔值)或assert (非布尔值)这样子的代码。规范的代码应该如下:
// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);
// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);
// Check for null.
var unicorn;
assert(unicorn == null);
// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Lists
几乎所有编程语言中最常见的集合,可能是数组或者是有序对象组。在Dart中,数组是List对象,所有我们可以把它们统称为列表。
这是一个简单的Dart列表:
var list = [1, 2, 3];
注意:以上代码中,Dart推断list的类型为List<int>。如果尝试将非整数对象添加到这个列表中,会引发错误。
Maps
一般来说,Map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键都应该是唯一的,只能出现一次。Dart支持使用Map字面值或者map类型来创建Map。
以下是使用Map字面值创建map的例子:
var skills = {
1: 'Java',
2: 'Python',
3: 'Dart'
};
注意:以上代码中,Dart推断skills的类型为Map<int, String>。如果尝试将错误类型的值添加到这个Map中,会引发错误。
还可以使用Map的构造函数来得到相同的效果:
var skills = new Map();
skills[1] = 'Java';
skills[2] = 'Python';
skills[3] = 'Dart';
也可以将新的键值对添加到现有的Map中:
skills[4] = 'C#';
通过skills[键]的方法来访问其中的值,如果其中没有这个键,将会返回null。使用skills.length会得到键值对的个数。
函数
Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以分配给变量或作为参数传递给其他函数。你也可以像调用一个函数一样调用一个类的实例。
下面是一个实现函数的实例:
bool haveSkills(String skill){
return skills.values.contains(skill);
}
对于只包含一个表达式的函数,可以使用简写语法:
bool haveSkills(String skill) => skills.values.contains(skill);
函数可以有两种类型的参数:必需的和可选的。首先列出必需的参数,再列出任何可选参数。
可选参数
可选参数可以是位置型或命名型的,但不能同时是它们。
可选的命名参数
在定义函数的时候,用{参数1, 参数2, …}来指定命名参数。这样在调用函数时,可以使用参数的名称来指定输入的是哪个参数(顺序不同也没关系)。没有输入的参数在函数中被当成null。示例:
void my_print({String name, String age}){
print("年龄为$age的$name。");
}
main() {
my_print(age: '21', name: 'yz');
// 参数的位置可以任意,因为已经由名称去指定了。
}
可选的位置参数
将函数的参数放在[]中,将它们标记为可选的位置参数:
String say(String from, String msg, [String device]) {
var result = '$from说$msg。';
if (device != null) {
result = '$from通过$device说$msg。';
}
return result;
}
main() {
print(say('杨增', '你好'));
print(say('杨增', '你好', '手机'));
}
// 输出:
// 杨增说你好。
// 杨增通过手机说你好。
默认参数值
可以使用=来为命名参数和位置参数设置默认值。这个默认值必需是编译时常量,如果没有提供默认值,则默认值为null。以下是为命名参数设置默认值的示例:
void my_print({String name = '无名氏', String age = '0'}){
print("年龄为$age的$name。");
}
main()函数
每个应用程序都必须有一个顶层的main()函数,它可以作为应用程序的入口点。该main()函数返回void并具有List<String>的可选参数。
函数作为第一类对象
你可以将一个函数作为参数传递给另一个函数,例如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// 将printElement()作为参数传递过去。
list.forEach(printElement);
匿名函数
大多数函数都有名字,比如main()。你也可以创建匿名函数,可以将一个匿名函数分配给一个变量。例如:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
词法作用域
Dart是基于词法作用域的,词法作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数定义时的环境中查询。
闭包
这是一个蛮有趣的例子:
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
}
add2和add4都是闭包,它们共享相同的函数定义,但是保存了不同的词法环境。从本质上讲,makeAdder是一个函数工厂。
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。
运算符
Dart定义的运算符如下表,你可以重载这些运算符。在运算符表中,从上往下运算优先级递减。
当你使用运算符时,你就创建了表达式。下面是一些运算表达式的例子:
a++
a + b
a = b
a == b
c ? a : b
a is T
注意:对于连接两个操作数的运算符,左边的操作数决定了使用哪个版本的运算符。比如有一个Vector对象aVector和一个Point对象aPoint,则aVector + aPoint使用的+为Vector中定义的+。