--

使用 rand() 產生亂數,會介於 0 ~ RAND_MAX(32767)
調整亂數範圍: 下限 + rand % (上限-下限+1)
設定亂數種子: srand( time(NULL));

--

關於 ++ 與 -- 的問題

1. 若 a=2, b=5 , x = ++(a*b)  /* lvalue required as increment operand| */

這是一個錯誤用法,當使用 ++ 或 -- 時必需要有 左值(l value)
講白一點就是說,使用 ++ 或 -- 時要有變數能去儲存運算,而不是一個式子。

x = y 對於 x 而言它表示的是一個address,而 y 表示的是address裡的內容
l-value在compiletime就知道了,r-value在runtime時才會被知道,也就是說
所有symbol的address在compiletime時就會知道

有些東西只能當 r-value, 例如 literals
有些東西既可當 r-value 又可當 l-value, 例如變數
有些運算子需要 r-value, 例如 + 的兩側
有些運算子需要一個運算元同時可以扮演 r-value 與 l-value 的角色,例如 ++ --


--
關於迴圈

do-while 的語法,在 while 後面要有一個分號。

  do
  {
    i++;
  } while (i<=10);

--
關於陣列

int num[3][3] = {0};                  /* 宣告時才能這樣用,設定為整個都是 0 */
int num[2][2] = {1,2,3,4};            /* 會自己分組 */
int num[2][2] = { {1,2} , {3,4} };    /* 同意       */

這樣宣告是錯的,對 c 語言來說,他不曉得每一組有多少人   (真怪 T_T)
  int num[][] = { {1,2,3} , {4,5,6} ,{7,8,9} };
像下面這樣要講出每組有多少人才對
  int num[][3] = { {1,2,3} , {4,5,6} ,{7,8,9} };

傳遞陣列給函式

  foo (int a[] )        /* 一維陣列,可以這樣搞!! */
  foo (int a[][3])      /* 一定要把每組有多少元素標出來 */
  foo (int a[][] )      /* 這是錯的,因為不知道怎麼分組 */


在 C++ 中並不會檢查陣列的註標,也就是說在使用時,可能不小心超出範圍

--
陣列與指標的配合

1. 字串指標所形成的陣列
char* array[] = { "this", "is", "a", "book" };  /* 宣告一個字元指標的陣列 */
在這個例子中 array[1] 是指向 "this" 這個字串

2. 陣列名稱是指標沒錯,但是它是一個「常數指標」,所以是不能更改的。
int num={1,2,3,4,5}
int *ptr = num;
cout << *(num);       /* 會得到 num[1] 沒錯 */
cout << *(num+1);     /* 希望 num 向下移一個 大錯!! num 是指標常數,不能動的 */
cout << *(ptr+1);     /* 這才是對的,ptr 是一個指標變數,可以移動的 */

3.
int num[] = {1,2,3,4,5};
int *ptr = num;
cout << *++ptr;      /* 會把 ptr 向下移到 num[2] 並印出 num[2] 的值 */
cout << *ptr++;      /* 印出 num[2] 的值,並把 ptr 向下移一格指到 num[3] */
cout << *ptr;        /* 目前 ptr 指到 num[2] */

4. 注意一下這邊 ptr 與 ++ -- 用法順序
main()
{
  int num[SIZE];
  int *ptr = num;

  for (int idx=0; idx<SIZE; idx++)
    cin >> *(ptr++);

  for (int idx=0; idx<SIZE; idx++)
    cout << *(--ptr);
}

5.
void showArray (int data[])   /* 改成 int *data 也是可以 */
{
  for (int idx=0;idx<SIZE;idx++)
    cout << *(data + idx);
}

main() { int num[SIZE]={1,2,3,4,5}; showArray(num); }

6.
int *ptr = NULL;
cout << ptr;     /* we got 0x0 */
cout << ptr+1;   /* we got 0x4 */

7. 使用 new 與 delete 建立二維陣列

int **data;
data = new int*[m];
for(int i = 0; i < m ; i++)
    data[i] = new int[n];


for(i=0; i<m; i++)
    delete [] data[i];
delete [] data;

8. 不要被指標給嚇到了 char* a[] 沒你想的那麼難

