第五节: Dart 函数使用,声明函数/匿名函数/可选参数/作用域/闭包

Dart 语法学习目录

第一节: Dart 语法了解,认识变量,常量,数据类型
第二节: Dart 操作符(运算符)
第三节: Dart 中流程控制,条件判断以及循环语句
第四节: Dart 中常用集合 List/Map
第五节: Dart 函数使用,声明函数/匿名函数/可选参数/作用域/闭包
第六节: Dart 面向对象/构造函数/静态属性
第七节: Dart中抽象类abstract/接口implements/混入Mixin
第八节: Dart中泛型/泛型定义与使用
第九节: Dart 中的库/自定义库/内置库/第三方库


函数的了解:

  1. Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function
  2. 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。 也可以把 Dart 类的实例当做方法来调用。
  3. 方法都有返回值,当没有指定返回值的时候,函数返回null,即最后默认执行一句return null,可以省略不写。


1. 函数的定义

1.1 普通的声明函数

自定义函数方法的基本格式,

返回类型 函数名称(参数1,参数2,...){

​ 函数体

​ return 返回值;

}

示例:

int result(int a,int b){
    return a + b;
}


接下来好好看看不同的使用情况

1.1.1 定义全局函数,

通过前面的学习,我们都知道dart有一个默认的入口函数main, 如果在函数main之外定义的函数,或是直接在main函数中定义的函数都是全局函数

示例:

// 入口函数main之外定义的全局函数
int fn(){
    print("main外的全局函数");
    return 123;
}

void main(){
    // 入口函数main里的全局函数
    int fun(){
        print("main里的全局函数");
        return 123;
    }

    // 执行全局函数
    fn();    // main外的全局函数
    fun();   // main里的全局函数
}


1.1.2 局部函数

除了main入口函数外,在其他函数中定义的函数都是局部函数

示例:

void main(){
    // 定义函数
    // 1. fn 全局函数
    void fn(){
        // 2. 定义局部作用域函数
        // scope是局部函数,只能在fn函数中使用
        scope(){
            return '你好';
        }

        // 局部函数只能在其父作用域中调用
        print(scope());  // 你好
    }

    fn();
}


1.1.3 函数的返回值类型可以省略

定义方法的返回值类型可以省略

void notType(){
    print("这个函数没有返回值");
}

notTypeTwo(){
    print("这个函数没有返回值");
}

建议明确函数或方法的返回值类型,既便于修改,也方便阅读。


1.1.4 指定函数的返回值类型

函数的返回值类型可以指定为任意类型

示例:


// 1 定义返回整型的函数
int returnInt(){
    return 123;
}

// 2 定义返回字符串类型的函数
String returnString(){
    return '我是字符串';
}

// 3 定义返回布尔类型的函数
bool returnBoolean(){
    return true;
}

// 4 定义返回集合类型的函数
List returnList(){
    return ['a','b','c'];
}

// 5 定义返回Map类型的函数
Map returnMap(){
    return {"name":"wuwei","age":18};
}

// 入口函数
void main(){
  

    // 1. 调用返回整型的函数
    var num = returnInt();
    print(num);   // 123

    // 2. 调用返回字符串类型的函数
    print(returnString());   // 我是字符串
    
    // 2. 调用返回布尔类型的函数
    print(returnBoolean()); // true

    // 4. 调用返回集合类型的函数
    print(returnList());    // [a, b, c]

    // 5. 调用返回Map类型的函数
    print(returnMap());     // {name: wuwei, age: 18}

}


1.1.5 定义返回类型为void的情况

如果不指定返回类型,或是指定返回类型却没有返回值,函数默认的返回值为null

示例:

void main(){
    // 未定义返回类型
    fn () {
        print("未定义返回类型");
    }

    // 定义返回类型无返回值
    int fun(){
        print("定义返回类型无返回值");
    }


    // 查看未定义返回类型的定义类型无返回值的返回值
    var fn2 = fn();    // 未定义返回类型
    print(fn2);        // null

    var fun2 = fun();  // 定义返回类型无返回值
    print(fun2);       // null
}

通过示例了解,为定义返回类型,和明确返回类型,如果没有return关键字,默认返回null


但是有一种特殊情况, 指定函数的返回类型为void关键字

注意,如果指定函数的返回类型为关键字void,表示无返回值,为空,这个时候,不能使用函数执行完后赋值的变量,否则就会报错

示例:

