第 13 章 一些異於 C 程式的 C++ 架構

C++ 程式 與 C 程式 除了 類型(class) 與 物件(object) 的差異外, 尚有一些基本架構的 差異, 本章將 簡略說明幾項 基本差異。

本 章 主 要 內 容 如 下:

回第 12 章
至第 14 章
回 C 程式主目錄


第 13.1 節 變數 的定義

於 C++ 程式中, 變數 可以 在 程式 任何地方 來定義。 一般而言, 變數 宣告 的地方 與 該 變數 引用處 距離 太遠, 常會產生 程式碼的 不易 看懂 及 維修的困難。 在 編寫 程式時, 如果 因程式 的 需要, 必須 再 定義 一個 新的 變數, 同時, 其使用範圍亦小, 我們 可 就近 定義 一個 變數, 而不須 移至 block 的 起點, 通常 這些 變數 都是 迴圈 變數 較多。 如此一來, 程式碼 會變得 較易 編寫、 較易 看懂、 較易 維修 等。

例 1:

       class CA{ ... }; // class CA 的宣告

       ...
         {
            ...
            for( int i=0; i <= n; i++) // 定義變數 i
            { ... }

            CA a; // 定義 class CA 的 一物件 a 
            ...
         }
回本章主目錄

第 13.2 節 資料成員 與 變數 起始值的設定

於 block 區 變數 起始值 的設定, 一般而言, 是在 變數 定義處 加上等號, 如同 一般 的 指派敘述。
於 C++ 中 變數 起始值 的設定 又多了一種 設定的方式, 其 設定的 方式 如下:

	main()
	{
	    int x(10);
	    int y = 20;
            ....
	};
說明:
  1. 對於 int x(10); 而言, 變數 x 的 起始值 為 10。 變數 x 如同一 物件, 變數 起始值 的 設定, 如同 呼叫 class 的 constructor。
  2. 對於 int y = 20; 而言, 變數 y 的 起始值 為 20。
物件 起始值 的設定 則決定 於 該 class 的 constructor。 於 constructor 中 資料成員 起始值的設定 不同 於 指派敘述, 其設定的方式如 例 2 所示:

例 2:

	class className
	{
	    public:	// private 情況 亦同
		dataType1 id1;
		dataType2 id2;
                ...
	    public:
	        className():id1(value1), id2(value2)
                { ... }

	        className(dataType1 v1, dataType2 v2)
                   :id1(v1), id2(v2)
                { ... }
		...
	};

說明:

  1. 對於 constructor className(), 緊接其後 id1 與 id2 起始值的 設定 分別為 value1 與 value2。
  2. 對於 constructor className(dataType1 v1, dataType2 v2), id1 與 id2 起始值的 設定 分別為 v1 與 v2。
註: 我們 亦可以 指派指令 來達到 相同 結果, 如例 3 所示。 但是, 對於 class 中 的 const 則無法 達到 相同的 結果。 我們 將於 第 4 節 討論 class 中 const 起始值 設定的 方法。

例 3:

	class CA
	{
	    public:	// private 情況 亦同
		int  id1;
		char id2;
                ...
	    public:
	        CA()
                { id1 = 10; id2 = 'a'; }

	        CA(int v1, char v2)
                { id1 = v1; id2 = v2; }
		...
	};

回本章主目錄


第 13.3 節 參考型態(reference type)

函數的呼叫, C++ 多了 call by reference, C 程式僅有 call by value。 如此一來, C++ 也像 Pascal 程式語言 具有這兩種函數 呼叫的好處。 於 C 程式語言中, 因沒有 class 與 object, 函數 呼叫中, 引數的 傳遞, 可 經由指標 的 傳遞, 來達到 傳遞中 記憶体 的 精簡。
C++ 多了 call by reference 及 const 的架構, 更可達到 傳遞中 記憶体 的 精簡, 以及 call by value 的好處, 可 避免 實質引數 (actual parameter) 遭到 改變。

