Dart语法系列(三):函数与异常

一、 Functions (函数)

Dart 是一个真正的面向对象语言,函数也是一个对象并且具有其类型:Function。这意味着,方法可以赋值给变量,也可以当做其他方法的参数。您还可以像调用函数一样调用Dart类的实例。详情请参考 Callable classes

Dart是不支持函数的重载的****,一个类中不允许存在同名的函数出现,这点与C语言一样。虽然不支持函数重载但是它支持可选参数的使用

下面是定义方法的示例:

bool isNoble(int atomicNumber) {
  return atomicNumber != null;
}

Dart中函数的返回值类型 和 参数类型都可以省略****,但是Effective Dart 中推荐最好不要省略

isNoble(atomicNumber) {
  return atomicNumber != null;
}

对于只包含一个表达式的函数,可以使用简写语法:(return必须省略)

bool isNoble(int atomicNumber) => atomicNumber != null; //return必须省略

//同样可以省略类型,下面等价
isNoble(atomicNumber) => atomicNumber != null;

这个 => expr 语法是 { return expr; } 形式的缩写。=> 形式 有时候也称之为 箭头语法( arrow syntax)

注意: 在箭头 => 和冒号 ; 之间只能使用一个表达式(expression) ,而不能使用语句(statement)。 例如:你不能使用 if statement,但是可以 使用条件表达式 conditional expression

方法可以有两种类型的参数:必需的和可选的。命名可选参数也可以标记为@required

1.1、可选参数

可选参数可以是基于命名参数或者基于位置的参数,但是这两种参数不能同时当做可选参数。

如果参数列表中同时存在可选参数 和 必选参数,必选参数必需放在可选参数前面

Optional named parameters(可选命名参数)

在定义方法的时候,使用 {param1, param2, …} 的形式来指定命名可选参数。

在调用方法的时候,你可以使用这种形式 paramName: value 来指定命名参数,而不能直接传值:

//函数定义,注意有{} 
enableFlags({bool bold, bool hidden}) {
  // ...
}

//函数调用
enableFlags(hidden: false,bold: true);//OK 
enableFlags(bold: true);//OK
enableFlags();//OK
//enableFlags(false, true);//编译错误 

{}表示可选参数,当然你也可以按需传入可选参数,但是必选参数必选要传递

//a是必选参数, {}中的是可选参数
//void enableFlags({bool bold, bool hidden},int a) //错误定义,可选参数只能放在后面
void enableFlags(int a,{bool bold, bool hidden}) { 
  print(bold.toString()+" "+hidden.toString());
}

main() {
  enableFlags(3,bold: true); //true null
  enableFlags(3);  //null null
  //enableFlags(); 错误。有必选参数a存在,不能不传任何参数
}

注意,可选参数只能放参数列表的在最后

Optional positional parameters(可选位置参数)

把一些方法的参数放到 [ ] 中就变成可选 位置参数了,可选位置参数只能根据位置选择传入的参数,而不能通过命名的方式,其他的效果与可选命名参数一致:

//[]表示可选位置参数
void enableFlags(int a,[bool hidden, bool bold]) {
  print(bold.toString()+" "+hidden.toString());
}

main() {
  enableFlags(3,true,true); //true true
  enableFlags(3,true); //null true

}

Default parameter values(默认参数值)

在定义方法的时候,可以使用 = 来定义可选参数的默认值,只能用于可选参数(两种可选参数[ ]和{}都支持)。 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null (Dart中万物都是对象,没有基本类型之分)

void enableFlags(int a ,{bool hidden = true, bool bold }) {
  print(bold.toString()+" "+hidden.toString());
}


main() {
  enableFlags(3); //null true
  enableFlags(3,hidden: false); //null false
  enableFlags(3,hidden: false,bold: false); //false false
}

版本问题: 旧版本代码可能需要使用一个冒号 (:) 而不是 = 来设置参数默认值。 原因在于 Dart SDK 1.21 之前的版本,命名参数只支持 :。 : 设置命名默认参数值在以后版本中将不能使用, 所以我们推荐你 使用 = 来设置默认值, 并 指定 Dart SDK 版本为 1.21 或者更高的版本。

还可以使用 list 或者 map 作为默认值。 下面的示例定义了一个方法 doStuff(), 参数类型分别为list和map,并分别为其指定了默认值,由于默认值必须是编译时常量,所以必须使用const修饰

void doStuff(
    {List<int> list = const [1, 2, 3], Map<String, String> gifts = const {
        'first': 'paper',
        'second': 'cotton',
        'third': 'leather'
      }}) 
{
  print('list:  $list');
  print('gifts: $gifts');
}


main() {
 doStuff();
}

1.2、main函数

每个应用都需要有个顶级的 main() 入口方法才能执行。 main() 方法的返回值为 void 并且有个可选的 List<String> 参数

