# 6. 物件導向 ## 前提摘要 C\++與C語言的最大差別就是在於物件導向的有無,在C語言時期的時候是運用函式導向,C語言有struct(結構)類似於C\++的物件導向,但是struct僅擁有記憶體的存取的功能,並無運算資料的能力,所以C++的物件導向解決了這項問題,C\++的物件導向擁有「資料成員」和「成員函式」,資料成員就是所謂的物件,提供記憶體給變數存取資料,而成員函式擁有運算資料的能力,因此C\++的類別(class)就取代了C的結構(struct)。 ## 概述 首先要先了解什麼是類別,我這邊引用許裕永老師教學裡說的一段介紹文字,「類別就是一個製作說明書,材料是記憶體,配置出來的記憶體叫物件,但就程式語法來說,類別就是一個型別。」這段話清楚的表示出什麼是類別。 接著我們再來熟悉一下類別中有什麼,一個類別裡可以有「物件資料成員」、「物件成員函式」、「建構函式(建構子)」,大致上為這三個,的確還有其他種,但我大致上認為這三種最為重要。 在class裡用存取標籤 (access label) 來區分成員 (member) 的權限,而存取標籤有三種,public、private、protected,public的成員可以給所有物件在所有地方去呼叫,而private只能在class裡呼叫,protected 成員的使用範圍與 private 成員相同,而他們最大的差別是private裡的成員<span style = "color:red">不可以</span>被繼承,而protected裡的成員<span style = "color:red">可以</span>被繼承。 ## public 故名思義,大眾,就是說在public的成員裡是可以被所有物件呼叫,以下為一個例子: ```cpp= #include <iostream> using namespace std; class student{ public: float weight; float height; float bmi(){ height /= 100; return weight/(height*height); } }; int main(){ student max; cin >> max.weight >> max.height; cout << max.bmi(); return 0; } ``` 從這個例子中可以看到我在main裡宣告一個型態為student的一個物件叫max,而我可以自由的取用他裡面的資料成員和成員函式。 ## private 顧名思義,隱私,在private裡的成員只能被class裡面的成員呼叫,若被class外的物件呼叫會出現error,以下為使用private的例子。 ```cpp= #include <iostream> using namespace std; class student{ private: float weight = 70; float height = 170; public: float bmi(){ height /= 100; return weight/(height*height); } }; int main(){ student max; cout << max.bmi() << endl; return 0; } ``` 這個例子可以看到說我們只能在bmi函式裡呼叫weight和height,如果我們在main裡宣告會出現error。 那如果我們今天一定要在main裡更改private裡的值呢?我們可以使用成員函式來達成這個目標,例如: ```cpp= #include <iostream> using namespace std; class student{ private: float weight; float height; public: float bmi(){ height /= 100; return weight/(height*height); } void change_value(float w,float h){ weight = w; height = h; } }; int main(){ student max; max.change_value(70,170); cout << max.bmi() << endl; return 0; } ``` 而我們在這個例子中可以看到我們藉由change_value這個函式,間接更改weight和height的值。 ### 物件指標 首先先進行宣告,這邊要注意的是,我們宣告的是一個指標,和上面我們的宣告不一樣,我們上面宣告的是直接宣告一個物件,此時即有一群記憶體空間,而這邊如果要達到同樣目標,需new一個記憶體給它,如下: ```cpp= #include <iostream> using namespace std; class circle{ public: int radius; int height; double girth; double area; double volume; double get_girth(){ return radius*2*3.14; } }; int main(){ circle *pointer_one; circle *pointer_two = new circle; pointer_two->area = 100; cout << pointer_two << endl;//output = 0x10050d5f0 if(pointer_one == nullptr) cout << "NULL" << endl;//output = NULL cout << pointer_one << endl;//output = 0x0 return 0; } ``` 執行完上述程式碼可以看到如果我們沒有new一個記憶體給指標,他的值就會是空的(NULL),反之我們今天new一個記憶體給他,我們就可以得到一個物件的連續記憶體。 接著來了解一下為何我們要使用指標的方式來控制資料,今天我們如果單純宣告一個物件,接著要將物件傳入函式,此時函式裡是複製一份一模一樣的物件出來,當資料比較少時沒有感覺,但是當資料一大的時候會發現這樣做非常浪費記憶體空間,所我們要用指標的方式直接更改或運用它的值,既然這樣我們先看下面那個例子: ```cpp= #include <iostream> using namespace std; class circle{ public: int radius; int height; double girth; double area; double volume; double get_girth(){ return radius*2*3.14; } }; int main(){ circle *pointer_one = new circle; circle *pointer_two = new circle; //不想要pointer_two的記憶體位置了 delete pointer_two; pointer_two = pointer_one; cout << pointer_one << endl;//output = 0x10504fad0 cout << pointer_two << endl;//output = 0x10504fad0 return 0; } ``` 我們可以看到我們一開始給pointer_two一個記憶體位置,但是後來我們不想要了,所以我們要將它delete掉,不然就違背我們本意了,我們使用指標就是為了有效率的使用記憶體,如果今天不使用這個記憶體空間,又不將此空間釋放,這樣豈不是浪費這些記憶體了嗎? ### 物件指標當參數 今天如果會指標後我們在將物件傳入函式時就不用再讓函式複製一份資料,造成記憶體的浪費。 ```cpp= #include <iostream> using namespace std; class circle{ private: double girth; double area; double volume; public: int radius; int height; bool cmp(circle * c1){ return c1->radius > 10 ? true : false; } }; int main(){ circle *c1 = new circle; c1->radius = 9; bool output1 = c1->cmp(c1); string str1 = output1 ? "c1 is bigger than 10" : "c1 is not bigger than 10"; cout << str1 << endl; circle c2; c2.radius = 100; bool output2 = c2.cmp(&c2); string str2 = output2 ? "c2 is bigger than 10" : "c2 is not bigger than 10"; cout << str2 << endl; return 0; } ``` 第一份資料傳入時由於c1已經是物件指標了,所以不必再取址,但c2是一個物件,所以我們必須先取址,再將c2傳入 ### 物件的參考 參考和指標最大的差異是參考必須初始化,而指標不用。 簡單的做一個例子: ```cpp= #include <iostream> using namespace std; class circle{ private: double girth; double area; double volume; public: int radius; int height; void change(circle & c){ c.radius = 20; } }; int main(){ circle cir; cir.radius = 10; cir.change(cir); cout << cir.radius << endl;//output = 20 return 0; } ``` ### 物件導向封裝 到目前為止感覺class和struct感覺沒有太大的差別,此時就來說說封裝吧,前面有稍微的鋪梗,前面大致上介紹了private和public,這邊我就直接用。 舉個例子,平常我們在使用class裡的值時不會有太大的問題,但是如果你今天是開發者,要給使用者使用你開發的class的話,可能有些地方不希望使用者去更改到,而使用大多不知情,此時怎麼辦呢?這時就要用到封裝了,我拿circle為例,今天如果我們要求周長,而周長是半徑乘二乘3.14,我們必須按照這個規矩來計算,所以今天為了防範使用者不是利用上述算式來求得的周長,所以此時我們樣將周長封裝,此時使用者就不會犯這個錯了。 ```cpp= #include <iostream> using namespace std; class circle{ private: double girth; double area; double volume; public: int radius; int height; double get_girth(){ return radius*2*3.14; } double get_area(){ return radius*radius*3.14; } double get_volume(){ return get_area()*height; } }; int main(){ circle *c1 = new circle; c1->radius = 10; c1->height = 20; cout << c1->get_area() << endl; cout << c1->get_girth() << endl; cout << c1->get_volume() << endl; return 0; } ``` 看到上面的解釋和例子應該對封裝有點了解了吧,接下來我再舉一個例子幫助理解: .cpp檔: ```cpp= #include <iostream> #include "Header.h" using namespace std; int main(){ bmi student; student.get_height(171.2); student.get_weight(73.3); cout << student.get_bmi() << endl;//ouptut = 25.009 return 0; } ``` Header.h檔: ```cpp= class bmi{ private: double height = 100; double weight = 100; double BMI; public: void get_height(double h); void get_weight(double w); double get_bmi(); }; void bmi::get_height(double h){ if(h > 1 && h < 300) height = h; } void bmi::get_weight(double w){ if(w > 1 && w < 500) weight = w; } double bmi::get_bmi(){ height /= 100; BMI = weight/(height*height); return BMI; } ``` 上面的例子中可以清楚看到我們不希望讓使用者輕易就改變height和weight,所以我用一個函式來判斷輸入的height和weight,如果在合理範圍就採納,如果不合理,就不採納。簡單的說封裝的意義就是為了資訊的隱藏,確保物件的安全,同時防止外界呼叫、存取物件內部重要的資料。 ### 物件導向繼承