--- tags: "yoyo forum" --- # 面試遇到的OOP 以c++為例 ### 物件導向的三大特性(封裝、繼承、多型) 封裝(encapsulation) :將物件內部的資料隱藏起來(private or protected),只能透過物件本身所提供的介面取得五件內部屬性或是方法,物件內部的細節資料或是邏輯隱藏起來,要存取物件內部的資料,只能透過設定好的方法處理。 在上述的例子中,就是利用 getinformation() 取得車子資訊,但是不用知道如何取得的。 繼承(inheritance):一個類別的子類別,可以繼承父類別的屬性跟方法,也新增自己的屬性。如上述的車跟計程車 多型(polymorphism):1. 多載(overloading): 相同類別中,定一名稱相同,但是參數個數不同,可以利用參數個數或是型態互叫不同的方法,如同上述的Taxi 內,有三個變數跟四個變數的,就有不一樣的處理方法。2. 複寫(overriding) :覆蓋掉父類別的函式,ex 把 getinformation 變成 taxi版本。 補充: protect: 這個部分是為了讓子類別能夠存取某些base的資料,但是又不想要讓別人使用到的時候就會將這個變數放在protect 。 ### c++ OOP 三大特性 首先要先定義類別(class)跟物件(object),類別(class)是某類事物的抽象特點,類別會包含了資料的形式以及對資料的操作方法。 物件(object),又叫做類別的實例(instance),也就是利用class製作許多不同的資料。 舉例來說,類別叫做汽車,這樣可以利用汽車這個類別區分出各種不同的車款,BMW 拉! Honda拉!…. ```c++= #include <iostream> #include <string> using namespace std; class Car{ public: string brand; string type; int hp; Car(string a, string b, int c){ brand = a; type = b; hp =c; } ~Car(){ cout << "goodbye " << brand << " " << type << endl; } void getInformation(){ cout << "廠牌" << brand << " 型號" << type << " 馬力" << hp << endl; } }; class Taxi:public Car{ public: Taxi(string a, string b, int c): Car(a, b, c){ //套用原本class的操作 driverName = "default" ; } Taxi(string a, string b, int c, string d): Car(a, b, c){ //套用原本class的操作 driverName = d ; } //這裡也可以改成 //Taxi(string a, string b, int c, string d): Car(a, b, c), driverName(d){ //} string driverName; void setDriverName(string s){ driverName = s; } void getInformation(){ // override cout << "廠牌" << brand << " 型號" << type << " 馬力" << hp << " driver name " << driverName << endl; } }; int main(){ Car* BMW = new Car("BMW","X5",340);//宣告類別為Car的資料,並給定BMW變數 Car Porsche = Car("Porsche","Cayenne",300);//宣告類別為Car的資料,並給定Porsche變數 Porsche.getInformation();//印出資訊 Porsche.~Car(); BMW->getInformation();//印出資訊 // delete BMW; BMW->~Car(); cout << "then " << endl; Taxi new_taxi = Taxi("toyota","camery",300); new_taxi.getInformation(); Taxi taxi1 = Taxi("honda","civic",200, "suzuki"); taxi1.getInformation(); return 0; } //output /* 廠牌Porsche 型號Cayenne 馬力300 goodbye Porsche Cayenne 廠牌BMW 型號X5 馬力340 goodbye BMW X5 then 廠牌toyota 型號camery 馬力300 driver name default 廠牌honda 型號civic 馬力200 driver name suzuki goodbye honda civic goodbye toyota camery goodbye Porsche Cayenne */ ``` 在上面的程式碼中, 如果三個argument的名稱跟三個要assign的變數名稱相同,則會沒辦法把東西正確傳遞,要使用 this→brand = brand,才能夠把資料傳進去。 ### virtual function static binding vs dynamic binding binding 是把一個東西對應到另一個東西上,在C++裡面bindin指的是函示呼叫跟函式定義的連接,會發生在 compile-time or run-time. 在compile-time 發生的binding 稱之為static binding,又稱為early-binding 在run-time 發生的binding 稱之為dynamic binding 又稱為late-binding. 靜態繫結的優勢是所有函式鏈結的資訊都已經提前知道了,所以程式執行起來會比較快。 動態繫結的好處在run-time才決定,所以可以更彈性的呼叫函式。 函式宣告(function declare) 會告訴compiler 該函式的名字回傳型別跟傳入的參數,但是省略了實際的主體。 函式定義(function define) 提供compiler 該函式的主體跟內容等等細節。 如以下的式子 ```c++ // 函式原型宣告 int getMax(int x, int y); //------------------------------------ // 函式定義 //------------------------------------ int getMax(int x, int y) { if(x>y)return x; // 如果x大於y ,則傳回x else return y; // 否則傳回y } ``` 當我們提到inheritance時,要先想清楚該function想繼承的是 declare 還是 definition ,承襲了前面的部分, inheritance of interface 繼承宣告, inheritance of implementation 繼承函式的定義。 有三種狀況,只想繼承函式宣告。想繼承函式宣告跟定義,但允許override定義,想要繼承兩者,但不允許override定義。 ``` class Shape { { public: virtual void draw() const = 0; virtual void error(const std::string& msg); int objectID() const; … }; class Rectangle: public Shape{…}; class Ellipse: public Shape{…}; ``` 以上面的 Shape 來説,因為它包含了一個以上的 pure virtual 函式,所以他是個abstract class,所以他沒有辦法創造 Shape物件,只有在shape 的subclass (子物件)才可以。 1. 宣告pure virtual 函式的意義是,derived class 僅僅繼承函式的interface,definition要自己處理 Shape::draw 在通告所有的 derived class ,你必須要提供 draw 函式,要怎麼實作我不管。 像是要畫出矩形跟圓形的方法應該是不一樣的。 2. simple virtual function : 也就是有virtual 但是後面沒有0的 宣告 simple virtual 函式的意義為,derived class 繼承函式的 interface 以及其 default implementation 在simple virtual函式,derived class 繼承了函式的interface ,simple virtual 函式會提供基礎的implemetation,但derived class 可以override 。 Q: 一般的函式就不能override嗎 Shape 透過這個意思告訴所有 derived class「你要提供 error 函式,但如果你不想要寫自己的版本的話,你可以用我的版本」。 3. 宣告 non-virtual 函式的意義為,繼承函式的 interface 以及不更改函式原本的 implementation。 ### 純虛擬函式(pure virtual function) 純虛擬函式 (pure virtual function) 與抽象類別(abstract class) : 類別中可以定義純虛擬函式,純虛擬函式就是沒有函式內容的函式,方法為: `virtual typeT funcname() = 0;` 簡單來說可以說是,前有virtual 後有 =0 即是 在一個class中只要有至少一個純虛擬函式,該類別就被稱為抽象類別,一個類別不能產生物件,只能用來被繼承,如果被用來產生物件,則會報錯 ```c++ #include <iostream> using namespace std; class A{ public: virtual int C() =0; int a; }; class B:public A{ public: int C(); }; int B::C(){ // 如果這裡沒有實作純虛函數就會報錯 cout << "successful" << endl; return 0; } int main() { cout<<"Hello World"; B b; b.C(); // 如果沒有實作C() 則這裡會報錯 return 0; } ``` #### 總結 virtual 的好處是避免掉了程式碼的 replication,但缺點是 class 裡面會需要多佔一些空間以及 run-time 會比較慢一些,當然還有一些 virtual 的其他小細節,像是 polymorphic bases class 中 destructor 盡量要 virtual、盡量避免讓 virtual 遇上 inline、在 multiple inheritance 中 virtual base class 的意義、如果要 override virtual 函式的話最好加上 override 等等,有興趣的話可以繼續探索下去。 effective c++ item 34 Q virtual function and override ### polymorphism 在閱讀這篇文章的時候,對於文章的內容有點不理解,是在關於simple function 的override的部分,發現就override來説,好像simple function 跟 virtual function 沒有什麼差別的感覺。提出了以下的例子 ```c++ #include <iostream> using namespace std; class A{ public: virtual int b(){ cout << "test b func" << endl; return 0; } int c(){ cout << "test c func" << endl; return 0; } }; class B:public A{ public: int b(){ cout << "class B test b func" <<endl; return 0; } int c(){ cout << "class B test c func" <<endl; return 0; } }; int main() { A a; a.b(); a.c(); B b; b.b(); b.c(); return 0; } /* 輸出結果 test b func test c func class B test b func class B test c func */ ``` 根據以下的例子鄭教授為了讓我理解他的精妙,對我自己的例子進行修改,主要修改的點只有一個 把pointer都變成是base class的pointer 這樣的做法會發生什麼事情呢?請看output ```c++ #include <iostream> using namespace std; class A{ public: virtual int b(){ cout << "test b func" << endl; return 0; } int c(){ cout << "test c func" << endl; return 0; } }; class B:public A{ public: int b(){ cout << "class B test b func" <<endl; return 0; } int c(){ cout << "class B test c func" <<endl; return 0; } // int d(){ // cout << "only test" << endl; // return 0; // } }; int main() { A *ptr1, *ptr2; ptr1 = new A; ptr2 = new B; ptr1->b(); ptr2->b(); ptr1->c(); ptr2->c(); // ptr2->d(); // ptr2->A::b(); // ptr2->A::c(); ptr2->A::b();// 強制從B中執行A的function return 0; } /* test b func class B test b func test c func test c func test b func */ ``` 發現了嗎,問題是在A pointer,ptr1, ptr2 都變成程式A class 的pointer,因為他是同一個類別的衍生類,所以可以這樣使用。 雖然在建立實體的時候,一個類別是 A一個類別是B 但是他們都可以使用原始的base class的pointer進行指向。只是其中不一樣的事情是,virtual function 會依照指標的類別,去找相應的virtual table 進行function查找,所以如果是用A pointer他就只會去找 A class的virtual table。如果是非virtual 的simple function呢?因為他的記憶體已經寫死了,所以即便pointer 是class A 但是裡面的function的記憶體位子仍然是class B的function address 。 所以同樣是a pointer to A class ,當他指向 base class的時候,會是執行 base class的實作,如果改成指向 derived class ,那麼他就會去查找子類的表,找到子類的實作。 example : 交通工具都能夠前進,那我只要知道交通工具能夠前進就可以了,我不用知道他是哪一種交通工具,只要是交通工具的衍生類都會有前進的選項,在實行時期再看他是哪一種交通工具決定他是怎麼前進的。 所以叫做動態多型的理由就是因為他要執行什麼function 是在執行時期確定是哪個衍生類之後,再去virtual table 找到目標的。 不過上面的virtual table 被override這件事,並不代表在derived class 就沒辦法呼叫那些被取代的function ,只是要利用特殊的方法。 ptr2->A::b(); 不管ptr2 是 A* B*都可以成立。 只要是子類別,我都可以利用父類別的指標去指向我原本就有的函數,所以才叫多態,看起來是同個型態的指標可是行為是動態決定的,為了支援使用該funciton 的時候不需要確切知道他是哪種東西就可執行 ex : 遊戲的職業有很多種,魔法師拉戰士拉.... 他們都有一個叫做攻擊的func,用了這個方法我不需要在乎他本身是什麼類別,而是知道他就是一個人物,能夠攻擊,然後因為pointer 會指到該人物的virtual table 去找尋他的攻擊function 是什麼! 補如果是靜態多型…… 最後補一個完整的例子,如果有一個base class 叫做動物,他會叫,可以寫成以下形式, ```c++ class Animal{ public: virtual void speak(){//虛擬函式 cout<<"動物在說話"<<endl; } }; class Cat:public Animal{ public: void speak(){ cout<<"小貓在說話"<<endl; } }; class Dog:public Animal{ public: void speak(){ cout<<"小狗在說話"<<endl; } }; //執行doSpeak函式,地址早繫結,編譯階段確定函式地址 //如果想讓貓說話,那麼這個函式地址就不能提前繫結,需要在執行階段進行繫結,地址晚繫結 void doSpeak(Animal &animal){//Animal &animal=cat 父類引用指向子類的物件,C++中允許父子之間的型別轉換,不需要做強制型別轉換,父類的引用/指標可以直接指向子類物件 animal.speak; } void test01(){ Cat cat; doSpeak(cat); Dog dog; doSpeak(dog); } int main(){ test01(); } ``` ### 建構順序 ```c++ #include <iostream> using namespace std; class A{ public: A(){cout<< "A,";} ~A(){cout << "~A,";} }; class B{ public: B(){cout<< "B, ";} ~B(){cout << "~B, ";} }; class C:public A{ public: C(){cout << "C,";} ~C(){cout << "~C,";} }; class D{ public: D(){cout << "D,";} ~D(){cout << "~D,";} C _c; }; class E:public D{ public: E(){cout << "E,"; _pB = new B;} ~E(){cout << "~E,"; delete _pB;} A a; B* _pB; }; int main() { E e; return 0; } ``` 小到大,先做其他建構子,再處理