1. 程式人生 > >創建功能更強的類型

創建功能更強的類型

namespace clas iostream 模型 如何實現 解決方法 true 面積 轉換器

抽象的過程

*計算機的工作是建立在抽象的基礎上。 -機器語言和匯編語言是對機器硬件的抽象 -高級語言是對匯編語言和機器語言的抽象 *現有抽象的問題: -要求程序員按計算機的結構去思考,而不是按要解決的問題的結構去思考。 -當程序員要解決一個問題時,必須要在機器模型和實際要解決的問題模型之間建立聯系。 -而計算機的結構本質上還是為了支持計算,當要解決一些非計算問題時,這個聯系的建立是很困難的 面向對象的程序設計 *為程序員提供了創建工具的功能 *解決一個問題時 -程序員首先考慮的是需要哪些工具 -創建這些工具 -用這些工具解決問題 *工具就是所謂的對象 *現有的高級語言提供的工具都是數值計算的工具 過程化vs面向對象
以計算圓的面積和周長的問題為例 *過程化的設計方法:從功能和過程著手 -輸入圓的半徑或直徑 -利用S=πr2和C=2πr計算面積和周長 -輸出計算結果 *面向對象的程序設計方法: -需要什麽工具。如果計算機能提供給我們一個稱為圓的工具,它可以以某種方式保存一個圓,告訴我們有關這個圓的一些特性,如它的半徑、直徑、面積和周長。 -定義一個圓類型的變量,以他提供的方式將一個圓保存在該變量中,然後讓這個變量告訴我們這個圓的面積和周長是多少 面向對象的程序設計的特點 *代碼重用:圓類型也可以被那些也需要處理圓的其他程序員使用 *實現隱藏: -類的創建者創造新的工具 -類的使用者則收集已有的工具快速解決所需解決的問題 -這些工具是如何實現的,類的使用者不需要知道 *繼承:在已有工具的基礎上加以擴展,形成一個功能更強的工具。如在學校管理系統中,可以形成如下的繼承關系 *多態性: -當處理層次結構的類型時,程序員往往想把各個層次的對象都看成是基類成員。 -如需要對教師進行考核,不必管他是什麽職稱,只要向所有教師發一個考核指令。每位教師自會按照自己的類型作出相應的處理。如高級職稱的教師會按高級職稱的標準進行考核,初級職稱的教師會按初級職稱的標準進行考核。 *好處:程序代碼就可以不受新增類型的影響。如增加一個院士的類型,它也是教師類的一個子類,整個程序不用修改,但功能得到了擴展。 庫和類
*類是更合理的庫 *例:設計一個庫,提供動態整型數組服務,該數組滿足兩個要求: -可以任意指定下標範圍; -下標範圍可在運行時確定; -使用下標變量時會檢查下標的越界。 庫的設計 *數組的保存 -數組需要一塊保存數組元素的空間。這塊空間需要在執行時動態分配。 -數組的下標可以由用戶指定範圍。因此,對每個數組還需要保存下標的上下界。 -保存這個數組的三個部分是一個有機的整體,因此可以用一個結構體把它們封裝在一起。 *數組操作 -給數組分配空間 -給數組元素賦值 -取某一個數組元素的值 -由於這個數組的存儲空間是動態分配的,因此,還必須有一個函數去釋放空間 Array庫的頭文件
 1 #ifndef _array_h
 2 #define _array_h
 3 
 4 //可指定下標範圍的數組的存儲
 5 struct IntArray
 6 { int low;  
 7   int high;
 8   int *storage;
 9 };
10 
11 //根據low和high為數組分配空間。分配成功,返回值為true,否則為false
12 bool initialize(IntArray &arr, int low, int high);
13 //設置數組元素的值
14 //返回值為true表示操作正常,返回值為false表示下標越界
15 bool insert(const IntArray &arr, int index, int value);
16 //取數組元素的值
17 //返回值為true表示操作正常,為false表示下標越界
18 bool fatch(const IntArray &arr, int index, int &value);
19 //回收數組空間
20 void cleanup(const IntArray &arr);
21 #endif

