Hello, World!

Dart、Swift

字数统计: 4.8k阅读时长: 20 min
2020/01/23 Share

在学习Dart语言的过程中,发现很多语法点和Swift有异曲同工之妙,所以就顺便记录下,已便更好的理解掌握。

打印、注释、分号

打印

Dart和Swift都用print函数,输出内容到console面板上。

注释

相同点
单行注释: 以//开始,所有在//改行结尾之间的内容被编译器忽略。
多行注释: 以/*开始,以*/结尾,所有在/**/之间的内容被编译器忽略,注意多行注释可以嵌套
文档注释: 以///或者/**开始。在调用时候,有提示信息。

不同点
Dart中的文档注释,除非用中括号括起来,否则Dart编译器会忽略所有文本。使用中括号可以引用类、方法、字段、顶级变量、函数、和参数。括号中的符号会在已记录的程序元素的词法域中进行解析。

分号

Dart强制要求在每条语句的结尾处使用分号;,而Swift则可加可不加,但是如果你在同一行内写多条独立的语句,就必须要用分号。

声明常量和变量

相同点
1,都使用var来声明变量,都具备类型推断功能。

1
2
3
var dartStr = "hello world" // 类型推断为String

var swiftStr = "hello world" // 类型推断为String

也都可以通过添加类型注解来显示指定类型。

1
2
3
String dartStr = "hello world"

var swiftStr: String = "hello world"

不同点

1,常量声明方式。

如果不打算更改变量值,dart中可以使用final或者const。一个final变量只能被赋值一次,而const变量是编译时常量,定义时必须赋值。而Swift中使用let,一个let变量只能被赋值一次,最迟在构造器。

1
2
3
4
5
const maximumNumber = 10 // 定义时必须赋值

final maximumNumber = 10 // 只能被赋值一次,最迟在构造器赋值

let maximumNumber = 10 // 只能被赋值一次,最迟在构造器赋值

final和const有什么区别呢?
const在赋值时,赋值的内容必须是在编译期间就确定下来的。
final在赋值时,可以动态获取, 比如赋值一个函数。

Dart中使用dynamic或Object来定义的变量,其类型相当于Swift中的Any。

内建类型

Swift 包含了 Int表示整型值; DoubleFloat表示浮点型值;Bool是布尔型值;String是文本型数据。 还提供了三个基本的集合类型,ArraySetDictionary

Dart包含了 int表示整型值; double表示浮点型值;bool是布尔型值;String是文本型数据。 还提供了三个基本的集合类型,ListSetMap

SwiftDart 中不能判断非0即真,或者非空即真。

默认值

未初始化的变量默认值是null。即使变量是数字类型默认值也是 null,因为在 Dart 中一切都是对象,数字类型也不例外。

控制流

你可以通过下面任意一种方式来控制 Dart 程序流程:

1.条件语句

  • if and else
  • switch and case

2.循环

  • for loops
  • while and do-while loops

3.控制转移语句

  • break and continue
  • return and throw
  • assert:该语句只在开发环境中有效,在生产环境是无效的;

而Swift也提供了多种流程控制结构:

1.条件语句

  • if and else
  • switch and case

2.循环

  • for loops
  • while and repeat-while loops

3.控制转移语句

  • break and continue
  • return and throw
  • fallthrough

总结: 1,在循环语句中,Dartdo-whileSwiftrepeat-while。2,在Swift里,switch语句不会从上一个case分支跳转到下一个case分支中,fallthrough可以实现C风格的贯穿的特性。

运算符

前提:只介绍相对其他语言比较特殊的运算符。
1.除法、整除、取模运算符

1
2
3
4
var num = 7;
print(num / 3); // 除法操作, 结果2.3333..
print(num ~/ 3); // 整除操作, 结果2。
print(num % 3); // 取模操作, 结果1。

2.赋值运算符

dart中,有一个很多语言都不具备的赋值运算符??=

  • 当变量有值时,使用自己的值,当变量为null时,使用后面的内容进行赋值。
1
2
3
4
5
6
main(List<String> args) {
// var name = 'kobe';
var name = null;
name ??= 'james';
print(name); // 当name初始化为kobe时,结果为kobe,当初始化为null时,赋值了james。
}

swift中,如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:

1
2
let (x, y) = (1, 2)
// 现在 x 等于 1,y 等于 2

3.条件运算符

dart中,有一个比较特殊的条件运算符:expr1 ?? expr2

  • 如果expr1是null,则返回expr2的结果,如果expr1不是null,直接使用expr1的结果。
1
2
3
4
// var temp = 'why';
var temp = null;
var name = temp ?? 'kobe';
print(name);

