继承
文章目录
(一)继承的概念及声明
引出:
有时候,两个或多个类中,成员及成员函数有大量的重复,就造成了人力物力的浪费,效率低下。那有没有一种软件重用机制呢?当新创建一个类时,如果新类的内容在已有的类中已经大量存在了,那能不能将已有的类作为基础,再加上新的内容以提高效率呢? c++中的继承机制就是为了解决这样的问题。
一个新类从已有的类中获得其已有的特性,这种现象就叫做类的继承。换种角度,从已有的类中产生一个新类,成为类的派生。一个类只从一个基类派生,成为单继承。一个派生类有两个或多个基类成为多重继承。
声明派生类的一般形式为:
class 派生类名 : 继承方式 基类名
{
}
例如:
class A
{
}
class B:public A
{
}
(二)派生类成员的访问属性
派生类对基类的继承方式分为:
(1) public (公用继承)
(2)proteated (保护继承)
(3)private (私有继承)
1.公用继承
在定义派生类时将继承方式指定为public的,称为公用继承,定义出的派生类叫做公用派生类,其基类称为公用基类。
访问属性:
- 在派生类内部,可以访问基类的公用属性和保护属性的成员,不能访问私有属性的成员。
- 派生类实例化出是对象,可以访问派生类的公有属性成员以及基类的公有属性成员。
class Base
{
public:
void fun()
{ cout << "Base::fun()" << endl; }
protected:
void list()
{cout << "Base::list()" << endl;}
private:
void print()
{cout << "Base::print()" << endl;}
private:
int b;
};
class D :public Base //公有继承
{
void d()
{
fun();
list();
//print();//编译不通过,只能访问公有成员和保护成员
}
};
int main()
{
D d;
d.fun();
//d.list();
//d.print(); // 编译不通过,能将父类的信息继承,但是对象只能访问公有数据
return 0;
}
需要注意的是:
在继承时,派生类会将基类的全部信息继承过来,只是有一些成员可以访问,有一些成员不可以访问。接下来说的保护继承和私有继承也是如此。
2.保护继承
在定义派生类时将继承方式指定为protected的,称为保护继承,定义出的派生类叫做保护派生类,其基类称为保护基类。
访问属性:
- 在继承时,会将基类的所有成员属性全部变为保护属性。
- 在派生类内部,可以访问基类的公用属性和保护属性的成员,不能访问私有属性的成员。以此派生类为基类新派生出来的类,可以访问基类的公用属性和保护属性的成员。
- 派生类实例化出是对象,可以访问派生类的公有属性成员,但不能访问基类成员。
class Base
{
public:
void fun()
{ cout << "Base::fun()" << endl; }
protected:
void list()
{cout << "Base::list()" << endl;}
private:
void print()
{cout << "Base::print()" << endl;}
private:
int b;
};
class S :protected Base
{
void s()
{
fun();
list();
//print();//编译不通过,只能访问公有成员和保护成员
}
int main()
{
S s;
/*s.fun();
s.list();
s.print();*///都编译不通过,保护继承,会将父类的所有信息继承过来,但全都变为保护属性,对象不能访问
return 0;
}
3.私有继承
在定义派生类时将继承方式指定为private的,称为私有继承,定义出的派生类叫做私有派生类,其基类称为私有基类。
访问属性:
- 在继承时,会将基类的所有成员属性全部变为私有属性。
- 在派生类内部,可以访问基类的公用属性和保护属性的成员,不能访问私有属性的成员。以此派生类为基类新派生出来的类,也不能访问基类的公用属性和保护属性的成员。
- 派生类实例化出是对象,可以访问派生类的公有属性成员,但不能访问基类成员。
class Base
{
public:
void fun()
{ cout << "Base::fun()" << endl; }
protected:
void list()
{cout << "Base::list()" << endl;}
private:
void print()
{cout << "Base::print()" << endl;}
private:
int b;
};
class M :private Base
{
void m()
{
fun();
list();
//print();//编译不通过,只能访问公有成员和保护成员
}
};
int main()
{
M m;
/*m.fun();
m.list();
m.print();*///都编译不通过,私有继承,会将父类的所有信息继承过来,但全都变为私有属性,对象不能访问
return 0;
}
(三)派生类的构造函数和析构函数
基类的构造函数是不能集成的,在声明派生类时,派生类并没有将基类的构造函数继承过来,以此。对基类成员的初始化也要由基类来承担。所以在设计派生类的构造函数时,不仅要考虑派生类新增成员的初始化,也要考虑基类成员的初始化。
1.简单派生类的构造函数
这里指的是只有一个基类,并且只有直接派生类,没有间接派生类。一般形式为:
派生类函数构造名(总参数列表):基类构造函数名(基类参数表)
{派生类新增成员初始化}
比如;
class student
{
public:
student(int n, string nam, char s)
{
num=n;
nam=name;
sex=s;
}
private:
int num;
string name;
char sex;
}
class student1
{
public:
student1(int n, string nam, char s,int a,strint add):student(n,nam,s)
{
age=a;
addr=add;
}
private:
int age;
string addr;
}
2.有子对象的派生类的构造函数
类中的数据成员除了标准数据类型(int,char等),还可以是一个类,也就是内嵌一个类,成为子对象,即对象中的对象。
比如,在上述student1中,除了增加add和addr,还可以增加一个班长项,班长也属于学生,也属于student类,那么这时构造函数的形式为:
派生类函数构造名(总参数列表):基类构造函数名(基类参数表),子对象名(子对象参数表)
{派生类新增成员初始化}
比如:
class student
{
public:
student(int n, string nam, char s)
{
num=n;
nam=name;
sex=s;
}
private:
int num;
string name;
char sex;
}
class student1
{
public:
student1(int n, string nam, char s,int n1, string nam1, char s1,int a,strint add):student(n,nam,s),monitor(n1,nam1,s1)
{
age=a;
addr=add;
}
private:
int age;
string addr;
student monitor;
}
应当注意:
- 如果在派生类中没有定义构造函数或者定义了不带参数的构造函数。那么在派生类中可以不用写基类的构造函数,因为系统会自动生成。
- 如果在派生类中定义了带参数的构造函数。那么就必须在派生类中显式的定义基类的构造函数。
3.派生类的析构函数
派生时,同样不能继承基类的析构函数。也需要通过派生类的析构函数去调动基类的析构函数。在派生类中需要自己定义析构函数来对派生类的所有成员进行清理工作。基类的清理工作仍由基类的析构函数完成。在执行派生类的析构函数时,系统会自动调用基类的析构函数。
调用析构函数的顺序刚好和调用构造函数的顺序相反。
(四)多重继承
1.声明多重继承的方法
如果已经声明了A类、B类、C类。可以声明多重派生类D:
class D:public A,privatet B,protected C
{ D新增加的成员 }
上述D是多重继承的派生类,它一公用继承的方式继承了A,以私有继承的方式继承了B,以保护继承的方式继承了C。
2.多重继承的构造函数
格式:
派生类构造函数名(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),基类3构造函数(参数表)
{派生类中新增成员函数初始化}
比如:
class A
{
public:
A(int a):_a(a)
{}
private:
int _a;
}
class B
{
public:
B(int b):_b(b)
{}
private:
int _b;
}
class C
{
public:
C(int c):_c(c)
{}
private:
int _c;
}
class D
{
public:
D(int a,int b,int c,int d):A(a),B(b),C(c)
{
_d=d;
}
private:
int _d;
}
3.多重继承引起的二义性问题
如图:B、C继承了A,D又继承了D,假设所有的成员都是公用属性,所有的继承方式都是公用继承。通过前面的介绍我们知道,B、C在继承A的时候,都会继承一份A的成员,换句话说,在B和C中,都存在了一个a成员。而D又继承了B和C,那么,当D实例化的对象,在访问成员a的时候,是通过B访问呢?还是通过C访问呢?这就是我们说的多重继承引起的二义性问题。
class A
{
public:
int _a;
}
class B:public A
{
}
class C:public A
{
}
class D:public B,public C
{
}
int main()
{
//d.a=10; ???
}
那遇到这种问题的时候,我们该怎么解决呢?
- 可以加作用域限定符
int main()
{
d.::B a=10;
}
2.虚基类
4.虚基类
c++提供虚基类,使得在继承间接共同基类时(就类似上述所说情况),只保留一份成员。
声明方法:
class A
{……};
class B:virtual public A
{……};
class C:virtual public A
{……};
class D:public B,public C
{……};
注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定的继承方式时声明的。
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现。当B和C继承A时,会在B和C的空间生成一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr指的是虚基类表指针,该指针指向了一个虚基类表,虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。
有关虚基类以及虚基表的详情请访问以下链接:
https://blog.csdn.net/qq_41885826/article/details/86566643
文章系本人原创,转载请注明作者和出处。