前言
前些日子在公司尝试着使用 Flutter 开发一些功能。要想写好 Flutter 程序,首先 Dart 语言是基础。
当然,在实际开发中,可能由于时间的关系,不用了解到 Dart 的方方面面才开始 Flutter 的开发,可以边学边用。
为了形成一个完整的体系,我想先系统的讲解下 Dart 语言,然后在开始 Flutter 相关的介绍。
Dart 相对于 Java 来说有更强的变现力,甚至我觉得比 Kotlin 还要有表现力。Dart 不仅可以用来开发客户端,还可以用来开发Web前端和后端开发。
所以呢,我觉得 Dart 还是一门值得学习的语言,下面就开始我们的 Dart 学习之旅吧
本文是 Dart 相关的第一篇,主要介绍 Dart 语言的变量的定义和类型体系
Hello World
按照惯例,学习一门语言都是从 Hello World 开始的。下面我们看下 Dart 的 Hello World 程序:
main(){
var str = "Hello world!";
print(str);
}
- 首先我们定义了顶级的 main 函数,这个程序的入口,如果没有返回值可以省略函数的返回类型
- 然后我们通过 var 关键字定义了一个字符串,通过类型推导,str是一个字符串类型的变量
- 最后通过系统的 print 函数输出 str 变量
变量的定义
上面我们提到了 Dart 是支持类型推导的,所以在定义变量的时候可以通过 var 关键字来定义
当然也可以显示的指定 变量的类型,如:
// 通过 var 关键字定义变量,省略具体的类型
var str = "Hello world!";
// 显式指定变量类型
String str = "Hello World!"
在 Dart 中万物皆对象,如 null、函数、数字 都是对象
所以我们的定义的变量如果没有给初始值,那么这个变量的默认是就是 null
int lineCount;
assert(lineCount == null);
定义变量的时候,可以使用 final 关键字来修饰,如
final String name = "chiclaim";
name = "johnny"; // Error
final定义的变量,在定义的时候就要初始化,并且只能赋值一次
定义变量的时候还可以使用 const 关键字来修饰:
const name = "chiclaim"
name = "johnny"; // Error
const 关键字修饰变量,说明这个变量是 编译时常量,如数字、字符串字面量都是 编译时常量
const 和 final 区别是 final 表示这个变量只能被赋值一次,const 表示该变量不仅只能被赋值一次,并且该变量是一个常量
const 不仅可以修饰变量,还可以修饰变量的值,表示该值不能修改:
main(){
// 通过 const 关键字修饰foo变量的值
var foo = const [];
// 由于 foo 的值被 const 修饰,所以不能修改里面的值
// 运行时错误,Unsupported operation: Cannot modify an unmodifiable list
foo[0] = 0;
// 可以赋值,因为 foo 变量没有被 final 或 const 修饰
foo = [1,2,3];
// 由于foo变量被重新赋值,所以可以修改里面的值
foo[0] = 0;
print(foo);
}
Dart类型系统
Dart 类型系统如下所示:
- numbers(数字如整型,浮点型等)
- strings(字符串)
- booleans (布尔值)
- lists (集合框架和数组)
- sets (Set集合)
- maps (Map集合)
- runes (表达Unicode字符)
- symbols (一般用不到)
numbers
不像 C/C++/Java 等语言,在 Dart 表示数字的类型只有两个:int 和 double
int :表示没有小数点的整型,最大值不大于64-bit(-2^63 ~ 2^63-1)底层依赖的系统最大值也不同,如果编译后用于JavaScript,则最大值为 -2^53 ~ 2^53-1
double 表示双精度的浮点型
int age = 17;
double weight = 65.5;
double waistline = 30; // 相当于 30.0
字符串和数字类型之间互转:
// 字符串转化成整型
int num = int.parse("2");
// 字符串转化成浮点型
double pi = double.parse("3.14");
// 浮点型转成字符串
String piStr = 3.14.toString();
// 浮点型转成用小数表示的字符串(可以选择保留几位小数)
String piAsStringFix = 3.14159.toStringAsFixed(2);
string 字符串
字符串的创建
一个 Dart 字符串是个 UTF-16 码元(code units)序列,可以使用单引号或双引号来创建一个字符串
// 双引号创建一个字符串字面量
var s1 = 'Single quotes work well for string literals.';
// 单引号创建一个字符串字面量
var s2 = "Double quotes work just as well.";
// 单引号创建字符串字面量(需要转义符)
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";
创建原生(raw)字符串,在字符串前面加上前缀 r:
// 一般 \n 是换行符,如果我们想把 \n 当做字符串输出时候,也可以使用创建原生字符串的方式
var raw = r'In a raw string, not even \n gets special treatment.';
我们还可以将表达式(${expression})嵌入字符串中:
const name = "chiclaim";
// 将表达式${expression}嵌入字符串中
var info = "My name is ${name}";
// 如果表达式是一个标识符,可以省略花括号
var info2 = "My name is $name";
字符串的拼接:
// 1. 通过 + 号来拼接字符串
var s2 = 'The + operator ' + 'works, as well.';
// 2. 毗邻的字符串字面量
var s1 = 'String '
'concatenation'
" works even over line breaks.";
// 3. 通过多行字符串(使用三引号)
var s1 = '''
You can create
multi-line strings like this one.
''';
var s2 = """This is also a
multi-line string.""";
字符串常用的函数
1. 字符串查询相关
- contains(str) 字符串中是否包含某个字符串
- startsWith(str) 字符串是否以某个字符串开头
- endsWith(str) 字符串是否以某个字符串结尾
- indexOf(str) 字符串在目标字符串中的位置
// Check whether a string contains another string.
assert('Never odd or even'.contains('odd'));
// Does a string start with another string?
assert('Never odd or even'.startsWith('Never'));
// Does a string end with another string?
assert('Never odd or even'.endsWith('even'));
// Find the location of a string inside a string.
assert('Never odd or even'.indexOf('odd') == 6);
2. 字符串提取
- substring(startIndex,endIndex) 字符串截取
- split(Pattern) 字符串分割
- length 字符串长度
- [index] 通过索引获取字符串中UTF-16编码单元
- codeUnits 返回字符串的UTF-16编码单元的不可修改的List
// 字符串截取
assert('Never odd or even'.substring(6, 9) == 'odd');
// 使用正则表达式分割字符串,返回字符串集合
var parts = 'structured web apps'.split(' ');
// 通过索引获取字符串的UTF-16编码单元
assert('Never odd or even'[0] == 'N');
// 循环打印字符串中的字符串
for (var char in 'hello'.split('')) {
print(char);
}
// 获取字符串中的所有UTF-16码元
var codeUnitList ='Never odd or even'.codeUnits.toList();
// N 的 ASCII 编码的十进制就是 78
assert(codeUnitList[0] == 78);
3. 大小写转换
- toUpperCase 将字符串全部转成大写
- toLowerCase 将字符串全部转成小写
// 转成大写
assert('structured web apps'.toUpperCase() == 'STRUCTURED WEB APPS');
// 转成小写
assert('STRUCTURED WEB APPS'.toLowerCase() == 'structured web apps');
4. 字符串的头尾空格的移除和空字符串
- trim 去除头和尾的空格
- isEmpty 字符串是否为空
- isNotEmpty 字符串是否不为空
// 去除头尾的空格
print(' hello '.trim() == 'hello');
// 字符串是否为空
print(''.isEmpty);
// 空格不是空字符串
print(' '.isNotEmpty);
// 空格trim后,就变成空字符串
print(' '.trim().isNotEmpty);
5. 字符串部分替换
- replaceAll
字符串是不可变的,替换函数不是修改原字符串,而是会产生一个新的字符串
var greetingTemplate = 'Hello, NAME!';
var greeting = greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');
// greetingTemplate didn't change.
assert(greeting != greetingTemplate);
6. 字符串构建
通过编程的方式生成一个字符串,可以使用 StringBuffer 来实现,直到调用 StringBuffer.toString() 函数才会创建字符串
var sb = StringBuffer();
// 通过 .. 实现链式调用
sb
..write('Use a StringBuffer for ')
// 写入一个字符串列表,以空格分割
..writeAll(['efficient', 'string', 'creation'], ' ')
..write('.');
// 创建字符串
var fullString = sb.toString();
assert(fullString ==
'Use a StringBuffer for efficient string creation.');
Boolean
在 Dart 中通过 bool 关键字来描述布尔,布尔有两个值:true和false。 true 和 false 都是编译器常量
bool isSuccess = true;
bool isFailed = false;
List
List不仅用来表示List集合(有序的数据集合),也用来表示数组
下面来看下如何创建一个List 字面量
var list = [1, 2, 3]; // 类型推导出list是 List<int>
介绍 const 关键字的时候,也用 const 修饰 List 的值 如:
var constantList = const [1, 2, 3];
// constantList[1] = 1; // Error.
Dart2.3 提供了展开操作符(... 和 ...?) 让开发者更加方便的将多个值插入到集合中
var list = [1, 2, 3];
// 展开操作符(...) spread operator
var list2 = [0, ...list];
var list;
// 展开操作符(...?) null-aware spread operator
var list2 = [0, ...?list];
Dart2.3 除了提供了展开操作符,还有 collection if 和 collection for
// collection if
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
// collection for
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
collection if/for 在开发Flutter的时候很有用,可以简化很多代码
Set
Set 表示一个无序的集合,下面看下如何创建一个 Set 字面量
// halogens is Set<String>
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
创建一个空 Set :
// 通过var和泛型推导出Set类型
var names = <String>{};
// 显式指定是Set类型
Set<String> names = {};
需要注意的是下面是创建了一个Map
var names = {};
类似地,展开操作符 和 collection if/for 也可以用到 Set 中
Map
Map 是一个 键值对 集合,下面来看下如何创建 Map 字面量:
// gifts is Map<String, String>
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
// nobleGases is Map<int, String>
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
需要注意的时候,Key只能出现一次,否则会被后面的替换掉
通过中括号 [] 来添加、修改、获取Map中的元素:
var gifts = Map();
// 添加元素
gifts['first'] = 'partridge';
var gifts = {'first': 'partridge'};
// 获取通过Key获取Map中的元素
assert(gifts['first'] == 'partridge');
var gifts = {'first': 'partridge'};
// 修改Map中的元素
gifts['fourth'] = 'calling birds';
类似地,展开操作符 和 collection if/for 也可以用到 Set 中
关于 Dart 的 展开操作符 以及 collection if/for 更多内容可以查看:
(二)Flutter学习之Dart展开操作符 和 Control Flow Collections
Rune
在 Dart 中,Rune类型是一个字符串的UTF-32的码点(code points),我们在介绍 String 类型的时候,提到了 code units
那么在介绍 Rune 之前,我们先来看下 code points 和 code units 是什么?
Unicode 编码为世界上所有的字母、数字、符号定义了对应的数字来表示
- 码点:Unicode是属于编码字符集的范围。它所做的事情就是将我们需要表示的字符表中的每个字符映射成一个数字,这个数字被称为相应字符的码点(code points)
- 码元:就是码点多少个字节能存储,比如 UTF-8 码元就是 8 bit 也就是 1 byte,UTF-16 码元就是 2 byte
因为 Dart 字符串是 UTF-16 码元的序列,描述 32-bit Unitcode 的字符串需要特殊的语法
通常地, Unicode使用 \uXXXX 描述码点 ,这里的 XXXX 是一个4位十六进制的值,例如描述心形的符号使用 \u2665 表示
如果多于或者少于 4 个十六进制,则需要加上花括号,例如笑的表情使用 \u{1f600}
String 类有几个属性可以提取字符串的 rune 信息 (例如属性 runes),codeUnitAt 和 codeUnit 属性返回 16 bit的 码元(code units)
main() {
var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));
}
Object 和 dynamic
在 Dart 所有的类都是继承自 Object,和Java类似。dynamic 和 Object 允许所有的值赋值给它:
dynamic name;
Object email;
main() {
name = "chiclaim";
email = "chiclaim@gmail.com";
}
但是他们是两个完全不同的概念。你甚至可以把 dynamic 不要当做一个类型看待,dynamic 顾名思义就是动态的意思,它只是告诉编译器不要做类型检查
dynamic name;
Object email;
main() {
// 将 String 赋值给 name
name = "chiclaim";
// 打印 name 的长度
print(name.length);
// 将 int 赋值给name
name = 1;
// 编译器并不会报错(编译时不做类型检查),运行时才会报错
// NoSuchMethodError: Class 'int' has no instance getter 'length'.
print(name.length);
email = "chiclaim@gmail.com";
// 编译器报错,因为 Object 没有 length 属性
print(email.length);
}
通过上面的代码示例,相信你对 dynamic 和 Object 的区别有了比较清楚的理解
Dart 在官网的最佳实践中建议开发者,如果你想表达意思是任何对象都可以,使用 Object 代替 dynamic
如果你允许类型推导失败,一般推到失败,编译器会默认给dynamic,但是建议显式地写上 dynamic ,因为代码的阅读者不知道你是忘记了写类型还是:
// 建议
dynamic mergeJson(dynamic original, dynamic changes){
}
// 不建议
mergeJson(original, changes){
}
Dart几个重要的概念
- 万物接对象:null、函数、数字
- Dart有类型推导功能,定义变量是可以不指定变量类型,如果不想指定类型,可以使用 dynamic 类型
- Dart支持泛型,如 List<Int>、List<dynamic>
- Dart支持 top-level 函数,也支持成员函数和静态函数,同时也支持函数的嵌套
- Dart没有 public、private、protected 关键字来控制访问权限,Dart 通过下划线来控制访问权限,如果以下划线开头,则表示只能在 library 内可见
Reference
关于 Dart 变量和类型系统 就讲到这里, 更多的关于 Android
学习资料可以查看我的GitHub: https://github.com/chiclaim/AndroidAll
https://dart.dev/guides/language/language-tour
https://github.com/acmerfight/insight_python/blob/master/Unicode_and_Character_Sets.md
更多
所有关于 Retrofit 的使用案例都在我的 AndroidAll GitHub 仓库中。该仓库除了 Retrofit,还有其他 Android 其他常用的开源库源码分析,如「RxJava」「Glide」「LeakCanary」「Dagger2」「Retrofit」「OkHttp」「ButterKnife」「Router」等。除此之外,还有完整的 Android 程序员所需要的技术栈思维导图,欢迎享用。
下面是我的公众号,干货文章不错过,有需要的可以关注下,有任何问题可以联系我: