C++继承

311 阅读5分钟

一个类从另一个类获取成员变量和成员函数的过程

基本语法

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,直接访问就不会再有歧义了。