2019年2月19日 星期二

c++ 類型的複製型建構式與指定型運算式的差異與被呼叫的時機

寫 c++ 程式時, 當自訂的類型(class)如果沒有指標(*)的成員, 基本不用寫複製型建構式(copy constructor, 當建構式為 類型(類型 &) 的型式)或指定型運算式(copy assignment, 也就是函式為 類型 &operator =(類型 &) 的型式), 編譯器(compiler)會自動完成. 一旦有了指標的成員, 並在建構式中動態分配空間給指標使用時,  就必須在解構式(destructor)中釋放指標所佔用的空間(資源), 並且在複製型建構式中, 除了要動態分配空間給指標使用外, 也要將來源的資源一一加以複製, 甚至有些時候還要完成移動型建構式(Move Constructor)及移動型指定式(Move assignment), 否則可能造成無法編譯或是記憶體洩漏(memory leak),嚴重時會因資源共用,導致記憶體重複釋放的危機.  編譯器針對類型的 = 運算, 會去呼叫複製型建構式或指定型運算式(括一般型指定運算及移動型指定運算), 端看物件是否建構完成, 因此要特別注意資源分配及釋放的時機, 例如:
            auto   copy = *this;
上式的 copy, 在指定運算 = 之前, 物件尚未形成, 因此沒有任何方法可用, 所以編譯器呼叫的是複製型建構式(建構並複製)來構製物件,並非類型中的指定型運算式, 但如果將上式分成兩行來寫, 物件事先建構完成(參考下列的 assign 變數), 之後便能利用 = 運算式去指定產生新物件(這時編譯器用的才是指定型建算式), 參考以下範例:
            // aa.c
            #include <stdio.h>
            class  A { // 自訂的新類型
                int   *resource; // 指標成員, 初始化前不得使用
                int    size;           // 一般的整數成員             
                public:
                void Init (int n=16) {  // 初始化方法, 預設參數 n=16
                        size = n;      // 儲存空間的記憶量
                        resource = new int[size]; //  動態分配記憶空間, 初始化指標 resource
               } // init method
                ~A( ) {  // 0. 解構式, 解除資源
                        printf("deconstuctor\n");
                        if( resource != nullptr )  delete resource;
                }// deconstructor
                A ( ) {  // 1. 基本建構式, 陣列建構也是用此式
                    printf("Init constuctor\n");
                    Init( ); // 初始化資源
                } // basic constructor
                A  (A  &a ) {  //2. 複製型建構式, 簡稱複建式
                    printf("Copy constuctor\n");
                    Init( ); // 初始化資源
                    for(int i=0; i < size; i++ ) resource[i] = a.resource[i];
                }  // Copy constructor
                A  (A  &&a ) {  // 3. 移動型建構式, 簡稱移動式
                    printf("Move constuctor\n");
                    resource = a.resource; // 資源換手
                    size = a.size;                   // 複製一般成員
                    a.resource = nullptr;    // 避免資源被釋放
                }  // Move constructor
                A & operator = (A &a) { //4. 一般指定型運算式,
                     printf("Copy assignment\n");
                     if( &a != this ) {
                          resource = a.resource; // 資源共享(resource 不再另外分配空間)
                          size = a.size;                    // 但還是要複製一般成員
                     }
                     return *this; // 返回物件本身
                 }  // Copy assignment

                A & operator = (A &&a) { // 5. 移動型指定運算式,
                     printf("Move assignment\n");
                     if( &a != this )  {
                              if( resouce !=nullptr )  delete resouce;//避免記憶體洩漏
                              resource = a.resource; // 資源換手
                              size = a.size;                    // 仍要複製一般成員
                              a.resource = nullptr;    // 避免資源被釋放
                      }
                      return *this; // 返回物件本身
                 }  // Move assignment                 
                void testMethod() {      // 測試方法
                       A  copy = *this;       //  will call Copy constructor
                       A  assign;                 //  assign construct ready.
                       assign = *this;         // will call Copy assignment
                } // some test method
            };  //  class customize
