C++:继承

一、几种继承方式

二、举例与格式

cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
	A(int _a)
		:a(_a)
	{

	}

	int geta()
	{
		return a;
	}

private:
	int a;
};


class B :public A//继承格式
{
public:
	B(int _b,int _a)//派生类构造函数先写基类的构造参数,再写自己的参数
		:A(_a)
		,b(_b)
	{

	}

	int getb()
	{
		return b;
	}

private:
	int b;
};

三、注意事项

(1)三种继承方式

1.基类的private成员,在之后无论什么派生方式都是不可反问的内容。

2.对于 基类+一层派生 这种结构,private与protected这两种派生方式没有区别,都是派生类中可以访问基类中的非private对象,派生类对象不能访问基类的任何成员;而public继承则在派生类中和派生类对象都可以访问基类的非private成员。

3.对于 基类+一层派生+多层派生 protected与private才用于调整继承的关系,以private继承的二层无法访问基类,而以protected继承的则可以。

4.一般派生类对象的成员变量是从新开辟空间的,和基类成员变量使用不同的空间,不是拷贝的关系

(2)派生类的同名隐藏规则

cpp 复制代码
#include<iostream>
using namespace std;

class A
{
public:
	A(int _a)
		:a(_a)
	{

	}

	int geta()//与派生类同名
	{
		return a;
	}

private:
	int a;
};


class B :public A
{
public:
	B(int _a,int _b)
		:A(_a)
		,b(_b)
	{

	}

	int geta()//与基类同名
	{
		return b;
	}

private:
	int b;
};

int main()
{
	B b(1, 2);//A中为1,B中为2

	cout << b.geta() << endl;//同名隐藏,优先调用B的geta函数
	cout << b.A::geta() << endl;//强制调用A的geta函数

	return 0;
}

(3)赋值兼容规则

1.第一条

cpp 复制代码
class A
{
public:
	A(int _a)
		:a(_a)
	{

	}

	int geta()
	{
		return a;
	}

private:
	int a;
};


class B :public A
{
public:
	B(int _a,int _b)
		:A(_a)
		,b(_b)
	{

	}

	int getb()
	{
		return b;
	}

private:
	int b;
};

int main()
{
	A a(3);

	B b(1, 2);

	a = b;//派生类对象可以赋值给基类对象
	cout << a.geta() << endl;//改变了基类的成员变量的值(3变为1)

	return 0;
}

2.第二条

cpp 复制代码
class A
{
public:
	A(int _a)
		:a(_a)
	{

	}

	void display()
	{
		cout << 'A' << endl;
	}

private:
	int a;
};


class B :public A
{
public:
	B(int _a,int _b)
		:A(_a)
		,b(_b)
	{

	}

	void display()
	{
		cout << 'B' << endl;
	}

private:
	int b;
};

class C :public B
{
public:
	C(int _a, int _b,int _c)
		:B(_a,_b)
		, c(_c)
	{

	}

	void display()
	{
		cout << 'C' << endl;
	}

private:
	int c;
};

void fun(A& p)
{
	p.display();
}

int main()
{
	A a(1);
	B b(2, 3);
	C c(4, 5, 6);

	fun(a);//调用A的display
	fun(b);//调用A的display
	fun(c);//调用A的display

	return 0;
}

3.第三条

cpp 复制代码
class A
{
public:
	A(int _a)
		:a(_a)
	{

	}

	void display()
	{
		cout << 'A' << endl;
	}

private:
	int a;
};


class B :public A
{
public:
	B(int _a,int _b)
		:A(_a)
		,b(_b)
	{

	}

	void display()
	{
		cout << 'B' << endl;
	}

private:
	int b;
};

class C :public B
{
public:
	C(int _a, int _b,int _c)
		:B(_a,_b)
		, c(_c)
	{

	}

	void display()
	{
		cout << 'C' << endl;
	}

private:
	int c;
};

void fun(A* p)
{
	p->display();
}

int main()
{
	A a(1);
	B b(2, 3);
	C c(4, 5, 6);

	fun(&a);//调用A的display
	fun(&b);//调用A的display
	fun(&c);//调用A的display

	return 0;
}

(4)派生类构造函数