第 13.3.1 節 參考型態的變數 -- 變數的別名 (alias)

一個 變數 都有一相隨 的 記憶体, 於 C++ 中 一 記憶体 亦可附有 多個 辨識名稱, 如同 一個人 有姓名, 也有外號, 別名 等。 於 C 及 C++ 亦有 僅有記憶体 而名不存在 的變數, 如 例 4 所示。

例 4:僅有記憶体 而名不存在 的變數

	{
             int *pi;

             pi = new int(10);
             *pi += 2;
	};

說明:

  1. pi 為一指標, 指向一 可存放 int 的記憶体, 該 記憶体 並沒有 相關名稱。
  2. 如果 硬要說 該 記憶体 有 相關名稱, 其 名稱 應為 *pi。
我們 以 指標運算子 * 來宣告 一個 指標 變數, 以 地址運算子 & 來參考 一 變數, 我們 亦以 地址運算子 & 來 達到 一個 變數 有別名 的功能, 其用法 如例 5 所示。

例 5: 不同變數名稱 但 參考 同一 記憶体。

	{
             int i;
             int & referToi = i;  // 變數 referToi 與 變數 i 異名同体

             i = 10;
             cout << "referToi = " << referToi << endl;

             referToi = 20;
             cout << "i = " << i << endl;

	};
說明:
  1. 變數 referToi 與 變數 i 的相隨 記憶体 都一樣 並沒有 差別, 即 referToi 是 變數 i 的別名。
  2. 改變 referToi 與 i 當中 任何 一個 變數, 另一個 變數 也隨著 改變。
  3. referToi 本身所存的值是 變數 i 的地址, 但卻不是 變數 i 的 指標, 引用 變數 referToi 時, 是在 參考 變數 i。
  4. 該程式 執行 的 結果 為:
    referToi = 10
    i = 20
    

回本章主目錄

第 13.3.2 節 參考型態的引數 -- call by reference

對於 函數 的 呼叫, 我們 亦以 地址運算子 & 來 達到 call by reference 的功能, 其用法 如例 6 所示。

例 6: call by reference。

        void swap(int &x, int &y)   // call by reference
        {
            int temp;

            temp = x;  x = y;  y = temp;
        }

	main()
        {
             int i, j;

             cout << "Input 2 numbers:" << endl;
             cin >> i >>j;

             if( i > j ) 
                 swap(i, j);

             cout << "The smaller number is " << i << endl;
             cout << "The larger is " << j << endl;

	};
說明:
  1. 函數呼叫 swap(i, j) 時, 引數的 傳遞 如同
    int &x=i; int &y=j;
  2. 引數 x 為 變數 i 的 別名, y 為 變數 j 的 別名。
  3. 於函數 swap 中, 改變 引數 x 與 y 的值, 變數 i 與 j 也隨著 改變。
  4. 該程式 執行 的 結果 為:
    Input 2 numbers:
    100  50
    The smaller number is 50
    The larger is 100
    

參考型態 變數 可用來 作 class 的 member function 所 return 的值, private data member 亦可 變成 public, 如 例 7 所示。

例 7: member function 所 return 的值 為 參考型態 變數。

        class CA
        {
          private:
            int y;
          public:
            int x;
            CA():x(0){}     // x is initialized to 0
            int &f(int y)
            { x = y+20; return x;}
            int &g()
            { return y;}
        };

	main()
        {
             CA a;

             a.x = 2;
             a.f(2) = a.x + 3; // 函數呼叫 位於 等式 左側
             a.g() = 10;       // private 變成 public
             cout << "a.x = " << a.x << endl;
             cout << "a.y = " << a.g() << endl;
	};
說明:
  1. 函數呼叫 a.f(2) 時, a.x = 22, 又 a.f(2) 函數值 為 a.x, 因此,
    a.f(2) = a.x + 3;
    就 如同 下列 程式碼:
    a.x = y + 20;
    a.x = a.x + 3;
  2. 函數呼叫 a.g() = 10; 時, 即 a.y = 10;, 此時, private data member y 變成 public data member。
  3. 該程式 執行 的 結果 為:
    a.x = 25
    a.y = 10

