继承:
一个类可以被另一个类继承,被继承类称为基类,继承类称为派生类。派生类继承基类后拥有基类的公有和保护成员。继承是一种强联系,它代表着一种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拷贝赋值运算符,若该操作未定义且拷贝构造函数未定义,则该语句不合法。