《继承》

1.类的继承

  继承的概念就不具体说明了。说明一些基类和派生类:如果B类是A类继承而来的,那么A类就称为基类,B类就称为派生类。

  下面的demo是先创建一个box类,carton类继承box类。

box.h

#ifndef _BOX_H_
#define _BOX_H_

class box{
public:
    box(double lv=1.0, double wv=1.0, double hv=1.0);

private:
    double length;
    double width;
    double height;
};

#endif 

 

box.cpp

#include "box.h"

box::box(double lv, double wv, double hv) : length(lv), width(wv), height(hv)
{
    ;
}

 

carton.h

#ifndef _CARTON_H_
#define _CARTON_H_

#include "box.h"

/* carton类派生于box类 */
class carton : public box{
public:
    carton(const char *pstr = "Cardboard");
    ~carton();

private:
    char *pMaterial;
};

#endif 

 

carton.cpp

#include "carton.h"
#include <cstring>

carton::carton(const char *pstr)
{
    pMaterial = new char[strlen(pstr) + 1];
    std::strcpy(pMaterial, pstr);
}

carton::~carton()
{
    delete[] pMaterial;
}

 

main.cpp

#include <iostream>
#include "box.h"
#include "carton.h"

using namespace std;

int main()
{
    box mybox(10.0, 20.0, 30.0);
    carton mycarton;
    carton candycarton("thin cardboard");

    cout << endl
         << "mybox occupies " << sizeof mybox << " bytes" << endl;      
    cout << "mycarton occupies " << sizeof mycarton << " bytes" << endl;
    cout << "candycarton occupies " << sizeof candycarton << " bytes" << endl;

    return 0;
}

   在我们创建carton对象时,会先调用box的构造函数,再调用carton的构造函数。

2.继承下的控制访问(protected)

  如果在candycarton对象访问数据成员lenth,这个操作是不允许的。因为lenth是box类的私有成员。candycarton只能通过从box类继承下来的函数成员访问box类中的私有数据成员,而不能通过candycarton直接访问或者它本身的函数访问。

  那么如果在派生类也能访问到基类的私有成员呢?

  可以使用protected来替换private。在基类中如果把private替换成protected,不会对基类的数据成员有影响。这些受保护的类还是不能在类外访问,只能通过成员函数、友元类和类的友元函数访问。唯一的区别就是基类的protected的数据成员在派生类中也可以直接访问。

 

3.派生类成员的访问级别

  

  public继承:不改变基类成员的访问控制。
  private继承:派生类所继承的基类成员的访问控制都变为private。
  protected继承:基类中的private成员的访问控制不变,其余的都变为protected。

  可以使用using改变某个基类成员免受protected或private的影响。

class box{
public:
    box(double lv=1.0, double wv=1.0, double hv=1.0);

    double volume() const;

protected:
    double length;
    double width;
    double height;
};


class package : private box{
public:
    using box::volume;
};

  如上demo,即使package类是通过private继承了box类,但是通过using box::volume,volume函数就变成public(声明的时候不用传递返回值和参数)。当然也可以使用这样的方式来using数据成员。

  但是不能用using声明基类的私有成员,因为私有成员在派生类中不能访问。

 

 4.派生类中显式调用基类的构造函数

  在最上面的1中最后一句话说到:在我们创建carton对象时,会先调用box的构造函数,再调用carton的构造函数。这个时候是直接默认调用无参的box类构造函数。那么如果要在派生类中传递参数到box类的构造函数呢?

demo:

carton.h

#ifndef _CARTON_H_
#define _CARTON_H_

#include "box.h"

/* carton类派生于box类 */
class carton : public box{
public:
    carton(const char *pstr = "Cardboard");
    ~carton();

    carton(double lv, double wv, double hv, const char *pstr = "cardboard");

private:
    char *pMaterial;
};

#endif 

 

carton.cpp

carton::carton(double lv, double wv, double hv, const char *pstr) : box(lv, wv, hv)
{
    /**/
}

 

