基础语法
[TOC]
官方文档
中文文档
变量
dart是强类型
var
由类型推断确定变量类型
dynamic
可以动态改变变量类型
final
与java相同,只能赋值一次
const
声明为常量
- 其值为常量值,new出来的对象实例不是常量值,所以const不能修饰对象实例
- 顶层变量、方法中的临时变量用const修饰时不加
static
- 类的成员变量用const修饰时,必须加
static
常量的值可以是全为常量的表达式,例如常量相加,常量字符串插值
内建类型
numbers
int
平台不同,不超过64位
double
64位(双精度)浮点数
字面值的方法
与kotlin一样,字面值也可以调用方法,例如:
var tmp = 3.14159.toStringAsFixed(2);
字符与number互转
int aInt = 10;
double aDouble = 3.1415;
String aIntStr = "123";
String aDoubleStr = "5.312";
var bInt = int.parse(aIntStr);
var bDouble = double.parse(aDoubleStr);
var stringFromInt = aInt.toString();
var stringAsFixed = aDouble.toStringAsFixed(2);
String
- 可以用
''
,""
- 字符串中的单引号
\'
在""
中时,可以不转义直接写 - 可以使用插值表达式
$标识符
,${表达式}
- 字符重复:插值表达式中字符、字符串可以使用
*
重复n次 - 字符串拼接:用
+
拼接;相邻字符串字面量可以认为是拼接 - 多行字符串:开头和结尾用三个单引号,或者三个双引号表示
- 原始字符串:r开头的字符串,不转义
void useString() {
// 1. 单引号 双引号
var a = 'Single quotes';
var b = 'Double quotes';
var c = "I'm ok";
var d = 'It\'s wrong';
// 2. 插值表达式
int x = -100;
var e = "check i = $x";
var f = "abs i = ${x.abs()}";
// 3. 字符重复
var g = "am${'a' * 10}zing";
var h = "a${"ma" * 10}zing";
// 4. 字符串拼接
var i = "Welcome " 'to ' "China";
var j = "I'm" + " " + "tired";
// 5. 多行字符串
var k = '''
Initializing gradle...
Resolving dependencies...
Gradle task 'assembleDebug'...''';
var l = """
Performing hot reload...
Syncing files to device Android SDK built for x86...
Reloaded 3 of 422 libraries in 428ms.""";
// 6. 原始字符串
var m = r"will not \n start a new line";
print(
"use String:\n$a\n\n$b\n\n$c\n\n$d\n\n$e\n\n$f\n\n$g\n\n$h\n\n$i\n\n$j\n\n$k\n\n$l\n\n$m");
}
布尔类型bool
bool aBool = true;
非bool类型的值不能做为表达式的判断
// isEmpty查空串
var fullName = '';
assert(fullName.isEmpty);
// 检查数值
var hitPoints = 0;
assert(hitPoints <= 0);
// null判空
var unicorn;
assert(unicorn == null);
// isNaN算术 NaN
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
列表/数组类型List
- 类型是List
- 编译时常量列表:在列表字面量前使用
const
修饰符
void useList() {
List list = [1, 2, 3];
list[0] = list.length;
print("list's 0 item = ${list[0]}");
var constList = const [9,8,7];
print(constList);
}
字典/映射表类型Map
- 类型是Map
- 编译时常量字典:在字典字面量前使用
const
修饰符
Map map = {1: "1", 4: "4"};
map[10] = "10";
var tmp = map[4];
assert(null == map[7]);
map[1] = map.length.toString();
var constMap = const {1: 2, 3: 4};
print(map);
Runes
- Runes是String 字符串中字符的32位Unicode编码的Iterable
- String.codeUnits返回的是字符串的utf16编码
- String与Rune互转
void useRunes() {
String unicodeStr = '\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} '
'\u{1f44d}';
// String 转 runes
var runes = unicodeStr.runes;
runes = new Runes(unicodeStr);
// runes 转 String
var string = String.fromCharCodes(runes);
print("unicode string: ${unicodeStr}");
print("unicode string in utf16: ${unicodeStr.codeUnits}");
print("unicode string in utf32: ${unicodeStr.runes.toList()}");
}
symbols
函数
=>单表达式函数
=>expr
等价于 {return expr;}
参数列表
参数类型可以省略,但是不推荐
-
不能传与使用不符的类型,即使用了dynamic方式声明也不行
print(testParamNoType(2)); // print(testParamNoType('r')); // print(testParamNoTypeCompare('r')); // print(testParamNoType(null)); testParamNoType(val) { return 10 * val; } testParamNoTypeCompare(dynamic val) { return 10 * val; }
The following assertion was thrown while handling a gesture: type 'String' is not a subtype of type 'num'
参数列表必须先声明必传参数,再声明可选参数
可选参数
与Java的可变参类似。
给一个函数定义可选参数,只能选择位置参数或者命名参数中的一种,不能同时使用两者
参数默认值
位置参数
- 参数列表中被
[]
包裹的参数 - 可以不传,只传部分,传全部
- 位置参数必须按照声明顺序入参,多个位置参数时,使用时,可以只传递部分参数,但是不可以跳过中间的某些参数
命名参数
- 参数列表中被
{}
包裹的参数 - 命名参数可以不传,只传部分,传全部,或者改变顺序传,但是必须带上参数的命名
testVA() {
testParamPos(1);
// 位置参数使用时不能带上参数名
// The named parameter 'c' isn't defined.
// testParamPos(2, c:"e");
// 位置参数不能跳过中间的参数
// The argument type 'int' can't be assigned to the parameter type 'String'
// testParamPos(2, 1);
testParamPos(2, "a");
testParamPos(3, "b", 4);
testParamName(1);
testParamName(2, d: 6);
// 命名参数必须带上参数名
// too many positional arugments ...
// testParamName(2,5,8);
testParamName(3, c: 7);
testParamName(4, c: 8, d: 0);
testParamName(5, d: 9, c: 0);
}
//testParam(int req,[int c],{int a= 100,int b=300}){}
//testParam(int req,{int a= 100,int b=300},[int c]){}
testParamPos(int req, [String c = "h", int d]) {}
testParamName(int req, {int c = 8, int d}) {}
返回值
没有指定返回值,则返回值为null,类似js undefined
main
应用程序入口是一个顶层函数main
void main(){}
void main(List args){}
函数也是对象
- 可以将函数作为参数传递给另一个函数,类似c的函数指针
- 可以将函数赋值给变量
int sum = 0;
countSum(int val) {
sum += val;
}
testFunction() {
var list = [32, 123, 43, 31, 55, 346, 786];
sum = 0;
list.forEach(countSum);
print(sum);
var addFunc = (int a, int b) {
return a + b;
};
var minusFunc = (int a, int b) => a - b;
print(addFunc(4, 3));
print(minusFunc(4, 3));
}
判断函数引用是否相等
- 引用同一个顶层函数的变量相等
- 引用同一个类的静态方法的变量相等
- 引用同一个类同一个实例的成员方法的变量相等
- 引用同一个类不同实例的成员方法的变量不等
转载一个demo看看就行了
void foo() {} // A top-level function
class A {
static void bar() {} // A static method
void baz() {} // An instance method
}
void main() {
var x;
// Comparing top-level functions.
x = foo;
assert(foo == x);
// Comparing static methods.
x = A.bar;
assert(A.bar == x);
// Comparing instance methods.
var v = A(); // Instance #1 of A
var w = A(); // Instance #2 of A
var y = w;
x = w.baz;
// These closures refer to the same instance (#2),
// so they're equal.
assert(y.baz == x);
// These closures refer to different instances,
// so they're unequal.
assert(v.baz != w.baz);
}
匿名函数/lambda/闭包
- 没有名字的函数,有参数列表,方法体
- 与kotlin不同,没有默认的变量it
- 单表达式可以用=>简写
testAnonymousFunction() {
var list = [1, 5, 31, 57, 231, 6, 234, 7];
// list.forEach({
// print(it);
// });
// list.forEach(
// print(it);
// );
list.forEach((it) => print(it));
list.forEach((it) {
print(it);
});
}
函数中变量的作用域
- 嵌套函数内层函数可以访问外层直到顶层的变量;
- 外层函数访问不到内层函数的变量;
testNestedFunction() {
int a = 12;
void myNested() {
int b = 43;
void nest() {
int c = 55;
// 内层函数能够访问到外层函数的变量
a = 10;
}
}
// 外层函数访问不到内层函数的变量
// undefined name 'b'
// b = 10;
}
闭包传递的参数
闭包会捕获定义在其声明位置的作用域内的变量。即使闭包函数在其声明位置之外的作用域使用,也会记住被捕获的变量
A closure is a function object that has access to variables in its lexical scope, even when the function is used outside of its original scope.
Functions can close over variables defined in surrounding scopes. In the following example,
makeAdder()
captures the variableaddBy
. Wherever the returned function goes, it remembersaddBy
.
testClosureArgument() {
makeClosure(int a) {
return (num val) => val + a;
}
Function addClosure2 = makeClosure(2);
Function addClosure3 = makeClosure(3);
print(addClosure2(5));
print(addClosure3(5));
Function minusClosure2;
Function minusClosure3;
makeClosure2(int b) {
b = 2;
minusClosure2 = (num val) => val - b;
b = 3;
minusClosure3 = (num val) => val - b;
}
makeClosure2(9);
print(minusClosure2(5));
print(minusClosure3(5));
makeClosure3(int c) {
Function build(int i) {
return (num val) => val * i;
}
var list = new List(c);
for (int j = 0; j < c; j++) {
list[j] = build(j);
}
return list;
}
var funcList = makeClosure3(5);
funcList.forEach((func) => print(func(2)));
makeClosure4(int c) {
var list = new List(c);
for (int j = 0; j < c; j++) {
list[j] = (num val) => val * j;
}
return list;
}
var funcList2 = makeClosure4(4);
funcList2.forEach((func) => print(func(3)));
print("next>>>>>");
makeClosure5(int c) {
int len = c;
var list = new List(c);
for (int j = 0; j < len; j++) {
c = j;
list[j] = (num val) => val * c;
}
return list;
}
makeClosure5(6).forEach((func) => print(func(4)));
}
此例中,makeClosure2和makeClosure5中,传入闭包的并不是在其声明位置的函数中定义的变量,而是形参。结果就失效,闭包只能记住形参最后一次的值。
运算符
按照优先级排列
描述 | 操作符 |
---|---|
一元后置操作符 | expr++ expr-- () [] . ?. |
一元前置操作符 | -expr !expr ~expr ++expr --expr |
乘除运算 | * / % ~/ |
加减运算 | + - |
移位运算 | << >> |
按位与 | & |
按位异或 | ^ |
按位或 | | |
关系和类型测试 | >= > <= < as is is! |
相等 | == != |
逻辑与 | && |
逻辑或 | || |
是否为null | ?? |
天健判断(三元运算) | expr1 ? expr2 : expr3 |
级联 | .. |
赋值 | = *= /= ~/= %= += -= <<= >>= &= ^= |
要注意的运算符
除法运算符
/
和~/
,前者会返回小数部分,后者只返回整数部分-
后缀递增递减
a++
,a--
赋值时传递的是原值var a = 0; var b = a++; assert(b==0);
-
类型测试操作符,
is
和is!
用于判断实例是否是某个类型;as用于强转,失败则抛出异常;若实例先用is命中,则在其判断分支的代码里可以直接使用其真实类型的成员testType(){ print("ad" is String); print(1 is! String); Object child = Child(10); if(child is Child){ // is 检查命中后,实例可以直接使用真实类型的成员 var b = child.b; } // 没有使用is检查前,可以使用as强转指定类型, // 但是如果为null或者类型不符则抛出异常 var b2 = (child as Child).b; }
-
空值安全处理:
??=
如果变量为null才赋值;e1??e2
若e1结果为null,则返回e2的结构;obj?.member
如果obj不为null,则使用其成员,否则则放弃;testNull() { var child; print(child?.b); var aChild = child ?? Child(15); print(aChild.b); child ??= Child(12); print(child.b); }
位与位移操作符:
&
按位与;|
按位或;^
按位异或;~
按位取反;<<
左移;>>
右移
级联运算
- 级联(..)允许您在同一个对象上创建一个操作序列。除了函数调用之外,您还可以访问同一对象上的字段。
- 级联可以给一个对象的set赋值
- 级联操作只能从实例对象或者一个返回实例对象的方法开始
- 级联可以操作对象的所有成员方法
- 级联操作相当于调用了成员方法后,又返回了实例对象交由下一个方法使用
- 如果将连续的级联操作结果赋值给另一个变量,则实际上是将级联的起始对象赋值给变量,而不是最后一个带返回值的方法的返回值。
- 类的静态方法,不论是正常调用还是级联运算,都不能通过实例对象调用
testCascade() {
var user = User()
..age = 10
..name = "rexih"
..id = 1021
..setAlias("Lisa")
..sex = 1;
print(user);
// 类的静态方法,不论是正常调用还是级联运算,都不能通过实例对象调用
// Static method 'setNice' can't be accessed through an instance
// user.setNice(false);
// user..setNice(false);
StringBuffer sb = new StringBuffer();
sb.write("aa");
sb..write("a")..write("b");
print(sb.toString());
// 级联操作只能从实例对象或者一个返回实例对象的方法开始,
// 后续操作都是起始对象可以调用的成员方法
// 级联操作相当于调用了成员方法后,又返回了实例对象交由下一个方法使用
// The expression here has a type of 'void', and therefor cannot be used
// sb.write("v")
// ..write("c");
var name = User().findName();
// 级联操作可以从实例变量开始
var s = name
..substring(2);
// s的值没有变,因为级联操作只是为了处理被操作的对象,
// 赋值给另一个变量的不是方法的返回值,而是级联起始的对象
print(s);
String findName = User().findName()
..substring(1)
..toUpperCase();
print(findName);
}
class User {
int id;
String name = "anonymous";
int sex;
int age;
void setAlias(String alias) {
name = alias;
}
String findName() => name;
@override
String toString() {
return 'User{id: $id, name: $name, sex: $sex, age: $age}';
}
}
流程控制
for循环
正常for循环
forEach循环
-
for in循环
var list = [4, 5, 2]; for (var a in list) { print(a); }
-
关于官方文档中说可以避免js的闭包问题,跟之前说的传入闭包的参数一样,看传入的变量是否是在闭包声明位置的作用域内定义,如果不是,仍然捕获不到
void testFor() { var callbacks = []; for (var j = 0; j < 5; j++) { callbacks.add(() => print(j)); } callbacks.forEach((c) => c()); callbacks.clear(); print("show error"); int i = 0; for (; i < 9; i++) { callbacks.add(() => print(i)); } callbacks.forEach((c) => c()); }
运行结果
I/flutter (30393): 0 I/flutter (30393): 1 I/flutter (30393): 2 I/flutter (30393): 3 I/flutter (30393): 4 I/flutter (30393): show error I/flutter (30393): 9 I/chatty (30393): uid=10099(cn.rexih.flutter.flutterapp2) Thread-2 identical 7 lines I/flutter (30393): 9
switch
dart的switch支持字符串,整数和编译时常量,但是一个switch中只能比较一种类型
assert
- 条件表达式不为true才中断抛出异常
- 只在测试环境生效
- 可以在第二个参数传入断言信息
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');
异常
- 异常类型为Exception和Error
- throw抛出异常
- rethrow 在捕获中重新抛出
- finally做资源释放和清理操作
异常捕获
- 只捕获异常,不处理异常对象
- 处理异常对象
- 处理StackTrace堆栈跟踪对象
try {
breedMoreLlamas();
} on OutOfLlamasException {
//只捕获异常,不处理异常对象
buyMoreLlamas();
} on Exception catch (e) {
//处理异常对象
print('Unknown exception: $e');
//在捕获中重新抛出
rethrow;
} catch (e, s) {
//处理StackTrace堆栈跟踪对象
print('Exception details:\n $e');
print('Stack trace:\n $s');
} finally {
cleanLlamaStalls();
}