举例:ios
#include <iostream> using namespace std; class BaseA { public: void fun() { cout << "A.fun" << endl; } }; class BaseB { public: void fun() { cout << "B.fun" << endl; } void tun() { cout << "B.tun" << endl; } }; class Derived :public BaseA, public BaseB { public: void tun() { cout << "D.tun" << endl; } void hun() { fun(); } //此处调用出现二义性,编译没法经过 }; int main() { Derived d, * p = &d; d.hun(); return 0; }
类名限定函数
void hun() { BaseA::fun(); } //改写上述代码的第14行,用BaseA类名限定调用的函数 d.BaseB::fun(); //派生类对象调用基类同名函数时,使用类名限定 p->BaseB::fun(); //派生类指针调用基类同名函数时,使用类名限定
若是存在两个或多个包含关系的做用域,外层声明了一个名字,而内层没有再次声明相同的名字,则外层名字在内层可见;若是在内层声明了相同的名字,则外层名字在内层不可见——隐藏(屏蔽)规则。spa
在类的派生层次结构中,基类的成员和派生类新增的成员都具备类做用域,两者的做用域不一样:基类在外层,派生类在内层。若是派生类声明了一个和基类成员同名的新成员,则派生类的新成员就会屏蔽基类的同名成员,直接使用成员名只能访问到派生类新增的成员。(如需使用从基类继承的成员,应当使用基类名限定)指针
#include <iostream> using namespace std; class Base { public: void fun() { cout << "A.fun" << endl; } Base(int x = 1, int y = 2) :x(x), y(y) {} int x, y; }; class Derived :public Base{ public: void tun() { cout << "D.tun" << endl; } void fun() { cout << "D.fun" << endl; } Derived(int x = 0) :x(x) {} int x; }; int main() { Derived d, * p = &d; d.fun(); //输出的结果为 D.fun cout << p->x << " " << p->y << " " << p->Base::x << endl; //输出为 0 2 1 d.Base::fun(); //输出为 A.fun }
虚基类的使用目的:在继承间接基类时只保留一份成员。code
声明虚基类须要在派生类定义时,指定继承方式的时候声明,只须要在访问标号(public、protected、private继承方式)前加上virtual关键字。注意:为了保证虚基类在派生类中只继承依次,应当在该基类的全部直接派生类中声明为虚基类,不然仍会出现屡次继承。对象
派生类不只要负责对直接基类进行初始化,还要负责对虚基类初始化;若多重继承中没有虚基类,则派生类只须要对间接基类进行初始化,而对基类的初始化由各个间接基类完成(会所以产生多个基类的副本保存在各个间接基类中)。继承
#include <iostream> using namespace std; class Base { public: Base(int n) { nv = n; cout << "Member of Base" << endl; } void fun() { cout << "fun of Base" << endl; } private: int nv; }; class A:virtual public Base { //声明Base为虚基类,做为间接基类都须要使用virtual关键字 public: A(int a) :Base(a) { cout << "Member of A" << endl; } private: int na; }; class B :virtual public Base { //声明Base为虚基类,做为间接基类都须要使用virtual关键字 public: B(int b) :Base(b) { cout << "Member of B" << endl; } private: int nb; }; class Derived :public A, public B { public: Derived(int n) :Base(n), A(n), B(n) { cout << "Member of Derived" << endl; } //派生类的构造函数初始化列表,先调用基类Base的构造函数,再依次调用间接基类A、B的构造函数 //因为虚基类Base中没有默认构造函数(容许无参构造),因此从Base类继承的全部派生类的构造函数初始化表中都须要显式调用基类(包括间接基类)的构造函数,完成初始化 private: int nd; }; int main() { Derived de(3); de.fun();//不会产生二义性 return 0; }
关于虚基类的说明:接口
虚函数只能是类中的成员函数,且不能是静态的;内存
virtual关键字只能在类体中使用,即使虚函数的实如今类体外定义,也不能带上virtual关键字;ci
当在派生类中定义了一个与基类虚函数同名的成员函数时,只要该函数的参数个数、类型、顺序以及返回类型与基类中的彻底一致,则派生类的这个成员函数不管是否使用virtual关键字,它都将自动成为虚函数;
利用虚函数,能够在基类和派生类中使用相同的函数名定义函数的不一样实现,达到「一个接口,多种方式」的目的。当基类指针或引用对虚函数进行访问时,系统将根据运行时指针(或引用)所指向(或引用)的实际对象来肯定调用的虚函数版本;
使用虚函数并不必定产生多态性,也不必定使用动态联编。如:在调用中对虚函数使用类名限定,能够强制C++对该函数使用静态联编。
在派生类中,当一个指向基类成员函数的指针指向一个虚函数,而且经过指向对象的基类指针(或引用)访问这个虚函数时,仍将发生多态性。
#include <iostream> using namespace std; class Base { public: virtual void print() { cout << "Base-print" << endl; } }; class Derived :public Base { public: void print() { cout << "Derived-print" << endl; } }; void display(Base* p, void(Base::* pf)()) { (p->*pf)(); } int main() { Derived d; Base b; display(&d, &Base::print); //输出Derived-print display(&b, &Base::print); //输出Base-print return 0; }
使用虚函数,系统要增长必定的空间开销存储虚函数表,可是系统在进行动态联编时的时间开销时不多的,所以,虚函数实现的多态性是高效的。
派生类的对象从内存中撤销时通常先调用派生类的析构函数,而后再调用基类的析构函数。但若是用new运算符创建了派生类对象,且定义了一个基类的指针指向这个对象,那么当用delete运算符撤销对象时,系统会只执行基类的析构函数,而不执行派生类的析构函数,于是也没法对派生类对象进行真正的撤销清理工做。
若是但愿delete关键字做用于基类指针时,也执行派生类的析构函数,则须要将基类的析构函数声明为虚函数。
若是将基类的析构函数声明为虚函数,则由该基类所派生的全部派生类的析构函数也都自动成为虚函数,即便派生类的析构函数与基类的析构函数名字不相同。
C++支持虚析构函数,可是不支持虚构造函数,即构造函数不能声明为虚函数!
许多状况下,不能在基类中为虚函数给出一个有意义的定义,这时能够将它说明为纯虚函数,将具体定义留给派生类去作。纯虚函数的定义形式为:virtual 返回类型 函数名(形式参数列表) = 0;
包含有纯虚函数的类称为抽象类,一个抽象类只能做为基类来派生新类,所以又称为抽象基类,抽象类不能定义对象(实体)。
重载多态——函数重载、运算符重载
强制多态——也称类型转换
C++的基本数据类型之间转换规则:char→short→int→unsigned→long→unsigned→float→double→long double
能够在表达式中使用3中强制类型转换表达式:static_cast<T>(E)
或T(E)
或(T)E
其中E表明运算表达式(得到一个值),T表明一个类型标识符。强制多态使得类型检查复杂化,尤为在容许重载的状况下,会致使没法消解的二义性。
类型参数化多态——模板(函数模板、类模板)
包含多态——使用虚函数
至少含有一个虚函数的类称为多态类,虚函数使得程序可以以动态联编的方式达到执行结果的多态化。这种多态使用的背景是:派生类继承基类的全部操做,或者说,基类的操做能被用于操做派生类的对象,当基类的操做不能适应派生类时,派生类就须要重载基类的操做;其表现为C++容许用基类的指针接收派生类的地址或使用基类的引用绑定派生类的对象。
联编:将模块或者函数合并在一块儿生成可执行代码的处理过程,同时对每一个模块或者函数分配内存地址,而且对外部访问也分配正确的内存地址。
静态联编:在编译阶段就将函数实现和函数调用绑定。静态联编在编译阶段就必须了解全部函数的或模块执行所须要的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型。C语言中,全部的联编都是静态联编,C++中通常状况下的联编也是静态联编。
动态联编:在程序运行的时候才进行函数实现和函数调用的绑定称之为动态联编(dynamic binding)
#include <iostream> #define PI 3.14159265 using namespace std; class Point { public: Point(double x = 0, double y = 0) :x(x), y(y) {} double area_static() { return 0; } //不是虚函数,只会在编译期绑定,造成静态联编 virtual double area_dynamic() { return 0; } //用虚函数声明,则编译时只作赋值兼容的合法性检查,而不作绑定 private: double x, y; }; class Circle :public Point { public: Circle(double r = 1.0) :r(r) {} //因为基类中的构造函数非必须显式传参,因此系统会自动调用基类带默认参数的构造函数 Circle(double x, double y, double r=1.0) :Point(x, y), r(r) {} //重载一个可传坐标点、半径值参数的构造函数 double area_static() { return PI * r * r; } //静态联编 double area_dynamic() { return PI * r * r; } //动态联编(仍为虚函数),为使可读性更好,可在不缺省virutal关键字 private: double r; }; int main() { Point o(2.5, 2.5); Circle c(2.5, 2.5, 1); Point* po = &o, * pc = &c, & y_c = c; //下面五个所有为静态联编,不管指针指向的是基类仍是派生类,因为指针类型为基类类型,且调用的不是虚函数,则统一绑定为基类中的函数 cout << "Point area =" << o.area_static() << endl; //值为0 cout << "Circle area=" << c.area_static() << endl; //值为3.14159 cout << "the o area from po:" << po->area_static() << endl; //值为0 cout << "the c area from pc:" << pc->area_static() << endl; //值为0 cout << "the c area from cite y_c:" << y_c.area_static() << endl; //值为0 //下面三个为动态联编,有指针(或引用)、虚函数,则所调用的虚函数会在运行时经过vptr指针找到虚函数表 ,根据指针指向的实际对象(而非指针类型)来断定调用谁的函数 cout << "the o area from po:" << po->area_dynamic() << endl; //值为0 cout << "the c area from pc:" << pc->area_dynamic() << endl; //值为3.14159 cout << "the c area from cite y_c:" << y_c.area_dynamic() << endl; //值为3.14159 //强制使用静态联编 cout << "the c area calculated by Point::area_():" << pc->Point::area_dynamic() << endl; //值为0 return 0; }
当调用虚函数时,先经过vptr指针(编译虚函数时,编译器会为类自动生成一个指向虚函数表的vptr指针)找到虚函数表,而后再找出虚函数的真正地址,再调用它
派生类能继承基类的虚函数表,并且只要是和基类同名(参数也相同)的成员函数,不管是否使用virtual声明,它们都自动成为虚函数。若是派生类没有改写继承基类的虚函数,则函数指针调用基类的虚函数;若是派生类改写了基类的虚函数,编译器将从新为派生类的虚函数创建地址,函数指针会调用改写后的虚函数。