iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 5
0
自我挑戰組

從零開始的Flutter世界系列 第 5

Day05 Dart 語言介紹(三) 類別、函式

  • 分享至 

  • xImage
  •  

類別

  • 物件導向( Object Oriented Programming ),目的為了處理資料,設計一個有共同規則的物件,還有方法來方便對資料重複使用或做複雜的處理,可以想像是去跟資料庫做互動,所設計的模式,資料庫裡的每筆資料就像一個物件,而物件(Object),是從類別(Class) 實體化後而來,可以使用此類別定義的屬性和方法。

  • 新增物件

    需先分析要處理的每一筆資料細目,Attribute屬性(fileld欄位,datatype),再根據此規則建立類別,之後就能將每一筆資料都建立成此類別的物件

    語法:

    1. 類別 物件名稱 = new 類別名 ( [ 引數值 ] ) ;

      Call by reference

      等號左右兩邊的類別(class)要一樣或有繼承關係才可以,[引數值] : 可為空,與建構有關,之後會再詳談

      Parameters (引數、參數),傳入的值

    2. 類別 物件名稱 = 類別名 ( [ 引數值 ] ) ;

      new 在Dart 2後可以選擇不用加

    //個人資料表,有姓名,年齡,電話,住址
    class Person {
      //找出共同規則來建立類別
      String name;
      int age;
      String phoneNumber;
      String address;
    }
    
    void main() {
      Person a = new Person(); // 建立一個Person類別的物件,a 即為Person類別的 instance
      Person b = Person(); // 一樣為建立一個Person類別的物件,b 一樣為Person類別的 instance
    
      print(a); // 印出 Instance of 'Person'   
      print(b); // 印出 Instance of 'Person'
    
      //取得物件的型態
      print(a.runtimeType); // 印出 Person
      print(b.runtimeType); // 印出 Person
    
      //是否為同一個物件
      print(identical(a, b)); // 印出 false
    
      //還未對此物件設值,Person 的 name 也未給初始值
      print("a's name = ${a.name}"); // a's name = null
      point.a = "ryder"; // 使用預設setter 方法對變數name 賦值
      print("a's name = ${a.name}"); // a's name = ryder
    }
    
  • 建構式 ( constructors )

    1. 新增一個物件時最後會執行的步驟

      故物件初始化時,想傳值、對資料做處理或額外的判斷,可做在建構式裡面

    2. 名字必須與類別名稱一樣

    3. 全域( Global )/區域( Local )變數生命週期

      以變數宣告的位置分成全域變數或區域變數,其變數的生命週期只在該區域內 { },若在 { } 內呼叫變數會先從區域找起,再去全域,若在區域內想用同名稱的全域變數,需要用 this. 來呼叫

      this._ → 表示其物件本身的變數,即class的field區

      class Person {
      
        String name;
        int age;
      
        Person(String name, int age){
          this.name = name;
          this.age = age;
        }
      }
      
      void main() {
        Person a = new Person("Ryder",26); 
        print("a.name = ${a.name}, a.age = ${a.age}");  //印出 a.name = Ryder, a.age = 26
      }
      
      
    4. Default copnstructors(預設建構式)

      如果一個都沒有定義建構式,類別會自動建立一個預設建構式,預設建構式沒有任何引數

    5. Named constructors(命名建構式)

      一個類別只能有一個主建構式 Unnamed constructors,不像在Java 擁有多載(Overloading) 的特性,是用引數來區分,但是Dart 用另一種方法來滿足這個要求,並且能更清楚表達程式,增加易讀性,即使用 Named constructors(命名建構式)

      class Person {
      
        String name = "匿名";
        int age;
      
        //主建構式 Unnamed constructors
        //由於建構式引數賦值給物件屬性變數的情況太常見了,Dart提供了一個語法來簡化這個操作
        Person(this.name, this.age);
      
        //Named constructors
        Person.secretAge(this.name);
      
        //Named constructors
        Person.anonymous(this.age);
      }
      
      void main() {
        Person a = Person("Ryder",26); 
        print("a.name = ${a.name}, a.age = ${a.age}");  //印出 a.name = Ryder, a.age = 26
      
        Person b = Person.secretAge("Judy"); 
        print("b.name = ${b.name}, b.age = ${b.age}");  //印出 b.name = Judy, b.age = null
      
        Person c = Person.anonymous(65); 
        print("c.name = ${c.name}, c.age = ${c.age}");  //印出 c.name = 匿名, c.age = 65
      }
      
    6. Redirecting constructors(重定向建構式)

      有時候一個建構式會調動類別中的其他建構式,一個重定向建構式是沒有程式碼的,在建構式聲明後,使用冒號呼叫其他建構式

      class Point {
        num x;
        num y;
      
        // 主建構式
        Point(this.x, this.y);
      
        // 呼叫主建構式
        Point.alongXAxis(num x) : this(x, 0);
      }
      
      void main() {
        Point a = Point(6, 6);
        print("a.x = ${a.x}, a.y = ${a.y}"); //印出 a.x = 6, a.y = 6
      
        Point b = Point.alongXAxis(8);
        print("b.x = ${b.x}, b.y = ${b.y}"); //印出 b.x = 6, b.y = 0
      }
      
      

