C++进阶--继承

目录

目录

一.公有继承

二.多态公有继承

三.访问控制 private

四.包含对象成员的类

五.私有继承

六.保护继承

七.多重继承


C++的继承方式有三种,分别是:公有继承、保护继承、私有继承;

特征 公有继承 保护继承 私有继承
公有成员变成 派生类公有成员 派生类保护成员 派生类私有成员
保护成员变成 派生类保护成员 派生类保护成员 派生类私有成员
私有成员变成 只能通过基类接口访问 只能通过基类接口访问 只能通过基类接口访问
能否隐式向上转换 是(但只能在派生类中)

一.公有继承

1.派生类

首先创建一个基类,然后派生一个类,如下:

// tabtenn1.h -- a table-tennis base class
#ifndef TABTENN1_H_
#define TABTENN1_H_
#include 
using std::string;
// simple base class
class TableTennisPlayer
{
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer (const string & fn = "none",
                       const string & ln = "none", bool ht = false);
    void Name() const;
    bool HasTable() const { return hasTable; };
    void ResetTable(bool v) { hasTable = v; };
};

// simple derived class
class RatedPlayer : public TableTennisPlayer
{
private:
    unsigned int rating; //新增数据成员
public:
    RatedPlayer (unsigned int r = 0, const string & fn = "none", //自己的构造函数
                 const string & ln = "none", bool ht = false);
    RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
    unsigned int Rating() const { return rating; }               //新增成员函数
    void ResetRating (unsigned int r) {rating = r;}
};

#endif
//tabtenn1.cpp -- simple base-class methods
#include "tabtenn1.h"
#include 

TableTennisPlayer::TableTennisPlayer (const string & fn, 
    const string & ln, bool ht) : firstname(fn),
	    lastname(ln), hasTable(ht) {}
    
void TableTennisPlayer::Name() const
{
    std::cout << lastname << ", " << firstname;
}

// RatedPlayer methods
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
     const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
    rating = r;
}

RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
    : TableTennisPlayer(tp), rating(r)
{
}

程序说明:

(1)首先将 RatedPlayer 类声明为从 TableTennisPlayer 类派生而来:

class RatedPlayer : public TableTennisPlayer
{
    ...
}

冒号(:)指出 RatedPlayer 类的基是 TableTennisPlayer类;

public 表示公有派生;

使用公有,公有成员将成为派生成员的公有成员;

私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问;

(2)派生类可以根据自己需要添加数据成员和成员函数,且需要自己重新定义构造函数,在构造函数中需要给新成员和继承的成员提供数据,如下:

//声明
RatedPlayer (unsigned int r = 0, const string & fn = "none",
                 const string & ln = "none", bool ht = false);

//定义
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
     const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht) //给基类成员提供数据
{
    rating = r; //给新成员提供数据
}

因为派生类不能直接访问基类的私有成员,在创建派生对象调用构造函数时,会调用基类的构造函数给基类数据成员赋初值;

C++构造函数后面的冒号(:)后面的内容是初始化类的数据成员列表,所以 : TableTennisPlayer(fn, ln, ht) 就是初始化成员列表,这将会调用TableTennisPlayer的构造函数,上所以述构造函数也可以写成下面形式:

//定义
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
                         const string & ln, bool ht)
{
    TableTennisPlayer(fn, ln, ht)   //给基类成员提供数据
    rating = r;                     //给新成员提供数据
}

或者

RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
                         const string & ln, bool ht)
    : TableTennisPlayer(tp), rating(r)
{

}

因此派生类构造函数要下如下:

        首先创建基类对象;

        派生类构造函数通过初始化成员列表,将基类数据传递给基类构造函数;

        派生类构造函数应初始化新增的数据成员;

2.使用派生类

在派生类创建完成后,便可以使用派生类,示例程序如下:

// usett1.cpp -- using base class and derived class
#include 
#include "tabtenn1.h"

int main ( void )
{
    using std::cout;
    using std::endl;
    TableTennisPlayer player1("Tara", "Boomdea", false);
    RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
    rplayer1.Name();          // derived object uses base method
    if (rplayer1.HasTable())
        cout << ": has a table.\n";
    else
        cout << ": hasn't a table.\n";
    player1.Name();           // base object uses base method
    if (player1.HasTable())
        cout << ": has a table";
    else
        cout << ": hasn't a table.\n";
    cout << "Name: ";
    rplayer1.Name();
    cout << "; Rating: " << rplayer1.Rating() << endl;
// initialize RatedPlayer using TableTennisPlayer object
    RatedPlayer rplayer2(1212, player1);
    cout << "Name: ";
    rplayer2.Name();
    cout << "; Rating: " << rplayer2.Rating() << endl;
    // std::cin.get();
    return 0;
}