swift中,没有条件运算符,只能通过三目运算符实现。

4.级联语法

dart中,通过级联语法,可以对一个对象进行连续的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person {
String name;

void run() {
print("${name} is running");
}

void eat() {
print("${name} is eating");
}

void swim() {
print("${name} is swimming");
}
}

main(List<String> args) {
final p1 = Person();
p1.name = 'why';
p1.run();
p1.eat();
p1.swim();

final p2 = Person()
..name = "why"
..run()
..eat()
..swim();
}

函数

Dart是一门真正面向对象的语言,甚至其中的函数也是对象,并且有它的类型Function。且函数是一等对象可以被赋值给变量、作为参数传递或者作为函数的返回值。

函数的定义方式:

1
2
3
int sum(num num1, num num2) {
return num1 + num2;
}

Effective Dart建议对公共的API使用类型注解,但是如果我们省略掉了类型,依然是可以正常工作的。

1
2
3
sum(num1, num2) {
return num1 + num2;
}

箭头语法与隐式返回

如果函数中只有一句表达式,可以使用简写语法:
Dart中箭头语法:=> expr{ return expr; }的简写。

1
String greeting(String person) => "Hello, " + person + "!";

在箭头(=>)和分号(;)之间只能使用一个表达式,不能是语句。

Swift中隐式返回:如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。

1
2
3
func greeting(person: String) -> String {
"Hello, " + person + "!"
}

可选参数

Dart中,定义函数如下:

1
2
3
4
5
String magicBox(int kind, String name) {
String partFront = (kind == 1) ? "I'm a Dog!" : "I'm a Panda!";
String partBhand = (name == null) ? '' : "My name is $name";
return partFront + partBhand;
}

在调用时必须传入实参,称为必选参数。

也存在可选参数的方式定义函数。

  • 命名可选参数。
1
2
3
4
5
String magicBox(int kind, {String name}) {
String partFront = (kind == 1) ? "I'm a Dog!" : "I'm a Panda!";
String partBhand = (name == null) ? '' : "My name is $name";
return partFront + partBhand;
}

name被设置为可选参数,所以在调用该方法时,实参可传可不传。
可以使用=来定义可选参数的默认值。默认值只能是编译时常量。如果没有提供默认值,则默认值为null

1
2
3
4
5
String magicBox({@required int kind, String name}) {
String partFront = (kind == 1) ? "I'm a Dog!" : "I'm a Panda!";
String partBhand = (name == null) ? '' : "My name is $name";
return partFront + partBhand;
}

这种可选参数的写法与上面写法的效果是一模一样的。推荐这种写法。

  • 位置可选参数:
1
2
3
4
5
String magicBox(int kind, [String name]) {
String partFront = (kind == 1) ? "I'm a Dog!" : "I'm a Panda!";
String partBhand = (name == null) ? '' : "My name is $name";
return partFront + partBhand;
}

Swift中,没有可选参数的概念,但是可以通过默认参数值的方式来达到可选参数的效果。

1
2
3
4
5
func magicBox(kind: Int, name: String="") -> String {
let partFront = (kind == 1) ? "I'm a Dog!" : "I'm a Panda!";
let partBhand = (name == "") ? name : "My name is \(name)";
return partFront + partBhand;
}

将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。

顺便介绍下,Swift中每个函数参数都有一个参数标签(argument label)以及一个参数名称(parameter name)。默认情况下,函数参数使用参数名称来作为它们的参数标签。如果你不希望为某个参数添加一个标签,可以使用一个下划线(_)来代替一个明确的参数标签。

1
func someFunction(argumentLabel parameterName: Int) {}

someFunction函数的参数标签是argumentLabel在函数外部调用的时候使用,参数名称是parameterName 在函数体内使用。

1.二者都可以给参数设置默认值。2.在Dart中,支持可选参数,而Swift中不支持,但是可以通过设置默认值方式实现可选参数功能。

Dart中,每个对象都是一个类的实例,所有的类都继承于Object

构造方法

1.DartSwift中,类没有明确指定构造方法时,将默认拥有一个无参的构造方法。我们也可以根据自己的需求,定义构造方法,当有了自己的构造方法时,默认的构造方法将会失效,不能使用。

2.Dart不支持函数的重载(名称相同, 参数不同),而Swift支持。

3.在实现构造方法时,通常做的事情就是通过参数给属性赋值,为了简化这一过程,Dart提供了一种更加简洁的语法糖形式。

1
2
3
4
5
6
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 等同于
Person(this.name, this.age);

4.命名构造方法