參考型態 變數 可用來 改變 function 中 static 變數 的值, 如 例 8 所示。

例 8: function 中 static 變數 可以如同 一般 變數 來改變 其值。

        int &f()
        {
            static int x = 0;
            x++;     // x 可用來記錄函數被呼叫的次數
            return x;
        }

	main()
        {
             int i;

             f();f();f();
             i = f();
             cout << "函數 f() 被呼叫 " << i << " 次" << endl;

             i = f() = 0; // 從新設定 static 變數 x 為 0
             cout << "從新設定後, 函數 f() 被呼叫 " << i << " 次" << endl;
	};
說明:
  1. 函數 f() 所傳回的值為 函數 f() 中的 static 變數 x 的參考型態值, 即 變數 x 本身。
  2. 該程式 執行 的 結果 為:
    函數 f() 被呼叫 4 次
    從新設定後, 函數 f() 被呼叫 0 次
    

回本章主目錄


第 13.4 節 const 的宣告

常數的設定 除了 使用 巨集 #define 外, C++ 可以 在變數 定義 前 加上 const, 來標示 該變數 為一常數 ( 已經 不是 變數 了 ), 於程式中 任何地方 都不可以 改變 其值。

第 13.4.1 節 於 block 中 const 的定義

於 block 中 常數 的定義方式 如例 9 所示:

例 9: 於 block 中 常數 的定義。

	main()
        {
             const int MAXSIZE = 100; // 必須 給值
             int a[MAXSIZE];
             ...
//           MAXSIZE = 200; // 錯誤敘述 
	};
說明:
  1. MAXSIZE 為一常數, 其值為 100。

例 10: 於 block 中 常數 與 指標 的關係。

1       main()
2       {
3            const int MAXSIZE = 100; // 必須 給值
4            const int MINISIZE = 50; // 必須 給值
5            int j = 0;
6
7            int * const ip1 = &j;             // 必須 給值
8            const int * ip2 = &MAXSIZE;       // 必須 給值
9            const int * const ip3 = &MAXSIZE; // 必須 給值
10           ...
11           ip2 = &MINISIZE;
12           *ip1 = 10;
13           ip1 = &MAXSIZE;  // error!
14           ip2 = &j;        // ok!
15           *ip2 = 20;       // error!
16           ip3 = &MINISIZE; // error!
17           *ip3 = 10;       // error!
	};
說明:
  1. 由第 7 行 int * const ip1 = &j; 知, ip1 為一常數, 其值不可以被 改變, 但是, 其所指的 內容, 即變數 j 可以 被 改變, 因此, 第 12 行 是 沒有錯誤的, 但是, 第 13 行 是 有錯的。
  2. 由第 8 行 const int * ip2 = &MAXSIZE; 知, ip2 所指的 內容 是常數, 不可以 被 改變, 因此,第 15 行 是有錯的。 但是, ip2 本身卻是可以 變動的, 如 第 14 行。
  3. 由第 9 行 const int * const ip3 = &MAXSIZE; 知, ip3 本身 及 所指的 內容 均為 常數, 不可以 被 改變。

回本章主目錄

第 13.4.2 節 與 參考型態引數 的結合

函數 中 以 call by reference 的方式 來傳遞 引數, 則 實質 引數 將可 任意 被改變, 欲 限定 其使用 方式, 以達到 實質 引數 的被 保護, 同時, 資料 傳遞間 記憶體 的 精簡, 則 可以 利用 下列 方式 如 例 11 來達成 目的。

例 11:

       struct S{ int a1, a2, .....};
       struct T{ int b1, b2, .....};

       void g(const struct S &a, const struct T &b)
       { ... }

       main()
       {
            struct S x;
            struct T y;
            ...
            g(x, y);
            ...
	};
