继承
下级别的成员除了拥有上一级别的共性,还有自己的特性。
用继承的技术可以减少重复的代码。
三种继承方式:
公共继承、保护继承、私有继承
权限名称 | 类内、类外访问情况 | 继承访问情况 | |
---|---|---|---|
public | 公共权限 | 成员 类内可以访问、类外也可以访问 | |
protected | 保护权限 | 成员 类内可以访问、类外不能访问 | 继承中子类可以访问父类的保护内容 |
private | 私有权限 | 成员 类内可以访问、类外不能访问 | 继承中子类不能访问父类的保护内容 |
一、继承的基本语法和方式
语法:class 子类 : 继承方式 父类
子类 也称为 派生类
父类 也称为 基类
子类中的成员包含两大部分:
- 一部分是从基类继承过来的,一部分是自己增加的成员。
- 从基类继承过来的表现其共性,增加的成员表现其个性。
class Base
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
1.公共继承
父类中的 | 被继承后 | 子类 |
---|---|---|
公共权限成员 | 依然是公共权限 | |
保护权限成员 | 依然是保护权限 | |
私有权限成员 | 访问不到 |
//1.公共继承
class Son1 : public Base
{
public:
void func()
{
m_a = 10; //父类中的 公共权限成员 到子类中依然是公共权限
m_b = 20; //父类中的 保护权限成员 到子类中依然是保护权限
//m_c = 30; //父类中的 私有权限成员 子类访问不到
}
};
2.保护继承
父类中的 | 被继承后 | 子类 |
---|---|---|
公共权限成员 | 变为了保护权限 | |
保护权限成员 | 依然是保护权限 | |
私有权限成员 | 访问不到 |
//2.保护继承
class Son2 : protected Base
{
public:
void func()
{
m_a = 10; //父类中的 公共权限成员 到子类中变为了保护权限
m_b = 20; //父类中的 保护权限成员 到子类中依然是保护权限
//m_c = 30; //父类中的 私有权限成员 子类访问不到
}
};
3.私有继承
父类中的 | 被继承后 | 子类 |
---|---|---|
公共权限成员 | 变为了私有权限 | |
保护权限成员 | 变为了私有权限 | |
私有权限成员 | 访问不到 |
//3.私有继承
class Son3 : private Base
{
public:
void func()
{
m_a = 10; //父类中的 公共权限成员 到子类中变为了私有权限
m_b = 20; //父类中的 保护权限成员 到子类中变为了私有权限
//m_c = 30; //父类中的 私有权限成员 子类访问不到
}
};
//3.1 验证Son3的成员在类外访问不到的原因是私有继承,而不是保护继承。
class Grandson3 : public Son3
{
public:
void func()
{
//m_b = 100;
//m_a = 100;
//Base的成员m_a和m_b到了Son3中已经变为了私有成员,所以Grandson3即使作为Son3的儿子也是访问不到的。
}
};
测试编译结果
void test01()
{
Son1 s1;
s1.m_a = 100; //说明在Son1中m_a是公共权限,类外依然可以访问
//s1.m_b = 100; //说明在Son1中m_b是保护权限,类外访问不到
Son2 s2;
//s2.m_a = 100; //说明在Son2中m_a是保护权限,类外访问不到
//s2.m_b = 100; //说明在Son2中m_b是保护权限,类外访问不到
Son3 s3;
}
二、继承的对象模型
class Base
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son : public Base
{
public:
int m_d;
};
void test01()
{
cout << "size of Son = " << sizeof(Son) << endl;
}
由此可见一个Son
类对象所占的内存空间是16,意味着父类所有非静态成员属性都会被子类继承下去。父类中私有成员属性是被编译器给隐藏了,因此访问不到,但确实被子类继承了。
Tips of 利用开发人员命令提示工具查看对象模型
跳转盘符 F:
跳转文件路径 cd 具体路径
查看命令 cl /d1 reportSingleClassLayout查看的类名 所属文件名
三、继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
那么父类和子类的构造析构顺序是什么?
class Base
{
public:
Base()
{
cout << "父类的构造函数" << endl;
}
~Base()
{
cout << "父类的析构函数" << endl;
}
};
class Son : public Base
{
public:
Son()
{
cout << "子类的构造函数" << endl;
}
~Son()
{
cout << "子类的析构函数" << endl;
}
};
void test01()
{
Son s1;
}
结合编译结果,可以看出结论是:先构造父类,再构造子类;先析构子类,再析构父类。
四、继承中同名成员处理
当子类与父类出现同名的成员,
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
【注意】
当子类与父类拥有同名的成员函数时,子类会隐藏父类中同名成员函数,加作用域后才可以访问到父类中的同名成员函数。
class Father
{
public:
int m_a;
public:
Father()
{
m_a = 100;
}
void func()
{
cout << "Father的func函数调用" << endl;
}
void func(int a)
{
cout << "Father的func重载函数调用" << endl;
}
};
class Son : public Father
{
public:
int m_a;
public:
Son()
{
m_a = 200;
}
void func()
{
cout << "Son的func函数调用" << endl;
}
};
1.同名成员属性
//同名成员属性
void test01()
{
Son s1;
cout << "Son的m_a = " << s1.m_a << endl;
cout << "Father的m_a = " << s1.Father::m_a << endl;
cout << "*********************" << endl;
}
2.同名成员函数
//同名成员函数
void test02()
{
Son s2;
s2.func();
s2.Father::func();
cout << "*********************" << endl;
//s2.func(100); //无法访问到父类中重载后的成员函数func(int a)
s2.Father::func(100);
cout << "*********************" << endl;
五、继承中同名静态成员处理
静态成员属性在类外进行了初始化
class Father
{
public:
static int m_a;
public:
static void func()
{
cout << "Father的静态成员函数func调用" << endl;
}
};
int Father::m_a = 100;
class Son : public Father
{
public:
static int m_a;
public:
static void func()
{
cout << "Son的静态成员函数func调用" << endl;
}
};
int Son::m_a = 200;
1.同名静态成员属性
//同名静态成员属性
void test01()
{
//1.通过对象访问
cout << "通过对象访问: " << endl;
Son s1;
cout << "Son的m_a = " << s1.m_a << endl;
cout << "Father的m_a = " << s1.Father::m_a << endl;
//2.通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son的m_a = " << Son::m_a << endl;
cout << "Father的m_a = " << Father::m_a << endl;
//第一个::是“通过类名方式访问”。第二个::是“访问父类作用域”
cout << "Father的m_a = " << Son::Father::m_a << endl;
}
特别注意最后一行的Son::Father::m_a
,这里的第一个::是通过类名方式访问。第二个::是访问父类作用域。
2.同名静态成员函数
//同名静态成员函数
void test02()
{
//1.通过对象访问
cout << "通过对象访问: " << endl;
Son s2;
s2.func();
s2.Father::func();
//2.通过类名访问
cout << "通过类名访问: " << endl;
Son::func();
Son::Father::func();
}
六、多继承语法
cpp允许一个类继承多个类
语法: class 子类 : 继承方式 父类1, 继承方式 父类2 …
!!!多继承可能会引发父类中有同名成员出现,需要加作用域进行区分
!!!在实际开发中不建议使用多继承
举例:
下面创建3个父类Father 1 2 3
class Father1
{
public:
int m_a;
public:
Father1()
{
m_a = 100;
cout << "Father1调用" << endl;
}
};
class Father2
{
public:
int m_a;
public:
Father2()
{
m_a = 200;
cout << "Father2调用" << endl;
}
};
class Father3
{
public:
int m_a;
public:
Father3()
{
m_a = 300;
cout << "Father3调用" << endl;
}
};
class Son : public Father1, public Father2, public Father3
{
public:
int m_c;
int m_d;
public:
Son()
{
m_c = 10;
m_d = 10;
}
};
void test01()
{
Son s;
cout << sizeof(s) << endl;
}
由编译结果可见,Son
类继承了三个类Father1
,Father2
,Father3
。三个类的m_a
加上Son
自身的m_c
和m_d
,一共就是5个int型的数据,因此共占了20字节的内存空间。
七、菱形继承问题
两个派生类继承同一个基类,又有某个类同时继承了两个派生类。又称为钻石继承。
问题:菱形继承导致基类的数据有两份,造成资源浪费。
解决方法:利用虚继承,在继承前加上关键字virtual。
class Animal
{
public:
int m_age;
};
class Sheep : virtual public Animal{};
class Tuo : virtual public Animal{};
class SheepTuo : public Sheep, public Tuo{};
void test01()
{
SheepTuo st;
st.Sheep::m_age = 18;
st.Tuo::m_age = 28;
//因此下面不管是通过哪个方式访问,都只有一个数据
cout << "st.Sheep::m_age = " << st.Sheep::m_age << endl;
cout << "st.Tuo::m_age = " << st.Tuo::m_age << endl;
cout << st.m_age << endl;
}
由此可知继承的不是两份数据,而是两份指针,两个指针会通过偏移量找到唯一的数据。