在开发中,我们确实希望实现更多的构造方法,怎么办呢?
因为不支持方法的重载,所以我们使用命名构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Person {
String name;
int age;

Person() {
name = '';
age = 0;
}
// 命名构造方法
Person.withArgments(String name, int age) {
this.name = name;
this.age = age;
}

// 命名构造方法
Person.fromMap(Map<String, Object> map) {
this.name = map['name'];
this.age = map['age'];
}

@override
String toString() {
return 'name=$name age=$age';
}
}

// 创建对象
var p1 = new Person();
print(p1);
var p2 = new Person.withArgments('why', 18);
print(p2);
var p3 = new Person.fromMap({'name': 'kobe', 'age': 30});
print(p3);

5.初始化列表

我们来重新定义一个类Point, 传入x/y,可以得到它们的距离distance:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Point {
final num x;
final num y;
final num distance;

// 错误写法
// Point(this.x, this.y) {
// distance = sqrt(x * x + y * y);
// }

// 正确的写法
Point(this.x, this.y) : distance = sqrt(x * x + y * y);
}

6.重定向构造方法

在一个构造函数中,去调用另外一个构造函数(注意:是在冒号后面使用this调用)

1
2
3
4
5
6
7
class Person {
String name;
int age;

Person(this.name, this.age);
Person.fromName(String name) : this(name, 0);
}

7.常量构造方法

在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法.
将构造方法前加const进行修饰,那么可以保证同一个参数,创建出来的对象是相同的

1
2
3
4
5
6
7
8
9
10
11
main(List<String> args) {
var p1 = const Person('why');
var p2 = const Person('why');
print(identical(p1, p2)); // true
}

class Person {
final String name;

const Person(this.name);
}

注意点:
一:拥有常量构造方法的类中,所有的成员变量必须是final修饰的。
二: 为了可以通过常量构造方法,创建出相同的对象,不再使用new关键字,而是使用const关键字。如果是将结果赋值给const修饰的标识符时,const可以省略。

8.工厂构造方法

Dart提供了factory关键字, 用于通过工厂去获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
main(List<String> args) {
var p1 = Person('why');
var p2 = Person('why');
print(identical(p1, p2)); // true
}

class Person {
String name;

static final Map<String, Person> _cache = <String, Person>{};

factory Person(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final p = Person._internal(name);
_cache[name] = p;
return p;
}
}

Person._internal(this.name);
}

访问器与设置器

Dart中使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main(List<String> args) {
final p = Person();
p.setName = "kobe"; // 设置器赋值
print(p.getName); // 访问器获取值
}

// 所有的类都继承自Object,即使没有显示声明。
class Person {
var name;

// 设置器
set setName(String name) {
this.name = name;
}

// 访问器
get getName {
return this.name;
}
}

继承

面向对象的三大特性之一就是继承,不仅提高了代码的复用性,也是多态的前提。

Dart中,父类的所有成员变量和方法都会被继承,但是构造方法除外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
main(List<String> args) {
final p = Person();
p.name = "kobe";
p.run(); // 打印kobe is runing...
}

class Animal {
var name;
void run() {
print("$name is runing...");
}
}

// 所有的类都继承自Object,即使没有显示声明。
class Person extends Animal {}

子类可以拥有自己的成员,并且可以对父类的方法进行重写:

1
2
3
4
5
6
7
8
9
// 所有的类都继承自Object,即使没有显示声明。
class Person extends Animal {
var gender;

@override
void run() {
print("$name is $gender, he is runing...");
}
}

子类的构造方法在执行前,将隐式调用父类的无参默认构造方法,如果父类没有无参默认构造方法,则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
main(List<String> args) {
final p = Person("man", "kobe");
p.run();
}

class Animal {
var name;

Animal(this.name);

void run() {
print("$name is runing...");
}
}

// 所有的类都继承自Object,即使没有显示声明。
class Person extends Animal {
var gender;

Person(this.gender, String name) : super(name);

@override
void run() {
print("$name is $gender, he is runing...");
}
}

抽象类

约束子类共同的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 抽象类Shape
abstract class Shape {
double getArea();
void shapeName() {}
}

class Circle extends Shape {
double r;

Circle(this.r);

@override
double getArea() {
return pi * r * r;
}
}

class Reactangle extends Shape {
double w;
double h;

Reactangle(this.w, this.h);

@override
double getArea() {
return w * h;
}
}

1.抽象类中有声明没有实现的方法是抽象方法。
2.抽象类中的抽象方法必须被子类实现,抽象类中已经被实现方法,可以不被子类重写。
3.抽象类只能通过工厂构造器实例化。

隐式接口

Dart中,接口比较特殊,没有一个专门的关键字来声明接口。
默认情况下,定义的每个类都相当于也声明了一个接口,可以由其他的类来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Run {
void run() {}
}

