物件導向的程式設計
物件導向編程(Object Oriented Programming,OOP,物件導向程式設計) 的主要思想是把構成問題的各個事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘一個事物在整個解決問題的步驟中的行為。
面向過程就是分析出解決問題所需要的步驟,然後用函數逐步實現,再依次調用就可以了。
物件導向和面向過程是兩種不同的編程思想,沒有哪一種絕對完美,要根據具體需求擬定開發方案。例如,開發一個小型軟體或應用程式,工程量小,短時間內即可完成,完全可以採用面向過程的開發方式,使用物件導向,反而會增加代碼量,降低開發效率。
面向過程的程式設計語言(如C語言)不能建立類和對象,不能用物件導向的思想來開發程式;物件導向的程式設計語言(如C++、PHP等)保留了面向過程的關鍵字和語句,仍然可以採用面向過程的思想來開發程式。
物件導向是面向過程的補充和完善。
注意,不要“死磕”概念上的理解,很多有經驗的程式員甚至都不能完全解釋清楚物件導向和面向過程的區別,要重在實踐,不斷揣摩程式設計語言的思想。
類和對象的基本概念
為了方便說明,我們將從現實生活中的執行個體入手。
我們知道,工業上使用的鑄件(電飯鍋內膽、汽車地盤、發動機機身等)都是由模子鑄造出來的,一個模子可以鑄造出很多相同的鑄件,不用的模子可以鑄造出不同的鑄件。這裡的模子就是我們所說的“類”,鑄件就是我們所說的“對象”。
類,是建立對象的模板,一個類可以建立多個相同的對象;對象,是類的執行個體,是按照類的規則建立的。
屬性和方法
由模子鑄造出來的鑄件(對象),有很多參數(長度、寬度、高度等),能完成不同的操作(煮飯、承重、保護內部零件等)。這裡的參數就是對象的“屬性”,完成的操作就是對象的“方法”。
屬性是一個變數,用來表示一個對象的特徵,如顏色、大小、重量等;方法是一個函數,用來表示對象的操作,如奔跑、呼吸、跳躍等。
對象的屬性和方法統稱為對象的成員。
類的繼承
一個類(子類)可以繼承另一個類(父類)的特徵,如同兒子繼承父親的DNA、性格和財產。
子類可以繼承父類的全部特徵,也可以繼承一部分,由程式靈活控制。
C++物件導向程式設計舉例
這裡我們將通過幾個簡單的例子來示範如何站在物件導向的角度設計程式,以及使用類的好處。
【例】最簡單的例子。
#include <iostream>using namespace std;class Time //定義Time類{public : //資料成員為公用的 int hour; int minute; int sec;};int main( ){ Time t1;//定義t1為Time類對象 cin>>t1.hour;//輸入設定的時間 cin>>t1.minute; cin>>t1.sec; //輸出時間: cout<<t1.hour<<":"<<t1.minute<<":"<<t1.sec<<endl; return 0;}
運行情況如下:
幾點注意:
1) 在引用資料成員hour,minute,sec時不要忘記在前面指定對象名。
2) 不要錯寫為類名,如寫成
Time.hour,Time.minute,Time.sec
是不對的。因為類是一種抽象的資料類型,並不是一個實體,也不佔儲存空間,而對象是實際存在的實體,是占儲存空間的,其資料成員是有值的,可以被引用的。
3) 如果刪去主函數的3個輸入語句,即不向這些資料成員賦值,則它們的值是不可預知的。
【例】引用多個對象的成員。
1) 程式(a)
#include <iostream>using namespace std;class Time{public : int hour; int minute; int sec;};int main( ){ Time t1;//定義對象t1 cin>>t1.hour;//向t1的資料成員輸入資料 cin>>t1.minute; cin>>t1.sec; cout<<t1.hour<<":"<<t1.minute<<":"<<t1.sec<<endl;//輸出t1中資料成員的值 Time t2;//定義對象t2 cin>>t2.hour;//向t2的資料成員輸入資料 cin>>t2.minute; cin>>t2.sec; cout<<t2.hour<<":"<<t2.minute<<":"<<t2.sec<<endl;//輸出t2中資料成員的值 return 0;}
運行情況如下:
1032 4310:32:4322 32 4322:32:43
程式是清晰易懂的,但是在主函數中對不同的對象一一寫出有關操作,會使程式冗長。為瞭解決這個問題,可以使用函數來進行輸入和輸出。見程式(b)。
2) 程式(b)
#include <iostream>using namespace std;class Time{public : int hour; int minute; int sec;};int main( ){ void set_time(Time&);//函式宣告 void show_time(Time&);//函式宣告 Time t1;//定義t1為Time類對象 set_time(t1);//調用set_time函數,向t1對象中的資料成員輸入資料 show_time(t1);//調用show_time函數,輸出t1對象中的資料 Time t2;//定義t2為Time類對象 set_time(t2);//調用set_time函數,向t2對象中的資料成員輸入資料 show_time(t2);//調用show_time函數,輸出t2對象中的資料 return 0;}void set_time(Time& t) //定義函數set_time,形參t是引用變數{ cin>>t.hour;//輸入設定的時間 cin>>t.minute; cin>>t.sec;}void show_time(Time& t) //定義函數show_time,形參t是引用變數{ cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;//輸出對象中的資料}
運行情況與程式(a)相同。
3) 程式(c)
可以對上面的程式作一些修改,資料成員的值不再由鍵盤輸入,而在調用函數時由實參給出,並在函數中使用預設參數。將程式(b)第8行以下部分改為:
int main( ){ void set_time(Time&,int hour=0,int minute=0,int sec=0);//函式宣告 void show_time(Time&);//函式宣告 Time t1; set_time(t1,12,23,34);//通過實參傳遞時、分、秒的值 show_time(t1); Time t2; set_time(t2);//使用預設的時、分、秒的值 show_time(t2); return 0;}void set_time(Time& t,int hour,int minute,int sec){ t.hour=hour; t.minute=minute; t.sec=sec;}void show_time(Time& t){ cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;}
程式運行時的輸出為:
12:23:34 (t1中的時、分、秒)0:0:0 (t2中的時、分、秒)
以上兩個程式中定義的類都只有資料成員,沒有成員函數,這顯然沒有體現出使用類的優越性。在下面的例子中,類體中就包含了成員函數。
【例】改用含成員函數的類來處理。
#include <iostream>using namespace std;class Time{public : void set_time( );//公用成員函數 void show_time( );//公用成員函數private : //資料成員為私人 int hour; int minute; int sec;};int main( ){ Time t1;//定義對象t1 t1.set_time( );//調用對象t1的成員函數set_time,向t1的資料成員輸入資料 t1.show_time( );//調用對象t1的成員函數show_time,輸出t1的資料成員的值 Time t2;//定義對象t2 t2.set_time( );//調用對象t2的成員函數set_time,向t2的資料成員輸入資料 t2.show_time( );//調用對象t2的成員函數show_time,輸出t2的資料成員的值 return 0;}void Time::set_time( ) //在類外定義set_time函數{ cin>>hour; cin>>minute; cin>>sec;}void Time::show_time( ) //在類外定義show_time函數{ cout<< hour<<":"<< minute<<":"<< sec<< endl;}
運行情況與例8.2中的程式(a)相同。
幾點注意:
在主函數中調用兩個成員函數時,應指明對象名(t1,t2)。表示調用的是哪一個對象的成員函數。
在類外定義函數時,應指明函數的範圍(如void Time∷set_time( ))。在成員函數引用本對象的資料成員時,只需直接寫資料成員名,這時C++系統會把它預設為本對象的資料成員。也可以顯式地寫出類名並使用域運算子。
應注意區分什麼場合用域運算子“∷”,什麼場合用成員運算子“.”,不要搞混。
【例】找出一個整型數組中的元素的最大值。這個問題可以不用類的方法來解決,現在用類來處理,讀者可以比較不同方法的特點。
#include <iostream>using namespace std;class Array_max //聲明類{public : //以下3行為成員函數原型聲明 void set_value( ); //對數組元素設定值 void max_value( ); //找出數組中的最大元素 void show_value( ); //輸出最大值private : int array[10]; //整型數組 int max; //max用來存放最大值};void Array_max::set_value( ) //成員函數定義,向數組元素輸入數值{ int i; for (i=0;i<10;i++) cin>> array[i];}void Array_max::max_value( ) //成員函數定義,找數組元素中的最大值{ int i; max=array[0]; for (i=1;i<10;i++) if(array[i]> max) max=array[i];}void Array_max::show_value( ) //成員函數定義,輸出最大值{ cout<< "max="<< max;}int main( ){ Array_max arrmax; //定義對象arrmax arrmax.set_value( ); //調用arrmax的set_value函數,向數組元素輸入數值 arrmax.max_value( ); //調用arrmax的max_value函數,找出數組元素中的最大值 arrmax.show_value( ); //調用arrmax的show_value函數,輸出數組元素中的最大值 return 0;}
運行結果如下:
12 12 39 -34 17 134 045 -91 76 (輸入10個元素的值)max=134 (輸入10個元素中的最大值)
請注意成員函數定義與調用成員函數的關係,定義成員函數只是設計了一組作業碼,並未實際執行,只有在被調用時才真正地執行這一組操作。
可以看出: 主函數很簡單,語句很少,只是調用有關對象的成員函數,去完成相應的操作。在大多數情況下,主函數中甚至不出現控制結構(判斷結構和迴圈結構),而在成員函數中使用控制結構。在物件導向的程式設計中,最關鍵的工作是類的設計。所有的資料和對資料的操作都體現在類中。只要把類定義好,編寫程式的工作就顯得很簡單了。