类的继承
1. 派生类
派生类对象存储了基类的数据成员(派生类继承了基类的实现)
派生类对象可以使用基类的方法(派生类继承了基类的接口)
派生类需要自己的构造函数
派生类可以根据需要添加额外的数据成员和成员函数
2. 构造函数
派生类应当使用初始化列表的方式将值传递给基类的构造函数,否则将使用默认的基类构造函数
derived:derived(type1 x, type2 y):base(x, y) {}
首先创建基类对象。
派生类构造函数应通过成员初始化列表将基类信息传递给基类的构造函数。
派生类构造函数应当初始化派生类新增的初始化成员。
3. 派生类和基类之间的重要关系
a. 派生类对象可以使用基类的方法
b. 基类指针可以在不显式类型转换的情况下指向派生类对象
c. 基类的引用可以在不显式类型转换的情况下引用派生类对象
但是,基类指针和引用只能调用基类的方法,不能调用派生类的方法。
引用类型的兼容性可以将基类对象初始化为派生类对象
假如RatedPlayer是TableTennisPlayer的派生类
下面的代码:
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer olaf2(olaf1);
匹配的函数原型为拷贝构造函数:TableTennisPlayer(const TableTennisPlayer&);
当然也可以将派生类赋值给基类对象,此时调用的是基类的赋值运算符重载
4. 多态公有继承
有两种重要的机制用于实现多态公有继承:
在派生类中定义基类的方法
使用虚方法:如果方法是采用引用或者指针而不是对象调用的,它将确定使用哪一种方法,如果没有使用关键字virtual,程序将根据引用类型或者指针类型确定方法,如果使用了virtual,程序将根据引用或者指针指向的对象的类型来选择方法。
虚函数的工作原理:
每给对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,这个数组称为虚函数表,虚函数表中存储了为类对象进行声明的虚函数的地址。基类对象包含一个指针,该指针指向基类中所有虚函数的地址表,派生类将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将包含新函数的地址,如果派生类没有新定义虚函数,则该表保存基类虚函数的地址,如果派生类定义了新的虚函数,则新的函数地址也被添加到虚函数表中。
使用虚函数,在内存和执行速度方面都有一定的成本,包括:
每个对象都将增大,增大量为存储地址的空间
对于每一个类,编译器都将创建一个虚函数地址表(数组)
对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。
在基类方法的声明中使用virtual可以使该方法在基类以及所有的派生类中是虚的
如果使用指向对象的引用或指针来调用虚方法,程序将使用对象类型定义的方法,而不使用为引用或指针类型定义的方法,这称为动态联编,
a. 构造函数:构造函数不能是虚函数
b. 析构函数:析构函数理应是虚函数,除非类不用做基类
c. 友元:友元函数不能是虚函数,友元函数不是类的成员函数,必须时候可以通过友元函数调用虚函数来解决
d. 没有重定义:使用函数的基类版本
e. 重新定义将隐藏方法:返回类型协变:重新定义继承的方法,应确保类型相同,但是如果返回类型是基类引用或者指针,可以修改为派生类的引用或者指针。
如果基类声明被重载了,应当在派生类中重新定义所有的基类版本。
#include <string> using namespace std; class Brass { private: string fullName; long acctNum; double balance; public: Brass(const string& s = "Nullbody", long an = -1, double bal = 0.0); void deposit(double amt); virtual void withDraw(double amt); double getBalance() const; virtual void viewAcct() const; virtual ~Brass() {} }; class BrassPlus : public Brass{ private: double maxLoan; double rate; double owesBank; public: BrassPlus(const string& s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125); BrassPlus(const Brass &ba, double ml = 500, double r = 0.11125); virtual void viewAcct() const; virtual void withDraw(double amt); void resetMax(double m) { maxLoan = m; } void resetRate(double r) { rate = r; } void resetOwes() { owesBank = 0; } };
#include "Brass.h" #include <iostream> using namespace std; typedef ios_base::fmtflags format; typedef streamsize precis; format setFormat(); void restore(format f, precis p); Brass::Brass(const string& s, long an, double bal) { fullName = s; acctNum = an; balance = bal; } void Brass::deposit(double amt) { if (amt < 0) { cout << "Negative deposit not allowed;" << "deposit is cancelled.\n"; } else { balance += amt; } } void Brass::withDraw(double amt) { format initialState = setFormat(); precis prec = cout.precision(2); if (amt < 0) { cout << "withdrawal amout must be positive;" << "withdrawal canceled.\n"; } else if(amt <= balance){ balance -= amt; } else { cout << "Withdrawal amount of $ " << amt << " exceeds your balance.\n" << "withdrawal canceled.\n"; } restore(initialState, prec); } double Brass::getBalance() const{ return balance; } void Brass::viewAcct() const { format initialState = setFormat(); precis prec = cout.precision(2); cout << "Client: " << fullName << endl; cout << "Account Number: " << acctNum << endl; cout << "Balance: $ " << balance << endl; restore(initialState, prec); } BrassPlus::BrassPlus(const string& s, long an, double bal, double ml, double r) : Brass(s, an, bal) { maxLoan = ml; owesBank = 0.0; rate = r; } BrassPlus::BrassPlus(const Brass& ba, double ml, double r) { maxLoan = ml; owesBank = 0.0; rate = r; } void BrassPlus::viewAcct() const { format initialState = setFormat(); precis prec = cout.precision(2); Brass::viewAcct(); cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout.precision(3); cout << "Loan Rate: " << 100 * rate << "%\n"; restore(initialState, prec); } void BrassPlus::withDraw(double amt) { format initialState = setFormat(); precis prec = cout.precision(2); double bal = getBalance(); if (amt <= bal) { Brass::withDraw(amt); } else if (amt <= bal + maxLoan - owesBank) { double advance = amt - bal; owesBank += advance * (1.0 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; deposit(advance); Brass::withDraw(amt); } else { cout << "Credit limit exceeded. Transactiion cancelled.\n"; } restore(initialState, prec); } format setFormat() { return cout.setf(ios_base::fixed, ios_base::floatfield); } void restore(format f, precis p) { cout.setf(f, ios_base::floatfield); cout.precision(p); }
#include "Brass.h" int main() { Brass* p_clients[CLIMENTS]; string temp; long tempnum; double tempbal; char kind; for (int i = 0; i < CLIMENTS; i++) { cout << "Enter client's name: "; getline(cin, temp); cout << "Enter client's account number: "; cin >> tempnum; cout << "Enter opening balance: $"; cin >> tempbal; cout << "Enter 1 for Brass Account of " << "2 for BrassPlus Account: "; while (cin >> kind && (kind != '1' && kind != '2')) cout << "Enter either 1 or 2: "; if (kind == '1') { p_clients[i] = new Brass(temp, tempnum, tempbal); } else { double tmax, trate; cout << "Enter the overdraft limit: $"; cin >> tmax; cout << "Enter the interest rate " << "as a decimal fraction: "; cin >> trate; p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate); } while (cin.get() != '\n') { continue; } } cout << endl; for (int i = 0; i < CLIMENTS; i++) { p_clients[i]->viewAcct(); cout << endl; } for (int i = 0; i < CLIMENTS; i++) { delete p_clients[i]; } cout << "Done.\n"; return 0;
5. c++和java如何访问父类的公有成员函数
如果访问的父类函数子类没有重写,那么可以直接使用函数名访问,如果重写了:
c++采用 父类名::函数名访问
java采用 super.函数名访问
6. 访问控制:protected
private和protected的区别只有在基类派生的类中才能够体现出来,派生类的成员可以直接访问基类的保护成员,但是不能访问基类的私有成员。对于外部世界而言,保护成员的行为和私有成员相似,对于派生类来说,保护成员的行为和公有成员相似。
7. 使用动态内存和友元的继承示例
#include <iostream> using namespace std; class baseDMA { private: char* label; int rating; public: baseDMA(const char* l = "NULL", int r = 0); baseDMA(const baseDMA& rs); virtual ~baseDMA(); baseDMA& operator=(const baseDMA& rs); friend ostream& operator<<(ostream &os, const baseDMA& rs); }; class lacksDMA : public baseDMA { private: enum { COL_LEN = 40 }; char color[COL_LEN]; public: lacksDMA(const char* c = "blank", const char* l = "NULL", int r = 0); lacksDMA(const char* c, const baseDMA& rs); friend ostream& operator<<(ostream &os, const lacksDMA& rs); }; class hasDMA : public baseDMA { private: char* style; public: hasDMA(const char* s = "none", const char* l = "null", int r = 0); hasDMA(const char* s, const baseDMA& rs); hasDMA(const hasDMA& hs); ~hasDMA(); hasDMA& operator=(const hasDMA& rs); friend ostream& operator<<(ostream& os, const hasDMA& rs); }; #include "DMA.h" #include <cstring> baseDMA::baseDMA(const char* l, int r) { label = new char[strlen(l) + 1]; strcpy(label, l); rating = r; } baseDMA::baseDMA(const baseDMA& rs) { label = new char[strlen(rs.label) + 1]; strcpy(label, rs.label); rating = rs.rating; } baseDMA::~baseDMA(){ delete[] label; } baseDMA& baseDMA::operator=(const baseDMA& rs) { if (this == &rs) { return *this; } delete[] label; label = new char[strlen(rs.label) + 1]; strcpy(label, rs.label); rating = rs.rating; return *this; } ostream& operator<<(ostream &os, const baseDMA& rs) { os << "Label: " << rs.label << endl; os << "Rating: " << rs.rating << endl; return os; } lacksDMA::lacksDMA(const char* c, const char* l, int r) : baseDMA(l, r) { strncpy(color, c, 39); color[39] = '\0'; } lacksDMA::lacksDMA(const char* c, const baseDMA& rs) : baseDMA(rs) { strncpy(color, c, COL_LEN - 1); color[COL_LEN - 1] = '\0'; } ostream& operator<<(ostream&os, const lacksDMA &ls) { os << (const baseDMA&)ls); os << "Color: " << ls.color << endl; return os; } hasDMA::hasDMA(const char* s, const char* l, int r) : baseDMA(l, r) { style = new char[strlen(s) + 1]; strcpy(style, s); } hasDMA::hasDMA(const char* s, const baseDMA& rs) : baseDMA(rs) { style = new char[strlen(s) + 1]; strcpy(style, s); } hasDMA::hasDMA(const hasDMA& hs) : baseDMA(hs) { style = new char[strlen(hs.style) + 1]; strcpy(style, hs.style); } hasDMA::~hasDMA() { } hasDMA& hasDMA::operator=(const hasDMA& hs) { if (this == &hs) { return *this; } baseDMA::operator=(hs); // 显示的调用父类的运算符重载函数 delete[] style; style = new char[strlen(hs.style) + 1]; strcpy(style, hs.style); return *this; } ostream& operator<<(ostream& os, const hasDMA& hs) { os << (const baseDMA&)hs; // 通过强制类型转换,以便匹配类型时能够选择正确的函数 os << "Style: " << hs.style << endl; return os; }