abstract class Fly {
void fly() {}
}

class Bird implements Run, Fly {
@override
void fly() {
// TODO: implement fly
}

@override
void run() {
// TODO: implement run
}
}

当类做接口使用时, 那么实现这个接口的类, 必须实现这个接口中所有方法。

Mixin混入

Dart中,通过implements实现某个类时,类中所有的方法都必须被重新实现,无论这个类原来是否已经实现过该方法。

但是某些情况下,一个类可能希望直接复用之前类的原有实现方案,Dart提供了Mixin混入的方案。

  • 除了可以通过class定义类之外,mixin关键字也可以定义一个类。
  • mixin定义的类用于被其他类混入使用,通过with关键字来进行混入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
main(List<String> args) {
final b = Bird();
b.fly();
b.run();
}

mixin Run {
void run() {
print('run');
}
}

mixin Fly {
void fly() {}
}

class Bird with Run, Fly {
@override
void fly() {
print('fly');
}
}

枚举类型

枚举类型中有两个比较常见的属性:
index: 用于表示每个枚举常量的索引,从0开始。
values: 包含所有枚举值的List。

1
2
3
4
5
6
7
8
9
10
11
12
13
main(List<String> args) {
print(Colors.red.index);
print(Colors.green.index);
print(Colors.blue.index);

print(Colors.values);
}

enum Colors {
red,
green,
blue
}

泛型

库的使用

在Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明。

导入

import语句用来导入一个库,后面跟一个字符串形式的Uri来指定要引用的库,语法如下:

1
import '库所在的uri';

1,常见的库URI有三种不同的形式

  • dart标准库
1
2
3
// `dart:`前缀表示Dart的标准库。
import 'dart:io';
import 'dart:core'; (但是这个可以省略)
  • 自定义库
    使用相对路径导入。
1
2
// 可以用相对路径或绝对路径的dart文件来引用
import 'lib/student/student.dart';
  • 第三方库
    Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package。
1
2
// Pub包管理系统中有很多功能强大、实用的库 
import 'package:flutter/material.dart';

2,库文件中内容的显示和隐藏

如果希望只导入库中某些内容,或者刻意隐藏库里面某些内容,可以使用showhide关键字。

show关键字:可以显示某个成员(屏蔽其他)
hide关键字:可以隐藏某个成员(显示其他)

1
2
3
import 'lib/student/student.dart' show Student, Person;

import 'lib/student/student.dart' hide Person;

3,库中内容和当前文件中的名字冲突

当各个库有命名冲突的时候,可以使用as关键字来使用命名空间。

1
2
3
import 'lib/student/student.dart' as Stu;

Stu.Student s = new Stu.Student();

自定义

1,在定义库时,可以使用library关键字给库起一个名字。

1
library math;

2,part关键字

在开发中,如果一个库文件太大,将所有内容保存到一个文件是不太合理的,我们有可能希望将这个库进行拆分,这个时候就可以使用part关键字了。不过官方已经不建议使用这种方式了。

mathUtils.dart文件

1
2
3
4
5
part of "utils.dart";

int sum(int num1, int num2) {
return num1 + num2;
}

dateUtils.dart文件

1
2
3
4
5
part of "utils.dart";

String dateFormat(DateTime date) {
return "2020-12-12";
}

utils.dart文件

1
2
part "mathUtils.dart";
part "dateUtils.dart";

3,export关键字

官方不推荐使用part关键字,那如果库非常大,如何进行管理呢?

将每一个dart文件作为库文件,使用export关键字在某个库文件中单独导入。

mathUtils.dart文件

1
2
3
int sum(int num1, int num2) {
return num1 + num2;
}

dateUtils.dart文件

1
2
3
String dateFormat(DateTime date) {
return "2020-12-12";
}

utils.dart文件

1
2
3
4
library utils;

export "mathUtils.dart";
export "dateUtils.dart";
CATALOG
  1. 1. 打印、注释、分号
    1. 1.1. 打印
    2. 1.2. 注释
    3. 1.3. 分号
  2. 2. 声明常量和变量
  3. 3. 内建类型
  4. 4. 默认值
  5. 5. 控制流
  6. 6. 运算符
  7. 7. 函数
    1. 7.1. 箭头语法与隐式返回
    2. 7.2. 可选参数
  8. 8.
    1. 8.1. 构造方法
    2. 8.2. 访问器与设置器
    3. 8.3. 继承
    4. 8.4. 抽象类
    5. 8.5. 隐式接口
    6. 8.6. Mixin混入
    7. 8.7. 枚举类型
  9. 9. 泛型
  10. 10. 库的使用
    1. 10.1. 导入
    2. 10.2. 自定义