C++primer -继承与动态绑定

继承:

一个类可以被另一个类继承,被继承类称为基类,继承类称为派生类。派生类继承基类后拥有基类的公有和保护成员。继承是一种强联系,它代表着一种is a的关系,比如鹰继承鸟类,圆形继承形状类。

形式:在类名之后加上冒号,在冒号后面列出该类继承的基类列表,例:

令类C继承类A、B,此后C的对象拥有A、B的公有和保护成员,它们在C中的访问权限也是公有和保护。

class A{};

class B{};

class C: public A, public B{}; // C继承类A、B(C++是多继承的)

派生类继承基类,则其继承基类的非静态的公有和保护成员,这个继承是指:

1、基类的成员函数被继承,那么派生类相当于隐式定义了相同的成员函数(同名同返回类型同参数列表),构造函数默认不被继承。

2、基类的数据成员被继承,那么其派生类相当于隐式定义了相同的数据成员(同类型同名同类内初始值,但访问说明符的类型可以不同)。

3、基类的私有成员不能被派生类继承和访问,这区别于友元,类的友元可以访问私有成员,但子类是不能访问父类的私有成员的。

继承列表中基类前面可以加访问说明符(取基类原访问说明符和继承时声明的访问说明符中权限较小的),这代表:

  • public表示继承来的成员维持原访问说明权限;
  • protect表示继承来的成员在派生类中都是protected的;
  • private表示继承来的成员在派生类中都是private的。

class的继承默认是private,struct的继承默认是public,继承的是函数的调用权,而不是函数的内存。

