继承
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。
class A :public B{
...
}
冒号指出A的基类是B,为公有继承。基类的公有成员将成为派生类的公有成员,基类的私有部分也将成为派生类的一部分。但只能通过基类的公有和保护方法来访问。
- 派生类对象存储了基类的数据成员(派生类继承了基类的实现)
- 派生类对象可以使用基类的方法(派生类继承了基类的接口)
- 派生类需要自己的构造函数
- 派生类可以根据需要添加数据成员和成员函数。
构造函数:访问权限的考虑
派生类不能直接访问基类的私有成员。而必须通过基类的方法来进行访问。所以派生类构造函数必须使用基类的构造函数。创建派生类对象时,程序首先创建基类对象。这意味着,基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法来完成这种工作。
A::A(int a,int b,int c) : B(b,c){
...
}
A的构造函数先将b和c传递给B的构造函数。如果省略成员初始化列表,将调用默认构造函数。
- 首先创建基类对象
- 派生类构造函数通过成员初始化列表将参数传递给基类的构造函数
- 派生类构造函数初始化派生类新增的数据对象。
释放对象的顺序和创建对象的顺序相反,所以先执行派生类的析构函数,然后自动调用基类的析构函数。
基类和派生类之间的特殊关系
- 派生类对象可以调用基类的非私有方法
- 基类指针可以在不进行显式类型转换的情况下指向派生类对象
- 基类引用可以在不进行显式类型转换的情况下引用派生类对象
A a;
B & rb = a;
B * pb = &a;
rb.b();
pb->b();//b是基类B中的方法
基类指针只能用于调用基类方法。
多态公有继承
如果希望同一个方法在派生类和基类中的行为是不同的。换句话说,方法的行为应取决于调用方法的对象。这种行为成为多态。一个方法,多种形态。方法的行为随上下文而异。有两种方法实现多态公有继承:
- 在派生类中重新定义基类的方法
- 使用虚方法
如果使用了virtual,程序将根据引用或指向的对象的类型来选择方法。
方法在基类中被声明为虚后,它在派生类中将自动成为虚方法。
所以在派生类中可以不使用virtual关键字。
如果要在派生类中重新定义基类的方法,通常应将基类的方法声明为虚,这样程序将通过对象类型来选择方法。为基类声明一个虚析构函数也是一种惯例。使用虚析构函数可以确保正确的析构函数序列被调用。因为析构函数是虚的,它将选择相应的对象类型的析构函数。
虚函数的工作原理
通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针。这种数组被称为虚函数表。一个类有一个虚函数表。 虚函数表中存储了为类对象进行声明的虚函数地址。
- 如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址。
- 如果没有,将保存原始版本函数的地址。
- 如果派生类定义了新的虚函数,则该函数的地址也将被添加进虚函数表。
使用虚函数的成本:
- 每个对象都将增大,增大量为存储地址的空间
- 对于每个类,编译器都创建一个虚函数地址表(数组)
- 对于每个函数调用,都要执行一项额外的操作,即到表中查找地址。
注意事项:
-
在基类中使用virtual关键字声明方法,可以使该方法在基类以及所有的派生类中是虚的。
-
如果使用指向对象的引用或者指针来调用虚方法。程序将使用为对象类型定义的方法。
-
如果定义的类将被用作基类,则应该将要在派生类中重新定义的方法定义为虚的。
-
构造函数不能是虚函数
原因:构造函数是用来创建并初始化对象的,在调用构造函数之前虽然分配了内存空间,但此时还没有实例化,没有虚函数指针。
虚函数的调用过程:
1.找到虚函数指针
2.通过虚函数指针找到虚函数表
3.在虚函数表中找到该虚函数的地址,并调用 -
析构函数应当是虚函数,除非类不用做基类。 给类定义一个虚析构函数并非错误,即使这个类不用做基类,这是效率方面的问题。
-
友元函数不能是虚函数
-
如果派生类没有重新定义,将使用函数的基类版本
-
如果在派生类中重新定义函数,将隐藏基类版本,无论函数的参数列表是否相同。
#include <iostream>
using namespace std;
class A
{
public:
int i;
virtual void func() {}
virtual void func2() {}
};
class B : public A
{
int j;
void func() {}
};
int main()
{
cout << sizeof(A) << ", " << sizeof(B); //输出 8,12
return 0;
}
在 32 位编译模式下,程序的运行结果是:
8, 12
如果将程序中的 virtual 关键字去掉,输出结果变为:
4, 8
任何有虚函数的类及其派生类的对象都包含这多出来的 4 个字节,这 4 个字节就是实现多态的关键——它位于对象存储空间的最前端,其中存放的是虚函数表的地址。
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着该虚函数表的指针(可以认为这是由编译器自动添加到构造函数中的指令完成的)。
虚函数表是编译器生成的,程序运行时被载入内存。一个类的虚函数表中列出了该类的全部虚函数地址。例如,在上面的程序中,类 A 对象的存储空间以及虚函数表(假定类 A 还有其他虚函数)如图 所示。
类 B 对象的存储空间以及虚函数表(假定类 B 还有其他虚函数)如图
多态的函数调用语句被编译成根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的一系列指令。
假设 pa 的类型是 A*,则 pa->func() 这条语句的执行过程如下:
- 取出 pa 指针所指位置的前 4 个字节,即对象所属的类的虚函数表的地址(在 64 位编译模式下,由于指针占 8 个字节,所以要取出 8 个字节)。如果 pa 指向的是类 A 的对象,则这个地址就是类 A 的虚函数表的地址;如果 pa 指向的是类 B 的对象,则这个地址就是类 B 的虚函数表的地址。
- 根据虚函数表的地址找到虚函数表,在其中查找要调用的虚函数的地址。不妨认为虚函数表是以函数名作为索引来查找的,虽然还有更高效的查找方法。
如果 pa 指向的是类 A 的对象,自然就会在类 A 的虚函数表中查出 A::func 的地址;如果 pa 指向的是类 B 的对象,就会在类 B 的虚函数表中查出 B::func 的地址。
类 B 没有自己的 func2 函数,因此在类 B 的虚函数表中保存的是 A::func2 的地址,这样,即便 pa 指向类 B 的对象,pa->func2();这条语句在执行过程中也能在类 B 的虚函数表中找到 A::func2 的地址。 - 根据找到的虚函数的地址调用虚函数。
由以上过程可以看出,只要是通过基类指针或基类引用调用虚函数的语句,就一定是多态的,也一定会执行上面的查表过程,哪怕这个虚函数仅在基类中有,在派生类中没有。
多态机制能够提高程序的开发效率,但是也增加了程序运行时的开销。虚函数表、各个对象中包含的 4 个字节的虚函数表的地址都是空间上的额外开销;而查虚函数表的过程则是时间上的额外开销。
访问控制:protected
关键字private和protected相似,在类外只能用公有类成员来访问protected部分的类成员。唯一区别在于基类的派生类中才会体现。派生类的成员可以直接访问protected成员,而不能直接访问基类的private成员。最好对类数据成员采用私有访问控制,不要使用保护访问控制;同时通过基类方法使派生类能够访问私有成员。
抽象基类(abstract base class ABC)
纯虚函数
virtual int test() const = 0;//纯虚函数声明
包含纯虚函数的的类只能用做基类。在C++中,允许纯虚函数有定义。
当类声明中包含纯虚函数时,则不能创建该类的对象。
私有继承
使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员。这意味着,基类方法不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
公有继承、私有继承和保护继承
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
基类成员访问属性的改变 | 基类public成员 | 基类protected成员 | 基类private成员 |
---|---|---|---|
public继承 | public | protected | private |
protected继承 | protected | protected | private |
private继承 | private | private | private |
总结:
- 基类的private成员始终是private,不能被派生类直接访问
- private成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- protected成员可以被派生类访问。
- private和protected成员在类外都不可以直接访问
公有继承:
#include<iostream>
#include<assert.h>
using namespace std;
class A {
public:
int a;
A() {
publicMem = 1;
protectedMem = 2;
privateMem = 3;
a = 4;
}
void fun() {
cout << a << endl; //正确
cout << publicMem << endl; //正确
cout << protectedMem << endl; //正确
cout << privateMem << endl; //正确
}
public:
int publicMem;
protected:
int protectedMem;
private:
int privateMem;
};
class B : public A {
public:
int a;
B(int i) {
A();
a = i;
}
void fun() {
cout << a << endl; //正确,public成员
cout << publicMem << endl; //正确,基类的public成员,在派生类中仍是public成员。
cout << protectedMem << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
cout << privateMem << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main() {
B b(10);
cout << b.a << endl;
cout << b.publicMem << endl; //正确
cout << b.protectedMem << endl; //错误,类外不能访问protected成员
cout << b.privateMem << endl; //错误,类外不能访问private成员
system("pause");
return 0;
}
保护继承:
#include<iostream>
#include<assert.h>
using namespace std;
class A {
public:
int a;
A() {
publicMem = 1;
protectedMem = 2;
privateMem = 3;
a = 4;
}
void fun() {
cout << a << endl; //正确
cout << publicMem << endl; //正确
cout << protectedMem << endl; //正确
cout << privateMem << endl; //正确
}
public:
int publicMem;
protected:
int protectedMem;
private:
int privateMem;
};
class B : protected A {
public:
int a;
B(int i) {
A();
a = i;
}
void fun() {
cout << a << endl; //正确,public成员
cout << publicMem << endl; //正确,基类的public成员,在派生类中变成protected成员。
cout << protectedMem << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
cout << privateMem << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main() {
B b(10);
cout << b.a << endl;
cout << b.publicMem << endl; //错误,类外不能访问protected成员
cout << b.protectedMem << endl; //错误,类外不能访问protected成员
cout << b.privateMem << endl; //错误,类外不能访问private成员
system("pause");
return 0;
}
私有继承:
#include<iostream>
#include<assert.h>
using namespace std;
class A {
public:
int a;
A() {
publicMem = 1;
protectedMem = 2;
privateMem = 3;
a = 4;
}
void fun() {
cout << a << endl; //正确
cout << publicMem << endl; //正确
cout << protectedMem << endl; //正确
cout << privateMem << endl; //正确
}
public:
int publicMem;
protected:
int protectedMem;
private:
int privateMem;
};
class B : private A {
public:
int a;
B(int i) {
A();
a = i;
}
void fun() {
cout << a << endl; //正确,public成员
cout << publicMem << endl; //正确,基类的public成员,在派生类中变成private成员。
cout << protectedMem << endl; //正确,基类的protected成员,在派生类中是private,可以被派生类的成员函数访问。
cout << privateMem << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main() {
B b(10);
cout << b.a << endl;
cout << b.publicMem << endl; //错误,类外不能访问private成员
cout << b.protectedMem << endl; //错误,类外不能访问private成员
cout << b.privateMem << endl; //错误,类外不能访问private成员
system("pause");
return 0;
}