void main(){
    // 通过关键字 void 指定函数返回值为空
    void fn () {
        print("返回值为void");
    }

    // 查看返回值
    var fn2 = fn();    
    print(fn2);      
    // 程序报错: Error: This expression has type 'void' and can't be used.
}

因此,如果知道函数返回类型,建议定义返回值类型.

如果不知道返回值的类型可以不定义返回值的类型,

如果指定未来不需要用到函数的返回值,函数也不会有返回值,可以使用关键字void定义返回类型


1.2 函数表达式

定义函数是除了可以使用普通的函数定义方式,还可以定义函数表达式, 将匿名函数赋值给变量

// 定义匿名函数表达式
var fn = (){
    print("这是一个匿名函数");   // 这是一个匿名函数
};

void main(){

    // 执行匿名函数
    fn(); 

}

函数表达式也可以使用Function来限定变量

main(){
    Function wuwei = (){
        print("我是一个函数表达式");  //我是一个函数表达式
    };
    wuwei();
}

既然讲到匿名函数, 就在来好好看看匿名函数


1.3 匿名函数

1、没有名字的函数,称之为匿名函数,有时候也被称为lambda 或者 closure 闭包
2、你可以把匿名函数赋值给一个变量, 然后你可以通过这个变量使用这个函数,即函数表达式.

匿名函数通常用在函数表达式, 自执行函数,或者一些方法的回调函数

示例:

void main(){
    // 1. 函数表达式
    var fn = (){
        print("这是一个匿名函数");   
    };
    fn(); 

    // 2. 自执行函数
    // 自执行函数
    void main(){
        ((){
            print("这是自执行函数");
        })();
    }

    // 3. 方法的回调函数
    var list = ["苹果","香蕉","橘子"];
    list.forEach((value){
        print(value);
    })'
}


1.4 箭头函数

箭头函数的语法: =>

注意

  1. 这个箭头函数跟我们前端所学的ES6箭头函数不一样

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

void main(){
    // dart箭头函数更像是函数声明,只有一个return语句时的简写
    // ES6 的箭头函数是一个函数表达式
    int fn(int a, int b) => a + b ;
  
    print(fn(10,20));  // 30
}

错误的写法

void main(){
    // 箭头函数不能有大括号, 这样写就会报错
    int fn(int a, int b) => {
      return a + b
    } ;
  
    print(fn(10,20));  // 30
}


1.5 入口函数

每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void

// 入口函数
void main(){
    
}


2. 函数的参数