基类的定义注意事项:
  • 基类通常都应该定义一个虚析构函数,即使该函数不执行任何操作也应如此,例: class Quote{virtual ~Quote();}

    • 原因:析构函数默认是非虚函数,那么当派生类绑定到基类指针时,delete该指针将会根据其静态类型,也就是基类类型的析构函数去析构对象,导致派生类的专有内容未被析构而内存泄漏。因此要显式将基类的析构函数定义为虚函数。

  • 通常将基类希望派生类覆盖的成员函数,声明为虚函数,形式:在函数声明之前加virtual,示例: class Quote{virtual double net_price(size_t n) const { return n * price;};

  • protected访问说明符:基类希望派生类能访问该成员,但不希望对外提供访问时,应将该成员声明为protected的,例:class Quote{protected:    double price;};

派生类的定义:
  • 派生类应用类派生列表声明所继承的基类,形式如: class Bulk_Quote: public Quote{};

  • 一个类要被用作基类时必须已定义:派生类的声明不包含其派生列表;

    • 只能继承已经定义过的类,不能继承一个不完全类型:

extern class Test; 
class testcs : public Test{}; // 会报错,提示Test不能是一个不完全类型。
  • 派生类不能控制其基类的部分初始化,应使用基类的构造函数初始化派生类的基类部分:

    • 例:Bulk_Quote类的构造函数Bulk_Quote(const std::string& book, double p, std::size_t qty, double disc ): Quote(book, p), min_qty(qty), discount(disc){}

    • 除非特别指出否则派生类的基类部分会被默认初始化。

    • 派生类的构造函数可以不显式调用基类构造函数,此时调用的是基类的默认构造函数。

    • 派生类默认不继承基类的构造函数,若希望派生类继承,应使用using 基类名::基类名;显式声明继承

  • 派生类的定义中可以直接访问基类的公有和保护成员:

    double Bulk_Quote:: net_price(size_t cnt)const {
        if(cnt >= min_qty) 
            return n * (1 - discount) * price; 
        else return cnt * price;// price 为基类Quote的保护成员,可以被派生类Bulk_Quote直接访问;
    } 

  • 派生类到基类的转换:可以将派生类对象绑定到基类的指针或引用中,在使用到基类指针或引用时可以用派生类、派生类的引用、派生类的指针代替。(因为派生类具有包含基类成员的子对象,也有包含派生类专有成员的子对象,所以可以转换为基类,但转换后只能使用其基类部分)

  • 派生类的析构函数只负责销毁派生类自身分配的成员,基类和其成员都是自动销毁的(基类的析构函数是被自动调用的,所以若基类的析构是删除的,则派生类的析构也是删除的)。析构函数的调用顺序与构造函数相反。合成的析构函数体为空。

  • 一个类可以在是基类的同时,是另一个类的派生类:

class Base; 
class Derived: public Base{}; 
class D2: puclic Derived{};

其他:

  • 若一个派生类是公有的,则其基类的公有成员,也是其接口的一部分;

  • 大多语言是单继承的,C++是允许多继承的;

虚函数(virtual function):

即在C++语言中,基类允许派生类覆盖的函数,与派生类不做改变直接继承的函数区分对待。通常对于基类希望派生类覆盖的函数,基类将其函数声明成虚函数;

形式:在声明前加virtual.

class bird{
public: 
    virtual void fly(){ cout << "普通飞行" << endl; }
};
class eagle:public bird{
public: 
    virtual void fly(){ cout << "盘旋飞行" <<endl; }// 鹰重写fly,飞行是盘旋飞行。
};
class seagull:public bird{};// 海鸥继承bird的fly(),飞行时是普通飞行

以上例子中:不同的鸟都会飞,但不同的鸟种类有不同的飞的形态,基类声明fly函数为虚函数,那继承了bird的派生类,可以重写(override)fly实现自己不同的飞的方式,也可以直接继承基类的fly函数使用默认的飞行方式。

使用基类的引用或指针时,编译器并不了解所绑定对象的真实类型:

静态类型:编译时已知的类型

动态类型:实际运行时才得到的类型,即指针或引用绑定到的实际类型。

例:对一个使用基类引用作形参的函数来说,形参是静态类型,实参是动态类型:

double print_total(ostream& os, Quote& item, size_t n){} 
// Quote&是对象item的静态类型 
print_total(cout, bulk, 20); 
// bulk是Quote的派生类Bulk_Quote类型的对象,item的静态类型是Quote,动态类型是bulk的类型Bulk_Quote。
静态类型决定对象成员的可见性(有哪些成员),动态类型决定对于被重写的内容调用哪个版本。
动态绑定:

对于虚函数成员,编译器将根据基类指针的动态类型决定调用基类还是派生类的成员,这称为动态绑定。

具体规则:将派生类绑定到基类指针或引用时,若派生类重写了基类的虚函数成员,那么当基类引用实际绑定到的是派生类,则调用的是派生类重写的成员;若使用时实际绑定的是基类,则调用的是基类的该成员(该条只对虚函数有用): 如:Quote类表示书籍,Bulk_Quote类继承了Quote类,表示打折书籍,Bulk_Quote类重写了Quote类用于计价的net_price()成员函数:

void print_total(const Quote& q){
    cout << "the price of those books is" << net_price() << endl;
}
// 若传入的是Bulk_Quote则使用Bulk_Quote类重写的net_price()成员,按打折方式计价,
// 若实际传入的是Quote则使用Quote的net_price()成员,按原价方式计价;
继承与静态成员:

基类中有静态成员,派生类在该静态成员为protected/public的情况下可调用该静态成员,但静态成员的定义只能有一个,派生类不能重写基类的静态成员函数

class  Base{
protected:
    static void statmem();
};
class Derived: public Base { void f(const Derived&);};
void Derived::f(Derived&){
    Base::statmem();// 基类可以访问基类的静态成员函数
    Derived::statmem();// 派生类可以访问基类的静态成员函数
    derived_obj.statmem(); // 派生类的对象也可访问基类的静态成员函数
    statmem(); // 通过this访问,即通过当前派生类对象访问基类的静态成员函数
}
final:

若不希望继承发生,可以使用final限制该类不能继承,形式为在类名后加final: class NoDerived final : public Base{}; (final限制的类不能作为基类但可以有基类)。

不存在基类向派生类指针或引用的隐式转换:
Quote qt;
Bulk_Quote& rbqt = qt; 
// 派生类的引用不能绑定到基类
Bulk_Quote* pbqt = &qt; 
// 派生类的指针不能指向基类
Bulk_Quote bqt;
Quote& rq = bqt;
Bulk_Quote& bqt2 = rq; 
// 错误,基类对象不能隐式转换为派生类,即使该基类对象绑定的是同一个派生类可以通过dynamic_cast或static_cast进行转换
Bulk_Quote& bqt2 = dynamic_cast<Bulk_Quote> rq;
在对象间不存在类型转换:

只能让派生类指针(或引用)转换基类指针(或引用),不能由派生类转换为基类,除非存在这样的非explicit构造函数。

Bulk_Quote bulk;
Quote item(bulk); 
// 这里使用的是Quote的拷贝构造函数Quote(const Quote);若该函数不存在,则该操作不合法
Quote item = bulk; 
// 这里使用的是Quote拷贝赋值运算符,若该操作未定义且拷贝构造函数未定义,则该语句不合法。

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值