继承的基本概念
例如猫和狗都可以有年龄,姓名的属性。那么就可以用一个父类animal动物。给父类年龄和姓名。子类就会继承这些属性。可以大大减少代码量。比如现在有需要十种不同的动物类,就可以把他们的共性抽象为一个父类即可。
#include<iostream>
using namespace std;
#include<string>
class animal {
public:
int age = 18;
string name = "abc";
};
class Dog :public animal {
public:
int height = 50;
};
void test01()
{
Dog dog;
cout << dog.name << endl;
cout << dog.age << endl;
cout << dog.height << endl;
}
int main()
{
test01();
}总结:
- 好处:可以减少重复的代码
- 语法:
class A: public B; - A类称为子类或派生类,B类称为父类或基类。
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
继承方式
继承方式一共有三种:·公共继承,保护继承,私有继承
#include<iostream>
using namespace std;
#include<string>
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base1 {
public:
void func()
{
m_A = 10;//父类公共权限成员,在子类依然是公共
m_B = 10;//父类保护权限成员,在子类依然是保护权限
//m_C = 10;报错,父类的私有成员,子类访问不到
}
};
class Son2 :protected Base1 {
public:
void func() {
m_A = 100;//父类公共权限成员,在子类变为保护权限
m_B = 100;//父类保护权限成员,在子类依然是保护权限
//m_C = 100;报错,父类的私有成员,子类访问不到
}
};
class Son3 :private Base1 {
public:
void func()
{
m_A = 100;//父类公共权限成员,在子类变为私有权限
m_B = 100;//父类保护权限成员,在子类变为私有权限
//m_C = 100;报错,父类的私有成员,子类访问不到
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//m_A = 10;报错,父类Son3的m_A已经变为了私有,在Son3的子类就无法访问。
//侧面验证了,private权限继承之后的属性确实变为了私有的。
}
};
int main()
{
Son1 s1;
s1.m_A = 10;
cout << s1.m_A << endl;
//s1.m_B = 10;报错,父类的保护权限在子类依然是保护权限,不能在类外访问。
Son2 s2;
//s2.m_A = 10;报错,m_A已经在子类变为了保护权限,类外访问不到。
Son3 s3;
//s3.m_A = 10;报错,m_A已经在子类变为了私有权限,类外访问不到。
}继承中的对象模型
#include<iostream>
using namespace std;
#include<string>
class Base {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Base {
public:
int m_D;
};
void test01()
{
Son s1;
cout << sizeof(s1) << endl;
}
int main()
{
//父类中所有的非静态成员属性都会被子类继承下去。
test01();//结果为16.验证了m_C会被继承,但是子类无法访问。
}结论:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
继承中构造和析构顺序
#include<iostream>
using namespace std;
#include<string>
class Base {
public:
Base()
{
cout << "Base构造函数" << endl;
}
~Base()
{
cout << "Base的析构函数" << endl;
}
};
class Son :public Base {
public:
Son()
{
cout << "Son的构造函数" << endl;
}
~Son()
{
cout << "Son的析构函数" << endl;
}
};
int main()
{
Son s1;
}打印结果:Base构造函数Son的构造函数Son的析构函数Base的析构函数
结论:父类先构造,后析构,子类后构造,先析构。
继承中同名成员的处理
#include<iostream>
using namespace std;
#include<string>
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base作用域下的func函数调用" << endl;
}
void func(int a)
{
cout << "Base作用域下的func(int a)函数调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
int m_A;
void func()
{
cout << "Son作用域下的func函数调用" << endl;
}
};
void test()
{
Son s;
cout << s.m_A << endl;
//如果通过子类对象访问父类成员,需要加作用域
cout << s.Base::m_A << endl;
}
void test1()
{
Son s;
s.func();
s.Base::func();
//如果子类出现了与父类同名成员函数,子类会隐藏掉父类所有的同名成员函数。
s.Base::func(100);
}
int main()
{
test1();
}总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
继承中同名静态成员的处理
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员直接访问即可
- 访问父类同名成员需要加作用域
#include<iostream>
using namespace std;
#include<string>
class Base {
public:
static int m_A;
static void func()
{
cout << "Base static func调用" << endl;
}
static void func(int a)
{
cout << "Base static func(int a)调用" << endl;
}
};
//静态成员变量类内声明类外初始化
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son static func调用" << endl;
}
};
int Son::m_A = 200;
void test()
{
//通过对象访问
Son s;
cout << s.m_A << endl;
cout << s.Base::m_A << endl;
//通过类名访问
cout << Son::m_A << endl;
cout << Son::Base::m_A << endl;//通过子类访问父类的静态成员变量
}
void test1()
{
//通过对象访问
Son s;
s.func();
s.Base::func();
//通过类名访问
Son::func();
Son::Base::func();//通过子类访问父类的静态成员函数
Son::Base::func(10);
}
int main()
{
test1();
}多继承
C++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分,所以实际开发不建议使用多继承。
#include<iostream>
using namespace std;
#include<string>
class Base1 {
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2 {
public:
Base2()
{
m_A = 200;
}
int m_A;
};
class Son :public Base1, public Base2 {
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test()
{
Son s;
cout << sizeof(s) << endl;
//cout << s.m_A << endl;
//报错,出现了二义性,父类1和2都有m_A变量。调用需要加作用域
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main()
{
test();
}菱形继承
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
例如,B、C类同时继承了父类A,然后D类又多继承了B和C,那么B和C都会有A类里的某个变量,同时给了子类D,子类D就继承了两份这样的变量。此时就会出现一些问题。
#include<iostream>
using namespace std;
#include<string>
class Animal {
public:
int m_A;
};
//在继承之前加上关键字virtual,表示虚继承,可以解决菱形继承的问题
//Animal类称为虚基类
class Sheep :virtual public Animal {
};
class Camel :virtual public Animal {
};
class Alpaca :public Sheep, public Camel {
};
void test()
{
Alpaca alpaca;
//alpaca.m_A = 18;报错,出现了二义性,需要加作用域
alpaca.Sheep::m_A = 18;
alpaca.Camel::m_A = 30;
//当菱形继承时,两个父类拥有相同的数据,需要加作用域区分
cout << alpaca.Sheep::m_A << endl;
cout << alpaca.Camel::m_A << endl;
//但是这份数据我们只需要一份,菱形继承造成了资源浪费。
}
int main()
{
test();
}总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题,可以正常访问alpaca.m_A.并且和父类的m_A是共享的同一份数据。