int main( ) {
      A a;
      a.testMethod( );
}
用 g++ aa.c && ./a.out 編譯並執行:
Init constuctor     <==  a 建構完成
Copy constuctor  <==  copy   建構複製
Init constuctor     <==  assign 建構
Copy assignment <==  assign 指定
deconstuctor        <==  assign.resource 釋放
deconstuctor        <==  copy.resource 釋放
deconstuctor        <==  a.resource 釋放

            

2019年2月10日 星期日

自訂運匴元的複數四則運算 c++ 程式碼

// z.c
#include <math.h>
#include <stdio.h>
#define nearZero   1e-9
struct Cpxnum { // 自訂的複數類型
    double r,i; // 實部與虛部
    Cpxnum(double x=0, double y=0){ r=x; i=y;}//建構式
    Cpxnum operator + (Cpxnum other) { // 加法
        double x = r + other.r;
        double y = i + other.i;
        return  Cpxnum(x,y);
    }   
    Cpxnum operator - (Cpxnum other) { // 減法
        double x = r - other.r;
        double y = i - other.i;
        return  Cpxnum(x,y);
    }
    Cpxnum operator * (Cpxnum other) { // 乘法
        double x = r * other.r - i * other.i;
        double y = r * other.i + i * other.r;
        return  Cpxnum(x,y);
    }
    Cpxnum operator / (Cpxnum other) { // 除法
        double den = other.r * other.r + other.i * other.i ;
        if( den > nearZero ) { // to prevent divided by zero, other's length must be larger than 0 !!
          double x = (r * other.r + i * other.i)/den;
          double y = (i * other.r - r * other.i)/den;
          return  Cpxnum(x,y);
        }
        return Cpxnum(0,0); // TODO: raise an exception!!
    }
    void dump(){ // 列印複數實部加虛部
        if( abs(r) <= nearZero )       printf("%+fi\n",i);
        else if( abs(i) <= nearZero )  printf("%+f\n",r);      
        else                          printf("%+f + %fi\n",r,i);
    }
};
int main(){
    Cpxnum a = Cpxnum(1,3); // a = 1 + 3i
    Cpxnum b = Cpxnum(2,3); // b = 2 + 3i
    (a + b) . dump();
    (a - b) . dump();
    (a * b) . dump();
    (a / b) . dump();
}
用 g++ 編譯並執行  g++ z.c && ./a.out
+3.000000 + 6.000000i
-1.000000
-7.000000 + 9.000000i
+0.846154 + 0.230769i


2019年2月7日 星期四

dart 簡單的 class 範例

// 將下列程式碼存檔:  mixinclass.dart
abstract class A    { call() => print("A")    ; }
abstract class B    { call() => print("B")    ; }
class O             { call() => print("Super"); }
class OxO extends O { call() => print("OxO")  ; }
class ExO extends O { }
class MxB with B    { }
class MBA with B,A  { }
class MAB with A,B  { }

main() {
     OxO()..call();
     ExO()();
     MxB()();
     MBA()();
     MAB()();
}
執行 dart  mixinclass.dart 看結果:
OxO
Super
B
A
B


2019年2月4日 星期一

dart 基礎

