今天主要会说明 Dart 各种变数宣告的方法及 Dart 的基本型别。
dart主要有四种方式宣告变数
分别为 const
var
type
final
const a = 10;
final b = 10;
var c = 123;
string d = '123'
首先 const
及 final
这两个宣告方式就跟 JS 的 const
一样是用於宣告一个「不可变的数值」。
亦即这之後不能将这些变数重新赋值。那为什麽还有分 const
及 final
呢?最主要的差异是const
更为严格,它代表的是「编译时的常数」,什麽意思?
int getNumber(a){
const b = a;
return b;
}
void main() {
int a = 10;
getNumber(a);
}
这边会看到我在 getNumber
里使用 const
宣告b 将a的值给b ,但这无法通过 Dart的编译器。因为 const
的是要在编译期间就已经是有数值的常数。所以const a = b ;
这种直到runtime才完成初始化的事情是不被允许的。
所以 const
较常用地方是一些**「永远不变的数值」**像是 const pi = 3.14;
或者const textColor = Color.fromARGB(255, 66, 165, 245)
而final
就会比较接近於 js里const的用法,就是在接下来的runtime这个数值都会是immutable。
var
就可以想成是 JS 的 let
就是可以被变更的变数。但最大的差异就是Dart在宣告完後就会进行型别推断(Type inference)意即如果 var b = 10
後不能在进行 b='123'
了(可以想像成ts一样),因为在初始化後就会将 b的type推断为int
了所以不能将string
给b了
而直接用型别宣告的方式就跟 var
差不多一样了就不详细介绍了,就差别是一个是交由dart 推断型别一个是我们自己宣告型别。
但其实在变数宣告以及初始化还有一些细节,这部分就留到之後提到「null safety」时再来说明。
Dart 有以下基本型别
int
, double
String
bool
List
Set
Map
Runes
Symbol
Null
基本的 int
(整数)、 double
(浮点数)、 String
(字串)、bool
(布林)就不多做介绍。
以下只稍微介绍一下比较常用到的 List
、 Set
、 Map
而List
就是其他语言中的阵列(Array)在Dart中的最基础形式如下:
final listA = [1,2,3,4]
在Dart里的List有提供其他建构子(constructor) .filled
及 . generate
都是可以用来动态产生list的建构子:
final listB = List.filled(3, 1); // [0, 0, 0]
final listC = List.generate(3, (index) => index); // [0, 1, 2]
差异是 filled
每一个element都是同一个reference而 generate
不是。
listB[0].add(1);
listC[0].add(1);
print(listB); // [[1], [1], [1]]
print(listC); // [[1], [], []]
以及其中有一个控制这个List是不是可变长度的named parameters growable
的预设值不一样。但详细就不赘述了有兴趣的读者可以到官方的API文件阅读。
定义为「没有索引值且不可重复的集合」
我们可以用 {}
来做初始化并用逗号分隔每一个元素:
final setA = {0, 1, 2, 3, 4};
print(setA); // {0, 1, 2, 3, 4}
也可以利用 Set.from
放入一个可迭代的值来产生 Set
final listX = [0, 1, 0, 0, 1, 2, 1, 3, 4, 5, 6, 7];
final setB = Set.from(listX);
print(setB); //{0, 1, 2, 3, 4, 5, 6, 7}
因为 Set
里并没有存放索引值,所以我们无法直接存取特定位置的值。但因为 Dart 底层实作的关系,其实还是有将 Set
的顺序存入,也因此我们迭代时是会跟初始化时的顺序一样:
setB.forEach((element) {
print(element);
});
// 0
// 1
// ...
// 7
Map
就是有key-value型式的资料结构,而且key不能重复,也因为了有了key所以我们有办法直接存取Map
。
final mapA = {
'a': 1,
'b':2,
'c':3
};
mapA['a'] // 1
当然Map
也有提供其他的constructor:
final mapB = Map.fromIterable([1, 2, 3, 4]);
// {1: 1, 2: 2, 3: 3, 4: 4}
final valueList = [0, 1, 2];
final keyList = ['z', 'x', 'c'];
final mapC = Map.fromIterables(keyList, valueList);
// {z: 0, x: 1, c: 2}
当然这些资料结构还有其他API可以介绍,但我觉得还是等到之後实际有用到时在一起介绍好了,有兴趣的读者可以先查阅Dart API的文件。
泛型(generic)最简单的解释大概就是型别有了参数。
通常都是使用 <>
来实作,像是List的实作是 List<E>
而这个E就是我们可以传入的型别:
final intList = <int>[1, 2, 3, 4];
final stringList = <String>['1', '2', '3', '4'];
而当我们使用了不一样的型别时就会跳出 Error,像是我在 List<String>
里放入一个 int
就会跳出以下错误:
当然我们也可以运用到其他地方像是 Class
或者 Fucntion
上
E ientityFunc<E>(E e) => e;
class A<T> {
T? value;
}
print(ientityFunc<int>(2)); // 2
final a = A<int>();
a.value = 'string'; // 这行会出错因为 A传入的是 int type
而关於型别有一些进阶应用像是利用 typedef
对Function的型别做更进一步的抽象
typedef Identity<E> = E Function(E e);
这行的意思就是我定义了一个型别叫做Identity,而这个型别代表的意义就是他是一个会回传
type E
的 Function
且只有一个type E
的参数。
class Utils<T> {
Identity<T> ientity = (x) => x;
}
final utils = Utils<int>();
print(utils.ientity(1));
我们可以从vs code中看到因为我们传入了 int
所以 utils.ientity
也变成有一个 int
参数且会回传 int
的 Function
这次的内容稍微有点杂乱,这类比较基础的语法在没有实际范例来看总会有一种「到底什麽时候会用到这些语法」的感觉。
今天的程序码有放到github上,有兴趣的读者可以clone下来跑跑看。
https://github.com/zxc469469/dart-playground/tree/Day04/type
明天会开始讲到 Dart/Flutter 里最常用到的东西:class
参考资料
>>: DAY 5 『 RGB调色盘 - layout ( 约束 ) 』Part4
看到这个标题,你或许会有一点疑惑,为什麽他不是以 Flask 开头?因为它是一个资料库的名称,而 F...
9.2 找出分离点对 (Separating Pair) 如果一个点的子集合移除以後,会让图 G 变...
终於30天了(烟 第二年参赛都顺利结束,回想第一年每天都在赶QQ 今年比前年顺利!题目根本不用想要写...
今天内容为房间载入的程序码设定,明天会教大家如何测试。 ...
现在我们会使用基本的伪类选择器做效果了,但看到变化过程一闪而过、冷冰冰的,想增添更多渐变效果,让动...