偷师移动,悄悄跟学一版

Dart语言基础

基本语法

入口函数

1
2
3
void main(){
    // Your code here...
}

输出语句

1
2
3
void main(){
    print("Hello World!"); // 这会在控制台打印 Hello World!
}

print()有一个缺点,当输出内容过长时,它有一个最大长度限制,超出限制的部分将会被直接丢弃。我们在实际开发中经常会遇到这种情况,比如当我们需要查看后端返回来的json数据时,最直接的方式就是将其打印到控制台,这个数据若是过长,那么将无法看到它的所有内容!

这时我们可以使用debugPrint()函数,这个函数的用法和print()一模一样,但它不会截断任何部分,而是原样输出。我们在Flutter的开发中也多用这个函数,而不是print()

字符串和数字相乘

这个有意思

1
print("学线" * 2); // 输出: 学线学线

字符串只能和非负整数相乘(乘0会变成空字符串"",注意不是null,表示重复当前字符串多少次。

模板字符串

在字符串中嵌入变量,使用${}包起来

1
2
String str1 = "移动";
print("我爱 ${str1}"); // 输出: 我爱 移动

数据类型

数组类型(List)

1
2
3
4
5
List lis = []; // 创建一个空数组, 常用
List lis1 = [1,2,3]; // 创建一个数组并且初始化其中的值

// 使用泛型创建数组, 限定数组中只能存入String类型的数据
List<String> list2 = ["ok", "mobile", "hello"];

问题来了,如果我不使用泛型,这个数组是不是什么类型都能存

添加元素

  • add() 在数组的末尾添加元素
  • insert(index,ele) 在数组的任何位置添加元素,index是新元素的索引
1
2
3
4
5
6
7
8
9
10
List lis = ["ok", "mobile", "Hello"];

lis.add("world!");
// lis == ["ok", "mobile", "Hello", "world!"]

lis.insert(0,"fine!");
// lis == ["fine!", "ok", "mobile", "Hello", "world!"]

lis.insert(1,"develop");
// lis == ["fine!", "develop" , "ok", "mobile", "Hello", "world!"]

删除元素

  • remove(ele) :接受一个元素作为参数,删除与传入元素相同的第一个元素,返回布尔值 。
  • removeAt(index) :接受一个索引作为参数,删除指定索引处的元素,将之后的元素向前移动一位。

数组的length属性可以表示数组的长度

Map类型

键值对存储

1
2
3
4
5
var fruits = {
'苹果': '红色',
'香蕉': '黄色',
'蓝莓': '蓝色'
};

dart和js差不多……都对于数据类型不算太严格的样子

访问键对应的值

1
print(fruits['苹果']);  // 输出: 红色

添加或更新值

1
2
fruits['葡萄'] = '紫色';  // 添加新的键值对
fruits['苹果'] = '绿色'; // 更新已有键的值

删除键值对

1
fruits.remove('香蕉');  // 删除“香蕉”

查看大小,可以使用length属性

1
print(fruits.length);  // 输出: 3

运算符

  1. 算术运算符

    除法(返回小数) / 如 10/2 = 5.0

    整数除法(舍去小数)~/ 如 10 ~/3 = 3

  2. 类型判定运算符

    用来检查变量的类型。它们帮助我们判断某个变量是否属于某种数据类型。

    运算符 作用 示例 结果
    is 是否属于某种类型 a is int truefalse
    is! 是否不属于某种类型 a is! String truefalse
    1
    2
    3
    4
    5
    6
    void main() {
    int a = 10;

    print(a is int); // 输出: true
    print(a is! String); // 输出: true
    }

变量声明

  • 直接类型声明

dart也可以直接用类型来声明变量,并且在实际开发中优先选择这种方式来声明变量

1
2
3
4
5
int? a; // 声明一个整型变量
double? b; // 声明一个浮点变量
int c = 10; // 声明一个整型变量并赋予初始值10
final int m = 20; // 声明一个常量,值为20
User u = User(); //使用构造函数声明一个自定义类型的变量
  • var关键字

    使用var关键字声明的变量算是”半个”动态类型,因为这个变量的类型将会在变量第一次被赋值后根据被赋值的值的类型来确定,例如:

    1
    2
    var my = "Hello"; // 此时my变量的类型被确定为String类型
    my = 123 // 报错:my已经是String类型,不能再被赋值为数字
  • dynamicObject

    Object是所有类的基类,可以用该类型的变量存放任何类型的值。

    dynamic关键字声明的变量也能接受任意类型的值

    dynamicObject没有var那样的限制,在赋值一种类型的值过后还能继续赋值其他类型的值,是真正意义上的动态类型

    这样的话,他们俩有什么区别呢

    由于Object类型的变量可以接受任意类型的值的原理是它是所有类型的父类,正因为如此,它不能调用其子类的方法,否则就会报错,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    dynamic a;
    Object b = "";
    main() {
    a = "";
    printLengths();
    }

    printLengths() {
    // 正常
    print(a.length);
    // 报错 The getter 'length' is not defined for the class 'Object'
    print(b.length);
    }

    所以在实际的开发中会用到动态类型的地方,我们一般都是使用dynamic

  • finalconst

    两者区别在于:const 变量是一个编译时常量(编译时直接替换为常量值),final变量在第一次使用时被初始化

    final或者const修饰的变量,变量类型可以省略

    1
    2
    3
    4
    5
    //可以省略String这个类型声明
    final str = "hi world";
    //final String str = "hi world";
    const str1 = "hi world";
    //const String str1 = "hi world";

空安全

如果定义一个数字,并且在它初始化之前使用它,如果没有检查机制,则不会报错;

1
2
3
4
test() {
int i;
print(i*8);
}

在 Dart 引入空安全之前,上面代码在执行前不会报错,但会触发一个运行时错误,原因是 i 的值为 null 。但现在有了空安全,则定义变量时我们可以指定变量是可空还是不可空。

1
2
3
4
5
6
7
int i = 8; //默认为不可空,必须在定义时初始化。
int? j; // 定义为可空类型,对于可空变量,我们在使用前必须判空。

// 如果我们预期变量不能为空,但在定义时不能确定其初始值,则可以加上late关键字,
// 表示会稍后初始化,但是在正式使用它之前必须得保证初始化过了,否则会报错
late int k;
k=9;

如果一个变量我们定义为可空类型,在某些情况下即使我们给它赋值过了,但是预处理器仍然有可能识别不出,这时我们就要显式(通过在变量后面加一个”!“符号)告诉预处理器它已经不是null了,比如:

1
2
3
4
5
6
7
8
9
10
11
12
class Test{
int? i;
Function? fun;
say(){
if(i!=null) {
print(i! * 8); //因为已经判过空,所以能走到这 i 必不为null,如果没有显式声明,则 IDE 会报错
}
if(fun!=null){
fun!(); // 同上
}
}
}

若去掉!,报错信息如下

1
2
The operator '*' can't be unconditionally invoked because the receiver can be 'null'.
Try adding a null check to the target

函数

Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断

对于只包含一个表达式的函数,可以使用简写语法(和js很像)

1
bool isNoble (int atomicNumber)=> true ;   

函数作为变量

1
2
3
4
var say = (str){
print(str);
};
say("hi world");

函数作为参数传递

有点意思

1
2
3
4
5
6
7
8
9
10
11
//定义函数execute,它的参数类型为函数
void execute(var callback) {
callback(); //执行传入的函数
}

void output(){
    print("Hello World!");
}

//调用execute,将箭头函数作为参数传递
execute(output)

将作为参数的函数称为回调函数,其含义在于,这个被传入的函数不是由你自己去调用,而是在其他地方被另一个函数调用,例如上方代码的第三行就调用了从外部传入进来的函数

函数参数

位置参数有两种,一种是位置参数,跟其他语言的传参相当,还有一种可选的位置参数

可选的位置参数:包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面

1
2
3
4
5
6
7
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}

