- 組合:
新的類是已有類的對象組合而成。//Point類的定義及成員函數的實現 class Point { friend istream &operator>>(istream &is, Point &obj) { is >> obj.x >> obj.y; return is;} friend ostream &operator<<(ostream &os, const Point &obj) { os << "( " << obj.x << ", " << obj.y << " )"; return os;} private: double x,y; public: Point(double a = 0, double b = 0) {x = a; y = b;} double getx() const {return x;} double gety() const {return y;} };
//Segment類的定義及成員函數的實現 class Segment { friend istream &operator>>(istream &is, Segment &obj); friend ostream &operator<<(ostream &os, const Segment &obj); private: Point start; Point end; public: Segment(double x1=0,double y1=0, double x2=0, double y2=0):start(x1,y1), end(x2,y2) {} Segment(Point p1, Point p2) { start = p1; end = p2; } double getLength() const; Point getMid() const; Point getStart() const { return start; } Point getEnd() const { return end; } }; istream &operator>>(istream &is, Segment &obj) { cout << "請輸入起點座標:"; is >> obj.start; cout << "請輸入終點座標:"; is >> obj.end; return is; } ostream &operator<<(ostream &os, const Segment &obj) { os << obj.start << " - " << obj.end ; return os; } double Segment::getLength() const { double x1 = start.getx(), x2 = end.getx(), y1 = start.gety(), y2 = end.gety(); return sqrt((x2-x1 )*(x2-x1) + (y2-y1)*(y2-y1)); } Point Segment::getMid() const { return Point((start.getx() + end.getx())/2, (start.gety() + end.gety())/2); } //---------------------------------------------------------------- //Segment的使用 int main() { Point p1(1,1), p2(3,3);
Segment s1, s2(p1, p2); cout << s1 << '\n' << s2 << endl; cin >> s1; cout << s1.getStart() << s1.getEnd() << s1.getMid() << endl; return 0; }
//triangle類的定義及成員函數的實現 class triangle { Point p1; Point p2; Point p3; public: triangle(double x1=0,double y1=0, double x2=0, double y2=0, double x3=0, double y3=0):p1(x1,y1),p2(x2,y2),p3(x3,y3) {} triangle(Point pt1, Point pt2, Point pt3):p1(pt1), p2(pt2), p3(pt3) {} double area() const; double circum() const; void sideLen(double &len1,double &len2,double &len3) const; }; void triangle::sideLen(double &len1,double &len2,double &len3) const { len1 = Segment(p1, p2).getLength(); len2 = Segment(p1, p3).getLength(); len3 = Segment(p3, p2).getLength(); } double triangle::circum() const { double s1, s2, s3; sideLen(s1,s2,s3); return s1 + s2 + s3; } double triangle::area() const { double len1, len2, len3, p; sideLen(len1,len2,len3); p = (len1 + len2 + len3) / 2; return sqrt(p *(p-len1)*(p-len2)*(p-len3)); } //--------------------------------------------------- //triangle類的使用 int main() { Point p1(1,1), p2(3,1), p3(2,2); triangle t1(0,0,0,1,1,0), t2(p1,p2,p3); cout << t1.area() << " " << t1.circum() << '\n' << t2.area() << " " << t2.circum() << endl; return 0; }
- 繼承:
在已有的類的基礎上,對它進行擴展,形成一個新類。
用繼承方式創建新類時,需要指明這個新類是在哪個已有類的基礎上擴展的。
已有的類成爲基類或者父類,繼承實現的新類稱爲派生類或子類,派生類本身也可能會成爲未來派生類的基類,派生出功能更強的類。
繼承的作用:①支持軟件重用。②對事物進行分類,使對象之間關係更加清晰。③支持軟件的增量開發。
- 派生類:
定義形式:
繼承方式:public、private(默認)、protectedclass 派生類名:繼承方式 基類名 { 新增的成員聲明; };
類中的訪問方式:
繼承方式及訪問特性:public公有成員:能夠被程序中的所有函數訪問。 private私有成員:只能被自己的成員函數和友元訪問。 protected被保護成員:是特殊的私有成員,不可以被全局函數或其他類的成員函數訪問, 但能被派生類的成員函數和友元函數訪問。沒有繼承關係時等同於private。
派生類初始化:一、基類public派生類時,基類的public成員會成爲派生類的public成員, 基類的protected成員會成爲派生類的protected成員。 二、基類protected派生類時,基類的public成員和protected成員都會成爲派生類的protected成員。 三、基類private派生類時,基類的public成員和protected成員成爲派生類的private成員。 通常的繼承方式是public,它可以在派生類中保持基類的訪問特性。
C++規定,派生類對象的初始化由基類和派生類共同完成,派生類的構造函數體只負責初始化新增加的數據成員,派生類在初始化列表中調用基類的構造函數初始化基類的數據成員。
構造函數形式:
基類構造函數中的參數值通常來源於派生類構造函數的參數表,也可以用常值。派生類構造函數名(參數表):基類構造函數名(參數表){ ... }
先執行基類的構造函數,再執行派生類的構造函數。
如果派生類中的數據成員有對象成員,創建對象時,先執行基類的構造函數,再執行對象成員的構造函數,最後執行自己的構造函數。
如果基類是通過默認構造函數初始化,派生類構造函數的初始化列表可以不出現基類構造函數調用,隱式調用。
析構過程:派生類的析構函數會自動調用基類的析構函數,先執行派生類的析構函數,再執行基類的析構函數。
重定義基類的函數:class pool { double area; double depth; public: pool(double a = 200, double d = 2):area(a),depth(d) {} double getArea() const { return area; } double getDepth() const {return depth;} }; class swimmingPool : public pool { char time[15]; double price; public: swimmingPool(double a, double d, char *t, double p):pool(a, d) { strcpy(time, t); price = p; } swimmingPool() { time[0] = '\0'; price = 0; } void setTime(char *t) { strcpy(time, t); } void setPrice(double p) { price = p; } const char *getTime() const { return time; } double getPrice() const { return price; } }; class fishPond : public pool { char type[15]; double quantity; public: fishPond(double a, double d, char *t, double p):pool(a, d) { strcpy(type, t); quantity = p; } fishPond() { type[0] = '\0'; quantity = 0; } void setType(char *t) { strcpy(type, t); } void setQuantity(double p) { quantity = p; } const char *getType() const { return type; } double getQuantity() const { return quantity; } };
由於派生類的成員函數不能訪問基類的私有成員,必須通過基類的公有成員函數實現,調用基類的公有函數,可以在函數名前加上基類名的限定。void display()const { car::display(); cout<<'\t'<<seat<<'\t'<<price<<endl; }
- 派生類賦值運算符重載:
派生類不能繼承基類的構造函數,但可以調用基類的賦值運算符重載函數。如果派生類沒有定義賦值運算符重載,系統會爲它提供默認賦值運算符重載,派生類的基類對象調用基類的賦值運算符重載函數賦值。
如果默認賦值運算符不能滿足派生類要求,可以在派生類中重載賦值運算符,需要顯式的調用基類的賦值運算符函數實現基類成員的賦值。// 爲People和Student類的重載賦值運算符,注意派生類的賦值運算符重載函數中對基類對象的複製 People &operator=(const People &other) { if (this == &other) return *this; delete name; name = new char[strlen(other.name) + 1]; strcpy(name, other.name); age = other.age; return *this; } Student &operator=(const Student &other) { if (this == &other) return *this; s_no = other.s_no; delete class_no; class_no = new char[strlen(other.class_no) + 1]; strcpy(class_no, other.class_no); People::operator=(other); return *this; }
派生類作爲基類:
//派生類作爲基類實例 class Base{ int x; public: Base(int xx) {x = xx; cout << "constructing base\n";} ~Base() { cout << "destructint base\n";} }; class Derive1:public Base{ int y; public: Derive1(int xx, int yy): Base(xx) { y = yy; cout << "constructing derive1\n";} ~Derive1() { cout << "destructing derive1\n";} }; class Derive2:public Derive1{ int z; public: Derive2(int xx, int yy, int zz):Derive1(xx, yy) { z = zz; cout << "constructing derive2\n";} ~Derive2() { cout << "destructing derive2\n";} };
派生類對象與基類對象的轉換:
C++規定派生類對象可以自動轉換成基類對象,而不必定義類型轉換函數。
將派生類對象賦給基類對象、將基類指針指向派生類對象,以及定義一個引用派生類對像的基類對象時,會執行自動類型轉換。
將派生類中的基類部分賦給此基類對象,派生類新增加的成員就會被丟棄,賦值後,基類對象和派生類對象再無任何關係。
當一個基類指針指向派生類對象時,由於它本身是一個基類指針,只能解釋基類成員,而不能解釋派生類新增的成員,因此,指向派生類的基類指針只能訪問派生類中基類部分,儘管這個派生類中有與基類相同的成員函數,但是基類調用基類的成員函數(未實現多態性)。
引用是一種隱式指針,當用一個基類對象引用派生類對象時,相當於給派生類中的基類部分取了別名,對基類對象引用的修改就是對派生類中基類部分的修改
派生類的對象可以隱式的轉換成基類的對象,但是基類對象無法隱式轉換成派生類對象,因爲無法解釋派生類新增加的成員,除非在基類定義一個想派生類轉換的類型轉換函數,才能將基類對象轉換成派生類對象。Derived d(1,2); Base &br = d;
同樣不能將基類對象的地址賦給派生類的指針,或將一個基類指針賦給一個派生類的指針,即使該基類指針指向的就是一個派生類的對象。Derived d, *dp; Base *bp = &d; dp = bp; //編譯器報錯 dp = reinterpret_cast<Derived *>bp; //強制轉換
- 多態性:
多態性,相當於對象有主觀能動性。
編譯時的多態性(靜態綁定)
運行時的多態性(動態綁定):通過虛函數和基類指針指向不同的派生類的對象來實現
基類指針或基類引用可以訪問派生類對象的基類部分,而不能訪問派生類新增的成員,但如果基類的成員函數定義爲虛函數時,表示該函數在派生類中可能有不同的實現,基類指針調用該虛函數時,首先會到派生類檢查函數是否被重新定義,如果派生類重新定義了這個函數,則執行派生類中的函數,否則執行基類的函數。
每個派生類都可以重新定義虛函數,當用基類的指針指向不同的派生類的對象時,會調用不同的函數,實現運行時的多態性。
虛函數:
定義形式:函數原型聲明前面加上關鍵字virtual
派生類重新定義時,它的函數原型(返回值類型、函數名、參數個數和參數類型)必須與基類中的虛函數完全相同,否則會被認爲是兩個重載函數。
可以 定義一個指向基類的指針數組,讓它的每個元素指向基類或不同派生類的對象// 虛函數的定義,其中的area和display函數都是虛函數 class Shape{ protected: double x, y; // x、y是圖形的位置 public: Shape(double xx, double yy) {x = xx; y = yy;} virtual double area() const {return 0.0;} virtual void display() const { cout << "This is a shape. The position is (" << x << ", " << y << ")\n";} }; class Rectangle:public Shape { protected: double w, h; // w、h是矩形的寬和高 public: Rectangle(double xx, double yy, double ww, double hh): Shape(xx,yy),w(ww),h(hh){} double area() const {return w * h;} //重定義虛函數area void display() const //重定義虛函數display { cout << "This is a rectangle. The position is (" << x << ", " << y << ")\t"; cout << "The width is " << w << ". The height is " << h << endl; } }; class Circle:public Shape { protected: double r; // r是圓的半徑 public: Circle(double xx, double yy, double rr): Shape(xx,yy),r(rr){} double area() const {return 3.14 * r * r;} void display() const { cout << "This is a Circle. The position is (" << x << ", " << y << ")\t"; cout << "The radius is " << r << endl; } };
注意:①派生類重新定義虛函數時,原型必須於基類中的虛函數完全相同,否則編譯器會認爲是重載函數。Shape *sp[3] = {&s, &rect, &c};
②派生類對基類的虛函數重定義時,關鍵字virtual可以寫也可以不寫,但最好是在重定義時寫上virtual。 - 虛析構函數:
構造函數不能是虛函數,但析構函數可以是虛函數,而且最好是虛函數
如果派生類新增加的數據成員中含有指針,指向動態申請的內存,那麼派生類必須定義析構函數釋放這部分空間,如果單純delete基類指針指向的派生類對象時,會造成內存泄漏。
將基類的析構函數定義爲虛函數,當基類指針指向的對象析構時,通過基類指針會找到派生類的析構函數,執行派生類的析構函數,派生類的析構函數在執行時會自動調用基類的析構函數,因此基類和派生類的析構函數都被執行,這樣就把派生類的對象完全析構掉。 - 純虛函數:
基類往往只表示一種抽象的意志,而不與具體事物相聯繫。
純虛函數是一個在基類中聲明的虛函數,在基類中沒有定義,但要求派生類定義自己的版本。
聲明形式:virtual 返回類型 函數名(參數表)=0; virtual double area()const = 0;
- 抽象類:
如果一個類至少含有一個純虛函數,被成爲抽象類。
如果抽象類的派生類沒有重新定義此純虛函數,只是繼承了基類的純虛函數,那麼派生類仍然是一個抽象類。
因爲抽象類有未定義全的函數,所以無法定義抽象類的對象,因爲一旦對此對象調用純虛函數,該函數無法執行。
但可以定義指向抽象類的指針,作用是指向派生類對象,以實現多態性。
抽象類的作用是保證進入繼承層次的每個類都具有純虛函數所要求的行爲,保證圍繞這個繼承層次所建立的類都具有抽象類規定的行爲,保證軟件系統的正常運行,避免這個繼承層次中的用戶由於偶爾失誤(忘了所建立的派生類提供繼承層次所要求的行爲)影響系統的正常運行。
- 小結:
組合是將一個或一組已定義類的對象作爲當前類的數據成員,從而得到一個更強的類,用組合方式定義類時,對象成員的初始化一般是通過在構造函數的初始化
列表中調用對象成員的構造函數實現的
繼承是在已有類的基礎上加以擴展,形成一個新類,成爲派生類,派生類的定義需要指定基類,以及在基類的基礎上擴展哪些數據成員和成員函數。構造派生類對象時,由派生類的構造函數爲新增的數據成員賦初值,而基類部分的初始化是調用基類的構造函數完成的。
運行時的多態性是通過虛函數和基類指針指向派生類對象實現的。