void show(char* a[])    /* a[] 是一個陣列 裡面裝字元指標 */
{
  for (int idx=0; idx<SIZE; idx++)
    cout << a[idx] <<endl;
}

char* a[SIZE] = {"this", "is", "a", "book"};
show(a);


--
指標的奧義

1. 使用 ptr++ 表示指標移向下一格,不要想錯了

int num[10];
int *ptr = num;       /* 表示 ptr 指向 num[1] */
ptr++;                /* 表示 ptr 指向 num[2] */

2. 同型別的兩個指標不允許相加;但指標可以相減,
   相減表示兩個指標間相差的元數個數(會自動幫你換算)。

  int num [] = {1,2,3,4};
  int *p = &num[0];          /* 0x23ff28 */
  int *q = &num[1];          /* 0x23ff2c */

  cout << p << endl;
  cout << q << endl;
  cout << q-p;               /* 1 相差一個元素,不要懷疑 */

3. 函式指標 (function pointer)

void swap (int *p, int *q)
{ int tmp = *p; *p = *q; *q = tmp; }

int main()
{ int a=10, b=20;
  void (*fp)(int*, int*);
  fp = swap;
  (*fp)(&a, &b);       /* 寫成 fp(&a, &b) 也行 */
}

4. 函數指標的奧義

double add(double x, double y)
{ return (x+y); }

double multi(double x, double y)
{ return (x*y); }

double run(double x, double y, double (*fp)(double, double) )
{ cout << fp(x,y); }               ^^^^^^^^^^^^^^^ 會送一個 function 進來

int main()
{
  run (18.2, 25.1, add);    /* 進行相加運算 */
  run (281, 2, multi);      /* 進行乘法運算 */
  return 0;
}

5. 窮極無聊的指標

fun ( int array[] [3] )   /* 陣列是3個整數為一組,有多少組不曉得 */
fun ( int (*array)[3] )   /* 與上同義,每個指標指出去的,都是 int array[3] */
fun ( int * array[3]  )   /* 有 3個整數指標,形成一個陣列 */

array → □□□
         □□□

array[0]   □→
array[1]   □→
array[2]   □→

--
static 的用法

static 用在變數上,表示變數所佔用的記憶體,直到程式結束才會歸還。

static 全域變數只允許在一個檔案內使用。而且變數生命週期是從定義處開始,
所以在沒有定義靜態全域變數的區域中,是無法使用 static全域變數。
即使用了 extern 也沒辦法使用。

int main()
{ extern int x; }     /* x 是靜態全域變數,生命週期是從定義處才開始 */
static int x;         /* 因此在 main() 內的用法,是錯的 */


static 區域變數
動態變數在 function 執行時,會在堆疊建立一個區塊供變數使用,
並沒有固定記憶體空間,這種方式建立變數稱為「宣告」

在編譯時如果已配有固定記憶體空間的變數,稱為「定義」如 static / extern
靜態區域變數。變數的生命週期不會隨著函數結束而結束。而要等到整個程式結束。

foo()
{ static int num; }

--
extern 的用法
1. extern 表示只是 宣告, 不是定義(define)
2. 用來表示變數會在其它的位置被定義,可能在同個檔案,或者其他文件中。

void foo()
{
  extern int x;   /* (這邊是宣告)變數x 在別處定義,不能寫成 extern int x=10 */
  x=99;           /* 一定要兩個式子拆開來寫 */
  cout << x;
}

double x=10;      /* 這邊定義全域變數 x */

--

函式的參數傳遞,有給預設引數的,要靠右邊

void foo (int a, float b, float c=2.3, char d='z')

呼叫時: foo(12, 84.1);          /* 合法 此時 c預設2.3 d預設值'z' */
         foo(12, 84.1, 10.1);    /* 合法 狀況同上,但 c 值2.3     */
         foo(12);                /* 不合法,變數少一個            */
--
inline 函數

當一個 function 被呼叫時,程式的控制權會轉到函數上,並且使用堆疊(stack)
記錄返回位址。如此動作會耗費一些時間。

C++ 中的 inline function 被呼叫時,會直接「抄一份」貼到呼叫處。
使用 inline 函數可以減少程式主控權不停轉換。但是 inline 函數會佔用較大記憶體。
因為同一份程式碼,抄了很多次。

