一个类从另一个类获取成员变量和成员函数的过程
基本语法
class 子类 : 继承方式 父类
子类也被称为 派生类,父类也被称为 基类
class Student: public People
三种继承方式
公共继承 public(常用)
-
父类中的公共权限到子类中依旧是公共权限
-
父类中的保护权限到子类中依旧是保护权限
-
子类无法访问父类中的私有权限
保护继承 protected
-
父类中的公共权限和保护权限在子类中变成了保护权限
-
子类无法访问父类中的私有权限
私有继承 private(默认)
-
父类中的公共权限和保护权限到子类中变成了私有权限
-
子类无法访问父类中的私有权限
不同继承方式对不同属性的成员的影响结果:
protected 成员和 private 成员类似,都不能通过对象访问。但是当存在继承关系时,protected 和 private 就不一样了;父类中的 protected 成员可以在子类中使用,而父类中的 private 成员不能在子类中使用。
改变访问权限
使用 using 关键字可以改变父类成员在子类中的访问权限,例如将 public 改为 private、将 protected 改为 public。
注意:using 只能改变父类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为父类中 private 成员在子类中是不可见的,根本不能使用,所以父类中的 private 成员在子类中无论如何都不能访问。
#include<iostream>
using namespace std;
//父类Teacher
class Teacher {
public:
void show();
protected:
char m_name;
int m_age;
};
void Teacher::show() {
cout << m_name << "的年龄是" << m_age << endl;
}
//子类Student
class Student : public Teacher {
public:
void learning();
public:
using Teacher::m_name; //将protected改为public
using Teacher::show; //将public改为protected
float m_score;
private:
using Teacher::m_age; //将protected改为private
};
void Student::learning() {
cout << "我是" << m_name << ",今年" << m_age << "岁,这次考了" << m_score << "分!" << endl;
}
int main() {
Student stu;
stu.m_name = '小明';
stu.m_age = 16;//编译错误
stu.m_score = 99.5f;
stu.show();
stu.learning();
return 0;
}
继承时同名成员处理方式
遮蔽问题:如果子类中的成员(包括成员变量和成员函数)和父类中的成员重名,那么就会遮蔽从父类继承过来的成员,不管它们的参数是否一样。在子类中使用该成员(包括在定义子类时使用,也包括通过子类对象访问该成员)时,实际上使用的是子类新增的成员,而不是从父类继承来的;而且父类成员函数和子类成员函数不会构成重载。
访问静态和非静态同名成员规则:
-
访问子类同名成员,直接访问即可
-
访问父类同名成员时,需要加上作用域
# include<iostream>
using namespace std;
//继承同名成员处理方式
class Base {
public:
Base() {
m_A = 100;
}
int m_A;
void say() {
cout << "我是父类的say函数" << endl;
}
};
class Son :public Base {
public:
Son() {
m_A = 200;
}
int m_A;
void say() {
cout << "我是子类的say函数" << endl;
}
};
void test01() {
Son s;
cout << "子类的m_A变量= " << s.m_A << endl;
cout << "父类下面的m_A变量= " << s.Base::m_A << endl;
}
void test02() {
Son s;
s.say();
s.Base::say();
}
int main() {
cout << "同名的成员属性处理方式" << endl;
test01();
cout << "同名的成员函数处理方式" << endl;
test02();
system("pause");
return 0;
}
父类和子类的构造函数
类的构造函数不能被继承。因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。但是子类的构造对象可以调用父类的构造函数。
构造函数调用顺序:先调用父类,再调用子类,只能调用直接父类的构造函数,不能调用间接父类的
析构函数调用顺序:先调用子类,再调用父类
多继承
C++支持多继承(Multiple Inheritance),即一个子类可以有两个或多个父类
语法:
class 子类:继承方式 父类1,继承方式 父类2...
多继承可能导致父类中同名成员的出现,需要加作用域加以区分
c++实际开发中,不建议用多继承
菱形继承
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承或者钻石继承:
如上:类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B-->D 这条路径,还是来自 A-->C-->D 这条路径。
//间接基类A
class A {
protected:
int m_a;
};
//直接基类B
class B : public A {
protected:
int m_b;
};
//直接基类C
class C : public A {
protected:
int m_c;
};
//派生类D
class D : public B, public C {
public:
void seta(int a) { m_a = a; } //命名冲突,为了消除歧义,可以在 m_a 的前面指明它具体来自哪个类
void setb(int b) { m_b = b; } //正确
void setc(int c) { m_c = c; } //正确
void setd(int d) { m_d = d; } //正确
private:
int m_d;
};
int main() {
D d;
return 0;
}
虚继承
虚继承解决了多继承时的命名冲突和冗余数据问题,使得在派生类中只保留一份间接基类的成员:
在继承方式前面加上 virtual 关键字就是虚继承:
//间接基类A
class A {
protected:
int m_a;
};
//直接基类B
class B : virtual public A { //虚继承
protected:
int m_b;
};
//直接基类C
class C : virtual public A { //虚继承
protected:
int m_c;
};
//派生类D
class D : public B, public C {
public:
void seta(int a) { m_a = a; } //正确
void setb(int b) { m_b = b; } //正确
void setc(int c) { m_c = c; } //正确
void setd(int d) { m_d = d; } //正确
private:
int m_d;
};
int main() {
D d;
return 0;
}
这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。