文章目录
1.继承
- 继承:在一个已经存在的类的基础上建立一个新的类。已存在的类称为父类、基类;新建立的类称为子类、派生类。
- 一个派生类只从一个基类派生称为单继承
- 一个派生类有两个或多个基类称为多重继承
- 图解单继承
- 图解多继承- 派生类是基类的具体化,而基类则是派生类的抽象
从上图中可以看出:
1.小学生、中学生、大学生、研究生、留学生是学生的具体化,都是从学生的共性基础上加上某些特点形成的子类
2.学生则是各类学生共性的提取形成的一个抽象的类
3.基类综合了派生类的公共特征,派生类则在基类的基础上增加某些特性,把抽象变的具体
- 派生类的声明形式
- class 派生类名 : [继承方式] 基类名{ 子类新增成员 };
- 继承方式:public(公用的) private(私有的) protected(受保护的)
- 如果不写继承方式则默认为private
- 派生类的成员有:(1)从父类继承来的成员 (2)修改从父类继承来的成员 (3)在派生类中增加的新成员
- 继承优点:继承性提供了重复利用程序资源的一种途径。通过继承机制,可以扩充和完善旧的程序设计以适应新的需求。这样不仅可以节省程序开发的时间和资源,并且为未来程序增添了新的资源。
2.子类成员的访问属性
- 无论是单继承还是多继承,都符合以下访问属性的限制规律
2.1公用继承
- 子类从父类的继承方式为public,称为公用继承
- 公用继承后父类成员访问属性表格
class A{
private:
int y; //private访问权限
protected:
int z; //protected访问权限
public:
int x; //public访问权限
};
class B:public A{
public:
void Change(){
x = 1;
//合法,基类中public成员在public继承之后依然是public访问权限
y = 1;
//不合法,基类中private成员在public继承之后无法访问
z = 1;
//合法,基类中protected成员在public继承之后依然是protected访问权限
}
};
2.2私有继承
- 子类从父类的继承方式为private,称为私有继承
- 私有继承后父类成员访问属性表格
class A{
private:
int y; //private访问权限
protected:
int z; //protected访问权限
public:
int x; //public访问权限
};
class B:private A{
public:
void Change(){
x = 1;
//合法,基类中public成员在private继承之后是private访问权限
y = 1;
//不合法,基类中private成员在private继承之后无法访问
z = 1;
//合法,基类中protected成员在private继承之后是private访问权限
}
};
2.3保护继承
- 子类从父类的继承方式为protected,称为保护继承
- 保护成员对于类的用户角度看,等价于私有成员,但是保护成员可以在子类的成员函数中使用
- 保护继承后父类成员访问属性表格
class A{
private:
int y; //private访问权限
protected:
int z; //protected访问权限
public:
int x; //public访问权限
};
class B:protected A{
public:
void Change(){
x = 1;
//合法,基类中public成员在protected继承之后是protected访问权限
y = 1;
//不合法,基类中private成员在protected继承之后无法访问
z = 1;
//合法,基类中protected成员在protected继承之后是protected访问权限
}
};
2.4类中成员访问属性
3.子类构造函数和析构函数
3.1构造函数
- 子类构造函数不仅仅需要为子类中特有的属性传值,还要为父类中继承过来的属性传值(即调用父类构造函数)
子类构造函数格式:
子类名(总参数列表) : 父类名(父类参数表){ 新增数据成员初始化 }
例:B(int x,int y,int z) : A(x,y){ this->z = z;}
B继承A类,A中有x,y两个参数,B中新增一个z参数
class A{
private:
int x;
int y;
int z;
public:
A(){
x = 0;
y = 0;
z = 0;
}
A(int x,int y,int z){
this->x = x;
this->y = y;
this->z = z;
}
};
class B:protected A{
private:
int m;
int n;
public:
//子类无参构造函数
B():A(){
m = 0;
n = 0;
}
//子类有参构造函数
B(int x,int y,int z,int m,int n):A(x,y,z){
this->m = m;
this->n = n;
}
};
- 无论继承结构多么复杂,无论继承了多少层,子类一定要负责对直接父类进行初始化
构造函数执行顺序:先基类后子类
多层继承:A派生出B,B派生出A
A: int x ; A构造函数:A(int x){ this-> x = x;}
B: int y ; B构造函数:B(int x,int y) : A(x){ this->y = y; }
C: int z ; C构造函数:C(int x,int y,int z):B(x,y){ this->z = z; }
调用C构造函数初始化对象时成员变量初始化顺序:
①初始化A的x
②初始化B的y
③初始化C的z
多重继承:A、B共同派生出C
A:int x; A构造函数:A(int x){ this-> x = x; }
B:int y ;B构造函数:B(int y) { this->y = y; }
C:int z;C构造函数:C(int x,int y,int z) : A(x),B(y){ this->z = z; }
调用C构造函数初始化对象时成员变量初始化顺序:
①初始化A的x
②初始化B的y
③初始化C的z
提醒:虽然A和B之间并无继承关系,本应初始化顺序并无先后,但是C中的构造函数是先初始化的A中的x,所以按照代码顺序执行初始化
3.2析构函数
- 子类是无法继承父类的析构函数的
- 子类必须通过自己的析构函数来调用父类的析构函数,来处理从父类继承来的成员,同时在自己的析构函数中处理自己新增的成员
class A{
private:
int x;
public:
A(){x = 0;}
A(int x){this->x = x;}
~A(){} //父类析构函数
};
class B:public A{
private:
int m;
public:
B():A(){m = 0;}
B(int x,int m):A(x){this->m = m;}
~B(){} //子类析构函数
};
5.虚基类
Q:看下面这段代码,你发现了什么问题?
class A{
public:
m(){}
};
class B : public A{
};
class C : public A{
};
class D : public B,public C{
public:
n(){
m();
}
}
Q:很显然:在多层继承加多重继承中,一个子类同时继承了两个父类,这两个父类又同时继承了同一个类,此时,中间层的两个类存在相同的方法或者属性,这时,如何在最底层的子类中区分相同的方法或属性呢?
A:由于同名方法或属性造成了二义性,这时解决方法有两个:第一个方法是使用作用域运算符进行指明,例如:将上述程序中的n方法修改成:n(){ B::m();},就明确指出了调用的是父类B中的m方法,这样便消除了二义性。第二个方法是使用虚基类
- 虚基类的作用:让继承间接基类时只保留一份成员
- 声明虚基类的形式:class 子类名 : virtual 继承方式 父类名
class B : virtual public A{
};
class C : virtual public A{
};
此时A被声明为虚基类,D类同时继承了B和C类,D中只会出现一个A中的属性与方法,并不会出现二义性
- 为了保证虚基类中的成员在子类中只被继承一次,需要将该基类的所有直接子类中声明为虚基类,否则,仍然会出现对基类的多次继承,如图:
6.父类对象与子类对象之间的转换
class A {
public:
int x;
};
class B : public A{
public:
int y;
};
(1)用子类对象给父类对象赋值
A a;
B b;
a = b;
(2)父类对象引用可以引用子类对象
B b;
A &a = b
- 引用对象a只是b对象中x部分的名字,只可以访问b对象中的x,无法访问y.
(3)父类类型指针变量指向子类对象
A *p;
B b;
p = &b;
p->x = 10; //合法
p->y = 10; //不合法
- 通过指向父类对象的指针来指向子类对象,此指针只能访问子类从父类继承来的属性和方法,无法通过此指针间接访问子类特有的属性和方法
7.类的组合
- 简单来说,类的组合就是类中属性包含其他类的对象
- 读懂以下程序就可理解类的组合、属性为对象时如何初始化
#include <iostream>
using namespace std;
class A{
private:
int x;
public:
A(){
x = 0;
}
A(int x){
this->x = x;
}
int getX(){
return x;
}
};
class B{
private:
int y;
A a;
public:
B(){ //对象a默认调用无参构造函数
y = 0;
}
B(int y,A &ra){
this->y = y;
a = ra; //为对象a初始化
}
void display(){
cout<<this->a.getX()<<" "<<this->y<<endl;
}
};
int main(){
A a(1);
B b1;
B b2(2,a);
b1.display();// 0 0
b2.display();// 1 2
return 0;
}
8.类的继承和组合
- 继承关系可以理解为 is a 关系:斗牛犬是一只狗
- 组合关系可以理解为 has a 关系:张三有一只斗牛犬
- 继承是纵向的,组合是横向的
9.继承的意义
- 面向对象的四大特征:抽象 继承 封装 多态
- 封装->继承->多态
- 继承是多态的基础