--

#define 巨集的使用

前置處理器會直接代換字串,不需要考慮型態問題,沒有函數的呼叫,
所以也省去了變數的定義、回傳值。所以如果十段相同的程式碼,
巨集 - 佔用的記憶體較大,但是程式控制權不用轉移,速度較快。
函數 - 程式較短,佔用的記憶體較小,但控制權要轉移,所以速度慢。

--
字串的處理
char str[10];

1. 讀取一行,可以包括空格
   cin.getline(str, 10);   /* 真正存的資料只會有9個,最後一個是 \0 佔用 */

2. 計算字串長度
   strlen(str)    /* 長度不包含最後 \0 符號,你輸入幾個就算幾個 */

2. 拷貝
   strcpy(a,b)    /* 字串 a 要準備 strlen(b)+1 的長度,因為最後還有一個 \0 */

--

關於 暫存器變數 register 用法
用來增加運算速度,將區域變數存在暫存器中,暫存器變數的活動範圍只在
所屬的左右大括號中。

foo()
{ register int num=10; }

--
關於 reference(參考) 的使用

1. 基本用法
int num;
int &ref = num;   /* 在這邊 ref 就是一個參考,指向 num */

2. 用來達到 swap 的功能
void swap(int &a, int &b)

3. 一個函式也能傳回 reference

int& foo (int &a, int &b) { return ( a>b? a:b); }  /* 這邊的 a,b 都是參考 */

main () { int num1, num2;
          num1=20; num2=30;
          foo(num1, num2)=99;  /* foo() 執行完後,會回傳較大者的位址 */
        }                      /* 之後把該變數值改成 99 */


--

volatile 加在變數的前面,表示該變數不會使用最佳化編譯。有時變數的值改變了
compiler 並不會馬上將他寫入記憶體中而先把結果放在CPU暫存器中 等到處理結束
之後 才寫入記憶體。若說這個變數是多執行緒的 flag 其他的執行緒要透過這個
變數來反應狀況。

而這個值卻又沒有寫入記憶體 這時便會發生意想不到的結果又或者是這變數為一個
硬體的暫存器會被硬體所改變然而compiler 並沒有正確的將值從硬體暫存器取出來
而是將自己暫存的值拿來使用。

這種情況 就是要用volatile 來宣告變數 告訴compiler不要自己暫存變數來提升速度
如此這個變數有任何的改變 便會馬上反應出來

*ptr = 0;
while(*ptr){     會被聰明的編譯器給最佳化→   *ptr = 0
    *ptr = 4 ;   但是每個值的改變,可能表     while(0) {}
    *ptr = 0 ;   示 hardware 的一個狀態,
}                不允許省略的!!

--

結構的用法
結構的大小是「欄位總長度」+「數個位元組」,讓存放在記憶體的下一個變數
起始處是偶數位址。(其實測的結果,長度都是 8 的倍數 8 16 24 32...)

1. 直接在定義後面,宣告出變數

struct myData
{
  char name[10];
  int age;
} Jack, Peter, Tom;      /* 宣告 Jack... 為 myData 格式 */

或者

struct myData Jack = { "jack", 18 };

cout << sizeof(Jack);   /* 得到結果是 16 */

2. 相同的結構允許直接拷貝
struct myData Jack = { "jack", 18 };
struct myData Tom;
Tom = Jack;

3. 位元欄位 (unsigned bit)  ... 有考過

struct File {
  unsigned int face :4    /* 使用 4位元 */
  unsigned int suit :2    /* 使用 2位元 */
  unsigned int color:1
};


--

Union 的用法   (省空間的作法 會取最大欄位那個)

union myUni
{
  char c;      /* 字元佔 1 byte       ■□□□         */
  int score;   /* 整數佔 4 byte       取最長的 4byte   */
};

myUni obj = {65} ;
cout << sizeof(obj);   /* 得到 4byte */
cout << obj.c;         /* 會得到英文字母 A (因為ascii A 65) */
cout << obj.score;     /* 會得到 65  */

--

enum 的用法

1. 基本值都是從 0 起跳,別忘了
enum myEn
{
  A,B,C,D=7,E,F       /* A=0, B=1, C=2, D=7!!, E=8 ... */
};                    /* 注意了,這邊的值要給整數 */

