一、概述
Dart 是 Google 公司在2011年10月发布的一种编程语言,主要是将其作为一种结构化的 Web 开发语言。可以用在Web、服务器、移动应用和物联网等领域的开发,是宽松开源许可证(修改的BSD证书)下的开源软件。官方网站:https://dart.dev/ 。
在 Dart 中,所有东西都是对象,变量,数字,函数等都是对象类型,都是类的实例,所有对象都继承自 Object
类。
在进行 Dart 开发前,需要安装 Dart SDK,安装方法官网网站有介绍,地址为:https://dart.dev/get-dart 。如直接进行 Flutter 开发,也可不用下载此 SDK ,直接安装 Flutter 即可。Flutter开发工具包中包含 Dart。
二、语法基础
1. 入口方法
Dart 语言文件格式为 .dart
,并以 main
方法作为程序的入口点,我们使用 VSCode 创建 demo1.dart
文件,代码如下:
main(){
print('hello dart!'); //输出结果:hello dart!
}
Dart 语言有很多简写形式,以上的 main
方法就是简写,完整的写法如下:
void main(List<String> args){
print('hello dart!');
}
参数为泛型的写法,List
为列表,也就是其他语言中的数组。 main
方法可以接收由命令行输入的参数,可以通过 args
参数在程序中获取。如下代码:
void main(List<String> args){
print('hello dart!');
print(args);
}
通过命令行的方式运行 dart
文件,首先 cd
到目标文件夹,运行如下命令: dart demo1.dart arg1,arg2
,输出结果如下:
hello dart!
[arg1,arg2]
一般情况下,使用省略方式即可。
在 Dart 中,print
方法不支持多参数打印,其函数原型如下:
void print(Object object);
只支持单参数打印,但是可以利用字符串插值的方式进行多参数打印,使用 $
美元符号,比如打印 a, b两个变量,如下:
print("$a $b");
以上是利用字符串插值进行多参数打印,但实质上依然是单参数,只不过利用了字符串的插值特性输出多个参数而已。当然依然可以利用多个 print
打印多个参数。
2. 数据类型
Dart 中所有类型都是对象,基础数据类型也是对象,都有自己的方法和属性。Dart 支持自动类型推断,所以在定义数据类型的时候,可以通过两种方式进行定义,直接使用具体数据类型定义和使用 var
关键字进行定义。当使用 var
定义时,编译器会根据具体的赋值数据类型推断出定义的变量或常量的数据类型。
可以使用 runtimeType
来查看对象的运行时类型。
因为 Dart 中所有的类型都是对象,所以定义一个数据类型以后,如果未对其进行初始化操作,则其默认值为null
。
Dart 中,变量是区分大小写的。
**2.1.1 数值类型 (Numbers) **
Dart 中数值类型有两种,整数( int
)和小数( double
),他们继承自 num
类。使用直接数据类型定义方式如下:
main(){
int a = 10;
double b = 20.1;
print(a); //输出10
print(b); //输出20.1
print(a+b);////输出30.1
}
也可以使用如下方式定义:
main(){
var a = 10;
var b = 20.1;
print(a);
print(b);
print(a+b);
print(a.runtimeType); //输出int
}
输出结果与上面相同,下面的方式编译器会自动推断出具体的数据类型。
Dart 中可以使用十进制和十六进制进行整数的赋值,不能使用其他进制。
2.1.2 字符串类型 (Strings)
使用关键字 String
来声明字符串类型,字符串使用单引号或双引号包起来,Dart 中字符串是UTF-16编码,如下:
main(){
String str1 = 'hello dart!';
var str2 = "hello world!";
print('$str1 $str2'); //输出 hello dart! hello world!
}
也可以在定义一个变量的同时直接赋值多个字符串,如下:
main(){
String str = 'hello dart! ' 'this is'
" a string";
print(str); //输出 hello dart! this is a string
}
可以看到上面的代码,我们定义了一个变量,但是赋值了三个字符串,编译器会将三个字符串合并为一个字符串进行处理。但是虽然我们分两行书写的字符串,但是在输出时并没有分行,也就是定义与输出的格式并不一样,如果需要定义与输出一样,可以使用如下方式:
main(){
//1.通过转义字符\n进行换行操作
String str = "hello dart! \nthis is a string ";
print(str);
//2.通过```或"""将字符串包裹起来为多行字符串,使用此方式时,需注意字符串的对齐方式,字符的前面的空格会包含在内,左边界为编译器的行头
var str1 = '''hello dart!
this is a string''';
print(str1);
}
输出结果如下:
hello dart!
this is a string
hello dart!
this is a string
可以使用加号和插值的方式进行拼接,如下:
main(){
String a = "hello";
String b = " dart!";
print(a + b); //输出 hello dart!
print("$a$b"); //输出 hello dart!
}
使用字符串插值的方式进行拼接时,如果 $
后为一个标识符则可直接使用,如果为表达式则需要使用 {}
将表达式包含起来,如下:
main(){
String a = "hello";
String b = " dart!";
print(a + b);
print("${a.runtimeType} ${b.runtimeType}"); //此处需要使用{}将表达式包含起来作为一个标识符使用
}
在 Dart 中,字符串也是一种集合类型,可以通过如下方式获取字符串中的单个字符:
main(){
String str = "hello dart!";
print(str[0]); //输出 h
print(str[4]); //输出 o
}
下标从0开始。
也可以通过 *
运算符对字符串进行拷贝操作,如下:
main(){
String str = "hello dart!";
print(str*2); //输出 hello dart!hello dart!
}
使用如下方式原样输出字符串,包括里面的转义字符:
main(){
var str = r"this is a \n string!";
print(str); //输出 this is a \n string!
}
2.1.3 布尔类型 (Booleans)
Dart 中,布尔类型只有两个字面量值:true
和 false
。
main(){
bool a = true;
var b = false;
print("$a $b"); //输出 true false
print(b.runtimeType); //输出 bool
}
上面说过,Dart中所有的类型都是对象,如果在定义 bool
类型时,没有进行初始化,则其默认值为 null
,在进行条件判断时会报异常,如下:
main(){
bool c;
if(c) {
print(c);
}
}
上面的代码编译不会报错,运行时会报如下异常:
Unhandled exception:
Failed assertion: boolean expression must not be null
所以在使用 bool
类型进行条件判断时,如果有为空的可能应先进行是否为 null
的判断,并且 Dart 中的条件判断必须使用 bool
类型,其他类型会编译错误。
2.1.4 集合类型
列表 (Lists)
列表用来存放一组数据,在很多其他编程语言中,列表也被称为数组,所以与其他语言一样,下标从0开始,最后一个数据元素的下标为 lenght-1
。列表的具体数据类型由其中的元素类型决定,使用方式如下:
main(){
var arr = [1, 2, 3];
var arr1 = [1, 2, 3, "string"];
List arr2 = [1, 2, 3];
List arr3 = [1, 2, 3, "string"];
print("$arr \t\t\t ${arr.runtimeType}");
print("$arr1 \t ${arr1.runtimeType}");
print("$arr2 \t\t\t ${arr2.runtimeType}");
print("$arr3 \t ${arr3.runtimeType}");
}
输出结果如下:
[1, 2, 3] List<int>
[1, 2, 3, string] List<Object>
[1, 2, 3] List<dynamic>
[1, 2, 3, string] List<dynamic>
以上方式直接创建 List
并进行初始化操作,定义空列表,使用 var arr = [];
即可。需要注意如果不进行初始化,其数据类型为 null
,即便使用 List arr;
定义,其 runtimeType
类型也为 Null
。
也可使用构造方法创建数组,如下:
main(){
var arr = new List(); //创建空列表
var arr1 = new List(3); //创建长度为3的列表,使用null填充
var arr2 = new List.filled(3, 1); //创建长度为3的列表,并使用整形数据1填充
var arr3 = new List.filled(3, "a"); //创建长度为3的列表,并使用字符串a填充
print("$arr \t ${arr.runtimeType}");
print("$arr1 \t ${arr1.runtimeType}");
print("$arr2 \t ${arr2.runtimeType}");
print("$arr3 \t ${arr3.runtimeType}");
}
输出如下:
[] List<dynamic>
[null, null, null] List<dynamic>
[1, 1, 1] List<int>
[a, a, a] List<String>
在 Dart 中,可是省略 new
关键字,如下:
main(){
var arr = List(2);
arr[0] = 10;
arr[1] = "str";
print("$arr \t ${arr.runtimeType}"); //输出 [10, str] List<dynamic>
print(arr.length); //输出 2
}
以上创建的列表都是可存放任意类型的数据的,如果想存放固定类型的数据,可以使用泛型进行数据类型约束,使用方式如下:
main(){
List<String> arr = ["str1", "str2"];
print(arr); //输出 [str1, str2]
var arr1 = List<int>();
arr1.add(1);
arr1.add(2);
print(arr1); //输出 [1, 2]
}
创建固定数据类型的列表,如果添加其他数据类型数据编译器会报错。
集合 (Sets)
Set
与 List
类似,但是 Set
为无序列表,所以不能通过下标的方式进行访问。 Set
中不能有重复数据,如果存在重复数据,只会保留一份数据。依靠此特性,一般使用 Set
来对列表进行去重处理。
main(){
Set set = {1, 2, 3, 4, 2};
print(set); //输出 {1, 2, 3, 4}
var set1 = {"str1", "str2", "str1", 1, 2, 3, 2};
print(set1); //输出 {str1, str2, 1, 2, 3}
var set2 = Set();
set2.add(1);
set2.add(2);
set2.add(2);
set2.add("str");
print(set2); //输出 {1, 2, str}
Set<int> set3 = Set();
set3.add(1);
// set3.add("str"); //错误
print(set3); //输出 {1}
Set set4 = Set<String>();
print(set4); //输出 {}
}
数组去重处理,如下:
main(){
var arr = [1, 2, 3, 4, 5, 6, 3, 4];
Set set = Set.from(arr);
var arr1 = List.from(set);
print(arr1); //输出 [1, 2, 3, 4, 5, 6]
}
字典 (Maps)
字典是一组键值对的集合,可以通过键完成对值的修改、查找、添加或删除操作,其中,键( key ) 必须是唯一的。创建 Map 类型,键 (key) 与值 (value) 要成对出现。一般情况下,键都是字符串类型,但是 Dart 中并没有严格约束键的数据类型,所以键可以为任意类型,值也可以为任意类型。如果需要创建固定类型的键值,可通过泛型进行约束。
main(){
var map1 = {
"name" : "zhangsan",
"age" : 20
};
var map2 = {
1 : 20,
2 : 30.2
};
Map map3 = {
"name" : "lisi",
1 : [1, 2, 3]
};
Map map4 = new Map();
Map map5 = Map.from(map1);
Map<String, int> map6 = {
"number" : 1,
"age" : 20
};
Map map7 = Map<String, dynamic>();
map7["name"] = "wanger";
map7["age"] = 20;
print(map1); //输出 {name: zhangsan, age: 20}
print(map2); //输出 {1: 20, 2: 30.2}
print(map3); //输出 {name: lisi, 1: [1, 2, 3]}
print(map4); //输出 {}
print(map5); //输出 {name: zhangsan, age: 20}
print(map6); //输出 {number: 1, age: 20}
print(map7); //输出 {name: wanger, age: 20}
}
2.1.5 泛型
泛型,即通用类型,使 Dart 中的类型更加动态,提高了代码的重用率。Dart 中使用 <T>
的方式来定义泛型,T
为标识符或关键字,一般用 T
表示。上面的例子中也使用了泛型来约束数据类型,例如列表:
main(){
var arr = List<String>();
arr.addAll(["str1", "str2", "str3"]);
print(arr); //输出 [str1, str2, str3]
}
2.1.6 符文 (Runes)
符文(runes)是字符串的UTF-32编码点,要在字符串中表示32位的Unicode值,可使用 \uxxxx
形式,xxxx 为四位十六进制值,如多于或少于四位,则需要使用 {}
包装。
main(){
var value = '\u{1F362}';
print(value); //输出
}
2.1.7 符号类型 (Symbols)
Symbol
表示运算符或标识符,使用相同名称创建的符号是相等的,有如下两种方式创建符号:
main(){
var symb = Symbol("s"); //通过构造方法创建符号
var symb1 = #s; //通过#创建符号
print(symb.runtimeType); //输出 Symbol
print(symb1.runtimeType); //输出 Symbol
if(symb == symb1) {
print("相同");
}
}
2.1.8 常量
以上声明的变量都是可变量,也就是变量,有时候需要一些数据是不可变的,称为常量。在 Dart 中,定义常量可以通过关键字 const
或 final
来定义,如下:
main(){
const a = 10; //声明具体数据类型和var关键字
final String b = 'hello dart!';
print("$a $b"); //输出 hello world! hello dart!
print("${a.runtimeType} ${b.runtimeType}");! //输出 String String
}
可以看出,在声明常量时,可以省略其数据类型,编译器会自动进行推断。无论使用 const
还是 final
声明的常量都是不可以改变的,并且在定义常量时,在定义的同时就需要对其进行初始化操作,否则会报错。如果试图对常量进行重新复制,也会报错。
上面的代码中,使用 const
和 final
声明的常量,其输出的最终数据类型都为 String
,那么具体他们之间的区别是什么呢?
const
定义的常量在定义时必须赋值为一个常量值,不可以通过表达式或函数进行赋值操作(常量表达式可以),而 final
定义的常量则可以通过表达式或函数赋值,也就是说 const
常量为编译时常量,在编译阶段就必须是可知的,而 final
常量是只能对其进行一次赋值,但是可以在运行时进行设置。 如下:
main(){
const a = 10;
final b = 20.5;
const c = a ;
final d = a;
// const e = a+b; //错误
final f = a + b;
const g = a * 10; //正确,因为a为常量,常量表达式在编译期就可以确定值
print(a); //输出 10
print(b); //输出 20.5
print(c); //输出 10
print(d); //输出 10
print(f); //输出30.5
print(g); //输出 100
}
const
还可以用来创建常量值以及声明创建常量值的构造函数(后期文章会介绍到),创建常量值如下:
main(){
List a = const [1, 2, 3];
List b = const [1, 2, 3];
if(a == b) {
print("相同"); //会输出 相同
}
}
如果不用 const
修饰,则a与b为不同,如下:
main(){
List a = [1, 2, 3];
List b = [1, 2, 3];
if(a == b) {
print("相同");
}else print("不同"); //输出 不同
}
并且 使用如下方式声明创建的常量值是可以改变的:
main(){
List a = const [1, 2, 3]; //可改变
const b = [1, 2, 3]; //不可改变
a = [4, 5, 6];
print(a);
}
但是对于 const [1, 2, 3]
中的单个数据元素是不可修改的,修改会抛出异常。对于 a
变量来说是可变的,因为未使用 const
修饰,所以对 a
重新赋值 [4, 5, 6]
以后,a
对象相当于如下定义:
var a = [4, 5, 6];
通过以下代码验证:
main(){
var a = const [1, 2, 3];
var b = const [4, 5, 6];
a = [4, 5, 6];
if(a == b) {
print("相同");
}else{
print("不同"); //输出不同
}
}
以下代码为对 const [1, 2, 3]
单个元素修改抛出异常的示例:
main(){
var a = const [1, 2, 3];
a[0] = 10; //错误,不可修改
}
2.1.9 动态数据类型
在确定数据类型以后再赋值其他数据类型则会报错,如下:
main(){
var a = 10;
a = 20.1; //错误
}
上面的代码会报错如下错误:
A value of type 'double' can't be assigned to a variable of type 'int'.
Try changing the type of the variable, or casting the right-hand type to 'int'.dart(invalid_assignment)
如果希望变量在赋值一个数据类型以后再赋值其他数据类型,可以使用关键字 dynamic
定义,如下:
main(){
dynamic a = 10;
print(a.runtimeType); //输出int
a = 20.1;
print(a); //输出20.1
print(a.runtimeType); //输出double
}
dynamic
的最终数据类型为最后赋值的数据的类型。
2.2.0 枚举 (enum)
enum Week{
Monday,
Tuesday,
Wednesday
}
main(){
var week = Week.Tuesday;
if(week == Week.Monday){
print("星期一");
}else if(week == Week.Tuesday) {
print("星期二");
}else if(week == Week.Wednesday) {
print("星期三");
}
print(Week.values); //输出所有枚举值
print(Week.Monday.index); //输出索引值
print(Week.Tuesday.index);
print(Week.Wednesday.index);
}
输出结果:
星期二
[Week.Monday, Week.Tuesday, Week.Wednesday]
0
1
2
3. 类型转换
main(){
var a = 10;
var b = 20.5;
var c = a + b.toInt(); //浮点型转整型
var d = a.toDouble() + b; //整型转浮点型
String e = b.toString(); //浮点型转字符串
double f = double.parse(e); //字符串转浮点,使用此方法应确定字符串可转换为浮点型,否则会报异常,如不确定是否可以转换应使用tryParse方法,无法转换会返回null
print("$c $d $e $f");
}
4. Object
与 dynamic
的区别
因为在 Dart 中,所有内容都是从 Object
类扩展而来,所以可以使用如下方式定义数据:
Object a = 10;
与 dynamic
对比,他们都可以定义一种数据类型以后再次赋值其他数据类型,如下:
main(){
Object a = 10;
a = "string";
print(a); //输出 string
dynamic b = 20;
b = "string"; //输出 string
print(b);
}
但是 Object
定义的对象在调用方法或属性是,可以在编译时就判断是否存在指定的方法或属性,如不存在,编译阶段就会报错。而 dynamic
动态类型,调用不存在的方法或属性,在编译阶段不会报错,运行时才会抛出异常,如下:
main(){
Object a = 10;
print(a);
dynamic b = 20;
print(b);
// a.run; 调用不存在的属性run,编译时就会提示错误
b.run; //编译时不会提示错误,运行时才会抛出异常
}
PS:以上只是很小一部分内容,因为一切皆对象,所以每个类都有自己的很多属性与方法,具体可以参看代码文档。