可以看到,在这个示例中,派生类都是通过基类公有方法访问基类私有成员数据,并没有直接访问基类私有成员数据;

输出如下:

Duck, Mallory: has a table.
Boomdea, Tara: hasn't a table.
Name: Duck, Mallory; Rating: 1140
Name: Boomdea, Tara; Rating: 1212

二.多态公有继承

多态是指同一方法在派生类和基类的行为是不同的,方法的行为取决于调用该方法的对象;

实现多态公有继承的机制:

        在派生类中重新定义基类方法;

        使用虚方法;

// brass.h  -- bank account classes
#ifndef BRASS_H_
#define BRASS_H_
#include 
// Brass Account Class
class Brass
{
private:
    std::string fullName;
    long acctNum;
    double balance;
public:
    Brass(const std::string & s = "Nullbody", long an = -1, double bal = 0.0);
    
    void Deposit(double amt);
    double Balance() const;
    
    //增加关键字 virtual ,虚方法,用关于不同方法定义
    virtual void Withdraw(double amt);
    virtual void ViewAcct() const;
    %123
    //虚析构函数
    virtual ~Brass() {}
};

//Brass Plus Account Class
class BrassPlus : public Brass
{
private:
    //新增3个私有数据成员
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const std::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 ,虚方法,用关于不同方法定义
    virtual void ViewAcct()const;
    virtual void Withdraw(double amt);

    //新增3个公有成员函数
    void ResetMax(double m) { maxLoan = m; }
    void ResetRate(double r) { rate = r; };
    void ResetOwes() { owesBank = 0; }
};

#endif

在基类和派生类的声明中,ViewAcct()、Withdraw()函数都加了 virtual 关键字,这样可以在不同类中实现同一个方法的不同定义,定义如下:

// brass.cpp -- bank account class methods
#include 
#include "brass.h"
using std::cout;
using std::endl;
using std::string;

// formatting stuff
typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;
format setFormat();
void restore(format f, precis p);

// Brass methods

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)
{
    // set up ###.## format
    format initialState = setFormat();
    precis prec = cout.precision(2);

    if (amt < 0)
        cout << "Withdrawal amount 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::Balance() const
{
    return balance;
}

void Brass::ViewAcct() const
{
     // set up ###.## format
    format initialState = setFormat();
    precis prec = cout.precision(2);
    cout << "Client: " << fullName << endl;
    cout << "Account Number: " << acctNum << endl;
    cout << "Balance: $" << balance << endl;
    restore(initialState, prec); // Restore original format
}

// BrassPlus Methods
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)
           : Brass(ba)   // uses implicit copy constructor
{
    maxLoan = ml;
    owesBank = 0.0;
    rate = r;
}

// redefine how ViewAcct() works
void BrassPlus::ViewAcct() const
{
    // set up ###.## format
    format initialState = setFormat();
    precis prec = cout.precision(2);

    Brass::ViewAcct();   // display base portion
    cout << "Maximum loan: $" << maxLoan << endl;
    cout << "Owed to bank: $" << owesBank << endl;
    cout.precision(3);  // ###.### format
    cout << "Loan Rate: " << 100 * rate << "%\n";
    restore(initialState, prec); 
}

// redefine how Withdraw() works
void BrassPlus::Withdraw(double amt)
{
    // set up ###.## format
    format initialState = setFormat();
    precis prec = cout.precision(2);

    double bal = Balance();
    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. Transaction cancelled.\n";
    restore(initialState, prec); 
}

format setFormat()
{
    // set up ###.## format
    return cout.setf(std::ios_base::fixed, 
                std::ios_base::floatfield);
} 

void restore(format f, precis p)
{
    cout.setf(f, std::ios_base::floatfield);
    cout.precision(p);
}

可以看到,ViewAcct()、Withdraw()函数在不同类中的定义是不同的;

使用这两个类示例:

// usebrass1.cpp -- testing bank account classes
// compile with brass.cpp
#include 
#include "brass.h"

