本文收录于dart入门潜修系列教程。
创作不易,转载还请备注。
前言
本篇文章会对dart语言的基本语法和内置类型做一个整体的阐述。
变量
dart为变量提供一个关键字var,这与js、kotlin等都很像,var只是声明一个变量,至于这个变量是个什么类型则由“类型关键字”来决定,如下所示:
//定义了一个变量var1,此时没有指定var1的类型,
//则由dart编译器自动推断:即var1是个字符串
var var1 = "test";
//显示指定变量str的类型为String
String str = "test";
在dart中一切都是对象(包括下面提到的内置类型)。因此,var var1 = "test"这句语句,实际上表达的意思是:有一个字符串对象test,其地址保存在了var1变量中。即var1实际上保存的是对象的引用地址,而不是对象本身,这点与很多面向对象的语言一致。
既然dart中一切都是对象,那么如果我们在定义的时候没有对其进行初始化,那么其默认值是什么?这个有朋友很容易推断出来,在其他语言中,对象的默认值显然是null!确实如此,dart中的变量默认值就是null,但是需要特别注意的是,其内置的诸如数字类型、布尔类型等的默认值同样是null,因为他们也是对象,如下所示:
void main() {
int i;
print(i);//打印'null'
bool j;
print(j);//打印'null'
}
在当前其他一些主流的语言中(比如java),内置的int、bool类型默认值是永远不可能为null的,所以如果之前学习的是java这类的语言,要特别注意这一点。
常量
除了变量外,dart还支持常量,dart中修饰常量同样有两个关键字:final和const。final变量只能被赋值一次,表示运行时常量,而const修饰的变量则表示是编译时常量,关于运行时常量和编译时常量,在我的另一个系列文章----->kotlin系列文章中的kotlin入门潜修之类和对象篇—属性和字段这篇文章中有过详细阐述(编译时常量章节)。这里再说下二者的区别:编译时常量,顾名思义,就是在编译的时候就可以确定的常量,即编译器编译成可执行文件的时候就已经保存了该常量值;而运行时常量则只能在运行的时候才可以获取其对应的常量值,编译器编译时只保证代码中没有其他地方可再次修改其值,这也是为啥被称为常量的原因。
dart中编译时常量和运行时常量示例如下所示:
void main() {
final str = "hello word";//运行时常量
const str2 = "hello word";//编译时常量
final String str3 = "hello word";//运行时常量,显示指定str3的类型
}
上面三个都是"常量",均无法再次进行赋值。在dart中,const实际上是隐含包含有final修饰符的,即const修饰的的常量一定是final的。
再来看个关于常量的例子,如下所示:
var arr = const [];
arr = [1, 2, 3];//示例1,正确
final arr1 = const [];
arr1 = [1, 2, 4];//示例2,错误,无法再被赋值
const arr2 = [];
arr2 = [1, 3, 4];//示例3,错误,无法再被赋值
首先需要注意区分示例1和示例2的区别,示例1表示arr是个变量,其指向了一个包含编译时常量元素的引用地址,即arr本身并不是常量,只是其包含的元素是常量,所以我们可以对其进行二次赋值;示例2是错误的,因为arr1是final的,无法再进行赋值;示例3的错误更是显而易见的,使用const修饰的也无法被赋值。
了解过dart中的变量、常量的定义之后,下面来看下kotlin中内置的基本数据类型。
内置类型
numbers(数字类型)
不像其他语言,dart的数字类型只有两种,一种是整型,使用int关键字修饰;一种是浮点型,使用double关键字修饰。
其中int类型的最大长度是64位,但这个会依赖于具体的平台,因为dart支持多种平台,比如在dart 虚拟机上,int的表示范围是-263 to 263 - 1,而当dart被编译为javascript的时候其表示范围为-253 to 253 - 1。
double类型最大长度也是64位,遵循IEEE 754标准。int和double都是num类型的子类,num定义了一些基本的运算操作,比如加减乘除等,dart中数字的使用示例如下所示:
void main() {
int i = 1;//定义一个整型变量i
double j = 2.1;//定义一个小数j
num result = i + j;//定义了一个num类型的result
print(result);//打印3.1
}
上面代码演示了数字类型的使用,其中在第三行中,我们使用num来接收i+j的结果,这是允许的,因为前面说过num是int和double的父类,这是面向对象语言的基本特点。下面来看下int和double类的声明,如下所示:
//整型int类的定义
abstract class int extends num {...}
//double类型的定义
abstract class double extends num {...}
//这就是num的定义
abstract class num implements Comparable<num> {
bool operator ==(Object other);
num operator +(num other);
num operator -(num other);
int operator <<(int shiftAmount);
//省略内容...
}
上面代码展示了数字类型的相关定义,也验证了我们前面的说法。从定义也可以看出,数字类型的默认值为什么为null,因为他们都是class类型,对应的实例变量如果没有被赋值,其默认值就是null。
实际上,数字类型除了提供了这些简便的操作符外,还提供了一些便利的方法,如求绝对值、取整等方法,来看几个示例:
void main() {
int i = -1; //定义一个整型变量i
double j = 2.1; //定义一个小数j
print(i.abs());//求i的绝对值,打印 ‘1’
print(j.ceil());//求j的向上最大整数,打印'3'
print(j.floor());//求j的向下的最大整数,打印'2'
print(j.round());//求离j最近的整数,四舍五入,打印'2'
print(j.truncate());//截取掉小数点取整,打印'2'
j = 2.6;//验证小数位超过0.5的情形
print(j.round());//求离j最近的整数,四舍五入,打印'3'
print(j.truncate());//截取掉小数点取整,打印'2'
print(j.clamp(1,3));//如果j再1-3之间则返回j,否则返回离其最近的边界值,这里打印j的值 ‘2.6’
print(j.clamp(3,4));//打印‘3’,原理同上
print(j.clamp(1,2));//打印‘2’,原理同上
}
booleans
dart中为布尔值提供了bool关键字,所谓布尔值就是非真既假的表达。在dart中使用bool还是需要稍加注意的,看个例子:
void main() {
bool isOk;
print(isOk ? "is ok" : "is not ok");//!!!编译错误
}
是的,上面代码会编译错误!原因是isOk没有被赋值,其默认值为null,而null不能作为bool表达式,所以在使用bool值之前一定要进行初始化,如下所示:
void main() {
bool isOk = true;
print(isOk ? "is ok" : "is not ok");//打印'is ok'
}
其实null在dart中是所有变量的“通病”,因为dart中一切都是对象,所以在做逻辑之前,一定要考虑变量值可能为null的情况。
dart中的布尔值只有两个对象值:true和false,这两个布尔值实际上是个编译常量。在dart中,除了bool类型之外,其他类型都无法用在if表达式和assert方法中,如下所示:
var str = '';
if (str) {//错误,str是个字符串类型,无法用于if表达式
}
if(str != null){//正确,str != null返回了bool值
}
Strings
dart中的字符串是用utf-16编码的字符序列。在dart中,创建字符串有两种方法:使用双引号和单引号,如下所示:
void main() {
var str1 = "hello word";//定义了一个字符串str1
var str2 = 'hello word';//定义了一个字符串str2
var str1AndStr2 = str1 + str2;//可以使用+号操作符,拼接两个字符串
var newStr1 = "new str1 say: " + str1;//同上
var newStr2 = "new str2 say: $str2";//也可以使用$获取字符串变量的值
var str3 = "`hello world'\"";//使用\转义特殊字符串
//dart同样提供了这种拼接形式,此时将会忽略字符串之外的
//换行和空格,此处打印 Hello world end...
var concatenateStr = 'Hello '
'world'
" end...";
//三引号表示保持书写的格式进行打印,即写的是什么格式就打印什么格式。
var concatenateStr2 = '''Hello
world
end..''';
print(concatenateStr2);
}
lists (列表或者数组)
几乎所有语言都有数组,表示有序的数据集合。除了数组之外,很多语言还提供了列表(list),列表底层有多种实现方式,数组就是其底层实现的常见方式之一。在dart中,数组就是包含对象的列表,因此他们统称为lists。换句话说,当提到dart中的lists时,其实就是数组的概念。
列表的示例如下所示:
void main() {
var list = [1, 2, 3];//定义了一个数据集合list
list[0] = 100;//可以通过下标进行赋值
for (int i = 0; i < list.length; i++) {//打印list中的各个元素
print(list[i]);
}
//定义了一个包含有编译时常量的列表list2
var list2 = const[1, 2, 3];
list2[0] = 200;//!!!编译错误,编译时常量无法被重新赋值
}
上面list的写法是字面量写法,我们还可以通过dart提供的List类型来完成上述功能,如下所示:
var list = List();//生成一个List类型的对象
list.add(1);//添加元素1
list.add(2);//添加元素2
print(list);//打印 ‘ [1, 2] ’
上面两种不同的写法,实际上生成的都是一个类型的集合:List<int>,即只能向集合中存入int类型的元素,这里涉及到泛型的概念,将会在后续文章中阐述。
maps(映射)
所谓映射就是提供key-value的一个数据存储结构。在dart中key和value可以是任何类型,但是key不能重复,而value可以重复。生成map对象同样有两种方式,一种是通过字面量的形式,一种是使用Map类型,示例如下:
void main() {
var map = {//通过字面量定义了一个map对象
"key1":"hello",
"key2":"world"
};
print(map["key1"]);//打印key1对应的值
var map2 = Map();//通过Map类型,生成一个对象
map2["key1"] = "map2 value";//添加 key1 : map2 value映射
map2[1] = "map2 value2";//添加 1 : map2 value映射
print(map2);//打印{key1: map2 value, 1: map2 value2}
}
runes
dart提供了一些可以使用utf-32编码表示的特殊字符,这些字符会被渲染成各种各样的可视化符号,比如我们常见的表情符、icon标识等,这就是所谓的runes(即神秘意义的符号),举个例子就很容易理解了:
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));
上面代码执行后打印如下:
♥ 😅 😎 👻 🖖 👍
dart中还有一个Symbol类型,使用#修饰(如#test),其字面量是一个编译时常量,但是实际中基本用不到,所以不再阐述。
至此,dart的基本语法和内置类型阐述完毕。