基类的构造函数不会被继承,派生类要自己写构造函数,定义构造函数时,需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,是自动调用基类构造函数完成的。 派生类的构造函数需要给基类的构造函数传递参数。

cpp 复制代码
class A
{
public:
	A(int _a)
		:a(_a)
	{

	}

private:
	int a;
};


class B :public A
{
public:
	B(int _a,int _b)//派生类构造函数先传基类的参数,再传自己的参数
		:A(_a)
		,b(_b)
	{

	}

private:
	int b;
};

class C :public B
{
public:
	C(int _a, int _b,int _c)//派生类构造函数先传基类的参数,再传自己的参数
		:B(_a,_b)
		, c(_c)
	{

	}

private:
	int c;
};

关于构造函数的执行顺序:

  1. 调用基类构造函数 顺序按照它们被继承时声明的顺序(从左向右)。
  2. 对象成员的初始化顺序按照它们在类中声明的顺序,由初始化列表提供参数。
  3. 执行派生类的构造函数体中的内容。
cpp 复制代码
#include <iostream>
using namespace std;

class Base1 
{ // 基类Base1,构造函数有参数
public:
    Base1(int i) 
    {
        cout << "Constructing Base1 " << i << endl;
    }
};

class Base2 
{ // 基类Base2,构造函数有参数
public:
    Base2(int j) 
    {
        cout << "Constructing Base2 " << j << endl;
    }
};

class Base3 { // 基类Base3,构造函数无参数
public:
    Base3() {
        cout << "Constructing Base3 *" << endl;
    }
};

class Derived : public Base2, public Base1, public Base3 
{
public:
    Derived(int a, int b, int c, int d) : Base1(a), member2(d), member1(c), Base2(b) 
    {
        // 注意基类名的个数与顺序,注意成员对象名的个数与顺序,此处的次序与构造函数的执行次序无关
    }

private:
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

int main() 
{
    Derived obj(1, 2, 3, 4);
    
    return 0;
}

(5)派生类的拷贝构造函数

cpp 复制代码
#include <iostream>
using namespace std;

class Base1 
{
public:
    Base1(int i) 
    {
        cout << "Constructing Base1 " << i << endl;
        value1 = i;
    }

    // 基类的拷贝构造函数
    Base1(const Base1& other) 
    {
        cout << "Copy constructing Base1 " << other.value1 << endl;
        value1 = other.value1;
    }

protected:
    int value1;
};

class Base2 
{
public:
    Base2(int j) 
    {
        cout << "Constructing Base2 " << j << endl;
        value2 = j;
    }

    // 基类的拷贝构造函数
    Base2(const Base2& other) 
    {
        cout << "Copy constructing Base2 " << other.value2 << endl;
        value2 = other.value2;
    }

protected:
    int value2;
};

class Base3 
{
public:
    Base3() 
    {
        cout << "Constructing Base3 *" << endl;
        value3 = 0;
    }

    // 基类的拷贝构造函数
    Base3(const Base3& other) 
    {
        cout << "Copy constructing Base3 *" << endl;
        value3 = other.value3;
    }

protected:
    int value3;
};

class Derived : public Base2, public Base1, public Base3 //注意继承顺序是2,1,3
{
public:
    Derived(int a, int b, int c, int d) 
        : Base1(a)
        , member2(d)
        , member1(c)
        , Base2(b) 
    {
        cout << "Constructing Derived" << endl;
        value4 = d;
    }

    // 派生类的拷贝构造函数
    Derived(const Derived& other)
        : Base2(other)      // 显式调用基类的拷贝构造函数
        , Base1(other)      // 按照继承顺序调用(2,1,3)
        , Base3(other)
        , member1(other.member1)  // 复制成员对象
        , member2(other.member2)
        , member3(other.member3) 
    {
        cout << "Copy constructing Derived" << endl;
        value4 = other.value4;  // 复制派生类自身的成员
    }

private:
    Base1 member1;
    Base2 member2;
    Base3 member3;
    int value4;
};

int main() {
    Derived obj1(1, 2, 3, 4);
    cout << "\nCreating obj2 as a copy of obj1:\n";
    Derived obj2(obj1);  // 调用拷贝构造函数
    return 0;
}

(6)派生类的析构函数

派生类的析构函数一般用基类的就行了,如果自己新开了空间就自己写。此外,析构函数的调用次序与构造函数相反。

(7)二义性问题

此外还会产生内存冗余问题

cpp 复制代码
#include <iostream>
using namespace std;

class Base0 
{ // 定义基类Base0
public:
    int var0;