int main()
{
  myEn X=E;    /* we get 8 here , X 是列舉形態,要用列舉的格式 */
  cout << X;
  myEn Y;
  Y=1;         /* 在 c 語言可以這樣用,但在 c++ 要改 Y=static_cast<myEn>(1) */
  return 0;
}

2.

enum myEn
{ tennis, swimming, baseball };   /* tennis=0 swimming=1 baseball=2 */

enum myEn X=tennis;
switch (X)
{
  case tennis:              /* 這邊也可改成 case 0 */
    cout << "tennis";
    break;
  case swimming:
    cout << "swimming";
}

--
typedef 的用法

typedef struct
{
  int hour;
  int min;
} myTime;              /* myTime = struct {...} */

myTime clock[3];       /* 宣告時鐘陣列,每個元素都是 myTime 結構 */

--

談類別之前,先提一下 this 指標

  在 C++ 中這是指標,指向物件自身。           this->name
  在 JAVA 中是參考,語法有些微差異勿混用。    this.name

--

討論一下類別

class CWin
{
private:
  int height;
  int width;
public:
  CWin() {}
  CWin(int height, int width) { this->height = height; this->width=width; }
  int area();
};

/* scope resolution operator 範疇解析運算子 */
int CWin::area() { return (width*height); }

動態建立一個物件   CWin *ptr = new CWin();
使用指標呼叫       ptr->area();
刪除物件           delete ptr;

--

討論一下建構子 (constructor) / 解構子 (destructor)

class CWin
{
  private:
    char id;
    int width, height;
}

0. 非常重要的事 A為父類別,B為子類別
   建構子執行順序 A()  -> B()
   解構子執行順序 ~B() -> ~A()

1. 在建構子內直接初始化成員 (千萬記得,這招只能在建構子使用)
   CWin( char x='Q', int y=10, int z=15 ):id(x), width(y), height(z)
   直接指定 id=x, width=y, height=z

2. 建構子:不會有傳回值,但也不能宣告為 void!!
   因為系統建立物件時會主動呼叫 constructor,建構子跑完才會回到主程式。
   所以不用傳回值。

3. 解構子:與類別名稱同名,但多加一個 ~ 的函式稱為解構式。
   與建構式相同,解構式也不能有傳回值。

4. 跟 JAVA 相同 C++ 預設會為每個類別建立一個空的「預設建構子」
   預設建構子不會有任何引數,也不會做任何事。以類別 CWin 來說:

   CWin() {}  這就是預設建構子

   但如果我們有自訂建構子,那麼系統就不會自動建立預設建構子

5. 建構子的引數也可以提供預設值,當某個引數缺少時,會直接使用此值。

6. 注意建構子發生混淆的狀況
   建構子有兩個:
     CWin( char x='Q', int y=10, int z=15 ) {} /* 內建預設值,使用時可省略 */
     CWin( ) {}
   主程式中使用:  /* 對應錯誤!! 不知選哪個建構子 !! */
     CWin obj;     /* 要解讀成第一個,還是第二個?    */

7. 最先建立的物件,會是最後才進才解構

     myClock(char str[]) { strcpy(id,str);  cout << "create:" << id <<endl;}
     myClock::~myClock() { cout << "bye" << id <<endl; }
     main() { myClock obj("no.1"); myClock obj2("no.2"); }

   會得到
     create:no.1
     create:no.2
     byeno.2            /* 2號最後建立  但是最先解構 */
     byeno.1

--
拷貝建構子 (之前很少用過,其實也沒什麼)
用法大概是這樣

class myClock { char *name, int hour, int min }

myClock obj1("no.1", 12, 37);
myClock obj2(obj1);              /* 建立 obj2 物件,並把obj1 的資料拷過來 */

系統給的 預設 拷貝建構子會是這種型態: myClock(const myClock &)

[注意] 這邊會有一個 bug 當我們的成員變數有指標時,他會把指標的位址抄一份過來
       也就是說在上例中,obj1 與 obj2 的 *name 指向「同一份 no.1」
       這是不正確的。應該要存有兩個副本,否則一者 delete 掉之後,另一者就無法
       參考到。
                 obj1.name ---> [no.1] <--- obj2.name