說明:
  1. 由 函數的呼叫 g(x, y); 與 函數 void g(const struct S &a, const struct T &b) 的關係,得知
    a 的實際內容 為 變數 x 的 地址, 但引用 a 如同 引用 x
    b 的實際內容 為 變數 y 的 地址, 但引用 b 如同 引用 y。
  2. 由 函數void g(const struct S &a, const struct T &b) 得知 a 與 b 為常數, 即 x 與 y 不因函數 g() 的呼叫 而 被改變。

回本章主目錄

第 13.4.3 節 於 class 中 const 的定義

於 class 中 const data member 的定義方式 如例 12 所示:

例 12:

        class CA
        { public:
             CA(): MAXSIZE(100){s = new int[MAXSIZE];}
             CA(int n): MAXSIZE(n){s = new int[MAXSIZE];}
             int size(){return MAXSIZE;}
             ...
          private:
             const int MAXSIZE; // error if "const int MAXSIZE = 100;"
             int *s;
        };

	main()
        {
             CA a1, a2(200);
             ...
	};
說明:
  1. 於 class 中 MAXSIZE 為一常數, 其值的設定 必須 在 class 的 constructor 中 起始值 設定區 來 設定。
  2. a1.size() 為 100。
  3. a2.size() 為 200。

一個 class 的 object 可以 定義 為 一 const object, 但 僅有 有加上 const 用來標明 該 object 是一個 常數 的 member function 方能 被 引用, 如 例 13 所示。

例 13:

        class CA
        { public:
             CA(): MAXSIZE(100){s = new int[MAXSIZE]; dummy=0;}
             CA(int *a, int n): MAXSIZE(n)
             {  dummy=1;
                s = new int[MAXSIZE];
                for(int i=0; i < n; i++) s[i]=a[i];
             }
             int size(){return MAXSIZE;}
             int getAverage() const; // for const object only
             void setConstant(int n)
             { for(int i=0; i < MAXSIZE; i++) s[i]=n; }

          private:
             const int MAXSIZE;
             int *s, dummy;
        };

     int CA::getAverage() const // const 仍然要標示出來
        {
            int sum = 0;
//          dummy = 10;  //Error, since the object is specified const.
//          s[0] = 6;    //OK, since s in not changed!
            for(int i=0; i < MAXSIZE; i++)
               sum = sum + s[i];
            return sum/MAXSIZE;
        }

	main()
        {
             int a[]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
             const CA ca(a, 10);
//           ca.setConstant(10);   // Error, since object ca is a constant!
             cout << "Average = " << ca.getAverage() << endl;
	}
說明:
  1. 於 class 中的 member function getAverage() 標示 該 object 為一常數。 因此, 在函數中 class 的 data member 都不可以 被 改變。 即 MAXSIZE, s, dummy 均不可 放在 指派敘述 的 左側。
  2. 雖然 s 於 member function getAverage() 中 不可以 被 改變。 但是, s 所指的 內容 卻 容許被 改變。 因此, 於 該 member function 中 s[0] = 6; 是被允許的。
  3. 於 main 中, object ca 是一 const。 因此, 只能 引用 class 宣告 中 具有 標示 使用 const object 的 member function, 如 getAverage()。 對於 member function setConstant(10) 而言, 就不能 被引用。

回本章主目錄


第 13.5 節 overloaded 函數

於 C 程式 一 個檔案中 函數 的名稱 必須 是 唯一 的, 於 C++ 程式 就沒有 這種 限制, 同一個 函數 的名稱 可以 有 多種 不同 的定義, 只要 其形式 引數 的個數 不一 或 其中 有一個 資料型態 不一樣 就可以, 即其 signature 不一樣 就可以, 如 例 14 所示。

例 14:

        int f(){ return 10;}
        int f(int x){ return x+100; }

        int f(int x, int y){return x*y;}

	main()
        {
           cout << "f() = " << f() << endl;
           cout << "f(100) = " << f(100) << endl;
           cout << "f(8, 9) = " << f(8, 9) << endl;
	};