2.1 普通函数传参
void main(){

    // 1. 普通函数传参
    // 定义函数
    String printUserInfo(name, age){
        return 'name:$name--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明", 18)) ;  // name:小明--age:18

}

普通函数的传参, 参数可以传递任何数据类型.

但是有的时候我们需要考虑用户是否会乱传参数,导致程序崩溃,所以我们可以限定参数类型


2.2 限定传递参数的类型

就像定义变量一样,在形参前使用特定的数据类型来限定形参能接收到 数据类型

void main(){

    // 2. 限定传参类型
    // 定义函数
    String printUserInfo(String name, int age){
        return 'name:$name--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明", 18)) ;  // name:小明--age:18
    print(printUserInfo("小明", '18')) ; // 报错,第二个参数不符合参数类型
}

限定了传递参数类型后,如果用户传递的参数类型不对就会报错

同时我们回发现, 我们传递的实参个数必须跟形参个数完全对应.有多少个形参就必须传递多少个实参, 这样,用户使用的灵活性就会下降,所以我们可以定义一些可选参数,可选参数在传不传实参都不会报错


2.3 可选参数

使用中括号来确定哪些参数是可选参数

void main(){

    // 3. 可选传参
    // 定义函数
    String printUserInfo(String name, [ int age ]){
        return 'name:$name--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明", 18)) ;  // name:小明--age:18
    print(printUserInfo("小明")) ;     // name:小明--age:null
}

可选参数只能放到方法参数的末尾,不能放到必需参数的前面,否则就会报错

示例:

void main(List args){
    // 可选参数name 放在了必须的参数age之前, 就会报错
    String printInfo([String name],int age){
        return "name:$name---age:$age";
    }
}

示例会报错,可选参数不能放在必须的参数之前.

注意, 可选参数在没有传递实参的情况下, 值为null


所以可以通过判断来确定用户是否给可选参数传递了实参,如果没有的话可以自己赋一个默认值

void main(){

    // 3. 可选传参
    // 定义函数
    String printUserInfo(String name, [ int age ]){
        if(age == null){
            age= 20;
        }
        return 'name:$name--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明", 18)) ;  // name:小明--age:18
    print(printUserInfo("小红")) ;     // name:小红--age:20
    // 第二次调用就启用了默认值
}


2.4 默认值,

在定义函数的时候,可以使用 = 来定义可选参数的默认值。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。

在形参定义默认值就是使用=赋值,

void main(){

    // 4. 参数默认值
    // 定义函数
    String printUserInfo(String name, [ int age , String sex ="男" ]){
        if(age == null){
            age= 20;
        }
        return 'name:$name--sex:$sex--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明", 18)) ;  // name:小明--sex:男--age:18
    print(printUserInfo("小红",)) ;     // name:小红--sex:男--age:20
}

在没有传递sex参数的时候,默认为"男",

其实我们可能会注意到, 如果没传递实参,形参就为null, 那么是不是就意味着形参的值只要为null就会启用默认值,如果你这么想就错了

我们可以显示的传递值为null,来看看

void main(List args){
    String printInfo([String name,age = 30]){
        return "name:$name---age:$age";
    }

    print(printInfo("小明",null));   // name:小明---age:null
}

我们会发现,当你显示的传入null以后, 形参的值也是null, 但是却没有启用默认是, 而是使用你复制的null.


注意: 我们在定义形参的时候要注意参数顺序, 上面的例子,如果我想改变传递sex参数,就必须先传递age参数, 如果可选参数调换了位置, 你在传递age参数时,必须先传递sex参数,否则会报错

void main(){

    // 4. 参数默认值
    // 定义函数
    String printUserInfo(String name, [ String sex ="男",int age   ]){
        if(age == null){
            age= 20;
        }
        return 'name:$name--sex:$sex--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明", "女",18)) ;  // name:小明--sex:女--age:18
    print(printUserInfo("小红","不详")) ;     // name:小红--sex:不详--age:20
}

这种按照顺序传递的方式, 会有一些不利的情况, 那么我们怎样选择自己想传递的参数呢, 就可以使用命名参数


2.5 命名参数

命名参数就是根据名称匹配你想给那一个形参赋值

命名参数的形参使用大括号进行罗列

void main(){

    // 5. 命名参数
    // 定义函数
    String printUserInfo(String name, { String sex ="男",int age  }){
        if(age == null){
            age= 20;
        }
        return 'name:$name--sex:$sex--age:$age';
    }

    // 执行函数
    print(printUserInfo("小明",age:18)) ;  // name:小明--sex:男--age:18
    print(printUserInfo("小红", sex: "不详")) ;   //name:小红--sex:不详--age:20
}

命名参数可以理解也是可选参数的一种, 中括号语法的可选参数, 只能按照顺序赋值,

命名参数就需要你在传递实参的时候带上形参的名字,确定你要给那个形参赋值.


2.6 参数可以是函数

函数可以作为参数传递给另外一个函数

void main(){

    // 6.参数函数
    getInfo(String info){
        print(info);     // 就是普通字符串
    }

    runFn(fn){
        fn("就是普通字符串");
    }

    runFn(getInfo);
}


3.其他

3.1 词法作用域

Dart 是一门词法作用域的编程语言,就意味着变量的作用域是固定的, 简单说变量的作用域在编写代码的时候就已经确定了。 花括号内的是变量可见的作用域。

下面示例关于嵌套函数的变量作用域:

void main(){
    getInfo(){
        // 当前变量是定义在getInfo 作用域内,
        // 变量只能在getInfo作用于中使用
        num number = 10;

        // 作用域内使用变量, 没有任何问题
        print(number);   // 10
    }

    getInfo();

    // 在getInfo作用域外调用里面的变量,就会报错
    print(number); // 报错
}


3.2 词法闭包

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量。

函数可以封闭定义到它作用域内的变量。

下面的示例中, add() 捕获了变量 number。 无论在什么时候执行返回函数,函数都会使用捕获的 number 变量。

void main(){
    getInfo(){
        // 当前变量是定义在getInfo 作用域内,
        // 变量只能在getInfo作用于中使用
        num number = 10;
        return (num info){
            number += info;
            return number;
        };
    }

    Function add = getInfo();
    print(add(5));
    print(add(1));
    print(add(2));

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348