Array庫的實現文件

 1 #include "array.h"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 //根據low和high為數組分配空間。
 6 //分配成功,返回值為true,否則為false
 7 bool initialize(IntArray &arr, int low, int high)
 8 {arr.low = low;
 9  arr.high = high;
10  arr.storage = new int [high - low + 1];
11  if (arr.storage == NULL) return false; else return true;
12 }
13 
14 //設置數組元素的值
15 //返回值為true表示操作正常,返回值為false表示下標越界
16 bool insert(const IntArray &arr, int index, int value)
17 {if (index < arr.low || index > arr.high) return false;
18  arr.storage[index - arr.low] = value;
19  return true;
20 }
21 //取數組元素的值
22 //返回值為true表示操作正常,為false表示下標越界
23 bool fatch(const IntArray &arr, int index, int &value)
24 {if (index < arr.low || index > arr.high) return false;
25  value = arr.storage[index - arr.low] ;
26  return true;
27 }
28 //回收數組空間
29 void cleanup(const IntArray &arr)  { delete [ ] arr.storage; } 

Array庫的應用

 1 #include <iostream>
 2 using namespace std;
 3 #include "array.h" 
 4 
 5 int main()
 6 { IntArray array; //IntArray是array庫中定義的結構體類型
 7   int value, i;
 8 
 9  //初始化數組array,下標範圍為20到30
10   if (!initialize(array, 20, 30))
11       { cout << "空間分配失敗" ; return 1;} 
12 for (i=20; i<=30; ++i) {
13      cout << "請輸入第" << i << "個元素:";
14      cin >> value;
15      insert(array, i, value); 
16  }
17  while (true) {
18      cout << "請輸入要查找的元素序號(0表示結束):";
19      cin >> i;
20      if (i == 0) break;
21      if (fatch(array, i, value)) cout << value << endl;
22          else cout << "下標越界\n";
23  }
24  cleanup(array); //回收array的空間
25  return 0;
26 }

Array庫的問題

*這個數組的使用相當笨拙。每次調用和數組有關的函數時,都要傳遞一個結構體給它。 *我們可能在一個程序中用到很多庫,每個庫都可能需要做初始化和清除工作。庫的設計者都可能覺得initialize和cleanup是比較合適的名字,因而都寫了這兩個函數。 *系統內置的數組在數組定義時就指定了元素個數,系統自動會根據元素個數為數組準備存儲空間。而我們這個數組的下標範圍要用initialize函數來指定,比內置數組多了一個操作。 *當數組使用結束後,還需要庫的用戶顯式地歸還空間。 *對數組元素的操作不能直接用下標變量的形式表示。

Array庫的改進

*將函數放入結構體 *好處: -函數原型中的第一個參數不再需要。編譯器自然知道函數體中涉及到的low, high和storage是同一結構體變量中的成員 -函數名沖突的問題也得到了解決。現在函數名是從屬於某一結構體,從屬於不同結構體的同名函數是不會沖突的。 改進後的Array庫的頭文件
 1 #ifndef _array_h
 2 #define _array_h
 3 struct IntArray
 4 {
 5   int low;  
 6   int high;
 7   int *storage;
 8   bool initialize(int lh, int rh);
 9   bool insert(int index, int value);
10   bool fatch(int index, int &value);
11   void cleanup();
12 };
13 #endif

改進後的Array庫的實現文件

與原來的實現有一個變化:函數名前要加限定

1 bool IntArray::initialize(int lh, int rh)
2 {low = lh;
3  high = rh;
4  storage = new int [high - low + 1];
5  if (storage == NULL) return false; else return true;
6 } 

改進後的Array庫的應用

函數的調用方法不同。就如引用結構體的成員一樣,要用點運算符引用這些函數
 1 int main()
 2 {IntArray array;
 3  int value, i;
 4  if (!array.initialize(20, 30)) { cout << "空間分配失敗" ; return 1;}
 5  for (i=20; i<=30; ++i) {
 6      cout << "請輸入第" << i << "個元素:";     cin >> value;
 7      array.insert(i, value);
 8  }
 9  while (true) {
10      cout << "請輸入要查找的元素序號(0表示結束):";
11      cin >> i;
12      if (i == 0) break;
13      if (array.fatch(i, value)) cout << value << endl;  
14           else cout << "下標越界\n";
15  }
16  array.cleanup();
17  return 0;
18 }

將函數放入結構體的意義

