470 likes | 635 Views
第 å二 ç« . 類別 (Class). 大綱. 物件導å‘程å¼è¨è¨ˆçš„特性 何謂類別?功用何在? 類別定義 類別與物件 æˆå“¡å‡½æ•¸ 建構å與解構å 共享æˆå“¡å‡½æ•¸ 共享資料æˆå“¡ —static 之使用 物件指標之使用 類別與çµæ§‹. 類別 (Class). ç”± C 進展入 C++ 之最é‡è¦è§€å¿µç‚ºç”± çµæ§‹åŒ–ç¨‹å¼ ( Structure programming ) 擴充到 物件導å‘程å¼è¨è¨ˆ ( Object-oriented programming, OOP ) 。 類別 ( Class ) 則是物件導å‘程å¼è¨è¨ˆçš„基礎,也是了解物件導å‘程å¼è¨è¨ˆç‰¹æ€§çš„最é‡è¦åž‹æ…‹ :
E N D
第十二章 類別(Class)
大綱 • 物件導向程式設計的特性 • 何謂類別?功用何在? • 類別定義 • 類別與物件 • 成員函數 • 建構子與解構子 • 共享成員函數 • 共享資料成員—static之使用 • 物件指標之使用 • 類別與結構
類別(Class) • 由C進展入C++之最重要觀念為由結構化程式(Structure programming)擴充到物件導向程式設計(Object-oriented programming,OOP) 。 • 類別(Class)則是物件導向程式設計的基礎,也是了解物件導向程式設計特性的最重要型態: • 封裝(Encapsulation) 、多型(Polymorphism) • 繼承(Inheritance)、多型應用之重載(Overloading)、 • 繼承應用之衍生類別(Derived class) 。 • 樣版庫(Standard Template Library,STL)、 • 視覺化元件庫(Visual Component Liarary,VCL) 。
12-1物件導向程式設計的特性 • 就程式設計而言,有兩種方式: • 1.以設計的程式來影響資料(Around the code),這是結構化程式設計的重點,任何資料的改變都需要程式,程式與資料分開。 • 2. 以資料來影響程式(Around the data),即資料控制著程式碼的存取(data controlling access to code),這是物件導向程式設計的特色,唯有你所定義的資料與程式(函數)才可以對資料作存取。
12-1物件導向程式設計的特性 • 物件導向程式設計的三個特性: • 封裝(Encapsulation) • 多型(Polymorphism) • 繼承(Inheritance)
封裝(Encapsulation) • 封裝(Encapsulation): • 一種將資料(data)與程式碼(code)整合在一起的一種機制(mechanism) ,以確保安全與避免來自外面的誤用。 • 在物件內之資料、程式碼或兩者可以是私有的(private)或公用的(public) 。 • 私有的資料或程式碼只有物件內其他部分有存取權,物件外的程式是無法取得或看到的。 • 若資料或程式碼為公用的,則物件內與物件外的程式都可以存取;原則上公用的為提供一個良好介面以控制存取私有的資料或程式碼。
多型(Polymorphism) • 所謂多型在物件程式設計上就是「一種介面有多種方法(one interface, multiple methods) 」之意,簡單的說就是以單一介面來控制存取一般類別資料的行為。 • 在物件導向程式設計上有其應用,如你要處理三種不同型態之絕對值, 整數(int), 浮點數(float), 倍精度(double),因多型的關係可定義單一介面函數absolute( ),就可使用該三種方法,在程式內須對同一名稱定義不同的方法,編譯器就會自動去判斷在何種情況下使用何種方法。 • 多型可減少程式的複雜度,整合許多相類似或相同的功能附以統一的介面(即函數名稱), 以完成相異的工作。如:函數重載與運算子重載。
繼承(Inheritance) • 所謂繼承是一種承襲的過程,亦即一個物件除了可具有另一物件的特性外,自己也有自己的特性,而自己也可讓另一物件所繼承;宛如家譜或分類之分類,形成一階層式之架構。 • 如紅蘋果是蘋果之一類,蘋果又是水果之一類,水果又是食物之一類等,若不經過分類,將對每一種物體作明確的特性說明;經過分類後,只要簡單描述該物體是屬於何類及其特性即可。
12-2 何謂類別? • 在C++內若要建立一物件就必須建立類別,該類別須以保留字「class」起頭,類別就像結構,它是C++最重要的一種特性,不但包含有結構的特性,即是不同資料型態的集合外,又包含有如何處理該資料的函數,是資料與程式碼結合在一起的資料結構。 • 類別元素稱為成員(member)而不叫欄位(field),其成員分成員變數(member variables)或資料成員 (data member)與成員函數(member function),只有成員函數具有存取各個元素的權利。
12-2 何謂類別? • 類別內對各元素劃分成三大部分: • 一為私有部份(private) • 二為公用部份(public) • 三為保護部份(protected)
12-3 類別定義 • classkey classname • { member list • } • classkey:類別種類可為class,struct或union。 • classname:類別名稱。 • member list:類別內之資料成員與成員函數。
類別一般格式 class classname //類別名稱 { private: //存取指定子後需有冒號「:」 data and functions//私有資料與函數 public: data and functions //公用資料與函數 protected: data and functions //保護資料與函數 } object_name_list; //物件=類別變數
存取指定子 • 存取指定子(access-specifier): • private (私有的) • public(公用的) • protected(保護的) • 擺放位置可循環,但習慣上都把相同部份之資料與函數放在一起,若該資料與函數沒有指定則內定為private。 • object name list為該類別變數,稱為物件(object),類別定義完後可立即指定物件,或在程式內再宣告。
函數介面定義 • 函數介面定義 • 1.內部定義 • 將函數主體直接寫在類別內。 • 2.外部定義 • 將函數主體寫在類別外。
類別定義範例(介面內部定義) • class date //類別名稱 • { private: //私有 • unsigned int year, month, day; • public: //公有 • void set_date (int _year, int _month, int _day) • { year = _year; //函數主體,將外部傳入之資料設定給資料成員。 • month = _month; • day = _day; } • void list_date( ) //將資料輸出 • { cout << "year=" << year<< endl • << "month=" << month << endl • << "day=" << day << endl; } • } birth; //類別定義
12-4 類別與物件 • 類別經定義後就產生一個新的資料型態。 • 宣告為該資料型態之變數稱為類別變數在物件導向程式內稱為物件(Object) 。 • 如前面所定義之類別: • date mybirth, boybirth; • mybirth, boybirth為date類別名稱之類別變數,稱為物件(object)。 • 物件(object),跟一般的變數不同,包含了實作部份(介面),資料也有相當的隱密性,有動作、有行為、有方法,同類別而不同的物件可以共享這些方法,但其資料是不同的。
一般物件之使用 • 類別是一種邏輯上的定義,真正實作就有賴物件。 • 物件是類別的實例(instance),也只有物件才能使編譯器將記憶體分配給它。 • 物件在實作時需依類別定義之介面部分即public內之成員函數或資料成員來運作。 • 成員函數是一種函數,也是類別內的元素,所以物件在存取函數時是以點運算子「.」(dot operator)為之。
物件之範例(date) • date mybirth; • mybirth.set_data(1965, 12, 5); //設定資料 • mybirth.list_data( ); //輸出資料 • 執行結果為 • year=1952 • month=12 • day=5
例題: 設定出生日期資料後,將其輸出。 void list_date( ) //將資料輸出 { cout << "year=" << year << endl << "month=" << month << endl << "day=" << day << endl; } }; void main( ) { date mybirth; //物件宣告 mybirth.set_date(1970,12,5); mybirth.list_date( ); } #include <iostream> //cout using namespace std; class date //類別名稱 {private: //私有 unsigned int year, month, day; public: //公有 void set_date(int _year, int _month, int _day) //自外界取得資料 { year = _year; month = _month; day = _day; } 執行結果如下: year=1970 month=12 day=5
例題:定義圓資料之類別,輸入半徑資料後,將面積輸出。例題:定義圓資料之類別,輸入半徑資料後,將面積輸出。 float list_area( ) //公有成員函數 { return area; } }; void main( ) { Area circle; //物件宣告 circle.get_radius( ); //呼叫成員函數 cout <<"面積="<< circle.list_area( ); } #include <iostream> //cout using namespace std; classArea {private: int radius; //私有資料成員 float area; public: void get_radius( ) //公有成員函數 { cout << "半徑="; cin >> radius; area = 3.14159 * radius * radius; } 執行結果: 半徑=10 面積=314.159
例題:輸入資料到陣列求和與平均後輸出。 void list_data( ) { cout <<"總分="<< total << endl; cout <<"平均="<< ave << endl; } void main( ) { Total_Ave array; //物件宣告 array.get_array( ); //呼叫成員函數 array.list_data( ); //輸出資料 } #include <iostream> using namespace std; class Total_Ave { private: int a[5], total; float ave; public: void get_array( ) { total=0; cout << "輸入5個資料="; for (int n=0; n<5; n++) { cin >> a[n]; total += a[n]; } ave = total /5.0; }
例題:輸入資料到陣列求和與平均後輸出最大值,最小值。例題:輸入資料到陣列求和與平均後輸出最大值,最小值。 int get_max( ){ int max=a[0]; for (int i=1;i<5;i++) if (a[i] > max) max = a[i]; return max;} int get_min( ){ int min=a[0]; for (int i=1;i<5;i++) if (a[i] < min) min = a[i]; return min;} }; void main( ) { Total_Ave array; //物件宣告 array.get_array( ); //呼叫成員函數 cout <<"最大值="<<array.get_max( )<<endl; cout <<"最小值="<< array.get_min( )<<endl; } #include <iostream> using namespace std; class Total_Ave { private: int a[5], total; float ave; public: void get_array( ) { total=0; cout << "輸入5個資料="; for (int n=0; n<5; n++) { cin >> a[n]; total += a[n]; } ave = total /5.0; }
12-5 成員函數 • 成員函數是類別對外的介面,如何使類別內元素發揮其功能以及看起來清楚易懂,就需要將成員函數之主體定義在外面,這就需要藉助範圍運算子「::」,而對較少程式碼如一二列者,能在編譯時放在類別內以加快執行速度,這就需要藉助inline。
範圍運算子「:: 」 • 將成員函數程式碼定義在類別外面,類別定義內只要作函數原型宣告就可。 • 格式如下: • 資料型態 類別名稱 :: 函數名稱(參數) • { • 函數主體; • }
範圍運算子「:: 」 • 一. 類別名稱必須指名,且須放在函數名稱之前。 • 二. 在類別名稱與函數名稱之間需擺上範圍運算子「::」 • 如: • void AA :: get_data( ) {函數主體} • void BB :: get_data( ) {函數主體} • get_data( )在AA與BB類別名稱都相同,但因隸屬於不同的類別而有差異,這是範圍運算子之功用。
例題:輸入資料到陣列求和與平均後輸出。(將成員函數定義在類別外)例題:輸入資料到陣列求和與平均後輸出。(將成員函數定義在類別外) void Total_Ave :: list_data( ) { cout <<"總分="<< total << endl; cout <<"平均="<< ave << endl; } void main( ) { Total_Ave array; //物件宣告 array.get_array( ); //呼叫成員函數 array.list_data( ); //輸出資料 } #include <iostream> //cout using namespace std; class Total_Ave { private: int a[5], total; float ave; public: void get_array( ); //函數原型宣告 void list_data( ); //輸出 }; void Total_Ave :: get_array( ) { total=0; cout << "輸入5個資料="; for (int n=0; n<5; n++) { cin >> a[n]; total += a[n]; } ave = total / 5.0; }
例題:輸入資料到陣列求和,最大值,最小值與平均後輸出。(將成員函數定義在類別外)(1)例題:輸入資料到陣列求和,最大值,最小值與平均後輸出。(將成員函數定義在類別外)(1) int Total_Ave :: get_max( ) { int max=a[0]; for (int i=1;i<5;i++) if (a[i] > max) max = a[i]; return max;} int Total_Ave::get_min( ) { int min=a[0]; for (int i=1;i<5;i++) if (a[i]<min) min = a[i]; return min;} void main( ) { Total_Ave array; //物件宣告 array.get_array( ); //呼叫成員函數 cout <<"最大值="<< array.get_max( ) << endl; cout <<"最小值="<< array.get_min( ) << endl; } #include <iostream> //cout using namespace std; class Total_Ave { private: int a[5], total; float ave; public: void get_array( ); //函數原型宣告 int get_max( ); int get_min( ); }; void Total_Ave :: get_array( ) { total=0; cout << "輸入5個資料="; for (int n=0; n<5; n++) { cin >> a[n]; total += a[n]; } ave = total / 5.0; }
inline函數 • 編譯時會將類別外定義成員函式(簡短程式碼)放入類別定義內的成員函數稱為inline函數。 • 提高執行效率。 • 格式: • inline 資料型態 類別名稱 :: 函數名稱(參數) • { 函數主體; } • 類別內之原型宣告為: • inline 資料型態 函數名稱(參數);
例題:將Area之list_area定義為inline完整程式。 inline float Area :: list_area( ) { return area; } void main( ) { Area circle; //物件宣告 circle.get_radius( ); cout << "面積= " << circle.list_area( ); } #include <iostream> //cout using namespace std; class Area { private: int radius; float area; public: void get_radius( ); inline float list_area( ); }; void Area :: get_radius( ) { cout << “半徑=”; cin >> radius; area = 3.14159 * radius * radius;}
成員函數重載 • 函數重載(Function overloading)也可使用在成員函數內,只要物件對這些參數給與不同的數目(參數)或不同的資料型態,就可使編譯器判斷何種函數可以啟動。 • void get_radius( ); //由使用者者輸入 • void get_radius(int ); //傳數值給函數 • void Area :: get_radius( ) //無參數 • { cout << “半徑=”; • cin >> radius; • area = 3.14159 * radius * radius; } • void Area :: get_radius(int _radius) //單一參數 • { radius = _radius; • area = 3.14159 * radius * radius; }
例題:成員函數重載之應用。 • void Area::get_radius(int _radius) • { radius = _radius; • area = 3.14159 * radius * radius;} • inline float Area::list_area() //inline函數 • { return area;} • void main(){ • Area circle; //物件宣告 • circle.get_radius(); //呼叫成員函數 • cout << "面積="<< circle.list_area(); • cout << endl; • circle.get_radius(20); //傳初值 • cout << "半徑20之面積=“ • << circle.list_area() << endl; • } • #include <iostream> • using namespace std; • class Area{ • private: • int radius; • float area; • public: • void get_radius(); //無參數 • void get_radius(int ); //單一參數 • inline float list_area(); //inline之原型 • }; • void Area::get_radius( ) • { cout << "半徑="; cin >> radius; • area = 3.14159 * radius * radius; • }
資料成員之初值 • 設定類別資料成員之初值有下面兩種: • 以成員函數來設定 • 只要物件一產生就使資料成員歸零或設定在某一數值
成員函數設定初值 • 將類別名稱Area內之資料成員radius及area設為0或某一初值,作法為: • void Area :: init( ) • { radius =0; • area=0;} • 或 • void Area :: init(int _radius, float _area) • { radius = _radius; • area = _area;} • 起始資料為(Area circle;) • circle.init( ); 或 circle.init(0, 0);
例題:物件初值之建立。 • void Area::init(int _radius,float _area){ • radius = _radius; • area=_area; } //設定某一資料 • inline float Area::list_area() //inline函數 • { return area;} • void main() • { Area circle; //物件宣告 • int _radius = 10; • float _area=PI*_radius*_radius; • circle.init(_radius,_area); • cout << "面積="<< circle.list_area(); • cout << endl; • circle.init(); //清除資料 • cout << "面積="<< circle.list_area() << endl; } • #include <iostream> using namespace std; • const float PI=3.14159; • class Area{ • private: • int radius; • float area; • public: • void init(); //無參數之啟始 • void init(int,float); //啟始某一初值 • inline float list_area(); //inline之原型 • }; • void Area::init(){ • radius=0; • area=0; } //清除資料
12-6 建構子與解構子 • 可設定初值之成員函數稱為建構子(Constructor),建構子也可以攜帶參數,所以也有重載之特性,有了建構子自然也有解構子(Destructor)。 • 建構子是一個特殊的成員函數,要定義建構子必需遵循下列兩個原則: • 建構子的名稱需與所屬類別名稱相同,才能告訴編譯器這是建構子。 • 建構子不能傳回資料型態,因為它是系統自行呼叫,自然就不必傳回任何資料。 • 建構子之定義可定義在類別內部也可定義在外部。
解構子 • 解構子(Destructor)就是將建構子所配置記憶體釋放出來。 • 要自行定義解構子應注意: • 解構子在一個類別只能有一個,名稱也需與類別名稱相同。 • 解構子必須在類別名稱前加上波浪型符號「~」。 • 解構子不可以有參數,也不可以傳回資料型態。 • 當物件消滅時會自動呼叫,請勿自行呼叫。
例題:以建構子完成初值之設定。 inline float Area::list_area(){ return area; } void main(){ Area circle; //物件宣告circle cout << "面積="<< circle.list_area()<<endl; } • #include <iostream> • using namespace std; • class Area{ • private: • int radius; • float area; • public: • Area(); //無參數建構子 • inline float list_area(); • }; • Area::Area(){ • radius=0; • area=0; • cout << “我是建構子”<<endl; • }//類別外宣告
建構子與解構子 • 如: • class App{ • private: • int data; • public: • App( ) { data=0; } //建構子 • ~App( ){ }; //解構子 • };
12-7 共享成員函數 • 當一個類別定義之後,可以建立許多物件(或稱類別變數),電腦會對這些物件隨資料型態大小分配靜態記憶體給它們,若連成員函數也要每個物件都要分配,將使記憶體資源恨快就會耗盡,實際上,不管同類別之物件有幾個,除各別擁有資料成員外,成員函數都只有一份,因不同的物件不會同時啟動同一成員函數,即使可能發生,作業系統也會有列隊(Queue)的管理,所以讓物件共享成員函數有下列優點: • 1. 減少記憶體分配 • 2. 加快執行速度
資料成員與成員函數之關係 物件A 物件B 物件C 資料 資料 資料 成員函數
12-8 共享資料成員—static之使用 • 成員函數對任何同類別之物件而言是只有一份,大家共同使用,資料成員在類別定義時也可宣告那些是共用的,如可宣告一資料成員來累計一共產生多少物件,或用來計數磁碟讀取的狀況,總之它是共享的,任何物件都可存取,此時就需static之幫忙 。
資料成員共享 • 資料成員共享應注意: • 在資料型態前加上保留字static。如: static int number; • 共享資料成員第一次執行除非有另設初值,否則數值為0,字串為空字串,如: number之初值為0。 • 必須在類別外定義該資料成員,其格式為: 資料型態 類別名稱 :: 資料成員=初值;
資料成員共享 • 如: • class Car • {static int count; //共享資料成員 • int id; • public: • Car( ); //沒有參數的建構子 • Int list_car( ); }; • int Car :: count; //靜態共享資料之定義,初值為0 • 等於 • int Car :: count = 0; 或 • int Car :: count = 10; //設定初值為10
例題:靜態資料成員之應用。 Car::Car(){ //建構子 ++count; //靜態資料加一 id = count; }//將結果設定給id void Car::list_car(){ cout<<“總數有”<<count <<“部,這是第”<<id<<“ 部\n”;} void main(){ Car A,B,C,D; //物件宣告 A.list_car(); B.list_car(); C.list_car(); D.list_car(); } • #include <iostream> • using namespace std; • class Car{ • private: • static int count; //靜態資料 • int id; • public: • Car(); //無參數之建構子 • void list_car(); • ~Car(){}; //解構子 • }; • int Car::count; //靜態宣告
12-9 物件指標之使用 • 一般物件在建立時是靜態的,所需之記憶體在編譯時就已經配置好,直到程式結束前仍保有該處之記憶體。 • 若用指標來使物件建立時才取得記憶體,用完後歸還,不但不會佔用空間也可達到資源回收之目的,使用指標建立物件稱物件指標(pointer of object) 。 • 指標之使用: • 以new配置記憶體。 • 以箭頭運算子「->」來取得各成員函數。 • 以delete釋放記憶體。
12-10 類別與結構 • 結構(struct)也是類別之一種,它是將資料集合成群。 • 類別(class)是將資料與函數封裝在一起。 • 兩者之使用方式相同,兩者唯一不同之處是,在預設的情況下類別之所有成員為私有的(private),而結構則為公有的(public)。
類別單元練習題 • 設計一個CBox類別,具有length,width與height三個整數的資料成員,並完成下列程式設計:(a)定義Volume( )函數,用來計算CBox物件的體積。(b)定義SurfaceArea( )函數,用來計算CBox物件的表面積。 • 設計一個Calculator類別,並完成下列程式設計: (a)定義add(a,b )函數,用來計算二數之和。 (b)定義sub(a,b )函數,用來計算二數之差。 (c)定義mul(a,b )函數,用來計算二數之積。 (d)定義div(a,b )函數,用來計算a / b。 • 設計一個加法類別Addition,並設計sum( )函數的多載,使其具有下面功能: (1) sum(int a, int b)。 (2)sum(double a, double b)。 (3) sum(int a, int b, int c)。 (4) sum(double a, double b, double c)。 • 設計一個Person類別,公用資料包含︰姓名,年齡,性別;私用資料包含:電話,身分證號碼。並設計一個建構函式,用來初始化各項資料。