說明:
  1. 該程式 執行 的 結果 為:
    f() = 10
    f(100) = 200
    f(8, 9) = 72

回本章主目錄


第 13.6 節 函數模式 與 類別模式

設計程式時, 我們 可能會 碰到 一種情況, 如 欲利用 insertion sort 對於 兩種或多 種 不同 的 資料 來 排序, 對於 一種 資料 型態 就要 寫 一 種 insertion sort, 這也太麻煩了, 畢竟 這些函數 的架構都一樣, 因此, C++ 程式 提供了 函數模式, 使用 者 只要 將 資料 填入, 就可以 直接 使用一種新的 函數。

函數模式 設定 的架構 如下:

      template< class type[, class Type] >
      returnDataType functionName(parameter list)
      { body of the function }
以例來說明,如例 15。

例 15:

        template< class T >
        void insertionSort(T *a, const int n)
        {
            T current;

            for (int i=1; i < n; i++)
            { current = a[i];
              for (int k = i-1; k >= 0 ; k--)
                 if (current < a[k])   
                    a[ k+1 ] = a[k];
                 else break;
              a[ k+1 ] = current;
            }
        }

	main()
        {
             int a[]={21, 12, 33, 14, 51, 26, 7, 78, 9, 10};
             char b[]={'t','7','z','K','S','1','u','B','0','A'};

             insertionSort(a, 10);
             insertionSort(b, 10);

             for(int i=0; i < 10; i++)
                cout<< a[i] << " ";
             cout << endl;

             for(i=0; i < 10; i++)
                cout<< b[i] << " ";
             cout << endl;
	};
說明:
  1. 該程式 執行 的 結果 為:
    7 9 10 12 14 21 26 33 51 78
    0 1 7 A B K S t u z

對於 class 的 設計 來說 也有 相同 的情形, 譬如, stack 可以 用於不同 的資料 型態, 其 class 的 設計 也都完全一樣, 只是 資料 型態不同 而已。 因此, C++ 程式 提供了 class 模式, 使用 者 只要 將 資料型態 填入, 就可以 直接 使用一種新的 class。

class 模式 設定 的架構 如下:

      template< class type[, class Type] >
      class className
      { body of the class };
以例來說明,如例 16。

例 16:

        template< class T >
        class CStack
        {
            public:
              CStack():MAXSIZE(100){top=0; s = new T[MAXSIZE];}
              CStack(int n):MAXSIZE(n){top=0; s = new T[MAXSIZE];}
             ~CStack(){delete [] s;}

              int isEmpty(){ return top==0;}
              int isFull(){ return top == MAXSIZE;}
              void push(T x){s[top++] = x;}
              T    pop(){ return s[--top];}

            private:
              T *s;
              int top;
              const int MAXSIZE;
        };

	main()
        {
             int a[]={21, 12, 33, 14, 51, 26, 7, 78, 9, 10};
             char b[]={'t','7','z','K','S','1','u','B','0','A'};

             CStack< int > s1;
             CStack< char > s2;

             for(int i=0; i < 10; i++)
                s1.push(a[i]);

             for(i=0; i < 10; i++)
                s2.push(b[i]);

             while(!s1.isEmpty())
                cout << s1.pop() << " ";
             cout << endl;

             while(!s2.isEmpty())
                cout << s2.pop() << " ";

             cout << endl;
	};
說明:
  1. 該程式 執行 的 結果 為:
    10 9 78 7 26 51 14 33 12 21
    A 0 B u 1 S K z 7 t

回本章主目錄


第 13.7 節 inline 函數

當函數的定義僅有兩三行時, 函數的呼叫, 其額外的處理動作, 相對於函數的呼叫就相當大。 此時, 函數的呼叫如果能像巨集指令一樣, 在程式編譯時, 函數的呼叫, 就以 函數的定義 來替代, 如此一來, 程式的執行就可免去 函數呼叫的額外處理動作。 C++ 程式語言就提供了 這種功能, 只要在函數的定義之前加上 inline 即可, 不過 函數的 程式碼 是否被替代, 則由 編譯器 自行裁決, 如 例 17 所示。

