继承
1. 概念:
- 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特
性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,
体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
2. 继承格式
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
//子类(派生类) //父类(基类)
class Student : public Person //继承格式
{
protected:int _stuid; // 学号
};
//子类(派生类) //父类(基类)
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
3. 继承基类成员访问方式的变化
4. 基类和派生类对象赋值转换
- 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。也称对象的切片。
- 基类对象不能赋值给派生类对象
class Base
{
public:
void show()
{
cout << "Base::show()" << endl;
}
private:
int m_b = 0;
};
class D : public Base
{
void show()
{
cout << "D::show()" << endl;
}
private:
int m_d = 0;
};
void main()
{
D d;
Base b;
//赋值兼容规则
//3种方式
b = d; //子类对象直接给父类对象赋值
Base *pb = &d; //子类对象的地址可以直接给父类指针赋值
Base &rb = d; //子类对象可以赋值(初始化)父类的引用
}
5. 继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
5. 派生类默认成员函数
/*
1. 子类对象创建调用构造方法,首先要调用父类的构造方法,然后在调用子类
2. 子类对象创建通过拷贝构造方法,首先要调用父类的拷贝构造方法,然后在调用子类
3. 子类对象创建通过赋值语句方法,首先要调用父类的赋值语句方法,然后在调用子类
4. 子类对象析构,先调用子类, 在调用父类
5. 子类对象初始化先调用父类在调用子类构造
6. 子类对象的析构清理先调用子类析构再调用父类的析构
*/
class Base
{
public:
Base(int b = 0) : m_b(b)
{
cout << "Base::Base()" << endl;
}
Base(const Base &b)
{
cout << "Base::Base(const Base &b)" << endl;
}
Base& operator=(const Base &b)
{
cout << "Base::operator=(const Base &b)" << endl;
m_b = b.m_b;
return *this;
}
private:
int m_b;
};
class D : public Base
{
public:
//显示调用父类构造函数
D(int d = 0) : m_a(d), Base(10)
{
cout << "D::D()" << endl;
}
D(const D &b)
{
cout << "D::D(const D &b)" << endl;
}
D& operator=(const D &d)
{
cout << "D::operator=(const D &b)" << endl;
m_a = d.m_a;
return *this;
}
private:
int m_a;
};
void main()
{
//基础构造
D d(100);
//拷贝构造
D d1 = d;
//赋值语句
D d2;
d2 = d;
}
/*
多继承
class D : public Base1, public Base2, public Base3;
调用构造方法的顺序 Base1, Base2, Base3 D
析构顺序 ~D, ~Base3, ~Base2, ~Base1
*/
6. 基类友元函数
- 友元关系不能继承(不能传递),也就是说父类友元不能访问子类私有和保护成员。因此要使用基类的友元函数,需要在派生类中声明。
7. static 静态成员
- 父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。 无论派生出多少个子类,都只有一个static成员实例。
- static成员必须在类外部进行初始化。
8. 菱形继承
#include<iostream>
using namespace std;
/*
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
但是其他地方,不要随意使用。
*/
/*
在底层中, 正常继承父类, 子类数据空间的前面会将父类的成员的数据空间
若 virtual继承父类, 而子类数据空间的前面 将会是 父类的成员 的数据空间 的偏移量, 会按照虚基表的形式存储。
*/
class A
{
public:
int m_a = 1;
};
class B : virtual public A // A是虚基类, 虚父类
{
public:
int m_b = 2;
};
class C : virtual public A
{
public:
int m_c = 3;
};
class D : public B, public C
{
public:
int m_d = 4;
};
void main()
{
D d;
d.m_d = 40;
d.m_c = 30;
d.m_b = 20;
//因为a既来自于 类B或C, 又可能来自于类A, 因此产生二义性
// d.m_ 10; //错误无法访问,二义性, 如果不虚拟继承
//可以这样修改访问, 不同的空间
//d.B::m_a = 100;
//d.C::m_a = 200;
//采用虚拟继承
//此时无论来自哪里的a其所指空间都是相同的。
d.m_a = 100;
}
9. 继承和组合
- public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 继承是比如基类是动物,那么人,鸟 都继承动物,是动物的一种。此时就要使用继承。
- 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。组合就是比如一个大类汽车,其包含4个轮子,4个门,这都是汽车的一部分,都是汽车的成员。
- 优先使用对象组合,而不是类继承 。