可以不带这个参数调用函数,也可以带这个参数

可选的命名参数

定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。例如

1
2
3
4
//设置[bold]和[hidden]标志
void enableFlags({bool bold, bool hidden}) {
// ...
}

调用函数时,可以使用指定命名参数。如:paramName: value

1
enableFlags(bold: true, hidden: false);

注意:不能同时使用可选的位置参数和可选的命名参数

匿名函数

顾名思义,匿名函数就是没有名字的函数

  • 大括号法
1
2
3
4
final f = () {
    // Your function's code
}; // 正常函数的定义被视作代码块所以不写分号,但是这里一定要写分号,因为这被视作一行语句
f(); // 调用函数
  • 箭头法

    有些函数是有返回值的,对于只有一行代码,且这行代码是返回语句的函数来说,可以使用箭头函数法来书写;与大括号法没什么区别,只是更简洁了

    1
    2
    3
    4
    5
    6
    7
    final f = () => "Hello World!"; // 同样的,应该写上分号
    f(); // 调用函数

    // 等效的大括号法书写
    final f = () {
        return "Hello World!"; // 注意这里也要写分号哦,因为这也是一个语句
    };

匿名函数可以直接作为参数传入另一个函数中,由另一个函数去调用

1
2
3
4
5
6
7
8
9
10
11
// 一个普通的函数
void proxyInvoke(var f){
    f();
}


void main(){
    proxyInvoke(
        (){print("Hello World!");}
    ) // 输出: Hello World!
}