這個問題可以自行定義拷貝建構子,來解決。改寫下面這個建構子。

myClock (const myClock &src)     /* 注意傳進來的一定是 const 參考。 */
{
  /* 把字串拷貝過來 這樣就不是共享同一份 自己指定要寫入什麼值 */
  name = new char[ strlen(src.name)+1 ]
  strcpy (name, src.name);
  hour = src.hour;
  min  = src.min;
}

--

多載運算子( operator overloading )

class myClock
{
  int hour, min;
  myClock(int x, int y) { hour=x, min=y; }

  void operator+ (const myClock &rhs)  /* 傳 reference 進來,而且是 const */
  { hour = (hour + rhs.hour) ; min  = (min  + rhs.min); }
}

使用時:
  myClock obj1(12,31);
  myClock obj2(1,1);
  obj1+obj2;         /* 也可以寫成 obj1.operator+(obj2) */

我也可以把加法改成這樣,算完之後會回傳一個結果:

  myClock operator+ (const myClock &rhs)
  { return ( myClock( (hour+rhs.hour) , (min+rhs.min) ) ); }

使用時:
  myClock obj1(12,31);
  myClock obj2(1,1);
  myClock obj3 = obj1+obj2 ;    /* 記得準備一個變數來接 相加後傳回的結果 */


另一個要注意的就是: 等於(=)運算子
這邊會出現的問題就跟「拷貝運算子」那邊是一樣的,當物件的資料如果有指標時
去做 等於= 運算時只會把 address 抄一份,而不是複製出另一份實體。
造成兩個變數共用一份實體。

class myClock
{
private:
  int hour, min;
  char *name;

public:
  void operator= (const myClock &rhs)
  {
    hour = rhs.hour, min=rhs.min;
    name = new char[ strlen(rhs.name)+1 ] ;
    strcpy (name, rhs.name);                 /* 直接拷一份過去 */
  }
}


--

類別中的 靜態(static)變數與函式
一個 static 變數必需在類別內部宣告,但初值指定要在 class 外部 !!

class myClock
{
private:
  int hour,min;
  const static double pi=3.14;   /* 靜態的 const 變數,可以在類別內初始化 */
  static int dummy_count;
public:
  static int count;                             /* 宣告靜態變數 */
  static void get_count() { cout << count; }    /* 宣告靜態方法 */
  myClock(){ hour = min = 0; count++; }         /* 每新生一個物件,計數加1 */
};

/* C++ 的 類別靜態成員 不可以在建構子內初始化 */
int myClock::count = 0;
/* 即使是 private 變數仍是這樣做 */
int myClock::dummy_count = 0;

int main()
{
  myClock obj[10];
  cout << myClock::count;   /* 這邊的計數值=10 因為上面有10個物件產生 */
  myClock::get_count();     /* 同樣道理,這是示範 static method 用法  */
  return 0;
}


靜態函數有一些限制,因為沒有物件產生:
  靜態函數 --不能存取--> 類別成員
  靜態函數 --不能呼叫--> 類別函數
  靜態函數 --不能使用--> this 這個關鍵字

但是反過來就不一樣了。

--

有關參考(reference)
CWin win1;
CWin &ref = win1;
cout << ref.width;           /* 使用時 */


--

談論一下 友誼函數 (friend function)

1. 友誼函數可以存取 類別 內的公有、私有類別
2. 友誼函數本身不屬於類別,所以無 public/private 問題
3. 友誼函數在類別內宣告 Prototype 就好,實際的 Body 寫在函數外面。

class CWin
{
private:
  int width, height;
public:
  CWin() { width=height=10; }
friend void getValue(CWin);        /* 定義友誼函數的 prototype */
};

void getValue(CWin X)            /* 友誼函數的 body 注意這邊參數的傳遞 */
{ cout << X.width; }

main()
{  CWin obj;  getValue(obj); }

--

C++ 的 try-catch 機制

C++ 有提供一套 try-catch 機制,在拋出例外時,什麼型別都可丟。
這點不像 Java 有內建了一套 Exception 類別。