補充:Dart 的一些概念

  • Dart 支援top-level 方法,如main方法,同時還支援在類別中定義函式(靜態函式和例項函式),還可以在方法中定義方法,Dart支援top-level 變數,也支援類別變數(static,後面會介紹) 和物件變數
  • Dart 沒有publicprotectedprivate封裝關鍵字。如果某個變數名以底線_開頭,代表這個變數為private
  • Dart 工具可以指出兩種問題:警告和錯誤。警告只是說你的程式碼可能有問題,但是並不會阻止你的程式碼執行。錯誤可以是編譯時錯誤也可以是執行時錯誤。遇到編譯時錯時,程式碼將無法執行;執行時錯誤將會在執行程式碼的時候導致一個異常。

函式(方法)

  • 函式是類別中定義的方法,是物件可執行的行為,方法也是物件具有的一種型別,可以賦值給變數,也可以當做其他方法的引數,甚至把類別的instance (例項)當作方法來呼叫

  • 語法:

    類型 方法名(引數) {

    ​ 欲執行的步驟

    }

    => expr 是語法{return expr;}形式的縮寫

  • 類型:

    1. 不傳值 → void
    2. 傳值 → return (與"="一樣,即指定)

    Effective Dart推薦用明確的型態類型定義方法,但一樣可以選擇忽略型別來定義方法

    class Student {
      String _name;
      int _chi;
      int _eng;
      int _sum;
      double _ave;
    
      Student(this._name, this._chi, this._eng) {
        _sum = _chi + _eng;
        _ave = (_chi + _eng) / 2;
      }
    
      void show1() {
        print(
            "name = $_name, chinese = $_chi, english = $_eng, sum = $_sum, avg = $_ave");
      }
    
      show2() {
        print(
            "name = $_name, chinese = $_chi, english = $_eng, sum = $_sum, avg = $_ave");
      }
    
      void show3() => print(
            "name = $_name, chinese = $_chi, english = $_eng, sum = $_sum, avg = $_ave");
    
    }
    
    main() {
      Student a = Student("Ryder", 70, 80);
      a.show1(); //印出 name = Ryder, chinese = 70, english = 80, sum = 150, avg = 75
      a.show2(); //印出 name = Ryder, chinese = 70, english = 80, sum = 150, avg = 75
      a.show3(); //印出 name = Ryder, chinese = 70, english = 80, sum = 150, avg = 75
    }
    
  • 函數可以有兩種類型的參數 → 必須 required 可選 optional,若一個函式同時有兩種,required parameter 需放在首位,後面再放optional parameter

    • optional parameter 可選參數

      可選參數可以是命名參數,也可以是位置參數,但不能同時為兩種

      • Optional Named parameters 可選命名參數:可以增強程式的可讀性

        當定義一個函數時,使用{param1, param2, … }的形式来制定命名参数

        void enableFlags({bool bold, bool hidden}) {...}
        

        當使用一個方法時,可以使用paramName:value的形式來指定命名參數

        enableFlags(bold: true, hidden: false);
        

        可以對命名參數註釋 @required,來表明該命名參數是必須 required 的。 ( depend on the meta package and import package:meta/meta.dart)

        const Scrollbar({Key key, @required Widget child})
        

        當使用建構 Scrollbar時,如果沒傳參數 child,就會報錯

      • Optional positional parameters 可選位置參數:

        將一些方法的引數放到[ ]中就變成可選位置參數了

        main() {
          //定義一個方法 [String device]是可選位置參數 也就是呼叫這個方法可以不傳這個引數
          String say(String from, String msg, [String device]) {
            //required parameter 放前面,可選參數需要放在後面
            var result = '$from says $msg';
            if (device != null) {
              result = '$result with a $device';
            }
            return result;
          }
        
        //不使用可選引數呼叫方法
          print(say('Ryder', 'hello world')); //印出 Ryder says hello world
        
        //使用可選數呼叫方法
          print(say('Ryder', 'hello world', 'Dart')); //印出 Ryder says hello world with a Dart
        }
        
    • Default parameter value (預設引數值):

      在定義方法的時候,可以使用=來定義可選引數的預設值,預設值只能是編譯時常量( const ),如果沒有提供預設值,則預設值為null

      main() {
      //定義一個返回型態為空的方法,方法中可選命名參數bold、hidden 預設值為false
        void enableFlags({bool bold = false, bool hidden = false}) {
          print("bold = $bold, hidden = $hidden");
        }
      
      //呼叫方法 沒有傳hidden的值,那預設值就是false
        enableFlags(bold: true); //印出 bold = true, hidden = false
      
      //定義一個方法 有一個可選位置參數device 的預設參數是carrier pigon
        String say(String from, String msg,
            [String device = 'carrier pigeon', String mood]) {
          var result = '$from says $msg';
          if (device != null) {
            result = '$result with a $device';
          }
          if (mood != null) {
            result = '$result (in a $mood mood)';
          }
          return result;
        }
      
      //呼叫上面的方法,沒有傳device的值,那預設值就是carrier pigeon
        print(say('Ryder', 'hello world')); //印出 Ryder says hello world with a carrier pigeon
      }
      

      還可以使用list或者map作為預設值

      main(){
        doStuff(); //印出 list:  [1, 2, 3]
                   //gifts: {first: paper, second: cotton, third: leather}
      }
      
      //List和Map都取了預設值
      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');
      }
      
  • The main() function(入口函式)

    每個app 都有一個top-level 的main()入口方法,有它app 才能執行。main()方法的返回值為void並且有個可選的List<String>引數

    建立一個 .dart 檔

    void main(List<String> arguments) {
      print(arguments);
    }
    

    在終端機 terminal 下指令

    dart main.dart test1 test2
    

    印出 [test1, test2]

  • Functions as first-class objects

    函數本身可以作為一個參數傳遞給另一個函數

    main() {
      void printElement(int element) {
        print(element);
      }
    
      var list = [1, 2, 3];
    
      // 傳遞printElement方法 給List 類別的forEach 方法當參數用
      list.forEach(printElement); //印出 1
                                  // 2
                                  // 3
    }
    

    方法也可以賦值給一個變數:

    main() {
      var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; // (msg) 是匿名方法,等等會介紹
      print(loudify("hello")); //印出 !!! HELLO !!!
    }
    
  • 匿名函數:

    大部分方法都會命名,例如main()或者printElement()。但是也可以建立沒有名字的方法,稱為匿名方法,有時候也被稱為lambda或者clourse閉包。可以把匿名方法賦值給一個變數,並可以使用這個方法,比如新增集合或者從集合中刪除。匿名函式和命名函式類似,在括號之間可以定義一些參數,並使用逗號分割,其中參數也可以是可選參數等

    語法:

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

    main() {
      //toUpperCase 即為匿名方法
      Function toUpperCase = (String item){
          return item.toUpperCase();
      };
      print("hello 轉大寫 = ${("hello")}"); //印出 Hello
    
      //可以不用先宣告方法,直接使用
      var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
    	list.forEach((i) {
     	 print(list.indexOf(i).toString() + ': ' + i); 
    	});
      /*印出:
    	0: apples
    	1: oranges
    	2: grapes
    	3: bananas
    	4: plums
    	*/
    
      // => 的寫法
      list.forEach((i) => print(list.indexOf(i).toString() + ': ' + i));
      /*印出:
    	0: apples
    	1: oranges
    	2: grapes
    	3: bananas
    	4: plums
    	*/
    }
    
  • Lexical scope

    同前面介紹過的,變數、方法的生命週期只在該區域內 { }

    //top-level scope start
    var topLevel = true;
    
    main() {//main scope start
      var insideMain = true;
    
      myFunction() {//myFunction scope start
        var insideFunction = true;
    
        nestedFunction() {//nestedFunction scope start
          var insideNestedFunction = true;
    
          //在nestedFunction()內,所有變數都可以使用
          assert(topLevel);
          assert(insideMain);
          assert(insideFunction);
          assert(insideNestedFunction);
    
        }//nestedFunction scope end
      }//myFunction scope end
    }//main scope end
    //top-level scope end
    
  • Lexical closures(詞法閉包)

    Closure閉包,是一個方法物件,不管該物件在何處被呼叫,該物件都可以使用其作用域內的變數,方法可以封閉定義到其作用域內的變數。

    Function makeAdder(num addBy) {
      return (num i) => addBy + i;
    }
    
    main() {
      var add2 = makeAdder(2);
      // add2 為一個匿名方法物件 (num i) => 2 + i;
    
      var add4 = makeAdder(4);
      // add4 為一個匿名方法物件 (num i) => 4 + i;
    
      print(add2(3)); //印出 5
      print(add4(3)); //印出 7
    }
    
