C++物件導向程式語言簡明教程
<第一章 概論>
撰文:程式創作區小樹 使用編譯器:Turbo C++ 3.0
修改:lcr(小牛)
修改前言: 這篇小樹寫的 oop 概念蠻容易懂的, 蠻適合初學者看一下..
只是筆者覺得寫的有點亂亂的 (^_^), 所以小弟在這整理了一下,
並沒有修改內容....請小樹哥哥見諒啦....:p
Lcr (1996.10.13)
讀者須知:
本文章由C 開始談起, 算是一篇C++的入門參考書, 文內假設各位已經有
C 語言的基礎,是C++的初學者, 讀者至少必須對C有相當程度的熟悉, 如流程
控制(if,for,while,switch...),指標,陣列,結構(struct), 都必須非常熟悉
, 否則恐怕會有閱讀上的困難....
本文件的目的是想要讓C++更好學, 使大家都能輕易的瞭解C++的精義。
因此本文件歡迎傳閱, 若有需要也歡迎做適當的修改, 以修正錯誤或增加可
讀性. 但請勿加入不雅的言語, 而且修改者務必註明修改者名稱及修改日期,
並保留原作者之簽名. 謝謝各位!
<1> 從結構談起
首先我丟給各位一個題目, 假設各位正在設計一個遊戲, 螢幕上有十顆
子彈,各在不同的位置, 往下一齊飛去, 直到飛出螢幕為止,向這樣的程式你
會怎麼做呢? 給你五分鐘想一想.........要想喔!
想完了才可以繼續往下看喔!
Ok!想好了嗎? 讓我來公佈答案吧!
作法一: 剛學習C 或者是慣用Basic 的人也許會這麼做~~~~
┌─────────────────────────────────┐
│void show(int x,int y); //這是一個副程式,可以在(x,y)上畫出一點, │
│ //至於要怎麼寫就先不管他,反正你知道有這麼│
│ //一個函式就對了.... │
│void main() │
│{ │
│ int x[10],y[10];//這是記錄十顆子彈位置的陣列 │
│ │
│ while(1) │
│ for(int i=0;i<10;i++) │
│ { │
│ show(x[i],y[i]); //畫出每一個子彈 │
│ y[i]++; //將子彈往下移一格 │
│ } │
│} │
└─────────────────────────────────┘
這種作法可以說是一般人最常用的作法了, 對小程式也就夠了! 缺點是一
旦程式大起來了, 要處理的東西變多了, 例如螢幕上有十顆子彈, 十艘船, 十
顆隕石, 十顆背景行星或更多那程式就會變得凌亂不堪了!! (想像一下)....
光是讀懂程式是做什麼的就是一件很辛苦的事....有另外一種作法如下:
作法二: 對C比較有概念的人應該會這麼做~~~~
┌────────────────────────────────┐
│void show(int x,int y); //這是一個副程式,可以在(x,y)上畫出一點,│
│ //至於要怎麼寫就先不管他,反正你知道有 │
│ //這麼一個函式就對了...... │
│void main() │
│{ │
│ struct _Bullet //這是一個 "子彈" 的 "模子". │
│ { │
│ int x; │
│ int y; │
│ }; │
│ │
│ _Bullet Bullet[10]; //用模子做出10顆子彈. │
│ │
│ while(1) │
│ for(int i=0;i<10;i++) │
│ { │
│ show(Bullet[i].x,Bullet[i].y); //將子彈秀出來. │
│ Bullet[i].y++; //將子彈往下移一格. │
│ } │
│} │
└────────────────────────────────┘
這個程式好瞭解多了, 你可以想像 _Bullet 是子彈的 "模子", 我們用
這個模子做出了10顆名字分別為Bullet[0], Bullet[1]....的子彈, 位置是
子彈的一個"屬性"(就好像身高體重是人的屬性一樣), 程式可以由
Bullet[i].x或Bullet[i].y來取用這些屬性, 這樣子程式就好懂多了, 無論
程式寫得多大, 也可以很容易的知道某個變數到底是誰的,做什麼用的, 不會
像上一個程式一樣凌亂不堪了!
總結:
其實兩個程式最大的差別, 在於是否"擬物化"在程式一,子彈被化成一個
個的陣列,分居各處, 沒什麼關連在程式二, 子彈被程式語言模擬成一個個的
"子彈"彷彿我們操作的真是一顆顆的子彈似的. 簡單的說,第一個程式是問題
遷就程式, (硬把"子彈"拆成毫不相關的陣列)而第二個程式則是程式模擬問題
. (用struct _Bullet做出十顆"子彈")而傳統程序導向和物件導向設計的差別
之一就在此, 傳統程序導向的設計讓程式設計變得很抽象, 程式的結構與我們
所經驗到的世界大不相同, 這種結果導致程式很難被瞭解(因為違背了日常經驗
嘛!)除錯和維護起來就很困難!!
而物件導向程式設計基本上就是要將程式語言拿來模擬世界的運作!!!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
這樣子設計程式其實就是在模擬世界的運行, 程式會更好了解,更容易製作,
維護起來也輕鬆多了!
<2> 物件與類別:
物件和類別是物件導向程式設計中第一個要接觸到的觀念, 讓我們來看看
什麼是物件,何謂類別:
我們在上一篇中用"子彈的模子"(struct _Bullet) 做出了10顆"子彈"
(Bullet[i])如果用比較學術性的講法, 其中"子彈的模子"就是"類別",在英
文以"Class"來表示, 那一顆顆的"子彈"就是"物件",英文是"Object".
在C++中,為了強調這種觀念,通常不用struct來做"模子", 而改用
"class"來做!! 以後我們也將只用class來做"模子"(就是類別啦!!) 基本上
兩個敘述是差不多的,但仍有一些小差別, 至於差別在哪兒,這個我們稍後再
敘.
上一個程式中的"Struct _Bullet"其實也可以寫成這樣, 效果一模一樣呢!!
class _Bullet
{
public: //多了這個敘述
int x;
int y;
};
我們可以廣義的這樣說, 若有一行敘述 :
double a,b;
我們可不可以說double 是一個倍精度浮點數的模子呢? 而是不是a,b正
是由double這個模子做出來的浮點數"物件"呢? 同樣的,所有的C 語言基本資
料型態, 例如int啦! char啦! 指標啦! 基本上都是一種"模子". 也就是一種
"類別" 而由這些模子所做出來的變數其實都是一個個的物件!
更廣義的說,其實所有的資料型態都是類別, 而所有的變數都是物件, 不
是嗎? 只要這樣想, 應該就不難瞭解類別和物件的意義吧 ?!
<3> 類別成員函數
前面所講到的程式裡,我們用"子彈模子"(即子彈類別)做出了十顆"子彈"
然後用一些敘述及show()函式去使子彈前進並畫出來, 這樣子說實話還不夠像
真實的子彈, 因為真正的子彈是"自己"飛的, 哪是由外力操縱的呢? 其實我們
可以這麼做:
┌─────────────────────────────────┐
│void main() │
│{ │
│ class _Bullet//這是一個 "子彈" 的 "模子",改用class來寫 │
│ { │
│ public: │
│ int x; │
│ int y; │
│ void show(int x,int y); //這是一個函式,可以在(x,y) │
│ //上畫出一點,內容先不管. │
│ void go() //這是一個函式,呼叫他可以讓│
│ { //子彈往下移一格並畫出來. │
│ show(x,y); │
│ y++; │
│ } │
│ int collision(int X,int Y) //這是一個函式,用來判斷子彈│
│ { //是否撞到東西, 不是則傳回0│
│ return (x==X && Y==y); │
│ } │
│ }; │
│ │
│ _Bullet Bullet[10];//用模子做出10顆子彈 │
│ │
│ while(1) │
│ for(int i=0;i<10;i++) │
│ Bullet[i].go(); //下命令給子彈,要子彈往下移│
│ //一格,並將自己畫出 │
│} │
└─────────────────────────────────┘
哇塞!太誇張了吧!! 函式區然可以放到"模子"裡面去!!!! 別懷疑,這是C++
的語法,
而且放到"模子"裡的函式還可以以 物件名.函式名(參數); 的方法呼叫喔!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
或許你會說:把函式放到class敘述中有什麼好處呢? 其實我們可以這樣想:
Bullet[i]是一顆顆的子彈, 而這個子彈是有生命的, 也就是說我們可
以下命令給他(用點想像力). 而Bullet[i].go()中的"go()" 其實就是命令
的名稱, 意思就是跟子彈說:"衝啊!衝到下一格去" 子彈接到命令後他"自
己"自然就會照作, 而有的命令是必須有附帶一些訊息的, 例如collision
是問子彈是否撞到了(X,Y)這個點, 因此點的位置必須告訴子彈, 而敘述要
寫成Bullet[i].collision(X,Y); 如果子彈發現撞到了,他會告訴你於是你
可以做一些措施,例如畫出爆炸畫面...或什麼的...
*像此類放在類別敘述內的函式稱為類別的函式成員。
~~~~~~~~
*而像int x,y 這些在類別內的物件則稱為該類別的的資料成員。
~~~~~~~~
這樣可以瞭解嗎?
<4> 私用及公用成員,資訊隱藏:
投過販賣機嗎? (廢話!!!) 販賣機其實是一個很複雜很複雜的機器, 可是
他的使用方法卻非常非常的簡單, 只要投個硬幣進去(呼叫成員函式), 按下所
要的飲料 (成員函式的參數), 飲料自然就會掉出來!! 你不必管販賣機是怎麼
做到的, 你只要會操作就可以了! 原因是販賣機把複雜的部份藏起來了, 只露
出了供人使用的部份.
同樣的道理, 我們的子彈"類別"也可以這樣, 其實我們希望不管子彈是怎
麼飛的, 只要我們叫他飛他就飛就可以了. 而且操作方法是越簡單越好, 所以
啦! 一些並不會被使用者使用到的函式, 如show()啦! 以及一些不足為外人道
的資料,如int x,y 啦! 如果能夠把他們藏起來(就像販賣機把機器部份包起來
) 只給類別自己的成員函式去使用, 而露出那些公用的操作介面, 提供給所有
的人使用, 那該有多好!
幸好C++早就想到這一點了, 提供了"private:"以及"public:"這兩個敘述
供我們使用, 請看改進後的子彈類別:
┌──────────────────────────────────┐
│class _Bullet//這是一個 "子彈" 的 "模子",改用class來寫 │
│{ │
│ private: //私用成員(販賣機的內部),除了類本類別的函式成員外 │
│ int x; //,其他人皆不得存取或呼叫之 │
│ int y; │
│ void show(int x,int y);//這是一個函式,可以在(x,y)上畫出一點, │
│ public: //公用成員(販賣機的操作方法),提供給所有的人使用 │
│ void go() //這是一個函式,呼叫他可以讓子彈往下移一格並畫出來│
│ { │
│ show(x,y); │
│ y++; │
│ } │
│ int collision(int X,int Y) //這是一個函式,用來判斷子彈是 │
│ //否撞到東西 │
│ { │
│ return (x==X && Y==y); //不是則傳回0 │
│ } │
│}; │
└──────────────────────────────────┘
"private:"是一個C++的敘述, 他告訴編譯器說:以下的東東只能在這個類別
內使用, 也就是只能給類別的函式成員使用, 因此在類別的外面絕對不能存取或
呼叫這些私有的變數或函式, 否則編譯器是會告訴你語法錯誤的!
"public:"則相反, 他告訴編譯器說:以下的東東所有的人都可以使用. 程式
可以在任何地方使用它!
這樣子一來, 子彈就像販賣機一樣, 你只要知道怎麼操作他, 而不必知道他
如何動作了, 也不怕會不小心存取到不該存取的東西, 而破壞掉子彈的運行, 因
為一旦你想要這麼做, 編譯器會阻止你的!! 像這種把沒有必要公開的資訊隱藏起
來,
在學術上叫做封裝,也稱為資訊隱藏,(要記起來喔!)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
主要是為了降低程式設計師的負擔, 減少錯誤的發生. 以前物件導向的觀念
還沒流行以前, 程式師要背負的一個很大的包袱就是要牢牢的記住所有變數的名
稱及功用, (有幾千個喔!) 因為所有的變數都是隨時隨地可以存取的, 一個對變
數錯誤的存取, 就會造成整個系統難以除錯的Bug!! 而物件導向將沒有必要公開
的資料與函式封裝起來, 可以大大減少這種錯誤的發生, 況且, 資訊隱藏可以讓
類別條理分明, 容易瞭解. 將外人不需要知道的東西包裝起來, 這樣子看的人(
包括你自己喔!)可以很容易的知道在這個類別中提供了哪些運作方法, 以及這個
類別大概是做什麼的.
而不會被一大堆無用的資訊搞得一頭霧水, 浪費寶貴的時間和精力! Ok! 或
許你現在還不能很瞭解資訊隱藏的好處! 不過相信我, 當你開始寫物件導向程式
的時候, 你就會感謝C++的!!!
class 和 struct 有什麼差別呢? 我終於可以告訴你了!!!
他們差在struct的是內定為"public:"的, 而class是內定為"private:"的, 也就
是說:
void main()
{
class _Bullet
{
int x,y;
};
_Bullet Bullet;
Bullet.x=5; //這一行不合法!!!!!
};
這樣瞭解嗎?
<5> 函式複載
再出一個題目給大家, 寫一個函式,名為max 可以接收兩個整數,傳回比較
大的那一個, 一樣大就隨便傳一個, 你會怎麼寫呢?
給你兩分鐘......一定要想喔!!!
OK!想必大家都是這樣寫:
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
很好!再出一個題目給大家: 假設上一個題目的函式已存在, 我現在想要一
個功能一樣的函式, 但是是處理倍精度浮點數的(double), 你會怎麼寫呢?
Ok!想必應該是這樣:
double maxbouble(double a,double b) //注意!函式名不同了!這是C 的規則!
{
if(a>b)
return a;
else
return b;
}
嗯...很好很好.....嘿嘿!! 如果我還要一個處理長整數,一個處理字元,一
個處理指標的呢? .............天啊! 光是命名就讓人昏倒,而且使用起來很
不自然,不是嗎? 告訴你,其實你可以不要寫的那麼辛苦的, 上一個例子也可以寫
成這樣:
double max(double a,double b)//注意喔!名字和int max(~~)一樣喔!
{
if(a>b)
return a;
else
return b;
}
不會吧!不是不可以這樣寫嗎?" 哈哈!告訴你,這就是C++的新語法"函式複載"
或許你會問:"他怎麼知道我要用的是哪一個?" 放心吧!他知道的! 例如如果你呼叫
max(1,2); 他就會用處理整數的那一個, 如果你呼叫:max(1.3,0.005); 他就會用
處理浮點數的那一個神奇吧!
複載在學術上又稱為靜態連結, 因為到底要呼叫那個函式其實在編譯的時候
就決定了, 另外還有一種叫動態連結的, 要等到執行的時候才能決定, 這個我們
以後再說.
<6> 建構函式
一個物件在建立的時候, 有時候我們會希望能替他初始化, 例如說我們希望
在建立子彈物件時, 若沒有指定初值, 例如:_Bullet Bullet; 則子彈剛建立的時
候位置是(0,0), 若有指定初值, 例如:_Bullet Bullet(10,10); 則子彈的位置可
以設為我們要的位置, 則這樣子要怎麼做呢?
C++ 提供了建構函式(constructor), 可以讓我們在物件剛建立的時候做一些
事情, 將物件初始化, 請看下例:
┌───────────────────────────────────┐
│class _Bullet //這是一個 "子彈" 的 "模子",改用class來寫 │
│{ │
│ private: //私用成員 │
│ int x; │
│ int y; │
│ void show(int x,int y); //這是一個函式,可以在(x,y)上畫出一點, │
│ public: //公用成員 │
│ _Bullet(){x=y=0;} //無參數之建構函式 │
│ _Bullet(int X,int Y){x=X;y=Y;} //有兩個參數之建構函式 │
│ void go() //這是一個函式,呼叫他可以讓子彈 │
│ { //往下移一格並畫出來 │
│ show(x,y); │
│ y++; │
│ } │
│ int collision(int X,int Y) //這是一個函式,用來判斷子彈是否 │
│ { //撞到東西, 不是則傳回0 │
│ return (x==X && Y==y); │
│ } │
│}; │
└───────────────────────────────────┘
其中_Bullet()是一個無參數的建構函式, 當物件以 _Bullet Bullet; 的方法
建立的時候, C++會自動的呼叫此函式來作物件的初始化,而 _Bullet(int X,int Y)
則是一個有兩個整數參數的建構函式, 例如當物件以 _Bullet Bullet(2,17)來建立
的時候, C++會自動的呼叫此函式來作物件的初始化.
要注意的是建構函式的名稱必須和類別名稱一樣, 建構函式不可以有傳回值
(連寫void都不行) 結束時也最好不要用return;命令結束, 不然在某些編譯器上
會有問題. 其他的事項都和一般類別內函式相同.
<7> new 和 delete
在C 中,配置記憶體的方法最常用的就是 malloc() 和 free() 了! 當然在C++
中, 你一樣可以運用這兩個函式來配置記憶體. 但是使用這兩個函式來配置記憶體
在C++中卻有一些不妥!! 因為malloc()雖然會配置記憶體給物件用, 但是卻不會呼
叫物件的建構函式, 這樣子我們就不能再建立物件之初將其初始化了!! 幸好C++想
到了這點, 提供了兩個新的記憶體配置運算子----new 和 delete!! 他們的用法相
當簡單! 請看:
int *p=new int; //建立指標p,並配置一個int的記憶體給他.
int *q; //和上面的效果一樣,但是先宣告後配置.
q=new int;
int *r=new int[10]; //建立指標r,並配置10個int的記憶體給他.
_Bullet *s=new _Bullet[10]; //和上面一樣,只不過int換成_Bullet.
_Bullet *t=new _Bullet(1,2)[10]; //和上面一樣,但指定了初值.
delete p; //歸還p所配置的記憶體
delete q; //歸還q所配置的記憶體
delete [] r; //歸還r所配置的記憶體
delete [] s; //歸還s所配置的記憶體
delete [] t; //歸還t所配置的記憶體
值得注意的是, 使用new時, C++會自動的呼叫每一個物件的的建構函式. 例如
在t 中:
*(t).x , *(t+1).x ,....*(t+9).x 都會是 1
*(t).y , *(t+1).y ,....*(t+9).y 都會是 2
使用delete時, 若當初配置的是單一物件則不要加括號, 若當初配置的是物件
陣列的話則一定要加括號!! 不然會有問題! 切記!切記!
<8> 解構函式
有建構當然就會有解構啦!!! 有的時候我們會希望在物件被消滅的時候(例如被
delete的時候) 能夠做一些措施. 而做這種做施的函式就叫解構函式.(destructor)
, 請看下例:
class test
{
int *p;
public:
test(){p=new int;} //建構函式
};
void main()
{
test a;
}
這個類別在建立時會配置一個int的記憶體給指標p, 但是當程式結束時並沒有
將p 所指到的記憶體歸還給系統, 這樣會造成一些BUG. 解決的方法是加入解構函式
請看:
class test
{
int *p;
public:
test(){p=new int;} //建構函式
~test(){delete p;} //解構函式
};
void main()
{
test a;
}
這個新的版本中加入了解構函式, 當物件將要被毀滅時,(程式結束時,或物
件被delete掉時) C++會自動執行解構函式,以妥善的進行善後工作, 值得注意的
是解構函式名為類別名前面再加上 "~" 符號而成不可以有參數,也不可以有傳回
值, 不得複載!! 函式中不可以有return;敘述. 否則可能會有問題!!
<9> 繼承
讀過生物吧!!(又是一句廢話!!) 動物是一種生物, 靈長目又是一種動物,人
類又是一種靈長目, 李登輝是一個人類的個體. 向這種 "是一種" 的關係就叫繼
承. C++是為了模擬世界而創的, 像這種繼承的機制當然是不會放過的啦!
請看我們的例子:
生物有一些生物的特性, 生物會繁殖、會生長. 動物既然是一種生物, 當然
也一定會繁殖會生長囉! 可是動物還加了自己的一些特性:動物會動(廢話!) 靈長
目也是一種動物囉!! 所以動物該有的特性他都有囉! 可是靈長目他又多了一種自
己的特性, 就是他有IQ囉! 同樣的,人類是一種靈長目, 所以人類會繁殖,會生長,
會動,有IQ, 而且人類還多了自己的特性----人類有名字!!!
李登輝是個人類! 因此他有自己的名字:他叫李登輝, 有自己的IQ---雖然我
不知道有多少, 會動,會繁殖,也會生長!!
以上拉拉雜雜的一大堆以C++的方法來表示就是:
class life //生物類別
{
public:
void gorw(){~~~}; //命令生物"生長"的函式,內容先不管
void bear(){~~~}; //命令生物"繁殖"的函式,內容先不管
};
class animal:public life //動物類別,繼承自生物,也就是說"動物是一種生物"
{
public:
void move(){~~~}; //命令動物"移動"的函式,內容先不管
};
class primates:public animal //靈長目,繼承自動物
{
int IQ; //這是靈長目的IQ
};
class human:public primates //人類,是一種靈長目
{
char name[6]; //名字,三個中文字
public:
human(char *p){strcpy(name,p);} //設定名字的建構函式
};
void main() //程式開始
{
human LDW("李登輝"); //誕生李登輝並替他取名字(將name[]設為"李登輝")
LDW.grow(); //李登輝在"生長"(天天喝克寧,你就會長得跟"小樹"
//一樣喔!)
LDW.move(); //李登輝做"運動"(嘿休!嘿休!)
LDW.bear(); //李登輝在"繁殖"!!??%^#$%(18歲以下兒童請勿觀看)
}
這個例子只是博君一笑, 事實上繼承的用處是很大的. 他可以讓程式更擬
物化, 也可以讓程式具有重用性... 我這樣說你可能會很模糊, 沒關係,等到我
們開始實做程式的時候你自然就會瞭解了!
<第一章結束>
小樹
--
※ 發信站: 批踢踢實業坊(ptt.twbbs.org)
◆ From: cherry.cs.nccu.