下面是一个 web 应用的 main() 方法:

void main() {
  querySelector("#sample_text_id")
    ..text = "Click me!"
    ..onClick.listen(reverseText);
}

下面是一个命令行应用的 main() 方法,并且使用了方法参数作为输入参数:

// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

1.3、函数是一等公民(Functions as first-class objects)

函数可以作为参数来进行调用,例如:

printElement(element) {
  print(element);
}

main() {
  var list = [1, 2, 3];
  list.forEach(printElement);  
}

输出结果为:1、2、3。 我们可以看看forEach的函数定义,直接声明一个函数的声明作为函数的参数

img

我们也可以为变量分配一个函数,例如下面:(这个例子使用一个匿名函数。下一节将详细介绍这些内容)

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

1.4、Anonymous functions(匿名函数)

你有可以创建没有函数名的函数,称之为匿名函数,有时候也被称为 lambda 或者 closure 闭包。 你可以把匿名方法赋值给一个变量, 然后你可以使用这个方法,比如添加到集合或者从集合中删除。您可以为变量分配一个匿名函数,例如,然后你可以使用这个方法,比如添加到集合或者从集合中删除。

匿名函数看起来类似于命名函数—有0个或者多个参数,在括号之间用逗号和可选类型标注分隔。后面的代码块包含函数的主体:

([[Type] param1[, …]]) { 
  codeBlock; 
}; 

下面的示例定义了一个无类型参数item的匿名函数。list 中的每个元素都会调用这个函数来 打印出来,同时来计算了每个元素在 list 中的索引位置:

var list = ['apples', 'bananas', 'oranges'];
//1、匿名函数允许直接赋予给一个变量
var a =  (item) {
  print('${list.indexOf(item)}: $item');
};
list.forEach(a);//a指向一个匿名函数

//2、也允许直接传入一个匿名函数,与上面的两句效果是等价的
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

/*打印结果
0: apples
1: bananas
2: oranges
*/

如果函数只包含一个语句,可以使用箭头语法缩写

var list = ['apples', 'bananas', 'oranges'];
  
var a = (item) => print('${list.indexOf(item)}: $item');
list.forEach(a);

//与上面两句等价
list.forEach((item) => print('${list.indexOf(item)}: $item'));

1.5、Lexical scope(静态作用域)

Lexical Scope (also called Static Scope)。Dart 是静态作用域语言,变量的作用域在写代码的时候就确定过了。 基本上大括号里面定义的变量就只能在大括号里面访问,和 Java 作用域类似。

这里有一个嵌套函数的例子,每个作用域级别上都有变量:

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

nestedFunction() 可以访问所有的变量, 包含顶级变量。

1.6、Lexical closures(词法闭包)

一个 闭包(匿名函数) 是一个函数对象不管该对象在何处被调用, 该对象都可以访问其作用域内的变量

在下面的示例中,makeAdder()捕获变量addBy。无论返回的函数到哪里,它都会记住addBy:

/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
  return (num i) => addBy + i;  //匿名函数作为Function类型的对象进行返回
}

void main() {
    // Create a function that adds 2.
    var add2 = makeAdder(2);

    // Create a function that adds 4.
    var add4 = makeAdder(4);

    assert(add2(3) == 5);
    assert(add4(3) == 7);
}

在上面的示例中我们可以看出:普通函数,匿名函数都是一个Function类型的对象,并且可以作为返回值进行返回。

例如下面例子,add2指向的就是test函数对象,当执行add2()的时候实际上调用的是test()函数:

test(){
  print("Hello World");
}
 
Function makeAdder(addBy) {//返回类型可以省略
  return test ;
}

main() {
  var add2 = makeAdder(2);
  add2(); //Hello World 
}

1.7、判断函数相等

下面是测试顶级方法静态函数实例函数 相等的示例:(下面都是执行通过的)

foo() {}               // A top-level function

class A {
  static void bar() {} // A static method
  void baz() {}        // An instance method
}

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 = new A(); // Instance #1 of A
  var w = new 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);
}

上面的例子可以看出 同一个类的不同实例之间,对于同一个方法来说其对象是不相等的,既var v = A()var w = A() 中 v.bar 与 w.bar 方法对象是不相等的。

1.8、Return values(返回值)

所有函数都返回一个值。如果没有指定返回值,则 默认把语句 return null,作为函数的最后一个语句执行

foo() {}

assert(foo() == null);

二、异常

代码中可以出现异常和捕获异常。异常表示一些 未知的错误情况。如果异常没有捕获, 则异常会抛出,导致 抛出异常的代码终止执行。

和 Java 不同的是所有的 Dart 异常是非检查异常 unchecked exceptions(既不需要一定要用try ...catch处理)方法不声明它们可能抛出哪些异常,也不要求您捕获任何异常:

test(){//方法体也不用声明
  throw new FormatException("Test one ");
}

void main() {
    test();//不需要try-catch
}

Dart 提供了 ExceptionError 类型, 以及一些子类型。你还 可以定义自己的异常类型。但是, Dart 代码可以抛出任何非 null 对象为异常,不仅仅是实现了 Exception 或者 Error 的对象。( Dart抛出的异常类型不一定要是Exception和Error类型,任何非空类型都可以)。

2.1、throw

下面是抛出一个异常的示例:

throw new FormatException('Expected at least 1 section');//FormatException是Exception的子类

你也可以抛出任意对象:

throw 'Out of llamas!';

注意:在正式使用中通常抛出的是实现了Error或Exception类型的对象;

由于抛出异常是一个表达式,所以可以在 => 语句中使用,也可以在其他能使用表达式的地方抛出异常:

void distanceTo(Point other) => throw UnimplementedError();

2.2、Catch

与java一样,Dart中可以使用 try-catch来捕获异常,避免异常继续传递(除非你重新抛出rethrow异常)。不过与java不同的事,使用on ExceptionType来声明捕获具体的异常类型。on ExceptionType catch(e) 通过catch可以捕获异常对象e。如果没有指定异常类型,直接使用catch(e),则表示可以捕获任何异常类型。

只捕获OutOfLlamasException异常:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

对于可以抛出多种类型异常的代码,你可以指定 多个捕获语句:

void main() {
  try {
   // throw new FormatException("Hello"); 只触发第一个on FormatException 
    
   // throw new TimeoutException("hello");只触发第二个on Exception catch (e)
  
      throw "Hello EeveyOne"; //只触发第三个catch (e)
      
  } on FormatException {
    //捕获具体的异常类型FormatException,没有异常对象
    print("I'm a OutOfLlamasException");
  } on Exception catch (e) {
    //只要是Exception类型的异常都能捕获,并携带异常对象e
    print('Unknown exception: $e');  
  } catch (e) {
    // 任何throw抛出的类型都能捕获
    print('Something really unknown: $e');
  }

}

如上面代码所示,你可以使用on 或者 catch 来声明捕获语句,也可以 同时使用使用 on 来指定异常类型,使用 catch 来 捕获异常对象

函数 catch() 可以带有一个或者两个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 (一个 StackTrace 对象)。

void main() {
  try {
      throw "Hello EeveyOne";  
  }  on Exception catch (e) {
    print('Unknown exception: $e');
  } catch (e,s) { //e为异常对象,s为堆栈信息
    print('Exception details:\n $e');
    print('Stack trace:\n $s');
  }
}
img

使用 rethrow 关键字可以 把捕获的异常给 重新抛出,当然也可以使用 throw抛出异常,不过使用rethrow更方便:

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
    // throw  e;等价于上面
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

输出:

misbehave() partially handled NoSuchMethodError.
main() finished handling NoSuchMethodError.

2.3、Finally

要确保某些代码执行,不管有没有出现异常都需要执行,可以使用 一个 finally 语句来实现。<u>如果没有 catch 语句来捕获异常, 则在执行完 finally 语句后, 异常被抛出了</u>:

void main() {
  try {
      throw "Hello EeveyOne";
  } finally{
      //finally执行了,但是异常并没有被捕获,执行后立马抛出异常
    print("My Name Finally");
  }
    
   print("End");//没有而被执行
}

打印如下:

img

finally子句在所有匹配到的catch子句之后运行:

void main() {
  try {
      throw "Hello EeveyOne";
  } catch (e){
    print("Exception is $e");
  } finally{
    print("My Name Finally");
  }
  print("End");
}

输出结果为:

Exception is Hello EeveyOne
My Name Finally
End

想了解更多请阅读[Exceptions]章节。

欢迎关注我的公众号【不喝咖啡的程序员】,最新的文章会在上面发布:


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

推荐阅读更多精彩内容

  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,732评论 2 9
  • 上一篇: 一、Flutter环境搭建下一篇: 三、Flutter基础—ListView入门 我之前只有OC开发经...
    OOOlive阅读 4,139评论 4 13
  • Dart重要概念:1,在变量中可以放置的所有东西都是对象,而每个对象都是类的实例。无论数字、函数、和null都是对...
    哥哥是欧巴Vitory阅读 789评论 0 1
  • 此文章是v1.0+时编写,年代久远,小心有毒,谨慎食用!!! 一些重要概念 所有的东西都是对象,所有的对象都是类的...
    soojade阅读 10,041评论 2 27
  • 1、前言: 最近在学习Flutter,作为Flutter的编程语言,Dart语言语法的学习自然就编程重中之重。本文...
    Yagami3zZ阅读 1,486评论 0 0