在 try{} 中使用 throw 這個指令丟出例外,
在 catch(型別 變數名) {} 會去比對拋出的例外資料,是什麼型別。有符合才做處理。

  int a=10;
  try
  { if (a==10)
      throw ("EXCEPTION");    /* 拋出了一個字串常數!! */
  }
  catch (const char *str)     /* 所以進行型別比對,如果是字串常數,才來這邊 */
  { cout << str ;  }
  catch (int x)               /* 如果拋出一個整數,就會在這邊處理 */
  { cout << x ;    }
  catch (...)                 /* 你沒看錯 "..." 表示什麼例外通吃
  { cout << "..."; }

  cout << "try catch end";    /* 不論是否補捉到,try-catch 完之後就接這邊 */

--

樣板(template)的使用 - 背起來

template <class T, class U>   /* 允許前後型別不同   */
T add ( T x, U y)             /* 吃兩個不同型別變數 */
{                             /* 注意,回傳型別是 T 記得考慮資料精確度 */
  return (x+y);
}

int main()
{
  cout << add <double,int> (12.7 , 10);  /* 也可直接寫成 add(12.7, 10) */
  return 0;
}

上面是 template 用在函式上面;如果用在類別上面,寫法會不同(一場災難):

template <class T>
class myClock
{
private:
  T hour, min;
public:
  myClock(T x, T y) {hour=x, min=y;}
  void setValue(T x, T y) { hour=x, min=y; }   /* 實作在裡面比較省事 */
  void getValue(); /* 用來示範如何在類別外實作,很麻煩!! */
};

template <class T>            /* 不要忘記寫這行,這樣下面函式的 T 才有作用 */
void myClock<T>::getValue()   /* 這個 <T> 的位置也要放對,非常小心 */
{ cout << hour << endl << min; }

int main()
{
  myClock <int>obj(9,23);     /* (易忘)用的時候也要小心,型別要放在物件前 */
  obj.getValue();             /* 這邊只有一個 <int> 而不是 <int, int>  */
  return 0;
}

底下這個例子跟上面一模一樣,但允許傳入 2 個型別。

template <class T, class U>
class myClock
{
private:
  T hour;
  U min;
public:
  myClock(T x, U y) { hour=x , min=y; }
  void setValue(T x, U y);
  void getValue();
};

template <class T, class U>
void myClock<T, U>::setValue(T x, U y)
{ hour=x, min=y; }

int main()
{
  myClock <int, float>obj(12,34);     /* 也是一樣,使用時要先講好,載入的格式 */
  return 0;
}

--

討論一下繼承


class Shape
{
private:
  int width, height;
public:
  Shape()
  {
    cout << "Shape Create";
    width = height = 0;
  }
  void setWidth(int x) { width = x; }
  int getWidth() { return width; }
};

class Circle:public Shape
{
private:          /* 在 Java 中可以使用 super.func() 來呼叫父類的方法 */
  int color;      /* 但 C++  則是該使用 父類別::func() 來進行呼叫,別弄混了 */
public:
  void getWidth() { cout << Shape::getWidth(); }
  void setWidth(int x) { Shape::setWidth(x); }   /* 呼叫父類的 setWidth() */

};

int main()
{
  Circle obj;
  obj.setWidth(18);
  obj.getWidth();
  return 0;
}

--


父類別            |    子類別
public:           |    public:
show_member()     +--> show_member()
                  |
private:          |    private: (子類別成員不能存取)
id, width, height +--> id, width, height
                  |
建構元            +--> 不能繼承
                  |

class father
{ public: show_member();
  private: int id, width, height;
  father()
}

class son:public father      /* 使用 public 方式來繼承,原本屬性不變 */
{
  - 兒子可以呼叫 show_member()
  - 兒子擁有 id, width, height 變數,但是不能存取,要透過父類函式。
  - 父親的建構元是不會繼承給兒子的
}

在 C++ 繼承中,建立子類別物件時,會先呼叫父類別中沒有引數的建構元。
再執行子類別的建構元。

兒子若想呼叫父親特定建構元呢?!
在兒子建構子後面加上  :父類建構子()
就像 son(int x, int y):father(x ,y)   /* 把 x y 傳給父建構子 */

class father
{
  father(int, int, int)
  father()
  father(char, int)
}