int main()
{
    using std::cout;
    using std::endl;

    Brass Piggy("Porcelot Pigg", 381299, 4000.00);
    BrassPlus Hoggy("Horatio Hogg", 382288, 3000.00);
    Piggy.ViewAcct();
    cout << endl;
    Hoggy.ViewAcct();
    cout << endl;
    
    return 0; 
}

输出:

#Piggy.ViewAcct();
Client: Porcelot Pigg
Account Number: 381299
Balance: $4000.00

#Hoggy.ViewAcct();
Client: Horatio Hogg
Account Number: 382288
Balance: $3000.00
Maximum loan: $500.00
Owed to bank: $0.00
Loan Rate: 11.125%

可以看到,不同类调用同一个成员函数,采用的方式不同;

然而,上面的方法是通过对象直接调用的,而不是使用指针或引用,没有使用虚方法特性;

因此,可以创建一个指向基类 Brass 的指针数组,实现同一个指针数组表示多种类型的对象,这就是多态,如下:

// usebrass2.cpp -- polymorphic example
// compile with brass.cpp
#include 
#include 
#include "brass.h"
const int CLIENTS = 4;

int main()
{
   using std::cin;
   using std::cout;
   using std::endl;

   Brass * p_clients[CLIENTS];
   std::string temp;
   long tempnum;
   double tempbal;
   char kind;

   for (int i = 0; i < CLIENTS; 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 or "
            << "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 < CLIENTS; i++)
   {
       p_clients[i]->ViewAcct();
       cout << endl;
   }
              
   for (int i = 0; i < CLIENTS; i++)
   {
       delete p_clients[i];  // free memory
   }
   cout << "Done.\n";         
 /* code to keep window open 
   if (!cin)
      cin.clear();
   while (cin.get() != '\n')
      continue;
*/
   return 0; 
}

程序说明:

创建一个指向 Brass 的指针数组:

Brass * p_clients[CLIENTS];

在数组不同元素中,使用 new 创建并初始化不同类型的对象:

p_clients[i] = new Brass(temp, tempnum, tempbal);
p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);

最后直接调用 ViewAcct() 函数,此时指针指向哪个对象,就用哪个对象调用  ViewAcct() 函数:

for (int i = 0; i < CLIENTS; i++)
   {
       p_clients[i]->ViewAcct();
       cout << endl;
   }

三.访问控制 private

与 private 类似,在类外只能使用公有类成员函数来访问 protected 部分中的类成员;

区别在于派生类的成员可以直接访问基类的保护成员,但不能直接访问基类私有成员;

所以,对于外界来说,保护与私有成员函数相似;但对于派生类,保护和公有成员相似;

示例:

class Brass
{
    protected:
    double balance;
    ...
}

void BrassPlus::Reset(double amt)
{
    balance = amt;
}

保护访问控制,优点在于,它让派生类能够访问公众不能使用的内部函数。

四.包含对象成员的类

 包含是指在一个类中包含其他类对象:

class Student
{    
private:
    std::string name;               // use a string object for name
    std::valarray scores;   // use a valarray object for scores
    ,,,
};

上述数据成员被声明为私有的,这意味着Student类的成员函数可以使用 string 和 valarray 的公有接口访问 name 和 score,也就是Student获得了string 和 valarray的实现,但不能获得继承接口(不像公有继承那样使得基类函数变成自己的成员函数)。

比如只能:

scores.sum();
name.size();

//而不是
student.sum();
student.size();

初始化:

 Student(const char * str, const double * pd, int n)
        : name(str), scores(pd, n) {}

 因为初始化的是成员对象,而不是像公有继承那样的继承对象,所以初始化中使用的是成员名(: name(str), scores(pd, n)),而不是类名;

成员函数定义:

double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum()/scores.size();  
    else
        return 0;
}

因为被包含的对象接口不是公有的,所以不能直接使用;

又因为是包含,所以与初始化类似,使用成员对象调用基类方法,而不是通过类调用,这点区别于下面的私有继承;

五.私有继承

私有继承是使用关键字 private 来定义类,示例如下:

// studenti.h -- defining a Student class using private inheritance
#ifndef STUDENTC_H_
#define STUDENTC_H_