*將函數放入結構體是從C到C++的根本改變 *在C中,結構體只是將一組相關的數據捆綁了起來,它除了使程序邏輯更加清晰之外,對解決問題沒有任何幫助。 *將處理這組數據的函數也加入到結構體中,結構體就有了全新的功能。它既能描述屬性,也能描述對屬性的操作。事實上,它就成為了和內置類型一樣的一種全新的數據類型。 *為了表示這是一種全新的概念,C++用了一個新的名稱 — 類來表示。 類的接口和實現分開 *與庫設計一樣,類的定義寫在接口文件中,成員函數的實現寫在實現文件中。 *某些簡單的成員函數的定義可以直接寫在類定義中。 *在類定義中定義的成員函數被認為是內聯函數。 this指針 *通常,在寫成員函數時可以省略this,編譯時會自動加上它們。 *如果在成員函數中要把對象作為整體來訪問時,必須顯式地使用this指針。這種情況常出現在函數中返回一個對調用函數的對象的引用 動態對象的初始化 *動態變量的初始化是在類型後面用一個圓括號指出它的實際參數表 *如果要為一個動態的IntArray數組指定下標範圍為20到30,可用下列語句:

p = new IntArray(20, 30);

*括號中的實際參數要和構造函數的形式參數表相對應。 為什麽要使用初始化列表 我們完全可以在函數體內對數據成員賦初值!!! *事實上,不管構造函數中有沒有構造函數初始化列表,在執行構造函數體之前,都要先調用每個數據成員對應的類型的構造函數初始化每個數據成員。 *在構造函數初始化列表中沒有提到的數據成員,系統會用該數據成員對應類型的默認構造函數對其初始化。 *顯然利用初始化列表可以提高構造函數的效率。在初始化數據成員的同時完成了賦初始值的工作。 必須使用初始化的情況 *數據成員不是普通的內置類型,而是某一個類的對象,可能無法直接用賦值語句在構造函數體中為它賦初值 *類包含了一個常量的數據成員,常量只能在定義時對它初始化,而不能對它賦值。因此也必須放在初始化列表中。 重載構造函數 *構造函數可以重載,導致對象可以有多種方式構造 *試設計一個時間轉換器,用戶可輸入秒、分秒或時分秒輸出相應的秒數。 時間轉換器的實現
 1 #include <iostream>
 2 using namespace std;
 3 class timer{  int second;
 4   public:
 5     timer(int t)    {second=t;}
 6     timer(int min, int sec)  {second=60*min+sec;}
 7     timer(int h, int min, int sec)  {second=sec+60*min+3600*h;}
 8     int gettime()    {return second;}
 9 }
10 main()
11 {timer a(20),b(1,20),c(1,1,10);
12  cout<<a.gettime()<<endl;
13  cout<<b.gettime()<<endl;
14  cout<<c.gettime()<<endl;
15 }

析構函數不能重載,沒有參數,沒有返回值

缺省的拷貝構造函數

*如果用戶沒有定義拷貝構造函數,系統會定義一個缺省的拷貝構造函數。該函數將已存在的對象原式原樣地復制給新成員 何時需要自定義拷貝構造函數 *一般情況下,默認的拷貝構造函數足以滿足要求。 *但某些情況下可能需要設計自己的拷貝構造函數。 *例如,我們希望對IntArray類增加一個功能,能夠定義一個和另一個數組完全一樣的數組。但默認的拷貝構造函數卻不能勝任。如果正在構造的對象為arr1,作為參數的對象是arr2,調用默認的拷貝構造函數相當於執行下列操作:

arr1.low = arr2.low;

arr1.high = arr2.high;

arr1.storage = arr2.storage;

前兩個操作沒有問題,第三個操作中,storage是一個指針,第三個操作意味著使arr1的storage指針和arr2的storage指針指向同一塊空間。

對象定義時

*拷貝構造函數用於對象構造時有兩種用法:直接初始化和拷貝初始化。 *直接初始化將初始值放在圓括號中,直接調用與實參類型相匹配的拷貝構造函數。如

IntArray array2(array1);

*拷貝初始化是用“=”符號。當使用拷貝初始化時,首先會用“=”右邊的表達式構造一個臨時對象,再調用拷貝構造函數將臨時對象復制到正在構造的對象。如

IntArray array = IntArray(20, 30);

IntArray array1=array2;