class son:public father
{
  son()      :father(10,20,30)    /* 這樣就會呼叫特定父親的建構元 */
  son(int  n)                     /* 這一會對應到 father() */
  son(char c):father (c, 28)
}

son obj;        /* 建構子對應到 son() 且會去呼叫 father(10,20,30) */
son obj(28);    /* 建構子對應到 son(int) 且會去呼叫 father() */
son obj('q');   /* 建構子會對到 son(char) 且會把傳入的字元,再傳給父建構子 */


類別繼承的存取模式

父 (A)            子 B:public A | 子 B:protected A | 子 B:private A
public               public     |    protected     |    private
protected            protected  |    protected     |    private
private              無法存取   |    無法存取      |    無法存取

--

多載(overload)
多載可以使用一個函式名稱來執行不同的實作,這是一種「編譯時期」就需決定的方式,
這是「早期繫結」(Early binding)、「靜態繫結」(Static binding),因為在編譯
時就可以決定函式的呼叫對象,它們的呼叫位址在編譯時就可以得知。

--

多型()
虛擬函式(Virtual function)可以實現「執行時期」的多型支援,是一個「晚期繫
結」(Late binding)、「動態繫結」(Dynamic binding),也就是指必須在執行時期
才會得知調用的物件。 (白話:虛擬函式是達到多型的一種方法)

透過指標或參考的操作,來達到多型的表現。

class A
{ virtual void show() {cout << "from A \n"; } }  /* 宣告 show() 為虛擬函式 */

class B : public A    { void show() { cout << "from B\n"; } }

  A *ptr;    /* 父類別指標 */
  A obj1;
  B obj2;
  ptr = &obj1; ptr->show();   /* 得到 from A */
  ptr = &obj2; ptr->show();   /* 得到 from B 因為 show() 設成 virtual */

或者像這樣用...

class A            { virtual void show() {cout << "from A \n"; } }
class B : public A { void show() { cout << "from B\n"; } }

void call_show( A &obj ) { obj.show(); }  /* 會去呼叫函數內建的show() */

  A obj1;
  B obj2;

  call_show(obj1);   /* 得到 from A */
  call_show(obj2);   /* 得到 from B */

--

純虛擬函數 (pure virtual function)
上面那個議題在討論 virtual function 但在這邊要講的,叫做 pure virtual function
這種函式只有一個空殼。將來會在子類別中實現。

在 Java 中這種函數叫做 抽象函數(abstract function) ;
而在 C++ 中則稱為 純虛擬函數(pure virtual function)
具有 純虛擬函數 的類別,則稱為 抽象類別(abstract class)...這點跟 JAVA 一樣

講白了:pure virtual function 就是只有標頭,至於身體要在子類別中實作。

語法: virtual void function() = 0;     /* 宣告他 =0 , 表示父親這邊不會實作 */

一個類別中如果含有純虛擬函式,則該類別為抽象類別(Abstract class),
該類別只能被繼承,不能用來直接生成實例。

純虛擬函數,也是實現多型的一種技巧。雖然函數名稱相同,但執行時會因為型別不同
而去呼叫對應類別中的函式。

這有一個範例

class Shape
{
public:
  int hight, width;
  void setValue(int x, int y) { hight=x , width=y;}
  virtual int area()=0 ;  /* 宣告 area() 為純虛擬函數 */
};

/* 各子類別去實作 area() 函式 */
class Triangle : public Shape
{ public:     /* public 別忘了加,很重要! 不然其他人怎麼用 */
  int area() { return (hight * width * 0.5); }
};

class Square : public Shape
{ public:  int area() { return (hight*width); } };

使用時:
  Square obj;
  obj.setValue(12, 11);
  cout << obj.area();      /* 會呼叫到 Square 的 area() */

--

memory leak 的問題

  A *ptr = new A;
  A obj;

  ptr = &obj;   /* 把 ptr 改指向另一個物件,原本 new 出來的物件 失連 */
  ptr->show();

  delete ptr;   /* 而且 obj 物件並不是動態配置產生,所以 delete 是沒效的 */
  ptr->show();  /* 仍是照舊可以呼叫 show() */

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 kcw 的頭像
    kcw

    kcw

    kcw 發表在 痞客邦 留言(0) 人氣()