參考資料: https://www.dartlang.org/guides/language/language-tour
重要概念:
1. 宣告成變數的都可成為物件, 所謂物件就是 class 的實體化,它們最終都繼承自 Object.
2. dart 是強型別的語言, 用 var 宣告變數, 而函數雖然可以選擇性的不用標註類型, 但最終從上下文可推斷出適當的類型, 如果要明確認定變數或函數適應各種類型,可以宣告成 dynamic.
3. dart 使用角括號支援廣泛類型,像是 List <int> 或 List <dynamic>, 可以區分出它是列舉整數或者列舉各種類型
4. dar t 允許區域性巢狀函式宣告, 例如在 main( ) 函式內也可以宣告內部函式, 類似把函式放在 class 當成方法
5. dart 在識別字用底線 _ 宣告成私有變數
6. dart 區分陳述值(expressions)別於陳述式(statements)在於 expressions 帶有運算元及運算子, 在執行時會產生一個明確數值,例如條件陳述式  (condition) ? true:false;根據條件產生一個 true 或 false 的陳述值, 而另一種條件陳述式 varName ?? value; 則是當 varName 是 null 時採取 value 的值, 否則套用  varName 本身的值, 等同用 (varName==null) ? varName:value; 縮寫的陳述值
7. dart 產生警告及錯誤兩種報告, 警告是說程式碼也許無法正常運作,但並不會禁止,但如果是在編譯時產生錯誤,就會禁止程式運行,如果在執行期產生錯誤就執行例外程式碼.
8. 底下列出 dart 內建的特殊關鍵字,不得當作變數的識別字:
    absrtract, as, assert, async, await
    break
    case, catch, class, const, continue, covariant
    default, deferred, do, dynamic
    else, enum, export, extends, external
    factory, false, final, for, Function
    get
    hide
    if, implements, import, in, interface, is
    library
    mixin
    new, null
    on, operator
    part, rethrow, return
    set, show, static, super, switch, sync
    this, throw, true, try, typedef
    var, void
    while, with
    yield
9. 如果是透過類型建構式實體化的物件常數就必須用 final 而不是 const.
10. 內建資料類型的關鍵字: num, int, double, bool, String, List, Map, Runes
11. 宣告類型關鍵字: dynamic, var, final, const, void
12. 數值關鍵字: true, false, null
13. 數學運算: +(加法), -(減法), *(乘法), /(除法), %(餘數), ~/(商數), ++(累加一), --(累減一)
14. 比較運算: <(小於),>(大於), <=(小於或等於), >=(大於或等於), ==(等於), !=(不等)
15. 邏輯運算: &&, ||, !
16. 類型測試: is, is!
17. 位元運算: ~, &, |, ^, >>, <<
18. dart 的函式可以推斷出函數的類型, 簡短函式可以用胖箭符來宣告.
19. 可忽略的參數傳遞,可以用中括號 [ ] 括起來, 預設參數值用等於符號 = 去指定預設值,兩者可互相搭配.
20. 當函式沒有用 return 傳回數值時, dart 仍會傳回 null 這個數值.
21. 類型內可供定義的運算元(operator).  != 其實就是 !( op1 == op2), 因此 != 無須重定義
    數學運算:  +(加法), -(減法), *(乘法), /(除法), %(餘數), ~/(商數)
    比較運算: <(小於),>(大於), <=(小於或等於), >=(大於或等於), ==(等於)
    位元運算: ~(NOT), &(AND), |(OR), ^(exclusiveOR), <<(shiftLEFT), >>(shiftRIGHT)
    陣列索引的 getter 參數是一個索引值: operator  [ ] (index)  { return readValue; }
    陣列索引的 setter 參數則有兩個: operator  [ ]= (index, value) { varWrite = value;  }
22. 新類型宣告關鍵字(class, abstract class, mixin)及類型附屬關鍵字(on, with, extends, implements):
       class  superClass { superClass( ){ }; }// 有建構式的正常類型
       abstract class abstractClass { }// 抽像方法類型, 只有框架, 用於繼承或實現或混合的類型
       class childClass1 extends abstractClass { }// 繼承自抽象類型
       class childClass2 implements abstractClass { }// 實現抽象方法的類型
       class childClass3 extends superClass implements abstractClass { }// 繼承並實現類型
       所謂 mixin (可混型)是一個只有方法(method)和特性(property)但不含建構式的抽象類型, abstract class  不可實體化, 但透過 class 宣告搭配  with 可以層層堆疊混型產生新類型:
       class multiMixin with downMixin, topMixin { }
       多重堆疊混型時, 若內部成員名稱相同, 後者覆蓋前者, 大括號內成員經堆疊後優先權最高. 
23. 類型內定義的靜態(static)成員無須實體化(透過建構式產生物件)就可直接加句點語法取用, enum 實際上就是一個簡化的靜態成員類型, 例如像是 enum colorClass {red, green, blue } 大括號內使用逗號將成員組成的有限集合類型