继承 与 派生
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。
派生(Derive)
继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。
被继承的类称为父类或基类,继承的类称为子类或派生类。
class 派生类名:[继承方式] 基类名{
// 成员
};
例:
class Student : public People { };
继承方式
继承方式包括 public(公有的)
、private(私有的)
和 protected(受保护的)
,此项是可选的,如果不写,那么默认为 private
。
- public继承方式
- 基类中所有 public 成员在派生类中为 public 属性;
- 基类中所有 protected 成员在派生类中为 protected 属性;
- 基类中所有 private 成员在派生类中不能使用。
- protected继承方式
- 基类中的所有 public 成员在派生类中为 protected 属性;
- 基类中的所有 protected 成员在派生类中为 protected 属性;
- 基类中的所有 private 成员在派生类中不能使用。
- private继承方式
- 基类中的所有 public 成员在派生类中均为 private 属性;
- 基类中的所有 protected 成员在派生类中均为 private 属性;
- 基类中的所有 private 成员在派生类中不能使用。
继承方式中的 public
、protected
、private
是用来指明基类成员在派生类中的 最高访问权限 的
基类中的 protected 成员
可以在 派生类
中使用,而基类中的 private 成员
不能在 派生类
中使用
基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,只是在派生类中不可见,导致无法使用。private 成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。
#include <iostream>
#include <string>
using namespace std;
// 模板类
class People
{
private:
// 私有,无论是直接还是继承 都无法访问
string m_name;
public:
// 共有,私有变量接口,访问并设置私有变量
string get_name() const { return m_name; };
void set_name(string name) { m_name = name; };
protected:
// 保护,无法直接访问,可以通过 继承 被 子类 调用
string Identity;
string get_Identity() const { return Identity; };
void set_Identity(string Identity) { this->Identity = Identity; };
};
class Student : public People
{
// 不能访问 People 类的 private 变量,但可以使用 get函数 和 set函数
private:
// 私有变量
string school_num;
public:
// 共有,私有变量接口,访问并设置私有变量
string get_school_num() const { return school_num; };
void set_school_num(string school_num) { this->school_num = school_num; };
// 成员函数
void message()
{
this->set_Identity("xxx xxx xxx xxx xxx xxx"); // 访问 保护 的成员函数
cout
<< "姓名:" << this->get_name() << endl
<< "身份证:" << this->Identity << endl
<< "学号:" << this->school_num << endl;
}
};
int main()
{
Student Sir;
Sir.set_name("马保国");
Sir.set_school_num("2020111401");
// Sir 不能访问 protected 的成员函数
Sir.message();
return 0;
}
结果:
姓名:马保国
身份证:xxx xxx xxx xxx xxx xxx
学号:2020111401
在派生类中访问基类 private 成员的唯一方法就是借助基类的非 private 成员函数,如果基类没有非 private 成员函数,那么该成员在派生类中将无法访问。
改变访问权限
使用 using 关键字可以改变基类成员在派生类中的访问权限
修饰符:
using 基类 : : 成员名;
注意:using 只能改变基类中 public
和 protected
成员的访问权限,不能改变 private 成员的访问权限
#include <iostream>
#include <string>
using namespace std;
// 基类
class People
{
private:
// 私有,无论是直接还是继承 都无法访问
string m_name;
public:
// 共有,私有变量接口,访问并设置私有变量
string get_name() const { return m_name; };
void set_name(string name) { m_name = name; };
protected:
// 保护,无法直接访问,可以通过 继承 被 子类 调用
string Identity;
string get_Identity() const { return Identity; };
void set_Identity(string Identity) { this->Identity = Identity; };
};
// 子类
class Student : public People
{
// 修改权限,不能修改 基类定义的 private 成员
private:
// 私有变量
string school_num;
// 修改权限
using People::Identity;
public:
// 修改权限,基类中为protected,继承过来依旧是 protected,外部无法访问,修改为 public ,可直接通过 类的实例对象访问
using People::set_Identity;
using People::get_Identity;
// 共有,私有变量接口,访问并设置私有变量
string get_school_num() const { return school_num; };
void set_school_num(string school_num) { this->school_num = school_num; };
// 成员函数
void message()
{
cout
<< "姓名:" << this->get_name() << endl
<< "身份证:" << this->Identity << endl
<< "学号:" << this->school_num << endl;
}
};
int main()
{
Student Sir;
Sir.set_name("马保国");
Sir.set_Identity("xxx xxx 1951xxxx xxxx"); // 修改权限,访问受保护的函数
Sir.set_school_num("2020111401");
Sir.message();
return 0;
}
结果:
姓名:马保国
身份证:xxx xxx 1951xxxx xxxx
学号:2020111401
继承时的名字遮蔽问题
如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。
#include <iostream>
#include <string>
using namespace std;
// 基类
class People
{
public:
int sum(int a, int b)
{
return a + b;
}
};
// 派生类
class Student : public People
{
public:
string sum(string a, string b)
{
return a + b;
}
};
void Study()
{
Student Sir;
//Sir.sum(6,9); 直接 error,强制类型转换
cout << Sir.sum("浑圆形意太极门掌门人", "马保国");
}
如果成员被遮蔽,但仍要要访问, 则就要加上类名和域解析符 来访问
Sir.People::sum(6,9);
基类成员函数和派生类成员函数不构成重载
成员函数,不管函数的参数如何,只要名字一样就会造成遮蔽
基类和派生类的构造函数
类的构造函数不能被继承
在派生类的构造函数中调用基类的构造函数,对基类的 private变量 进行 初始化
实现方式:
- 在 派生类构造函数定义 时,对 派生类成员变量 初始化,以及 基类 构造函数 初始化
#include <iostream>
#include <string>
using namespace std;
// 基类
class People
{
protected:
string m_name;
public:
// 构造函数
People(string name) : m_name(name) { cout << this->m_name << endl; };
// 析构函数
~People() { };
};
// 子类
class Student : public People
{
private:
string m_work;
public:
// 构造函数
Student(string name, string work) : m_work(work), People(name) { cout << this->m_work << endl; };
// 析构函数
~Student() { };
};
class Pupil : public Student
{
private:
int m_age;
public:
// 构造函数
Pupil(string name, string work,int age) : m_age(age), Student(name,work) { cout << m_age << endl; };
// 析构函数
~Pupil() { };
};
int main()
{
Pupil Sir("马保国", "浑圆形意太极门掌门人",69);
return 0;
}
结果:
马保国
浑圆形意太极门掌门人
69
注意:基类构造函数的调用放在函数头部,不能放在函数体中。 因为基类构造函数不会被继承,不能当做普通的成员函数来调用。
基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数
C++ 当存在多级继承,A -> B -> C , 其中禁止在 C 中显式地调用 A 的构造函数。
注意:构造参数显式的定义,系统不会再生成默认的构造函数,就必须传参,若不传参,就需要手动定义一个空的构造函数
Student Sir; // 错误,创建对象,系统不会创建默认的构造函数
基类和派生类的析构函数
和构造函数类似,析构函数也不能被继承
派生类的析构函数中不用显式地调用基类的析构函数
析构函数的执行顺序:
- 创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。
- 而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。
#include <iostream>
#include <string>
using namespace std;
// 基类
class People
{
protected:
string m_name;
public:
// 构造函数
People(string name) : m_name(name) { };
// 析构函数
~People() { cout << this->m_name << endl; };
};
// 子类
class Student : public People
{
private:
string m_work;
public:
// 构造函数
Student(string name, string work) : m_work(work), People(name) { };
// 析构函数
~Student() { cout << this->m_work << endl; };
};
// 子孙类
class Pupil : public Student
{
private:
int m_age;
public:
// 构造函数
Pupil(string name, string work,int age) : m_age(age), Student(name,work) { };
// 析构函数
~Pupil() { cout << m_age << endl; };
};
int main()
{
Pupil Sir("马保国", "浑圆形意太极门掌门人",69);
return 0;
}
结果:
69
浑圆形意太极门掌门人
马保国
多继承(多重继承)
派生类都只有一个基类,称为单继承(Single Inheritance)。
一个派生类可以有两个或多个基类, 称为多继承(Multiple Inheritance)。
多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。
格式:
class 子类 : 修饰符 基类1, 修饰符 基类2 ·····{
// 成员
}
例如:
class Student : public School , protected Family, private Person{
// 成员
}
构造函数:
子类(形参列表): 基类1(实参列表), 基类2(实参列表) ···{
// 其他
}
例如:
Student (int num, string grade, int height , int weight) : School(grade) , Family(num), Person(height, weight){
// 其他
}
基类构造函数的调用顺序 和它们在派生类构造函数中出现的顺序无关,而是 和声明派生类时基类出现的顺序相同。
#include <iostream>
#include <string>
using namespace std;
// 基类 1
class School
{
private:
string m_grade;
public:
// 构造函数
School(string grade) : m_grade(grade) { cout << "班级:" << this->m_grade << endl; };
};
// 基类 2
class Family
{
private:
int m_num;
public:
// 构造函数
Family(int num) : m_num(num) { cout << "家庭人口数:" << this->m_num << endl; };
};
// 基类 3
class Person
{
private:
int m_height;
int m_weight;
public:
// 构造函数
Person(int height,int weight) : m_height(height),m_weight(weight) {
cout
<< "身高:" << this->m_height << endl
<< "体重:" << this->m_height << endl;
};
};
class Student : public School, protected Family, private Person {
private:
string m_name;
public:
// 构造函数
Student(string name,int num, string grade, int height, int weight) : m_name(name) ,Family(num), School(grade), Person(height, weight) {
// 其他
cout << "姓名:" <<this->m_name << endl;
}
};
int main()
{
Student Sir("小明",5,"软件工程",168,120);
return 0;
}
结果:
班级:软件工程
家庭人口数:5
身高:168
体重:168
姓名:小明
例子中,继承的基类顺序 school
family
person
初始化顺序 成员变量
、family
、school
、person
输出结果可以清晰的看到:初始化 基类优先,基类中先继承的优先
命名冲突
当两个或多个基类中有同名的成员时,如果直接访问该成员,就会产生命名冲突,编译器不知道使用哪个基类的成员。
因此需要在成员名字前面加上 类名 和 域解析符 ::
,以显式地指明到底使用哪个类的成员,消除二义性。
#include <iostream>
#include <string>
using namespace std;
// 基类 1
class Family
{
protected:
string m_name;
public:
Family(string name) : m_name(name) {};
};
// 基类 2
class Person
{
protected:
string m_name;
public:
Person(string name) : m_name(name) {};
};
// 派生类
class Student : protected Family, private Person {
public:
Student(string n1,string n2) : Family(n1), Person(n2)
{
// cout << m_name << endl; // error,存在二义性,不确定是哪一个
cout << Family::m_name << endl;
}
};
int main()
{
Student Sir("小强","小亮");
return 0;
}
结果:
小强
虚继承和虚基类详解
多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。
graph LR
A-->B
A-->C
B-->D
C-->D
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,此时类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D ,另一份来自 A-->C-->D 。
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还会产生命名冲突。
#include <iostream>
#include <string>
using namespace std;
// 基类
class Family
{
public:
void say() { cout << "A类" << endl; }
};
// 子类 1
class Person : public Family{};
// 子类 2
class Student : public Family{};
// 派生类,多继承
class Man : public Person, public Student{};
int main()
{
Man Sir;
// Sir.say(); error,错误。不明确,不知道来自哪
Sir.Student::say();
Sir.Person::say();
return 0;
}
虚继承
虚继承(Virtual Inheritance)使得在派生类中只保留一份间接基类的成员。
虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
virtual
关键字 代表 虚继承
#include <iostream>
#include <string>
using namespace std;
// 基类
class Family
{
public:
void say() { cout << "A类" << endl; }
};
// 子类 1
class Person : virtual public Family{};
// 子类 1
class Student : virtual public Family{};
// 派生类,多继承
class Man : public Person, public Student{};
int main()
{
Man Sir;
// 均可以访问
Sir.say();
Sir.Student::say();
Sir.Person::say();
return 0;
}
C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。
在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性
假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
- 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
- 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
#include <iostream>
#include <string>
using namespace std;
// 基类
class Family
{
public:
void say() { cout << "A类" << endl; }
};
// 子类 1
class Person : virtual public Family{
public:
void say() { cout << "Person类" << endl; }
};
// 子类 1
class Student : virtual public Family{};
// 派生类,多继承
class Man : public Person, public Student{};
int main()
{
Man Sir;
Sir.say();
Sir.Student::say();
Sir.Person::say();
return 0;
}
- 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。
#include <iostream>
#include <string>
using namespace std;
// 基类
class Family
{
public:
void say() { cout << "A类" << endl; }
};
// 子类 1
class Person : virtual public Family{
public:
void say() { cout << "Person类" << endl; }
};
// 子类 1
class Student : virtual public Family{
public:
void say() { cout << "Student类" << endl; }
};
// 派生类,多继承
class Man : public Person, public Student{};
int main()
{
Man Sir;
//Sir.say(); error,错误,存在二义性
Sir.Student::say();
Sir.Person::say();
return 0;
}
虚继承时的构造函数
最终派生类的构造函数必须要调用虚基类的构造函数
虚基类是间接基类(间接继承),而不是直接基类(继承)。
#include <iostream>
#include <string>
using namespace std;
// 基类
class Family
{
private:
int m_a;
public:
// 构造函数
Family(int a) : m_a(a){
cout << "m_a=" << m_a << ";";
};
};
// 子类 1
class Person : virtual public Family{
private:
int m_b;
public:
// 构造函数
Person(int a, int b): m_b(b) ,Family(a) {
cout << "m_b=" << m_b << ";";
};
};
// 子类 2
class Student : virtual public Family{
private:
int m_c;
public:
// 构造函数
Student(int a,int c): m_c(c), Family(a) {
cout << "m_c=" << m_c << ";";
};
};
// 派生类,多继承
class Man : public Person, public Student{
private:
int m_d;
public:
// 构造函数
Man(int a, int b ,int c ,int d): m_d(d),Person(a,b),Family(a),Student(a,c) {
cout << "m_d=" << m_d << ";";
};
};
int main()
{
Man Sir(1,2,3,4);
cout << endl;
Person p(1, 2);
cout << endl;
Student s(1, 2);
cout << endl;
return 0;
}
结果:
m_a=1;m_b=2;m_c=3;m_d=4;
m_a=1;m_b=2;
m_a=1;m_c=2;
编译器总是先调用 虚基类的构造函数 ,再按照 出现的顺序 调用 其他的构造函数
派生类赋值给基类
类 是一种 数据类型 ,可以发生 数据类型转换 ,这种转换只有在 基类 和 派生类 之间才有意义,并且只能将派生类赋值给基类 ,包括将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,称为向上转型(Upcasting)。
将基类赋值给派生类称为 向下转型(Downcasting)
将派生类对象赋值给基类对象
赋值的本质是将现有的数据写入已分配好的内存中,对象的内存只包含了成员变量,所以对象之间的赋值是成员变量的赋值,成员函数不存在赋值问题。
#include <iostream>
#include <string>
using namespace std;
// 基类
class People
{
public:
int m_num;
public:
// 构造函数
People(int num) : m_num(num) {};
// 成员变量
void show()
{
cout << "num = " << this->m_num << endl;
}
};
// 子类
class Student : public People {
public:
int m_code;
public:
// 构造函数
Student(int code, int num) :m_code(code), People(num) {};
// 成员变量
void show()
{
cout << "code = " << this->m_code << ";"
<< "num = " << this->m_num << endl;
}
};
int main()
{
People sir_1(10);
sir_1.show();
Student sir_2(996, 777);
sir_2.show();
// 将 子类 赋值 基类
sir_1 = sir_2;
sir_1.show();
sir_2.show();
return 0;
}
结果:
num = 10
code = 996;num = 777
num = 777
code = 996;num = 777
子类 是由 基类 派生而来,因此将 子类对象赋值给基类对象,便可以修改基类相关的参数就会发生改变
image只能用派生类对象给基类对象赋值,而不能用基类对象给派生类对象赋值 ,因为 基类不包含派生类的成员变量,无法对派生类的成员变量赋值。同理,同一基类的不同派生类对象之间也不能赋值。
将派生类指针赋值给基类指针
编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数。
int main()
{
People *sir_1 = new People(10);
sir_1->show();
Student* sir_2 = new Student(996, 777);
sir_2->show();
// 将 子类 赋值 基类
sir_1 = sir_2;
sir_1->show();
sir_2->show();
return 0;
}
结果:
num = 10
code = 996;num = 777
num = 777
code = 996;num = 777
将派生类引用赋值给基类引用
引用和指针的类似,是因为引用和指针本质上区别不大,引用仅仅是对指针进行了简单封装
int main()
{
Student sir_2(996, 777);
sir_2.show();
People &sir_1 = sir_2;
sir_1.show();
sir_2.show();
return 0;
}
结果:
code = 996;num = 777
num = 777
code = 996;num = 777
向上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员
网友评论