Dart语言笔记(二)
1.重要概念
- 一切变量都是对象,包括数字、函数和null
- 对象是类的实例,所有类都继承于
Object
- 强类型但支持类型推断
- 如果不想指定具体类型可以使用
dynamic
- 支持泛型,比如
List<int>
或List<daynamic>
- 支持顶层函数、类方法、实例方法和嵌套函数
- 支持顶层变量、类变量、实例变量和局部变量
- 以下划线
_
打头的变量为私有,只能在库(Library)内部使用
- 标识符只能以字母或下划线打头,后面跟任意多个字符、数字或下划线字符
- 表达式在运行时可以求值,而语句不行
- 代码问题分为warning和error两种,warning不会阻止程序运行,但error会
- error分为编译时错误和运行时错误两种,运行时错误会导致抛出异常
2.变量
- 变量类型可自动推断,或者显示指定
- 未初始化的变量其值都是null,包括数字
- 如果某个变量的值在定义后不会再改变,那就声明为
final
或const
-
final
和const
变量只能赋值一次,但const
变量的值需要在编译时就能确定
-
final
和const
变量必须在其定义时就完成初始化
-
const
变量的值须为编译时常量,包括数字和字符串字面量、其他常量或算术运算结果
-
const
除了用来声明变量,也可用来声明值,也就是常值
- 初始化
const
变量时可以省去值前面的const
,因为const
变量的值肯定得是一个常值
- 常值用来防止内容变化,常量用来避免变量寻址,
final
变量用来提升代码安全性
3.内置类型
- 数字(num)
- num是int和double的基类
- int为64位整数,double为64位浮点数
- 字符串(String)
- 字符串是UTF-16码元(Code Unit)序列,每个Unicode字符映射为1个或2个码元
- 字符串字面量可以使用单引号或双引号
- 使用
${expression}
来插入变量值,如果expression
是个合法的标识符,则可以省去{}
- 使用毗连字符串常量或者
+
操作符来拼接字符串
- 使用三重引号(三重单引号或者三重双引号都可以)来定义多行字符串
- 使用
r
前缀来声明原始字符串,其内不作字符转义
- 只要内部插入的表达式为编译时常量,则字符串字面量为编译时常量
- 布尔(bool)
- 只有两个值true和false,都是编译时常量
- 在需要布尔类型的地方必须使用布尔值,没有非零即真的概念
- 列表(List)
- 列表就是其他语言里的数组,用来表示对象序列
- 列表索引序号从0开始,直到
list.length-1
- 在列表字面量前使用
const
来声明常值
- 使用
[]
操作符来操作列表元素
- 哈希表(Map)
- 哈希表用来关联键和值,键和值可以是任意类型的对象,但键必须唯一
- 使用
[]
操作符来操作哈希表的值
- 在哈希表字面量前使用
const
来声明常量
- 符文(Rune)
-
Rune
是UTF-32码点(Code Point)组成的字符串,一个Rune
字符对应一个Unicode字符
- 码点一般使用4个十六进制字符
\uXXXX
来表示,如果超过4个,则需要使用{}
将十六进制字符括起来
- 字符串和
Rune
可以直接互相转换
- 符号(Symbol)
void main() {
var x = 1;
var hex = 0xDEADBE;
var y = 1.1;
var exponents = 1.42e5; //科学计数法
double z = 1;
var addIntAndDouble = x + y; //int可以直接和double计算,会隐式转换为double
assert(addIntAndDouble == 2.1);
var one = int.parse('1'); //使用parse解析字符串
assert(one == 1);
var onePintOne = double.parse('1.1');
assert(onePintOne == 1.1);
String oneAsString = 1.toString(); //使用toString转换为字符串
assert(oneAsString == '1');
String piAsString = 3.1415926.toStringAsFixed(2); //转换为string并保留几位小数
assert(piAsString == '3.14');
assert((3 << 1) == 6);
assert((3 >> 1) == 1);
assert((3 | 4) == 7);
//定义常量
const msPerSecond = 1000;
const secondsUntilRetry = 5;
const msUntilRetry = secondsUntilRetry * msPerSecond;
var s1 = 'Single string'; //单引号
var s2 = "Double string"; //双引号
var s3 = 'It\'s easy'; //转义字符
var s4 = "It's easy"; //双引号中单引号就不用加转移字符
var s = 'string';
var uppeers = s.toUpperCase(); //转化为大写
assert(uppeers == 'STRING');
assert(s == uppeers.toLowerCase()); //转化为小写
//使用毗连字符串拼接
s1 = 'String '
'concat '
'work';
assert(s1 == 'String concat work');
//使用+连接字符串
s2 = 'String ' + 'concat ' + 'work';
assert(s2 == 'String concat work');
//使用'''或者"""定义多行字符串
s2 = '''String
concat
work''';
//在字符串前加r定义不转义的字符串
s = r'In a raw string,not even \n gets special treatment';
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a const string';
var aNum = 0;
var aBool = true;
var aString = 'a string';
//定义一个List常量
const aConstList = [1, 2, 3];
//后面都是常量,而且表达式编译时可确定值,所以可以赋值给一个常量字符串
const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aConstNum $aConstBool $aConstString';
var fullName = '';
assert(fullName.isEmpty); //使用isEmpty来判空
var hitPoints = 0;
assert(hitPoints <= 0);
//未初始化默认都是null
var unicorn;
assert(unicorn == null);
//这个不会抛出异常,类型是double,但是值是NaN
var iMeantToDoThis = 0 / 0;
print(iMeantToDoThis);
print(iMeantToDoThis.runtimeType);
assert(iMeantToDoThis.isNaN);
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 22;
assert(list[1] == 22);
//给list赋常值
var constList = const [1, 2, 3];
//常值的内容不能被改变
// constList[1] = 22;
//Map定义
var gifts = {
'first': 'part',
'seconed': 'tue',
'fifth': 'golden',
};
print(gifts.runtimeType); //JsLinkedHashMap<String, String>
var nobleGases = {2: 'helium', 10: 'neon', 18: 'argon'};
print(nobleGases.runtimeType); //JsLinkedHashMap<int, String>
//构造函数方式创建Map
gifts = Map();
gifts['first'] = 'part1';
gifts['seconed'] = 'tue1';
gifts['fifth'] = 'golden1';
gifts = {'first': 'part1'};
assert(gifts['fifth'] == null);
gifts['seconed'] = 'tue';
assert(gifts.length == 2);
//常值Map
final constMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
//final定义的只能一次赋值
// constMap = Map();
//常量不能修改内容
// constMap[20] = 'tw';
//每两个十六进制占一个字节,这里占3个字节,所以需要大括号括起来
var clapping = '\u{1f44f}';
print(clapping); //👏
// 打印码云序列,需要两个码云
print(clapping.codeUnits); //[55357, 56399]
// 转化为一个runes,得到runes的码点序列
print(clapping.runes.toList()); //[128079]
//小于等于2个字节不用加大括号
Runes input =
Runes('\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
//fromCharCodes:根据Unicode编码序列来得到字符串
print(String.fromCharCodes(input)); //♥ 😅 😎 👻 🖖 👍
}
4.函数
- 函数也是一种对象,类型为Function
- 函数参数和返回值类型可省略,以支持动态类型
- 如果函数体只包含一个表达式,可使用箭头语法来定义
- 可选参数
- 可选命令参数使用{}来指定,并且可使用注解
@required
标注为必需
- 可选位置参数使用[]来指定
- 可选参数默认值使用=来指定,如未指定则默认值为null
- 每个dart程序都应该有个位于顶层的main函数,它是程序的入口
- 函数可作为函数参数值,也可以赋值给变量
- 可定义匿名函数,一般作为函数参数值或赋值给变量
- 变量作用域静态确定,也就是同代码布局,每对大括号定义一个作用域,嵌套大括号定义嵌套作用域
- 闭包是一个能访问其外层作用域变量的函数,即便该函数在其他地方被调用
- 如果函数没有指定返回值,则默认返回null,如果确实不想返回任何值,则可指定返回类型为void
//全局变量 是否顶层
bool topLevel = true;
void main() {
var _nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
//最普通的函数定义
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//dart是一门兼具动态性和静态性的语言
//未指定参数类型,那么参数类型就是dynamic
//返回值为bool类型可以推断出来
//所以返回值类型和参数类型都可以省略
isNobleDynamic(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//单行表达式可以使用箭头
bool isNobleArrow(int atomicNumber) => _nobleGases[atomicNumber] != null;
//可选命名参数需要放在{}中
void enableFlags({bool? bold, bool? hidden}) {}
//可选命令参数调用时需显式指定参数名
enableFlags(bold: true, hidden: false);
//位置参数放在[]中
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
//可选参数设置默认值
void enableFlagsDefault({bool? bold = false, bool? hidden = false}) {}
enableFlagsDefault(bold: true);
String sayDefault(String from, String msg,
[String? device = 'carrier pigeon', String? mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
assert(sayDefault('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'seconed': 'cotton',
'third': 'leather'
}}) {
print('list: $list'); //list: [1, 2, 3]
print(
'gifts: $gifts'); //gifts: {first: paper, seconed: cotton, third: leather}
}
doStuff();
void printElement(int element) {
print(element);
}
var list1 = [1, 2, 3];
list1.forEach(printElement);
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
print(loudify('hello')); //!!! HELLO !!!
var list2 = ['apples', 'bananas', 'oranges'];
list2.forEach((item) {
print('${list2.indexOf(item)}: $item');
});
//单行表达式可以换为箭头函数返回
list2.forEach((item) => print('${list2.indexOf(item)}: $item'));
//作用域嵌套
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
//闭包,匿名函数
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
var add2 = makeAdder(2);
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
//无返回值默认返回值为null
foo() {}
assert(foo() == null);
}
5.操作符
- 操作符有优先级,从高到低为一元后缀、一元前缀、乘除、加减、移位、位运算、关系运算和类型测试、相等性运算、逻辑运算、null判断、三元表达式、级联调用和赋值
- 在优先级判断比较模糊的地方,使用()来提升可读性
- 算术运算
-
+
、-
、*
、/
、-expr
、~/
(整除)、%
(求余)
-
++var
、var++
、--var
、var--
- 相等性和关系运算
-
==
、!=
、>
、<
、>=
、<=
-
==
判断值是否相等,如果要判断是否为同一个对象,使用identical()
函数
- 类型测试
-
as
(类型转换)、is
(判断是否指定类型)、is!
(判断是否非指定类型)
- 赋值
-
=
、??=
(null则赋值,否则不赋值;一般用于初始化时防止重复初始化)
-
-=
、/=
、%=
、>>=
、^=
、+=
、*=
、~/=
、<<=
、&=
、|=
- 逻辑运算
- 位运算
-
&
、|
、^
(异或)、~expr
(按位取反)、<<
、>>
- 条件表达式
- 三元表达式
condition?expr1:expr2
- null判断
expr1 ?? expr2
:expr1
为非null则取expr1
的值,否则取expr2
的值
- 级联调用
-
..
,严格来说是dart独有的语法糖,一般用于在一个对象上连续的调用,比如同时设置多个成员的值。..
会强制返回调用者对象,而忽略掉当前调用方法的返回值。
-
?..
,若调用者可能为null,则使用这个操作符,如果调用者对象为null,则不会触发访问,非null则继续访问。这个操作符后面继续级联调用可以继续使用..
,因为..
都是返回调用者对象。
- 函数调用
()
- 下标访问
[]
- 成员访问
.
、?.
- 条件式成员访问
?.
: 如果调用者对象是null,,则不会访问成员,如果是非null,则访问成员
void main() {
var a = 1;
var b = 2;
var c = true;
print(a++);
print(a + b);
print(a = b); //赋值表达式的值就是赋值后变量的值
print(a == b);
print(c ? a : b);
assert(a is int);
var n = 4;
var d = 6;
var i = 2;
assert((n % i == 0) && (d % i == 0));
assert(n % i == 0 && d % i == 0);
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
a = 0;
//前缀表达式,先自增/自减 再返回值
b = ++a;
print(b); //1
a = 0;
//后缀表达式 先返回变量值 再自增/自减
b = a++;
print(b); //0
a = 0;
b = --a;
print(b); //-1
a = 0;
b = a--;
print(b); //0
List<dynamic> list = [1, 'a', 2, 'b'];
if (list[1] is String) {
print((list[1] as String).toUpperCase()); //A
}
a = 1;
//b未赋值才执行赋值
b ??= 2;
print(b); //0
var done = false;
var col = 3;
print(!done && (col == 0 || col == 3)); //true
final value = 0x22;
final bitmask = 0x0f;
//转化为16进制打印输出
print((value & bitmask).toRadixString(16)); //2
print((value & ~bitmask).toRadixString(16)); //20
print((value | bitmask).toRadixString(16)); //2f
print((value ^ bitmask).toRadixString(16)); //2d
print((value << 4).toRadixString(16)); //220
print((value >> 4).toRadixString(16)); //2
var isPublic = false;
var visibility = isPublic ? 'public' : 'private';
print(visibility); //private
String playerName(String? name) => name ?? 'Guest';
print(playerName(null)); //Guest
String playerNameArrow(String? name) => name != null ? name : 'Guest';
print(playerNameArrow(null)); //Guest
String playerNameIf(String? name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}
print(playerNameIf(null)); //Guest
//级联操作符
var sb = StringBuffer()
..write('foo')
..write('bar')
..write('test');
print(sb);//foobartest
}
6.控制流程
- If-else
- For loop
- 循环内的闭包会捕获循环变量的当前值
- 可迭代对象可使用for-in来迭代其内部元素
- While、do-while
- While先判断条件,满足后再执行循环体
- do-while先执行一次再判断条件
- Break、continue
- Break终止循环,continue跳过本次循环剩余代码,直接进入下一次循环
- Switch-case
- 使用==操作符来比较整数、字符串或编译时常量,包括枚举类型
- 非空case语句正常情况下应以break结束,也可使用continue、throw或return来结束
- 使用default语句来匹配其他情况
- 空case语句会落入下方case语句
- 可结合使用continue和label来跳转到其它case语句
- 一般用于解释器和扫描器,编写应用时尽量少用
- Assert
- 只在开发模式下有效,生产模式下会被忽略
- 条件判断失败时会抛出AssertionError异常,可通过第二个参数指定错误消息
void main() {
var isRaining = false;
var isSnowing = true;
//条件必须是bool类型
if (isRaining) {
print('bring rain coat');
} else if (isSnowing) {
print('wear jacket');
} else {
print('put top down');
}
//和C一样的for循环
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
//闭包捕获值
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());
//for-in
var collection = [0, 1, 2];
for (var item in collection) {
print(item);
}
//while
var isDone = false;
while (!isDone) {
print('Do sth');
isDone = true;
}
//do-while
var atEndOfPage = true;
do {
print('do sth');
} while (atEndOfPage == false);
//break退出循环
var shutDownRequested = false;
while (true) {
if (shutDownRequested) break;
print('process incoming requests');
shutDownRequested = true;
}
//continue进入下一次循环
var candidates = [1, 3, 5, 2, 4, 7];
for (var i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate < 5) {
continue;
}
print('interview');
}
//switch case
var command = 'OPEN';
switch (command) {
case 'CLOSED':
print('execute closed');
//continue配合label
continue openCase;
case 'PENDING':
print('execute pending');
break;
//空的case语句,可以fall-through到下一个case
case 'APPROVED':
// print('execute approved');
// break;
case 'DENIED':
print('execute denied');
break;
//定义了label
openCase:
case 'OPEN':
print('execute open');
break;
default:
print('execute unknown');
}
var urlString = 'https://www.google.om/';
//assert两个参数,第一个是bool表达式,第二个是可选的描述字符串
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');
}
7.异常
- 异常表示有一些预料之外的错误发生了
- 如果异常未被捕捉,则程序将终止运行
- Dart内置了Exception和Error两种类型,Exception核心库异常,Error用于应用代码异常
- 可使用throw抛出任何非null对象作为异常
- throw语句是一个表达式
- 使用try-catch语句来捕捉异常,以防止异常扩散,同时可使用on语句来处理特定类型的异常
- 使用rethrow来再次抛出异常
- 使用finally语句来执行无论是否出现异常都要运行的代码
void main() {
void foo() => throw UnimplementedError();
try {
foo();
} on UnimplementedError catch (e) {
// on表示明确捕捉某种异常
print('Unknown exception: $e'); //Unknown exception: UnimplementedError
} catch (e, s) {
// 第一个参数e是异常参数,第二个参数s异常调用栈
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
void misbehave() {
try {
dynamic foo = true;
print(foo++);
//布尔值无法执行++操作
} catch (e) {
print(
'misbehave() partinally handled ${e.runtimeType}'); //misbehave() partinally handled JsNoSuchMethodError
//这里还没处理完,还需要外层继续处理,rethrow继续抛出
rethrow;
}
}
try {
misbehave();
} catch (e) {
print(
'main() finished handling ${e.runtimeType}'); //main() finished handling JsNoSuchMethodError
}
}