由于学习Flutter的时候不可避免的会使用到Dart语言,Dart语言也因此显得格外重要。 一直想总结一篇语法基础作为备忘, 今天终于实现这么目标了。 尽管是个小目标。
这一篇文章总结一点Dart语法基础
开发环境的配置不作为本文的重点进行讨论。
本文是基于的dart sdk 是stable 2.4.0, devel 2.5.0-dev.1.0
由于笔者主要使用的语言是Java,文中大部分语法会和Java类比
主要分以下几个部分展开记录
import 'dart:mirrors';
main(List arguments) {
//Dart 语言本质上是动态类型语言,类型是可选的
//可以使用 var 声明变量,也可以使用类型来声明变量
//一个变量也可以被赋予不同类型的对象
var test;
test = 'type-test';
// 结果 String
print(reflect(test).type.reflectedType);
/// 之前的版本 不支持 + 操作符 结果 type-testtype-test
print(test+test)
/// 改变类型
test= 27;
//// int
print(reflect(test).type.reflectedType);
/// 54
print(test+test);
//但大多数情况,我们不会去改变一个变量的类型
//字符串赋值的时候,可以使用单引号,也可以使用双引号
var str1 = "Ok?";
//如果使用的是双引号,可以内嵌单引号
//当然,如果使用的是单引号,可以内嵌双引号,否则需要“\”转义
//String str2 = ‘It\’s ok!’;
String str2 = "It's ok!";
//使用三个单引号或者双引号可以多行字符串赋值
var str3 = """Dart Lang
Hello,World!""";
//在Dart中,相邻的字符串在编译的时候会自动连接
//这里发现一个问题,如果多个字符串相邻,中间的字符串不能为空,否则报错
//但是如果单引号和双引号相邻,即使是空值也不会报错,但相信没有人这么做
//var name = 'Wang''''Jianfei'; 报错
var name = 'Wang'' ''Jianfei';
//assert 是语言内置的断言函数,仅在检查模式下有效
//如果断言失败则程序立刻终止
assert(name == "Wang Jianfei");
//Dart中字符串不支持“+”操作符,如str1 + str2
//如果要链接字符串,除了上面诉说,相邻字符串自动连接外
//还可以使用“$”插入变量的值
print("Name:$name");
//声明原始字符串,直接在字符串前加字符“r”
//可以避免“\”的转义作用,在正则表达式里特别有用
print(r"换行符:\n");
//Dart中数值是num,它有两个子类型:int 和 double
//int是任意长度的整数,double是双精度浮点数
var hex = 0xF0F;
//翻了半天的文档,才找打一个重要的函数:转换进制
//上面提到的字符串插值,还可以插入表达式:${}
print("整型转换为16进制:$hex —> 0x${hex.toRadixString(16).toUpperCase()}");
/// 整型转换为16进制:3855 —> 0xF0F
}
const 和 final 用法和其他语言类似
当然这是使用const 和final 同时,使用这俩可以省略 var 或者其他类型
const 和 final 定义的都是常量,值不能改变
并且在声明的时候就必须初始化
但是有细微差别
final time = new DateTime.now();
//// print 2018-12-12 16:54:50.904585
const time = new DateTime.now();
/// Error 编译不通过 , 不是 const 常量
const time = 'xxx';
/// xxx
print(time);
简单来说,
var、final等在左边定义变量的时候,并不关心右边是不是常量 但是如果右边用了const,那么不管左边如何,右边都必须是常量
print(funcHello("mick"));
// assert(funcHello is Function);
// is 和 !is
assert(funcHello("jack") is String);
//// 返回类型 这东西是可以省略的, 但是一般还是留着吧,方便 funcHello()
String funcHello(String name){
return 'Hello '+name;
}
/// 还可以省略成一个表达式 =>
funcHello(name) => 'Hello $name!';
还可以省略成匿名函数
var funcHello = (name)=>'Hello $name!';
当看到用typedef定义函数别名的时候,不自觉的想到了C中的函数指针
// 定义别名 add
typedef String add(int a, int b);
//// 简写
int subtract(int a, int b) => a - b;
void main(){
///// 结果 true
print(subtract is Function);
//// 结果 false
print(subtract is add);
}
如果把 add 返回值类型改成 int 则 is 返回 true
初始化变量的时候,参数对应的是函数的参数num num1
调用函数类型变量的时候,
参数对应的是返回值中的参数num2
var funcName = makeSubstract(5);
print(funcName(2));
Function makeSubstract(num num1){
return (num num2) => num1 - num2;
}
Dart 中函数也是对象
// 这里边放的是函数指针
var callbacks = [];
for (var i = 0; i < 3; i++) {
// 在列表 callbacks 中添加一个函数对象,这个函数会记住 for 循环中当前 i 的值。
callbacks.add(() => print('Save $i'));
}
callbacks.forEach((subFunc) => subFunc());
// 分别调用子项Function类型的子item 输出 0 1 2
Dart中支持两种可选参数:
命名可选参数和位置可选参数
但两种可选不能同时使用
命名可选参数使用大括号{},默认值用冒号:
位置可选参数使用方括号[],默认值用等号=
///
///在命名可选参数的函数中,大括号外的a为必填参数
///大括号内的参数可以指定0个或多个
//并且与顺序无关,在调用函数的时候需要指明参数名
//没有赋值的参数值为null
funX(a, {b, c:3, d:4, e})
{
print('$a $b $c $d $e');
}
/////
///在位置可选参数的函数中,大括号内的参数可以指定0个
////或多个在调用的时候参数值会依次按顺序赋值
funY(a, [b, c=3, d=4, e])
{
print('$a $b $c $d $e');
}
打印结果:
1 3 3 5 null
1 3 5 4 null
这一部分不打算写太多, 因为Dart里面大多内容和Java C++ 没什么区别 , 只总结一下需要注意的地方
取整符号~/ 举个栗子
var num1 = 3.5;
var num2 = 1.5;
/// var num2 = 1.2;
print(num1 / num2);
print(num1 ~/ num2);
打印结果 :
2.3333333333333335
2
调整参数值之后 发现这符号的取整 和Java 中的Math.floor 向下取整 。 总觉得这东西怪怪的 有木有 ~~
当你要对一个单一的对象进行一系列的操作的时候
可以使用级联操作符 …
class Person {
String name;
String country;
void setCountry(String country){
this.country = country;
}
String toString() => 'Name:$name\nCountry:$country';
}
void main() {
Person p = new Person();
p ..name = 'Wang'
..setCountry('China');
print(p);
}
这里的… 就是对单一对象Person p 的操作 不再赘述
if(3>2){
print("a");
}else {
print("b");
}
打印结果 当然是 a ,
至于有的书上写的 if(1) ,我试了几次 编译报错
还有
if(!null){
print("a");
}else {
print("b");
}
这个编译不报错, 执行报错
Unhandled exception:
Failed assertion: boolean expression must not be null
算了 还是老老实实传bool值吧
当然还有三目运算符
int age = 60;
String status = age < 50 ? "AAAA" : "BBBB";
var sum =0;
for(int i = 1; i<=100; i++) {
sum +=i;
}
print(sum);
打印结果 5050
增强for 循环
如果迭代的对象是容器,那么可以使用forEach或者for-in
var collection = [0, 1, 2];//// 这里可以装anything , 例如上边我们装了 function
collection.forEach((x) =>print(x));//forEach的参数为Function
for(var x in collection) {
print(x);
}
还有 while 和do—while 没什么变化 就不再赘述了 。
这里的switch参数可以是 num 也可以是string ,基本用法和Java一样 就不再写了。
这里有一点要注意,如果分句的内容为空,想要fall-through(落空),可以省略break
如果分句的内容不为空,那么必须加break,否则抛出异常
var command = 'OPEN';
switch (command) {
case 'CLOSED':
print('CLOSED');
break;
case 'OPEN'://产生落空效果,执行下一分句
case 'NOW_OPEN':
print('OPEN');
break;
default:
print('Default');
}
如果你想要fall-through,case语句内容又不为空
而又不是按顺序落空,那么可以使用continue和标签
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
print('CLOSED');
continue nowClosed; // Continues executing at the nowClosed label.
case 'OPEN':
print('OPEN');
break;
nowClosed: // Runs for both CLOSED and NOW_CLOSED. 直接从'closed’跳转至前边的标记符 nowClosed 继续执行 。
case 'NOW_CLOSED':
print('NOW_CLOSED');
break;
}
在Java中我们知道所有抛出的异常根据异常的类型不同,但同时是Exception的子类, 当然Exception 继承自 Throwable 。
在Dart中可以抛出非空对象, 也就是说 不仅仅是 Exception 或 Error
throw new ExpectException('分母不能为0!');
throw '分母不能为0!';
你可以抛出多个类型的Exception,但是由第一个catch到的分句来处理
如果catch分句没有指定类型,那么它可以处理任何类型的异常
try {
throw 'This a Exception!';
} on Exception catch(e) {
print('Unknown exception: $e');
} catch(e) {
print('Unknown type: $e');
}
当然也有finally , 用法和Java没有什么区别。
try {
throw 'This a Exception!';
} catch(e) {//可以试试删除catch语句,只用try...finally是什么效果
print('Catch Exception: $e');
} finally {
print('Close');
}
当然 如果把catch 去掉会抛出 Unhandled exception:
Dart是一门使用类和单继承的面向对象语言
所有的对象都是类的实例,并且所有的类都是Object的子类
定义和java没什么区别 关键字 class
如果未显式定义构造函数,会默认一个空的构造函数 使用new关键字和构造函数来创建对象
class Point {
num x;
num y;
num z;
}
void main() {
var point = new Point();
print(point.hashCode);//未定义父类的时候,默认继承自Object
/// 打印结果 991467768 对象的hashCode
}
如果只是简单的参数传递,可以在构造函数的参数前加this关键字定义
或者参数后加 : 再赋值
class Point {
num x;
num y;
num z;
Point(this.x, this.y, z) { //第一个值传递给this.x,第二个值传递给this.y
this.z = z;
}
Point.fromeList(var list): //命名构造函数,格式为Class.name(var param)
x = list[0], y = list[1], z = list[2]{//使用冒号初始化变量
}
//当然,上面句你也可以简写为:
//Point.fromeList(var list):this(list[0], list[1], list[2]);
String toString() => 'x:$x y:$y z:$z';
}
void main() {
var p1 = new Point(1, 2, 3);
var p2 = new Point.fromeList([1, 2, 3]);
print(p1);//默认调用toString()函数
}
还可以创建一个不可变对象 定义编译时常量对象
需要在构造函数前加const 来个例子
void main(){
var immutablePoint = ImmutablePoint.origin;
var immutablePoint2 = new ImmutablePoint(0, 0);
print(immutablePoint.hashCode);
print(immutablePoint2.hashCode);
print(immutablePoint.toString());
}
class Point {
num x;
num y;
num z;
}
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
// 常量构造函数
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
// 创建一个常量对象不能用new,要用const
}
打印结果:
903836066
177869238
Instance of 'ImmutablePoint'
在Java 中为了保证字段的属性的私有 , Getter和Setter是用来读写一个对象属性的方法, 如果不声明如果具体某个字段的属性是私有的则对外不可访问。
在Dart中 每个字段都对应一个隐式的Getter和Setter
class Rectangle {
num left;
num top;
num width;
num height;
Rectangle(this.left, this.top, this.width, this.height);
// right 和 bottom 两个属性的计算方法
num get right{
return left + width;
}
//// 这个是单独声明的一个 getright()方法 不要和上边的get right 混淆了
num getright(){
return this.left+ this.width;
}
set right(num value) {
left = value - width;
}
// 这是简写不要奇怪 因为没有参数 所有 get bottom后边的() 也省掉了
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main(){
var rect = new Rectangle(3, 4, 20, 15);
print(rect.left);
rect.right = 12;
print(rect.left);
print(rect.getright());
}
上边的语法可以看出 如果像Java中的getXxx() , setXxx() 的话是访问不到的 ,其访问方式类似于Java中字段访问修饰符为public的时候 。
至于孰优孰劣 看不出来 各有所爱吧, 我还是喜欢Java中的访问方式。 至少可以使用插件自动生成 ~~~
打印结果:
3
-8
12
关键字和Java一样 , implements 和 extends
在Dart中类和接口是统一的,类就是接口
如果想重写部分功能,那么可以继承一个类
如果想实现某些功能,那么也可以实现一个类
使用abstract关键字来定义抽象类,并且抽象类不能被实例化
抽象方法不需要关键字,直接以分号 ; 结束即可
abstract class Person { //此时abstract关键字可加可不加,如果加上的话Person不能被实例化
String greet(who); //函数可以没有实现语句,名曰隐式接口,前面不用加 abstract 关键字
}
当使用 implements的时候,子类Student无法访问父类Person的参数
所以变量name放到了Student
如果生成了一个Student的实例,调用了某函数,那么Student中必须有那个函数的实现语句,无论Person是否实现
class Student implements Person {
String name;
Student(this.name);
String greet(who) => 'Student: I am $name!';
}
class Teacher implements Person {
String name;
Teacher(this.name);
String greet(who) => 'Teacher: I am $name!';
}
void main(){
Person student = new Student('Jack');
Person teacher = new Teacher('Rose');
print( student.greet('Chen'));
print(teacher.greet('Chen'));
}
打印结果:
Student: I am Jack!
Teacher: I am Rose!
是不是和Java很像。
可以试一下把Person前的abstract关键字去掉
然后添加构造函数和方法,对Student和Teacher没有影响
但那样的话Student和Teacher的name变量仍然不能省略,那样感觉就冗余了
根据您自己的情况修改吧!
总之,implements可以是实现多个类的多个函数
相比之下继承就好理解得多
Dart中是单继承,子类可以继承父类的非私有变量
当重写某个函数的时候
不用考虑abstract或者接口或者函数实现
直接重写,比如greet()函数
而且仍然保持多态性
class Person {
String name;
//// 添加构造函数
Person(this.name);
String greet(who) => 'I am $name!';
}
class Student extends Person {
Student(String name):super(name);
String greet(who) => 'Student: I am $name!';
}
class Teacher extends Person {
Teacher(String name):super(name); //// 调用父类构造函数
String greet(who) => 'Teacher: I am $name!';
}
void main() {
Person p1 = new Student('Wang');
Person p2 = new Teacher('Lee');
print(p1.greet('Chen'));
print(p2.greet('Chen'));
}
打印结果:
Student: I am Samuel!
Teacher: I am notes!
再来个例子
abstract class Shape {
// 定义了一个 Shape 类/接口
num perimeter();
// 这是一个抽象方法,不需要abstract关键字,是隐式接口的一部分。
}
class Rectangle implements Shape {
// Rectangle 实现了 Shape 接口
final num height, width;
Rectangle(num this.height, num this.width);
// 紧凑的构造函数语法
num perimeter() => 2 * height + 2 * width;
// 实现了 Shape 接口要求的 perimeter 方法
}
class Square extends Rectangle {
// Square 继承 Rectangle
Square(num size) : super(size, size);
// 调用超类的构造函数
}
void main(){
Shape rectangle = new Rectangle(100,200);
Shape square = new Square(100);
print(rectangle.perimeter()); /// 600
print(square.perimeter()); /// 400
}
有时候为了返回一个之前已经创建的缓存对象,原始的构造方法已经不能满足要求
于是工厂模式就应运而生, 这里工厂模式不作为我们的研究对象。
在Dart中我们可以使用工厂模式来定义构造函数
并且用关键字new来获取之前已经创建的缓存对象
class Logger {
final String name;
bool mute = false;
// 变量前加下划线表示私有属性
static final Map _cache = {};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(name + ": "+ msg);
}
}
}
void main(){
var logger = new Logger('UI');
logger.log('Button clicked');
logger.log('Button clicked2');
}
软件开发中容器的重要性就不多说了,下边开始学习。
列表,也就是常说的数组 在Java中我们常用 ArrayList, LinkedList
在Dart中
常见的添加、索引、删除等方法如下
// 使用List的构造函数,也可以添加int参数,表示List固定长度
var vegetables = new List();
// 或者简单的用List来赋值
var fruits = ['apples', 'oranges'];
// 添加元素
fruits.add('kiwis');
// 添加多个元素
fruits.addAll(['grapes', 'bananas']);
// 获取List的长度
print(fruits.length == 5);
// 利用索引获取元素
print(fruits[0] == 'apples');
// 查找某个元素的索引号 , 这个索引获取的方法和Java一样
print(fruits.indexOf('apples') == 0);
// 利用索引号删除某个元素
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
print(fruits.length); /// 4
// 删除所有的元素
fruits.clear();
print(fruits.length) /// 0
打印结果:
true
true
true
4
0
在Java中我们可以使用Collections 和Arrays 数组工具集合类的方法来操作。
在Dart中可以使用sort()对List的元素进行排序
并且必须制定比较两个对象的函数,函数的返回值中
return <0 表示小于,0表示相同,>0表示大于
var fruits = ['bananas', 'apples', 'oranges'];
//////// 这个方法和Java中的Comparable 的使用方法很相似 , 添加了排序规则方法。
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');
List以及其他的容器可以指定参数类型
// 下面的List只能包含String
var fruits = new List();
fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);
fruits.add(5); // 错误: 在checked mode中会抛出异常 这一点和 Java中的数组是一样的。
集合在Dart中无序,并且每个元素具有唯一性
因为它是无序的,因此你不能像List那样用索引来访问元素
var ingredients = new Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);
// 添加已存在的元素无效
ingredients.add('gold');
assert(ingredients.length == 3);
// 删除元素
ingredients.remove('gold');
assert(ingredients.length == 2);
// 检查在Set中是否包含某个元素
assert(ingredients.contains('titanium'));
// 检查在Set中是否包含多个元素
assert(ingredients.containsAll(['titanium', 'xenon']));
ingredients.addAll(['gold', 'titanium', 'xenon']);
// 获取两个集合的交集
var nobleGases = new Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));
// 检查一个Set是否是另一个Set的子集
var allElements = ['hydrogen', 'helium', 'lithium', 'beryllium',
'gold', 'titanium', 'xenon'];
assert(ingredients.isSubsetOf(allElements));
映射,也有人称之为字典
Map是一个无序的键值对容器
// Map的声明
var hawaiianBeaches = {
'oahu' : ['waikiki', 'kailua', 'waimanalo'],
'big island' : ['wailea bay', 'pololu beach'],
'kauai' : ['hanalei', 'poipu']
};
var searchTerms = new Map();
// 指定键值对的参数类型
var nobleGases = new Map();
// Map的赋值,中括号中是Key,这里可不是数组
nobleGase[54] = 'dart';
//Map中的键值对是唯一的
//同Set不同,第二次输入的Key如果存在,Value会覆盖之前的数据
nobleGases[54] = 'xenon';
assert(nobleGases[54] == 'xenon');
// 检索Map是否含有某Key
assert(nobleGases.containsKey(54));
//删除某个键值对
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
你可以用getKeys和getValues获取所有Key或者所有Values的迭代器
var keys = hawaiianBeaches.getKeys();
assert(keys.length == 3);
assert(new Set.from(keys).contains('oahu'));
var values = hawaiianBeaches.getValues();
assert(values.length == 3);
//迭代器中有一个有意思的函数any,用来检测迭代器中的数据
//当其中一个元素运行函数时return true,那么any的返回值就为true,否则为false
//与之相对的是函数every,要所有函数运行函数return true,那么every返回true
assert(values.any((v) => v.indexOf('waikiki') != -1));
// 你可以用foreach来遍历数据,但记住它是无序的
hawaiianBeaches.forEach((k,v) {
print('I want to visit $k and swim at $v');
});
//检索是否包含某个Key或Value
assert(hawaiianBeaches.containsKey('oahu'));
assert(!hawaiianBeaches.containsKey('florida'));
//V putIfAbsent(K key, Function V ifAbsent())函数,通过Key来查找Value
//当某个Key不存在的时候,会执行第二参数的Function来添加Value
var teamAssignments = {};
teamAssignments.putIfAbsent('Catcher', () => 'Catcher'.length);
assert(teamAssignments['Catcher'] != null);
这个操作和Java中的Map操作很相似 没什么好说的,多使用就是了。
Set、List、Map都继承自Iterable,是可以迭代的
//如果迭代的对象是容器,那么可以使用forEach或者for-in
var collection = [0, 1, 2];
collection.forEach((x) => print(x));//forEach的参数为Function
for(var x in collection) {
print(x);
}
打印结果:
0
1
2
0
1
2
在Dart中,你可以导入一个库来使用它所提供的功能
库的使用可以使代码的重用性得到提高,并且可以更好的组合代码
当然,你也可以自己定义一个库
Dart中任何文件都是一个库,即使你没有用关键字library声明
关键词import 和 Java中的import 一样
import语句用来导入一个库
后面跟一个字符串形式的Uri来指定表示要引用的库
//dart:前缀表示Dart的标准库,如dart:io、dart:html
import 'dart:math';
//当然,你也可以用相对路径或绝对路径的dart文件来引用
import 'lib/student/student.dart';
//Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
import 'package:args/args.dart';
当各个库有命名冲突的时候,可以使用as关键字来使用命名空间
import 'lib/student/student.dart' as Stu;
Stu.Student s = new Stu.Student();
show关键字可以显示某个成员(屏蔽其他)
hide关键字可以隐藏某个成员(显示其他)
import 'lib/student/student.dart' show Student, Person;
import 'lib/student/student.dart' hide Person;
这种情况很少见,但是碰上了我们也是要知道如何解决的。
library定义这个库的名字
但库的名字并不影响导入,因为import语句用的是字符串Uri
library person;
为了维护一个库,我们可以把各个功能放到各个dart文件中
但part of所在文件不能包括import、library等关键字
可以包含在part关键字所在文件中
建议避免使用part和part of语句,因为那样会使代码很难阅读、修改,可以多用library
part加字符串类型的Uri类似include,表示包含某个文件
part of加库名表示该文件属于那个库
// math.dart文件开头
library math;
part 'point.dart';
part 'random.dart';
// point.dart文件开头
part of math;
// random.dart文件开头
part of math;
看起来挺懵逼的, 算了明白知道有这东西就行了。 尽量不要使用。
你可以使用export关键字导出一个更大的库
library math;
export 'random.dart';
export 'point.dart';
也可以导出部分组合成一个新库
library math;
export 'random.dart' show Random;
export 'point.dart' hide Sin;
本人觉得这玩意儿很少使用, 知道即可,不懂再查。
这中间有个pubspec.yaml 的文件, Pub来管理自己的库
以便引用简洁,并且可以在yaml文件中很好的控制版本信息
environment:
sdk: '>=1.20.1 <2.0.0'
#dependencies:
# path: ^1.4.1
http: ^0.12.0+2
#dev_dependencies:
# test: ^0.12.0
# http: ^0.12.0+2
先这么着吧, Dart中一些简单基础语法先介绍到这里,后续会持续更新一些Dart的功能块化学习文章。 由于笔者能力有限文中有错在所难免,如有不对的地方,欢迎批评指正。
a. CNDartLang社区
b. Dart language Dev
c. Flutter官网