自定義拷貝構造函數舉例

 1 class point{ int x, y;
 2  public: point(int a, int b){x=a; y=b;}
 3          point(const point &p)    {x=2*p.x; y=2*p.y;}
 4          void print()   {cout<<x<<"  "<<y<<endl;}
 5 };
 6 void main()
 7 {point p1(10, 20), p2(p1), p3 = p1, p4(1, 2); 
 8  p1.print();  p2.print();  p3.print();  p4.print();
 9  p4 = p1;  p4.print(); //這個p4=p1並沒有調用拷貝構造函數,而是調用的賦值函數
10  }

const對象

*const對象的定義

const MyClass obj(參數表);

*const對象不能被賦值,只能初始化,而且一定要初始化,否則無法設置它的值。 *C++規定:對const對象只能調用const成員函數 const函數 *任何不修改數據成員的函數都應該聲明為const類型。如果在編寫const成員函數時,不慎修改了數據成員,或者調用了其他非const成員函數,編譯器將指出錯誤,這無疑會提高程序的健壯性。

class A { int x;

public:

A(int i) {x=i;}

int getx() const

{return x;}

};

常量成員

*const數據成員只在某個對象生存期內是常量,而對於整個類而言卻是可變的。同一類的不同的對象其const數據成員的值可以不同。 *常量成員的聲明

在該成員聲明前加上const。如

class abc {

const int x;

};

*不能在類聲明中初始化const數據成員。

class A

{

//錯誤,企圖在類聲明中初始化const數據成員

const int SIZE = 200;

int array[SIZE]; //錯誤,未知的SIZE

};

const數據成員的初始化

*const數據成員的初始化只能在類構造函數的初始化表中進行,不能在構造函數中對它賦值。 *例:

class A

{

A(int size); //構造函數

const int SIZE;

}

A::A(int size) : SIZE(size) //構造函數的初始化表

{…}

A a(100); //對象a的SIZE的值為100

A b(200); //對象b的SIZE的值為200

靜態數據成員

*靜態數據成員不屬於對象的一部分,而是類的一部分; *靜態數據成員的初始化不能放在類的構造函數中; *類定義並不分配空間,空間是在定義對象時分配 *但靜態數據成員屬於類,因此定義對象時並不為靜態成員分配空間 靜態數據成員的定義 *為靜態成員分配空間稱為靜態成員的定義 *靜態成員的定義一般出現在類的實現文件中。如在SavingAccount類的實現文件中,必須要如下的定義:

double SavingAccount::rate = 0.05;

該定義為rate分配了空間,並給它賦了一個初值0.05。

*如果沒有這個定義,連接器會報告一個錯誤 靜態數據成員的使用 *可以通過作用域操作符從類直接調用。如: SavingAccount::rate *但從每個對象的角度來看,它似乎又是對象的一部分,因此又可以從對象引用它。如有個SavingAccount類的對象obj,則可以用:obj.rate *由於是整個類共享的,因此不管用哪種調用方式,得到的值都是相同的 靜態成員函數 *成員函數也可以是靜態的。靜態的成員函數是為類的全體對象服務,而不是為某個類的特殊對象服務 *由於靜態成員函數不需要借助任何對象就可以被調用,所以編譯器不會為它暗加一個this指針。因此,靜態成員函數無法處理類中的非靜態成員變量。 *靜態成員函數的聲明只需要在類定義中的函數原型前加上保留詞static。 靜態成員函數的用途 *定義靜態成員函數的主要目的是訪問靜態的數據成員。 *如在SavingAccount類中,當利率發生變化時,必須修改這個靜態數據成員的值。為此可以設置一個靜態的成員函數

static void SetRate(double newRate) {rate = newRate;}

靜態成員函數使用說明

*靜態成員函數可定義為內嵌的,也可在類外定義。在類外定義時,不用static。 *靜態成員函數的訪問:可以通過類作用域限定符或通過對象訪問

類名::靜態成員函數名()

對象名.靜態成員函數名()

靜態的常量數據成員

*靜態的常量數據成員:整個類的所有對象的共享常量 *靜態的常量數據成員的聲明:

static const 類型 數據成員名 = 常量表達式;

*註意const數據成員和static const數據成員的區別。 老版本的兼容 *某些舊版本的C++不支持靜態常量 *解決方法:用不帶實例的枚舉類型 *如:

class sample {

enum {SIZE = 10};

int storage[SIZE];

};

創建功能更強的類型