box.h

#ifndef _BOX_H_
#define _BOX_H_

class box{
public:
    box(double lv, double wv, double hv);

    double volume() const;

protected:
    double length;
    double width;
    double height;
};

#endif 

 

main.cpp

carton candycarton(10.0, 20.0, 30.0, "thin cardboard");

   

  需要注意:虽然基类中非私有数据成员可以在派生类中访问,但它们不能在派生类构造函数的初始化列表中初始化。

carton::carton(double lv, double wv, double hv, const char *pstr) : length(lv), width(wv), height(hv)
{
    /**/
}

  这样是不被允许的,会提示length等数据成员不是carton类的成员。

  这是因为在初始化时,这些成员还不存在。初始化列表是在调用基类的构造函数,对象的主要部分创建之前处理。

  如果要显式初始化继承的数据成员,就必须在构造函数体中进行。

carton::carton(double lv, double wv, double hv, const char *pstr)
{
    length = lv;
    width = wv;
    height = hv;

    pMaterial = new char[strlen(pstr) + 1];
    std::strcpy(pMaterial, pstr);
}

 

5.派生类中的副本构造函数

  考虑一下以下情况:

box mybox(2.0, 3.0, 4.0);
box copybox(mybox);

  第二个语句中,对于这种初始化,编译器会自动调用副本构造函数,如果没有自定义副本构造函数,编译器会提供一个默认的版本,逐个复制原对象成员,创建一个新对象来。

  那么对于派生类来讲,使用副本构造函数时,基类会复制吗?

box.cpp和box.h加入:

/* box.cpp */
box::box(const box &abox) : length(abox.length), width(abox.width), height(abox.height)
{
    ;
}

/* box.h */
box(const box &abox);

 

carton.cpp和carton.h加入:

/* carton.h */
carton(const carton &acarton);

/* carton.cpp */
carton::carton(const carton &acarton)
{
    pMaterial = new char[strlen(acarton.pMaterial) + 1];
    std::strcpy(pMaterial, acarton.pMaterial);
}

  主要注意:对于副本构造函数中存在指针的数据成员。必须复制指针指向的数据,而不是指针。否则一旦其中一个指针被删除,对应的数据也会被删除,剩下的一个指针就没有作用了。

main.cpp调用:

carton candycarton(20.0, 30.0, 40.0, "glassine board");
carton copycarton(candycarton);

  运行后会发现,copycarton中box类的数据成员都是默认值1.0,而不是candycarton中的20、30、40。

  这是因为编译器在复制candycarton的box子对象的时候,调用的是默认的box构造函数。

  因此,我们需要在carton类的副本构造函数中调用box副本构造函数。

carton::carton(const carton &acarton) : box(acarton)
{
    pMaterial = new char[strlen(acarton.pMaterial) + 1];
    std::strcpy(pMaterial, acarton.pMaterial);
}

 

6.重复的数据成员名和函数成员名

6.1 基类和派生类重复数据成员名

class base{
public:    
    base(int num = 10) { value = num; }

protected:
    int value;
};

class derived : public base{
public:
    derived(int num = 20) {value = num;}
    int total() const;

protected:
    int value;
};

int derived::total const{
    return value + base::value;
};

  需要使用基类名和作用域解析运算符限定成员名base::value。

 

6.2 基类和派生类重复函数成员名

相同函数名,但是参数不同:

  使用using 声明,在派生类的作用域中引入基类成员函数的限定名。

class base{
public:    
    void dothat(int arg);
};

class derived : public base{
public:
    void dothat(double arg);
    using base::dothat;
};


derived object;
object.dothat(2);
object.dothat(2.5);

相同函数名,相同参数:

derived object;
object.base::dothat(2);

  使用类名和作用域解析运算符来调用基类函数。

 

7. 多重继承

  

 

 

 

 

 

 

 

 

  

 

posted @ 2023-03-01 16:11  一个不知道干嘛的小萌新  阅读(38)  评论(0编辑  收藏  举报