继承:
一个类可以被另一个类继承,被继承类称为基类,继承类称为派生类。派生类继承基类后拥有基类的公有和保护成员。继承是一种强联系。
形式:在类声明之后用冒号列出该类继承的基类 class A{}; class B{};class C: public A, public B{} // C继承A\B
在要继承的类前面可以加访问说明符,如:
- public:表示继承来的成员维持原访问标识权限;
- protect表示继承来的成员在派生类中都是protected的;
- private表示继承来的成员在派生类中都是private的。
继承的是函数的调用权,而不是函数的内存。
基类的定义注意事项:
-
根节点基类通常都应该定义一个虚析构函数,即使该函数不执行任何操作也应如此: class Quote{virtual ~Quote();} 原因:只有对于虚函数,基类指针绑定到派生类的情况下,会根据其动态类型决定使用哪个函数版本,此时对于一个基类指针delete会调用实际绑定对象的析构函数,也就是派生类的析构函数,但若析构函数不是虚函数,则会按其静态类型,即执行基类的析构函数,导致派生类的独有的成员未被析构而发生内存泄漏。有析构函数的类,即使是虚析构函数,也不会被合成移动操作。
-
通常将基类希望派生类覆盖的成员函数,声明为虚函数,形式:在函数声明之前加virtual,示例: class Quote{virtual double net_price(size_t n) const { return n * price;};
-
protected访问说明符:基类希望派生类能访问该成员,但不希望对外提供访问时,应将该成员声明为protected的,例:class Quote{protected : 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 为基类的保护成员,可以被派生类直接访问;
-
派生类到基类的转换:可以将派生类对象绑定到基类的指针或引用中,在使用到基类指针或引用时可以用派生类、派生类的引用、派生类的指针代替。(因为派生类具有包含基类成员的子对象,也有包含派生类专有成员的子对象,所以可以转换为基类,但转换后只能使用其基类部分)
-
派生类的析构函数只负责销毁派生类自身分配的成员,基类和其成员都是自动销毁的(基类的析构函数是被自动调用的,所以若基类的析构是删除的,则派生类的析构也是删除的)。析构函数的调用顺序与构造函数相反。合成的析构函数体为空。
-
一个类可以在是基类的同时,是另一个类的派生类:
class Base;
class Derived: public Base{};
class D2: Derived: puclic Derived{};
其他:
-
若一个派生类是公有的,则其基类的公有成员,也是其接口的一部分;
-
大多类是单继承的,C++是允许多继承的;
虚函数(virtual function):
即在C++语言中,基类允许派生类覆盖的函数,与派生类不做改变直接继承的函数区分对待。通常对于基类希望派生类覆盖的函数,基类将其函数声明成虚函数;
形式:在声明前加virtual.
class bird{ virtual void fly();};
以上例子中:不同的鸟都会飞,但不同的鸟种类有不同的飞的形态,继承了该类的函数要么实现该函数要么也声明该函数为虚函数
动态绑定:
对于继承了基类的派生类,在用到基类的地方,都可以用派生类代替。但实际使用时,若派生类重写了基类的虚函数成员,那么当基类引用实际绑定到的是派生类,则调用的是派生类重写的成员;若使用时实际绑定的是基类,则调用的是基类的该成员(该条只对覆盖有用,基类指针或引用不能调用派生类独有的函数或成员): 如: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()成员,按原价方式计价;
使用基类的引用或指针时,编译器并不了解所绑定对象的真实类型:
静态类型:编译时已知的类型 动态类型:实际运行时才得到的类型 对一个使用基类引用作形参的函数来说:形参是静态类型,实参是动态类型。:
double print_total(ostream& os, Quote& item, size_t n){}
// Quote&是静态类型
print_total(cout , item); // item的类型是动态类型
继承与静态成员:
基类中有静态成员,派生类在该静态成员为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限制的类可以继承别的类)。f
不存在基类向派生类指针或引用的隐式转换:
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;
在对象间不存在类型转换:
Bulk_Quote bulk;
Quote item(bulk);
// 这里使用的是Quote的拷贝构造函数Quote(const Quote);若该函数不存在,则该操作不合法
Quote item = bulk;
// 这里使用的是Quote拷贝赋值运算符,若该操作未定义且拷贝构造函数未定义,则该语句不合法。