#include 
#include 
#include    
class Student : private std::string, private std::valarray
{   
private:
    typedef std::valarray ArrayDb;
    // private method for scores output
    std::ostream & arr_out(std::ostream & os) const;
public:
    Student() : std::string("Null Student"), ArrayDb() {}
    explicit Student(const std::string & s)
            : std::string(s), ArrayDb() {}
    explicit Student(int n) : std::string("Nully"), ArrayDb(n) {}
    Student(const std::string & s, int n)
            : std::string(s), ArrayDb(n) {}
    Student(const std::string & s, const ArrayDb & a)
            : std::string(s), ArrayDb(a) {}
    Student(const char * str, const double * pd, int n)
            : std::string(str), ArrayDb(pd, n) {}
    ~Student() {}

    double Average() const;
    double & operator[](int i);
    double operator[](int i) const;
    const std::string & Name() const;

// friends
    // input
    friend std::istream & operator>>(std::istream & is,
                                     Student & stu);  // 1 word
    friend std::istream & getline(std::istream & is,
                                  Student & stu);     // 1 line
    // output
    friend std::ostream & operator<<(std::ostream & os,
                                     const Student & stu);
};

#endif
// studenti.cpp -- Student class using private inheritance
#include "studenti.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;

// public methods
double Student::Average() const
{
    if (ArrayDb::size() > 0)
        return ArrayDb::sum()/ArrayDb::size();  
    else
        return 0;
}

const string & Student::Name() const
{
    return (const string &) *this;
}

double & Student::operator[](int i)
{
    return ArrayDb::operator[](i);         // use ArrayDb::operator[]()
}

double Student::operator[](int i) const
{
    return ArrayDb::operator[](i);
}

// private method
ostream & Student::arr_out(ostream & os) const
{
    int i;
    int lim = ArrayDb::size();
    if (lim > 0)
    {
        for (i = 0; i < lim; i++)
        {
            os << ArrayDb::operator[](i) << " ";
            if (i % 5 == 4)
                os << endl;
        }
        if (i % 5 != 0)
            os << endl;
    }
    else
        os << " empty array ";
    return os; 
}

// friends
// use String version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
    is >> (string &)stu;
    return is; 
}

// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
    getline(is, (string &)stu);
    return is;
}

// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
    os << "Scores for " << (const string &) stu  << ":\n";
    stu.arr_out(os);  // use private method for scores
    return os;
}

说明:

1.格式

class Student : private std::string, private std::valarray
{
    ...     
}

这里使用了多个基类(string、valarray)的继承,称为多重继承(MI)

2.初始化基类组件(构造函数)

 Student(const char * str, const double * pd, int n)
            : std::string(str), ArrayDb(pd, n) {}

这里构造函数初始化的两个成员变量(name、scores)由基类提供,派生类没有新增新的成员变量,构造函数与公有继承类似;

注意这里与上面“包含对象成员的类”的区别,这里直接使用类来初始化,而包含是用对象初始化; 

 3.访问基类方法(成员函数定义)

double Student::Average() const
{
    if (ArrayDb::size() > 0)
        return ArrayDb::sum()/ArrayDb::size();  
    else
        return 0;
}

可看到,同样要通过基类类名和作用域符访问基类的方法,这一点和公有派生不同,公有派生可以直接访问基类的公有成员函数;

注意这里与上面“包含对象成员的类”的区别,这里直接使用基类调用方法,而包含是用对象调用方法; 

总结:大部分C++程序倾向于使用包含,因为更易理解! 

六.保护继承

保护继承是私有继承的变体,格式如下:

class Student : protected std::string, protected std::valarray
{
    ...     
}

保护继承后,基类的公有、保护成员都将变成派生类的保护成员;

与私有继承不同的是:

在派生类再派生一个类后,也就是第三代类,对于私有继承,第三代类将不能访问基类接口,因为基类的公有方法再派生类中变成了私有方法;而保护继承,基类的公有方法再第二代中变成保护的,因此第三代派生类可以使用他们;

那么在保护、私有派生中,如何确保基类的方法能够在多代派生类中仍然可以使用呢?

方法一:

 定义一个使用该基类方法的派生类方法,例如:

double Student::sum() const
{
    return std::valarray::sum();
}

 这样 Student 对象在调用 sum() 方法时,将会在函数中调用基类的 valarray::sum() 方法,相当于将基类的方法变成了派生类的方法;

方法二:

在派生类声明,声明方法前加上 using 关键字,如下:

class Student : private std::string, private std::valarray
{   
public:
    using std::valarray::min;
    using std::valarray::max;
...
}

上述声明使得

valarray::min、valarray::max 就像Student的方法min、max 一样;

七.多重继承

你可能感兴趣的:(C++,c++)