c++學習筆記(13) 繼承和多型
繼承:從已有的類派生出新類
基類和派生類
不同的類之間可能有很多共通的屬性和行為,這些屬性和行為允許在一個類中通用化並被其他類所共享。類C1從類C2擴充套件而來,則C1稱為派生類,C2稱為基類,基類也稱為父類或者超類,派生類稱為子類,一個子類繼承了其父類所有可訪問的資料域和函式。同時可以增加新的資料域和函式
例如:
定義基類geometric,在此基礎上生成circle類:
基類Geometric:
geometric.h檔案
#ifndef GEOMETRIC_H #define GEOMETRIC_H #include <string> using namespace std; class Geometric { private: string color; bool filled; public: Geometric(); Geometric(const string& color, bool filled); string getColor() const; void setColor(const string& color); bool isFilled() const; void setFilled(bool state); string toString() const; }; #endif
geometric.cpp檔案
#include <string> #include <iostream> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" using namespace std; Geometric::Geometric() { color = "white"; filled = true; } Geometric::Geometric(const string& color, bool filled) { this->color = color; this->filled = filled; } string Geometric::getColor() const { return color; } void Geometric::setColor(const string& color) { this->color = color; } bool Geometric::isFilled() const { return filled; } void Geometric::setFilled(bool state) { filled = state; } string Geometric::toString() const { return "Geometric object"; }
派生類Circle
circle.h檔案:
#ifndef CIRCLE_H #define CIRCLE_H #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include <string> class Circle: public Geometric // 表明circle類都是從Geomerric類中派生出來的 Circle繼承了所有的公有成員 { private: double radius; public: Circle(); Circle(double radius); Circle(double radius, const string& color, bool filled); double getRadius() const; void setRadius(double radius); double getArea() const; // 獲取面積 double getPerimeter() const; // 獲取周長 double getDiameter() const; // 直徑 string toString() const; }; #endif
circle.cpp檔案
#include <string>
#include <iostream>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
using namespace std;
Circle::Circle()
{
radius = 1;
}
Circle::Circle(double radius)
{
this->radius = radius;
}
Circle::Circle(double radius, const string& color, bool filled)
{
//this->radius = radius;
//this->color = color; // 這種寫法是錯誤的
//this->filled = filled; // 這種寫法是錯誤的
// 除了在Geomertic類中,它的私有成員color和filled無法在其他任何類中被訪問。
// 唯一的方法是通過呼叫get()和set()進行訪問
setRadius(radius);
setColor(color); // 繼承的成員函式
setFilled(filled); // 繼承的成員函式
}
double Circle::getRadius() const
{
//return radius;
return (radius>0)?radius:0;
}
void Circle::setRadius(double radius)
{
this->radius = radius;
}
double Circle::getArea() const
{
return 3.14*radius*radius;
}
double Circle::getDiameter() const
{
return 2*radius;
}
double Circle::getPerimeter() const
{
return 2*3.14*radius;
}
string Circle::toString() const
{
return "Circle object";
}
main.cpp檔案
int main(int argc, char *argv[])
{
Circle circle1(4.5, "black", true);
cout << "Circle1 radius is " << circle1.getRadius() << " and color is " << circle1.getColor() << " and area is " << circle1.getArea() << endl;
if(circle1.isFilled())
{
circle1.setColor(false);
}
return 0;
}
總結:
1.基類中的私有資料域不能在基類以外被訪問,因此在派生類中不能直接使用,但可以通過基類中定義的accessor和mutator來訪問和修改它們。
2.繼承用來建模is-a關係:
3.c++允許同時擴充套件多個類來得到一個派生類,該功能被稱為多重繼承。
泛型程式設計
當程式中需要一個基類物件時,向其提供一個這個基類的派生類物件是允許的。這種特性使得一個函式可以適用於較大範圍的物件實參,變得更加通用,稱之為泛型程式設計
例如:
void displayGeometric(const Geometric& shape)
{
cout << shape.getColor() << endl;
}
函式displayGeometric()的引數型別是Geometric,但是我們可以向它傳遞Geometric類的派生類作為引數也可以。
例如:
displayGeometric(Circle(5));
派生類的建構函式和解構函式:
派生類的建構函式在執行自身程式碼之前,首先呼叫它的基類的建構函式(派生類不繼承基類ide建構函式,僅僅是呼叫而已)。派生類的解構函式首先執行其自身的程式碼,然後自動呼叫其基類的解構函式(不繼承,同樣是呼叫)。
1.呼叫基類建構函式
派生類中的建構函式總是顯式或者隱式的呼叫基類中的建構函式。如果沒有被顯式呼叫,則會預設呼叫基類中無參的建構函式
例如:
Circle::Circle(double radius, const string& color, bool filled):Geometric(color, filled)
{
setRadius(radius);
}
建構函式鏈和解構函式鏈:
這裡用a->b表示b繼承a:
假設a->b->c->d, 則在例項化d物件時,d的建構函式執行前,會呼叫a的建構函式,而a的建構函式執行前,會呼叫b的建構函式,以此類推,就會按照繼承關係形成一個鏈,稱為建構函式鏈,而解構函式鏈與建構函式鏈正好相反。
tips: 如果一個類可能被繼承,則最好為它設計一個無參的建構函式
tips: 如果基類中有自定義的拷貝建構函式和賦值操作,因該在派生類中自定義這些來保證基類中的資料域被正確拷貝。
函式重定義
在基類中定義的函式能夠在派生類中可以被重新定義。需要在派生類的標頭檔案中新增函式原型,並在派生類的實現檔案中提供函式新的實現。
例如:toStrng()函式
如果在子類中任然需要呼叫父類中的函式,則需要基類名和作用域解析運算子
例如:
cout << circle1.Geometric::toString(); // 在circle物件中呼叫基類中的toString()函式
函式過載和函式重定義的區別:
函式過載提供多個名字相同,但簽名不同的函式。
函式重定義:必須在派生類中定義一個與基類中函式具有相同簽名和返回型別的函式。
多型
多型意味著一個超型別的變數可以引用一個子型別的物件!(對比泛型程式設計的概念)
OOP三支柱: 封裝, 繼承性, 多型
一個類定義一種型別:基類稱為超型別(subtype),派生類稱為子型別(supertype)。
函式dislpayGeometric()的引數型別為Geometric, 但是在呼叫的函式的時候可以傳入circle型別的物件。 這就是多型的概念。
#include <iostream>
#include <string>
//#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
using namespace std;
void displayGeometric(const Geometric& g)
{
cout << g.toString() << endl;
}
int main(int argc, char *argv[])
{
Circle circle1(4.5, "black", true);
displayGeometric(circle1); // 超型別的變數引用子型別的物件
return 0;
}
虛擬函式和動態繫結
一個函式可以在沿著繼承關係鏈的多個類中實現,虛擬函式使得系統能夠基於物件的實際型別決定在執行時呼叫哪一個函式。
#include <iostream>
#include <string>
//#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
using namespace std;
void displayGeometric(const Geometric& g)
{
cout << g.toString() << endl;
}
int main(int argc, char *argv[])
{
Circle circle1(4.5, "black", true);
Geometric g1;
displayGeometric(g1);
displayGeometric(circle1); // 超型別的變數引用子型別的物件
return 0;
}
上面的程式碼的輸出結果如圖所示:
在Geometric類和circle類總都定義了toString()函式,但是在語句:
displayGeometric(g1);
displayGeometric(circle1); // 超型別的變數引用子型別的物件
被執行時,呼叫的都是Geometric類中的toString()函式
如何使得在傳入相應型別的引數時,使得相應的類中的toString()函式被呼叫,而不是都呼叫Geometric類中的toString()函式?
虛擬函式就可以實現這一需求:
virtual string toString() const;
在基類中,函式toString()被定義成 virtual 型別,c++在執行時動態決定呼叫哪一個函式。這種功能叫做動態繫結。
例項:
如果不使用虛擬函式,呼叫Geometric, circle, Rectangle三個類物件的toString()方法,輸出的都是“Geometric object”
將基類的toString()函式定義為虛擬函式後,實現了動態繫結,輸出的結果為:
總結:
1.要實現動態繫結,函式在基類中要宣告為虛擬函式。而在派生類中不必再宣告為虛擬函式
2. 引用物件的變數必須以引用或者指標的形式傳遞
3.動態繫結與匹配函式簽名是兩個獨立的問題:編譯器根據引數型別,引數個數以及引數順序在編譯時尋找匹配的函式,這是靜態繫結,用於匹配函式簽名。 動態繫結是由變數所引用的物件的實際型別所決定的,這是動態繫結,一個虛擬函式可以在多個派生類中實現,C++在執行時動態繫結這些函式的實現。
-------------------------------------------分割線------------------------------------------------------
關鍵字protected
基類中定義的資料域和函式經常需要允許派生類訪問而不允許非派生類訪問,為此可以使用關鍵字protected。
抽象類和純虛擬函式
在類的繼承層次中,基類到派生類,類的內容越來越具體和明確,從派生類到基類,則越來越一般化和不具體。
在設計類時應該確保基類包含其派生類的共同特性。
例如:
Gemometric類定義幾何圖形的基類,描述幾何物件的公共屬性,例如派生類 circle,rectangle類都會包含getArea()函式和getPerimeter()函式,因為所有的幾何物件都可以計算面積和周長,因此按照類的設計原則應該在基類Gemometric中宣告getArea()和getPerimeter()函式。但是這個函式不能在Gemometric類中實現,因為其實現和具體的幾何物件有關。
這樣的函式就成為抽象函式,基類Gemometric稱為抽象類
純虛擬函式的宣告方法:
virtual double getArea() const = 0; // 純虛擬函式
virtual double getPerimeter() const = 0; // 純虛擬函式
注(一個需要注意的地方)
當定義了基類中toString()函式為純虛擬函式後,此時的Geometric類為一個抽象類,不能再宣告Geometric物件!!!!!
在基類中定義了純虛擬函式,就可以實現兩個不同派生類之間的一些操作了
例如:
需要比較circle和rectangle之間的面積大小:
定義如下函式即可:
bool equalArea(const Geometric& g1, const Geometric& g2)
{
return g1.getArea()==g2.getArea();
}
關於純虛擬函式的實現部分,這裡程式碼沒有報錯,但是執行結果總是不對,貼上來程式碼:
基類:Geometric
Geometric.h檔案
#ifndef GEOMETRIC_H
#define GEOMETRIC_H
#include <string>
using namespace std;
class Geometric
{
private:
string color;
bool filled;
public:
Geometric();
Geometric(const string& color, bool filled);
string getColor() const;
void setColor(const string& color);
bool isFilled() const;
void setFilled(bool state);
virtual string toString() const; // 虛擬函式,實現動態繫結
virtual double getArea() const = 0; // 純虛擬函式
virtual double getPerimeter() const = 0; // 純虛擬函式
};
#endif
Geometric.cpp檔案
#include <string>
#include <iostream>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
using namespace std;
Geometric::Geometric()
{
color = "white";
filled = true;
}
Geometric::Geometric(const string& color, bool filled)
{
this->color = color;
this->filled = filled;
}
string Geometric::getColor() const
{
return color;
}
void Geometric::setColor(const string& color)
{
this->color = color;
}
bool Geometric::isFilled() const
{
return filled;
}
void Geometric::setFilled(bool state)
{
filled = state;
}
string Geometric::toString() const
{
return "Geometric object";
}
派生類Circle
circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
#include <string>
using namespace std;
class Circle: public Geometric // 表明circle類都是從Geomerric類中派生出來的 Circle繼承了所有的公有成員
{
private:
double radius;
public:
Circle();
Circle(double radius);
Circle(double radius, const string& color, bool filled);
double getRadius() const;
void setRadius(double radius);
double getArea() const; // 獲取面積
double getPerimeter() const; // 獲取周長
double getDiameter() const; // 直徑
string toString() const; // 將派生類中的函式重定義
};
#endif
circle.cpp
#include <string>
#include <iostream>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
using namespace std;
Circle::Circle()
{
radius = 1;
}
Circle::Circle(double radius)
{
this->radius = radius;
}
Circle::Circle(double radius, const string& color, bool filled)
{
//this->radius = radius;
//this->color = color; // 這種寫法是錯誤的
//this->filled = filled; // 這種寫法是錯誤的
// 除了在Geomertic類中,它的私有成員color和filled無法在其他任何類中被訪問。
// 唯一的方法是通過呼叫get()和set()進行訪問
setRadius(radius);
setColor(color); // 繼承的成員函式
setFilled(filled); // 繼承的成員函式
}
double Circle::getRadius() const
{
//return radius;
return (radius>0)?radius:0;
}
void Circle::setRadius(double radius)
{
this->radius = radius;
}
double Circle::getArea() const
{
return 3.14*radius*radius;
}
double Circle::getDiameter() const
{
return 2*radius;
}
double Circle::getPerimeter() const
{
return 2*3.14*radius;
}
string Circle::toString() const // 提供重定義函式的實現
{
return "Circle object";
}
派生類rectangle
rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include <iostream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
using namespace std;
class Rectangle: public Geometric
{
private:
double width;
double height;
public:
Rectangle(); //無參建構函式
Rectangle(double width, double height);
Rectangle(double width, double height, const string& color, bool filled);
// accessor
double getWidth() const;
double getHeight() const;
double getArea() const;
double getPerimeter() const;
// mutator
void setWidth(double width);
void setHeight(double height);
string toString() const;
//void setFilled(bool filled);
//void setColor(const string& color);
};
#endif
rectangle.cpp
#include <iostream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h"
using namespace std;
Rectangle::Rectangle()
{
width = 1;
height = 1;
}
Rectangle::Rectangle(double width, double height)
{
setWidth(width);
setHeight(height);
}
Rectangle::Rectangle(double width, double height, const string& color, bool filled)
{
setWidth(width);
setHeight(height);
setColor(color);
setFilled(filled);
}
double Rectangle::getWidth() const
{
return width;
}
double Rectangle::getHeight() const
{
return height;
}
double Rectangle::getArea() const
{
return width*height;
}
double Rectangle::getPerimeter() const
{
return 2*(width+height);
}
void Rectangle::setWidth(double width)
{
this->width = width;
}
void Rectangle::setHeight(double height)
{
this->height = height;
}
string Rectangle::toString() const
{
return "Rectangle object";
}
主函式
main.cpp
#include <iostream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h"
#include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h"
using namespace std;
void displayGeometric(const Geometric& g)
{
cout << g.toString() << endl;
}
bool equalArea(const Geometric& g1, const Geometric& g2)
{
return g1.getArea()==g2.getArea();
}
int main(int argc, char *argv[])
{
Circle circle1(4.5, "black", true);
//Geometric g1;
Rectangle rec1;
//displayGeometric(g1);
displayGeometric(circle1); // 超型別的變數引用子型別的物件
displayGeometric(rec1);
cout << "rec1 area is " << rec1.getArea() << endl;
cout << equalArea(circle1, rec1);
cout << "circle area is " << circle1.getArea() << endl;
//cout << "The circle and rectangle area is equal? " << ((equalArea(circle1, rec1))?"Yes":"No") << endl;
return 0;
}
遇到的問題是程式碼不能執行
cout << equalArea(circle1, rec1);
這一句之後的程式碼都執行不了,到這一句直接顯示“按任意鍵結束”???