foo() {} //top-level方法

class A {
  static void bar() {} // 靜態方法,又稱類別類的方法。 static 後面會介紹
  void baz() {} // instance例項方法,又為物件類的方法
}

void main() {
  var x;

  // 比較top-level函式
  x = foo;
  print(foo == x); //印出:true

  // 比較靜態方法
  x = A.bar;
  print(A.bar == x);  //輸出:true

  // 比較例項方法
  var v = new A(); // Instance #1 of A
  var w = new A(); // Instance #2 of A
  var y = w;
  x = w.baz;

  //這些閉包引用相同的例項 w
  //所以相等
  print(y.baz == x);   //印出:true

  // 閉包引用不同的例項,所以不相等
  print(v.baz != w.baz);   //印出:true
}
  • 外部函式庫

    使用方式:

    1. 絕對路徑
    2. import
  • Instance methods(例項函式)

    //要用到外部函式庫的sqrt 方法,故需要import 此方法的函式庫
    import 'dart:math';
    
    class Point {
      num x;
      num y;
      Point(this.x, this.y);
    
      num distanceTo(Point other) {
        var dx = x - other.x;
        var dy = y - other.y;
        return sqrt(dx * dx + dy * dy);
      }
    }
    
    void main() {
      Point a = Point(3, 4);
      Point origin = Point(0, 0);
      print("a.distanceTo(origin) = ${a.distanceTo(origin)}"); //印出 a.distanceTo(origin) = 5
    }
    
    

    Getterssetters是用來設定和使用物件屬性的特殊函式,每個例項變數都預設的具有一個getter, 如果變數不是final的則還有一個setter,可以通過實行gettersetter來建立新的屬性,使用getset關鍵字定義gettersetter

    class Rectangle {
      num left;
      num top;
      num width;
      num height;
    
      Rectangle(this.left, this.top, this.width, this.height);
    
      // 定義兩個計算屬性:右和下
      num get right             => left + width;
          set right(num value)  => left = value - width;  //對right 設值會執行 left = value(對right 設的值) - width;
      num get bottom            => top + height;
          set bottom(num value) => top = value - height;
    }
    
    main() {
      var rect = new Rectangle(3, 4, 20, 15);
      print(rect.left); //印出 3
      rect.right = 12; // left = value - width; 所以 left = 12 - 20;
      print(rect.left); //印出 -8
    }
    
    
  • Return values (返回值)

    除了類型void函式,其他所有的函数都有返回值,如果没有指定返回值,则返回null

    main() {
      foo() {}
      print(foo()); //印出null
    }
    

今天就先介紹類別的觀念以及方法的應用,下一篇將講解 static的觀念,以及寫程式時,發生Exception 異常時,該如何去做例外處理


上一篇
Day04 Dart 語言介紹(二) 資料型態、條件式、迴圈
下一篇
Day06 Dart 語言介紹(四) static、Exception
系列文
從零開始的Flutter世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言