例 17:

        inline void swap(int &x, int &y)
        {   int temp; temp = x; x = y; y = temp; }

        main()
        {   int a, b;

            cout << "Input 2 numbers: ";
            cin >> a >> b;
            if(a > b)  swap(a, b);
            cout << "min(a, b) = " << a << endl;
            cout << "max(a, b) = " << b << endl;
        }
經過編譯後, 該程式就像下列的程式:
        main()
        {   int a, b;

            cout << "Input 2 numbers: ";
            cin >> a >> b;
            if(a > b)
            {  int &x = a; int &y = b;
               int temp;  temp = x; x = y; y = temp; }
            cout << "min(a, b) = " << a << endl;
            cout << "max(a, b) = " << b << endl;
        }

class 的 member function 也有相同功能, 一般而言, member function 的 定義於 class 的定義中, 就是一種 inline的定義。 通常 member function 的 定義僅有兩三行時, 我們才在 函數定義前加上 inline, 如 class CStack 的 定義, 可以在 member function isEmpty() 等之前 加上 inline。

回本章主目錄


第 13.8 節 檔案的輸入與輸出格式

C 程式 中 由 stdio.h 檔中 定義 了 檔案的 輸入 與 輸出 格式, 如

FILE *fp; 
fp = fopen("../input.txt", "r");
fscanf(fp, "%c %d", &cvaribale, &intvariable);
於 C++ 中 由 fstream.h 檔中 定義 了 檔案的 輸入 與 輸出 格式, 檔案 fstream.h 已經 include 了檔案 iostream.h, 因此, 如果 已經含 fstream.h 就不需再 將 檔案 iostream.h 含進去。

宣告輸入檔 的方式 如下:

  1.     ifstream fileObject("file name");
        if( ! fileObject )
        {  // open file failed
           return;
        }
        
  2.     ifstream fileObject;
    
        fileObject.open("file name");
        if( ! fileObject )
        {  // open file failed
           return;
        }
        
其輸入使用方式 如同 cin 的使用方式,如下:
  1. 變數的輸入
    fileObject >> variable1 >> variable2;
           
  2. 檔案結束的檢驗
    while( ! fileObject.eof() ){ ... }
           
  3. 檔案的關閉如同 C 程式 的 fclose(), 其使用方式如下:
    fileObject.close();
    

宣告輸出檔 的方式, 其使用方式 如下:

  1. 如果僅是輸出 而不是 附加 在檔案之後,
    ofstream fileObject("file name", ios_base::out);
    
    其中 ios_base::out 是標示 輸出 而不是 附加 在檔案之後。

  2. 或是以 預設 (default) 方式 (輸出 但不是 附加) 宣告 如下:
    ofstream fileObject("file name");
    
  3. 若是要標示 附加 在檔案之後, 其使用方式 如下:
    ofstream fileObject("file name", ios_base::app);
    
其輸出使用方式 如同 cout 的使用方式,如下:
fileObject << variable1 << string << endl;
fileObject.close();
一個檔案也可以宣告為 同時是 輸入檔 也是 append 輸出檔, 其使用方式 如下:
fstream fileObject("file name", ios_base::in | ios_base::app );

例 19:複製檔案 in.txt 至檔案 out.txt

     #include < fstream.h >

     void main()
     {
         ifstream infile("in.txt");
         ofstream outfile("out.txt");
         char c;

         if(!infile)
         {
             cout << "file in.txt not found!" << endl;
             return;
         }
         if(!outfile)
         {
             cout << "No space for file out.txt!" << endl;
             return;
         }

         while( !infile.eof() )
         {
             infile >> c;   outfile << c;
         }

         infile.close();
         outfile.close();
     }

回本章主目錄


回第 12 章
至第 14 章
回 C 程式 主目錄