    void fun0() 
    { 
        cout << "Member of Base0" << endl; 
    }
};

class Base1 : public Base0 
{ // 定义派生类Base1
public:
    // 新增外部接口
    int var1;
};

class Base2 : public Base0 
{ // 定义派生类Base2
public:
    // 新增外部接口
    int var2;
};

class Derived : public Base1, public Base2 
{
public:
    int var;
    void fun() 
    { 
        cout << "Member of Derived" << endl;
    }
};

int main() 
{ // 程序主函数
    Derived d;
    d.Base1::var0 = 2;
    d.Base1::fun0();
    d.Base2::var0 = 3;
    d.Base2::fun0();
    return 0;
}


当两个派生类一个共同的基类派生而来时,在这两个派生类中,从上一级基类继承来的成员拥有相同的名称,再进行一次派生时,在derived派生类的对象中,这些同名数据成员在内存中同时有多个拷贝,同一个函数名会有多个映射,产生内存的冗余,因此我们引入了虚基类。

(8)虚基类

将其共同基类设置为虚基类。这时,从不同路径继承来的同名数据成 员在内存中就只有一个拷贝,同一个函数名也只有一个映射,也可解决同名成员的唯一标识问题。

虚基类的成员b,与其派生类共同维护的是同一块空间。

注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。如图所示的那样,在派生类B和C中将类A声明为虚基类,而在派生类D中没有将类A声明为虚基类,则在派生类E中,虽然从类B和C路径派生的部分只保留一份基类成员,但从类D路径派生的部分还保留一份基类成员。


建立对象时所指定的类称为最远派生类。
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数。
在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。

cpp 复制代码
#include <iostream>
using namespace std;

class Base0 
{
public:
    Base0(int var) : var0(var) 
    {
        cout << "Base0" << endl;
    }
    void fun0() 
    { 
        cout << "Member of Base0" << endl; 
    }

    int var0;

};

class Base1 : virtual public Base0 
{
public:
    Base1(int var) : Base0(var) 
    {
    
    }

    int var1;
};

class Base2 : virtual public Base0 
{
public:
    Base2(int var) : Base0(var) 
    {
    
    }

    int var2;
};

class Derived : public Base1, public Base2 
{
public:
    Derived(int var) 
        : Base0(var)
        , Base1(var)
        , Base2(var) 
    {
    
    }
    void fun() 
    { 
        cout << "Member of Derived" << endl;
    }

    int var;
};

int main() 
{
    Derived d(1);
    d.var0 = 2;
    d.fun0();
    return 0;
}
相关推荐
霖0040 分钟前
C++学习笔记三
运维·开发语言·c++·笔记·学习·fpga开发
mit6.8241 小时前
[shad-PS4] Vulkan渲染器 | 着色器_重新编译器 | SPIR-V 格式
c++·游戏引擎·ps4
地平线开发者1 小时前
征程 6M 部署 Omnidet 感知模型
算法·自动驾驶
上单带刀不带妹1 小时前
JavaScript中的Request详解:掌握Fetch API与XMLHttpRequest
开发语言·前端·javascript·ecmascript
小白学大数据1 小时前
Python爬取闲鱼价格趋势并可视化分析
开发语言·python
秋说1 小时前
【PTA数据结构 | C语言版】线性表循环右移
c语言·数据结构·算法
ningmengjing_2 小时前
在 PyCharm 中安装并配置 Node.js 的指南
开发语言·javascript·ecmascript
晓13132 小时前
JavaScript基础篇——第五章 对象(最终篇)
开发语言·前端·javascript
tan77º2 小时前
【Linux网络编程】Socket - TCP
linux·网络·c++·tcp/ip
浩瀚星辰20242 小时前
图论基础算法:DFS、BFS、并查集与拓扑排序的Java实现
java·算法·深度优先·图论