C++ 类的继承和派生
个人笔记,仅供复习
1.继承和派生的概念
1.1 继承:在定义一个新类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个基类,把B作为基类的一个派生类(子类)。
1.2 派生:
- 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。
- 派生类一经定义后,可以独立使用,不依赖与基类。
- 派生类拥有基类的全部成员变量和成员函数,不论是private还是public或是protected。
- 在派生类的各个成员函数中,不能访问基类的private成员。
2.派生类的写法
2.1 格式:class 派生类名: public 基类名 { };
2.2 代码实例:
class Chuman{
private:
string name;
int age;
public:
string isman;
void talk(){
cout << "hello"<<endl;
}
};
class Cmen:public Chuman{
public:
void sayman(){
talk();
cout << "i'm a men" << endl;
}
};
其中Chuman是基类,Cmen是派生类,Cmen中有成员变量name、age和成员函数talk,以及新增的成员函数saymen。
2.3 派生类对象的内存空间:派生类对象的体积等于基类对象的体积加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。
3.类之间的两种关系
3.1 继承:“是”关系。
- 基类A,B是基类A的派生类。
- 逻辑上要求:“一个B对象也是一个A对象”。(例如:animal是基类,dog是派生类,那么一个dog也是一个animal)
3.2 复合:“有”关系
- 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系。
- 一般逻辑上要求:“D对象是C对象的固有组成部分”。(例如Battery是Phone的固有组成部分)
4.派生类覆盖基类成员
4.1 覆盖的定义:派生类可以定义一个和基类成员同名的成员,这叫做覆盖。
4.2 基类和派生类有同名成员的情况:在派生类访问这类成员时,缺省的情况是访问派生类的成员。如果要在派生类访问由基类定义的同名成员时,要使用作用域符号::
4.3 代码实例:
#include<iostream>
#include<string>
using namespace std;
class A{
int x;
int y;
public:
void sayA(){
cout << "A" << endl;
}
};
class B:public A{
int x;//同名成员,覆盖基类A中的x
public:
void sayA(){
cout << "I must say B" << endl;
}
};
int main()
{
B one;
one.sayA();
one.A::sayA();
return 0;
}
以上代码输出为:"I must say B"和“A”。5.类的保护成员
5.1 不同类型成员的访问权限:
基类的private成员:可以被下列函数访问
-基类的成员函数
-基类的友元函数
基类的public成员:可以被下列函数访问
-基类的成员函数
-基类的友元函数
-派生类的成员函数
-派生类的友元函数
-其他函数
基类的protected成员:可以被下列函数访问
-基类的成员函数
-基类的友元函数
-派生类的成员函数可以访问当前对象的基类的保护成员
5.2 注:只能访问当前对象的基类的保护成员
5.3 代码实例:
class A{
protected:
int x;
};
class B:public A{
public:
void Doit(){
x = 5;//ok
B b;
b.x = 6;//错,b不是当前对象
cout << x << endl;
}
};
6.派生类的构造函数与析构函数
6.1 构造函数的执行顺序:在创建派生类的对象时,需要调用基类的构造函数,初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
6.2 调用基类构造函数的两种方式:
-显式方式:在派生类的构造函数中,为基类的构造函数提供参数。
-隐式方式:在派生类的构造函数中,省略基类的构造函数时,派生类的构造函数则自动调用基类默认的构造函数。
6.3 代码实例:
class bugs{
private:
int x,y;
public:
bugs(int x,int y):x(x),y(y){}
};
class Nobugs:public bugs{
public:
int a,b;
Nobugs(int x,int y,int a,int b):bugs(x,y){//正确的构造函数
this->a = a;
this->b = b;
}
};
6.4 析构函数的执行顺序:执行完派生类的析构函数后,自动调用基类的析构函数。
6.5 包含成员对象的派生类的构造函数的写法:
class Skill {
public:
Skill(int n) { }
};
class FlyBug: public Bug {
int nWings;
Skill sk1, sk2;
public:
FlyBug( int legs, int color, int wings);
};
FlyBug::FlyBug( int legs, int color, int wings):
Bug(legs,color),sk1(5),sk2(color) ,nWings(wings) {
}
6.6 小结:
在创建派生类的对象时
- 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。最后执行派生类自己的构造函数
- 先执行派生类自己的析构函数,再依次执行各成员对象类的析构函数,最后执行基类的析构函数。
析构函数的调用顺序与构造函数的调用顺序相反。
7.public继承的赋值兼容规则(为多态奠定基础)
class base { };
class derived : public base { };
base b;
derived d;
7.1 派生类对象可以赋值给基类对象:b = d;
7.2 派生类对象可以初始化基类引用:base & br = d;
7.3 派生类对象的地址可以赋值给基类指针:base * pb = & d;
注:如果派生方式是private或protected,则上述三条不行。
7.4 protected继承和private继承:
- protected继承时,基类的public成员和protected成员成为派生类的protected成员。
- private继承时,基类的public成员成为派生类的private成员,基类的protected成员成为派生类的不可访问成员。
- protected和private继承不是“是”的关系。
7.5 基类与派生类的强制转换:公有派生的情况下,派生类对象的指针可以直接赋值给基类指针。
Base * ptrBase = &objDerived;
ptrBase指向的是一个Derived类的对象;*ptrBase可以看作一个Base类的对象,访问它的public成员直接通过ptrBase即可,但不能通过ptrBase访问objDerived对象中属于Derived类而不属于Base类的成员。
注:
- 即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。
- 通过强制指针类型转换,可以把ptrBase转换成Derived类的指针
Base * ptrBase = &objDerived;
Derived *ptrDerived = (Derived * ) ptrBase;
8.直接基类和间接基类
8.1 定义:类A派生类B,类B派生类C,类C派生类D,那么:
– 类A是类B的直接基类
– 类B是类C的直接基类,类A是类C的间接基类
– 类C是类D的直接基类,类A、B是类D的间接基类
8.2 声明:在声明派生类时,只需列出他的直接基类。派生类沿着类的层次自动向上继承它的间接基类。
8.3 派生类的成员:
- 派生类自己定义的成员
- 直接基类中的所有成员
- 所有间接基类的全部成员