首页 > 编程语言 >C++之子类继承与父类构造

C++之子类继承与父类构造

时间:2024-10-19 16:53:46浏览次数:8  
标签:无参 std 子类 price C++ 父类 构造函数

C++之子类继承与父类构造


文章目录


1.问题引出

笔者最近在阅读godot源码,之前学过一些半吊子的C++,对一些东西知其然不知其所以然,希望在阅读项目源码的过程中思考一些问题。言归正传,在godot的初始启动流程中,有如下代码:

//启动流程 Main.cpp文件下
Main::setup(const char *execpath, int argc, char *argv[], bool p_second_phase)
{
	OS::get_singleton()->initialize();
}

//OS::get_singleton()实现 os.cpp下
OS *OS::singleton = nullptr;
uint64_t OS::target_ticks = 0;

OS *OS::get_singleton() {
	return singleton;
}

上述实现了一个单例模式,返回的是一个在os.cpp文件下的全局变量OS *OS::singleton。该全局变量初始化的时候是一个nullptr,虽然空指针可以调用成员函数,但是这肯定不符合单例模式的设计理念。笔者最终找到了OS *OS::singleton实际赋值的地方:

OS::OS() {
	singleton = this;

	Vector<Logger *> loggers;
	loggers.push_back(memnew(StdLogger));
	_set_logger(memnew(CompositeLogger(loggers)));
}

这里是OS类的构造函数,当实例化一个OS类的时候,就会将实例化对象的指针赋值到singleton指针上。笔者又在启动流程下寻找OS的实例化位置,最终找到代码如下:

OS_LinuxBSD os;

在程序最开始的main函数位置,实例化了os,实例化的类是OS_LinuxBSD,并非OS。阅读代码发现存在以下继承关系,OS_LinuxBSD继承于OS_UnixOS_Unix继承于OS
这里引出如下几个思考:

  • 当子类继承父类,实例化子类的时候,父类构造的作用
  • 链式继承的时候,父类构造的作用
  • C++允许多继承,多个父类构造的作用

2.原则

此处部分参考了菜鸟教程,构造原则如下所示:

  • 如果一个子类没有定义自己的构造函数,当实例化对象的时候,调用父类的无参构造方法
  • 如果子类定义了构造函数,不论该构造函数有参还是无参,在实例化子类对象的时候,首先调用父类的无参构造函数,然后执行自己的构造方法
  • 实例化子类对象的时候,如果子类的构造函数没有显式调用父类的构造函数,则会调用父类的默认无参构造函数
  • 在实例化子类对象的时候,如果子类的构造函数没有显式调用父类的构造函数,且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
  • 在实例化子类对象的时候,如果子类的构造函数没有显式调用父类的构造函数且父类只定义了自己的有参构造函数,则会报错;在该情况下,子类必须显示调用有参构造方法。
  • 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式。
    上诉规则其实可以用一个思维导图总结如下:
    在这里插入图片描述

总之:当且仅当父类只有有参构造的时候,需要子类显式调用;如果父类存在无参构造,即使是默认无参,子类对象实例化的时候都会调用无参构造函数。

3.解析

3.1单一继承

3.1.1 父类无参构造函数
3.1.1.1子类无定义构造函数
#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }
};

class apple : fruit
{
public:
};



int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    apple a1;
    std::cout << "-----------------------------------------------" << std::endl;

    return 0;
}

上述代码里实现了父类fruit和子类apple,父类提供了无参构造,子类未实现任何构造函数,此时会默认存在一个无参构造函数,当我们实例化子类对象时,执行结果如下:
在这里插入图片描述

子类对象实例化时会默认调用父类的无参构造函数。

3.1.1.2子类定义构造函数
#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }
};

class apple : fruit
{
public:
	//子类无参构造
    apple()
    {
        std::cout << "class Apple parameterless constructor" << std::endl;
    }
    //子类有参构造
    apple(int price)
    {
        this->price = price;
        std::cout << "the price is: " << this->price << std::endl;
    }
    
};


int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    apple a1;
    std::cout << "-----------------------------------------------" << std::endl;
    apple a2(10);
    std::cout << "-----------------------------------------------" << std::endl;
    return 0;
}

父类是fruit,子类是apple,父类实现了无参构造,子类实现了无参构造和有参构造,同时没有显式调用父类构造函数。
执行结果如下:
在这里插入图片描述

从执行结果来看,不管是子类的无参构造函数还是有参构造函数,都会首先调用父类的无参构造函数。
如果我们没有定义父类无参构造呢,实际上也会存在一个默认无参构造,它不做任何事情。

3.1.2 父类存在无参构造函数和有参构造函数
#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }
    fruit(int price)
    {
        this->price = price;
        std::cout << "[fruit] the price is: " << this->price << std::endl;
    }
};

class apple : fruit
{
public:
	//子类无参构造
    apple()
    {
        std::cout << "class Apple parameterless constructor" << std::endl;
    }
    //子类有参构造
    apple(int price)
    {
        this->price = price;
        std::cout << "[apple] the price is: " << this->price << std::endl;
    }
    
};



int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    apple a1;
    std::cout << "-----------------------------------------------" << std::endl;
    apple a2(10);
    std::cout << "-----------------------------------------------" << std::endl;
    return 0;
}

修改后的代码如上所示,我们为fruit类实现了一个有参构造函数,依旧实例化两个子类对象,一个无参,一个有参,执行结果如下所示:
在这里插入图片描述

在子类没有显式调用的情况下,都会默认首先调用父类的无参构造,让后执行自己的对应的构造函数里的内容。

3.1.3 父类仅存在有参构造函数
#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    /*fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }*/
    fruit(int price)
    {
        this->price = price;
        std::cout << "[fruit] the price is: " << this->price << std::endl;
    }
};

class apple : fruit
{
public:
	//子类无参构造
    apple()
    {
        std::cout << "class Apple parameterless constructor" << std::endl;
    }
    //子类有参构造
    apple(int price)
    {
        this->price = price;
        std::cout << "[apple] the price is: " << this->price << std::endl;
    }
    
};



int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    apple a1;
    std::cout << "-----------------------------------------------" << std::endl;
    apple a2(10);
    std::cout << "-----------------------------------------------" << std::endl;
    return 0;
}

注释掉父类的无参构造对象,子类无显式调用,编译,结果如下:
在这里插入图片描述

编译器会报错,指明没有匹配的函数调用。
我们将子类构造修改为显式调用,因为无参构造函数没有参数提供,我们这里给一个默认参数:

#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    /*fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }*/
    fruit(int price)
    {
        this->price = price;
        std::cout << "[fruit] the price is: " << this->price << std::endl;
    }
};

class apple : fruit
{
public:
	//子类无参构造
    apple():fruit(15)
    {
        std::cout << "class Apple parameterless constructor" << std::endl;
    }
    //子类有参构造
    apple(int price):fruit(price)
    {
        this->price = price;
        std::cout << "[apple] the price is: " << this->price << std::endl;
    }
    
};



int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    apple a1;
    std::cout << "-----------------------------------------------" << std::endl;
    apple a2(10);
    std::cout << "-----------------------------------------------" << std::endl;
    return 0;
}

对于子类来说,想要显式调用父类构造,只要在对应的构造函数后加上冒号然后使用父类构造函数即可。我们将子类的默认无参构造函数的调用参数设置为15,在实例化a2的时候,使用的还是子类有参构造函数里的参数,执行结果如下所示:
在这里插入图片描述

我们看到两个实例化对象都会调用对应的父类有参构造函数,同时使用子类无参构造的实例化对象的价格,也被设置为了子类无参构造函数调用父类有参构造函数时的参数。

3.2链式继承

我们基本理清楚了一件事情,如果父类存在无参构造函数,那么子类不显式调用时,会默认首先调用父类无参构造;如果父类不存在无参构造,则子类必须显式调用父类有参构造,否则编译器会编译不过。接下来我们将场景设置复杂一点,设置一个链式继承关系,就像我开头的案例一样,代码如下:

#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }
    fruit(int price)
    {
        this->price = price;
        std::cout << "[fruit] the price is: " << this->price << std::endl;
    }
};

class apple : public fruit
{
public:
	//子类无参构造
    apple()
    {
        std::cout << "class Apple parameterless constructor" << std::endl;
    }
    //子类有参构造
    apple(int price):fruit(price)
    {
        this->price = price;
        std::cout << "[apple] the price is: " << this->price << std::endl;
    }
    
};

class green_apple : public apple
{
public:
    green_apple()
    {
        std::cout << "class green_Apple parameterless constructor" << std::endl;
    }
    green_apple(int price):apple(price)
    {
        std::cout << "[green_Apple] the price is: " << this->price << std::endl;
    }
};



int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    green_apple a1;
    std::cout << "-----------------------------------------------" << std::endl;
    green_apple a2(10);
    std::cout << "-----------------------------------------------" << std::endl;
    return 0;
}

我们定义了第三个类型green_apple继承于apple,分别实现了有参和无参构造,最后在实例化的时候分别实现了无参和有参的实例化,执行结果如下:
在这里插入图片描述

显而易见,会最先调用最基类的构造函数,之后逐层调用。其实按照理解来说,应该可以说是不显式调用的时候,会自动塞一个父类无参构造函数给子类构造函数,如果父类没有无参构造,对于编译器来说,就不知道如何去放置有参构造函数了,所以需要我们显式调用,自己去指定调用哪一个父类构造函数。当子类调用构造函数时,会先调用父类的构造函数,当父类也继承于另一个类时,父类构造又会先调用另一个类的构造,逐层递归,最终表现为最先调用最基类的构造函数。

3.3多继承

基于前面的分析,我们其实已经可以推测出来多继承的逻辑了,其一定是按照顺序分别调用父类的构造函数,设计代码如下:

#include <iostream>
#include <string>

class fruit
{
public:
    int price;
    std::string name;

public:
    //父类无参构造
    fruit()
    {
        std::cout << "class Fruit parameterless constructor" << std::endl;
    }
    fruit(int price)
    {
        this->price = price;
        std::cout << "[fruit] the price is: " << this->price << std::endl;
    }
};

class season
{

public:
    //父类无参构造
    season()
    {
        std::cout << "class season parameterless constructor" << std::endl;
    }
};

class apple : public fruit, public season
{
public:
	//子类无参构造
    apple()
    {
        std::cout << "class Apple parameterless constructor" << std::endl;
    }
    //子类有参构造
    apple(int price):fruit(price)
    {
        this->price = price;
        std::cout << "[apple] the price is: " << this->price << std::endl;
    }
    
};



int main()
{
    std::cout << "-----------------------------------------------" << std::endl;
    apple a1;
    std::cout << "-----------------------------------------------" << std::endl;
    apple a2(10);
    std::cout << "-----------------------------------------------" << std::endl;
    return 0;
}

实现了另一个父类seasonapple同时继承于fruitseason,代码执行结果如下:
在这里插入图片描述

无参实例化对象分别调用了fruitseason的无参构造函数。有参实例化对象先显式调用了fruit的有参构造函数,之后调用了season的无参构造函数,最后执行自己的有参构造函数部分。

4.结论

  • 子类会在构造函数中默认调用父类的无参构造函数
  • 父类不存在无参构造函数时,子类需要指定调用的父类有参构造函数
  • 链式继承时会先调用父类的构造函数,如果父类还存在父类,则父类构造会先调用父类的父类的构造函数,依此递归,最终的结果是按照辈分逐级执行构造
  • 多继承时按照继承顺序逐步调用父类构造函数

5.回到开头

由此回答我开头引入的问题,OS_LinuxBSD创建时调用了父类的父类OS的无参构造函数,将单例的指针设置为了自己。

标签:无参,std,子类,price,C++,父类,构造函数
From: https://blog.csdn.net/qq_44914161/article/details/143080496

相关文章

  • 【C++】C++中的继承,看这一篇就够了
    【C++】C++中的继承,看这一篇就够了一.继承的概念及定义继承的概念继承定义继承关系和访问限定符继承基类成员访问方式的变化二.基类和派生类对象赋值转换三.继承中的作用域四.派生类的默认成员函数五.继承与友元六.继承与静态成员七.复杂的菱形继承及菱形虚拟继承......
  • 移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——13.map&&set(无习题)
    C++中的set和map容器详细总结1.概述C++标准模板库(STL)提供了多种关联容器,用于管理键值对和集合的数据。其中,set和map是最常用的两种关联容器。set用于存储唯一的元素集合,而map则用于存储键值对,其中每个键都是唯一的。它们都使用红黑树(自平衡二叉搜索树)作为底......
  • YU_C++算法学习笔记 · 枚举
    1.1枚举类问题·枚举是什么?枚举也叫穷举,是计算机解决问题最基本的策略。其方法是一一列举所有的可能性,根据题意要求进行合理的判断或计算,最终得到答案,本质上就是一种搜索算法基础的枚举就是人们常说的“暴力”求解。对于不同的问题,不可过分依赖“暴力”求解,应该根据具体的......
  • 《 C++ 修炼全景指南:十六 》玩转 C++ 特殊类:C++ 六种必备特殊类设计的全面解析
    摘要这篇博客深入探讨了六种C++特殊类的设计及其技术细节。首先,介绍了如何设计只能在堆上或栈上创建对象的类,通过控制构造函数的访问权限来限定对象的内存分配区域。接着,探讨了如何设计一个不能被拷贝的类,避免资源重复释放的问题。随后,介绍了如何防止类被继承以及单例模......
  • 位置式与增量式PID控制器理论与C++实现
    1理论推导1.1PID式中: ——控制器的输出;——控制器的输入(常常是设定值与被控量之差);Kp——控制器的比例放大系数;Ti——控制器的积分时间;Td——控制器的微分时间;1.2位置式PID设为第k次采样时刻控制器的输出值,可得离散的PID算式,又称位置式PID算式:e(k):用户设定的值(目标......
  • C++内存模型实践探索
    前言C++对象模型是个常见、且复杂的话题,本文基于ItaniumC++ABI通过程序实践介绍了几种简单C++继承场景下对象模型,尤其是存在虚函数的场景,并通过图的方式直观表达内存布局。本文展示的程序构建环境为Ubuntu,glibc2.24,gcc6.3.0。由于clang和gcc编译器都是基于ItaniumC++ABI......
  • C++ WM\_COPYDATA 实现进程通信
    基于MFC用于存储数据的自定义结构体:structMSG_STRUCT{ wchar_tmsg[256];};发送端,发送按钮响应事件的代码voidCSendWM_COPYDATADlg::OnBnClickedSendmessage(){ MSG_STRUCTsmsg; GetDlgItemText(IDC_MESSAGE,smsg.msg,256); HWNDhTargetWnd=NULL; hTargetWnd=::Fi......
  • C++使用共享内存实现进程间通信
    C++使用共享内存实现进程间通信文件映射是一种实现进程间单向或双向通信的机制。它允许两个或多个本地进程间相互通信。为了共享文件或内存,所有的进程必须使用相同的文件映射的名字或是句柄。为了实现共享文件,第一个进程先调用CreateFile方法。接下来调用CreateFileMappin......
  • 算法笔记 C/C++快速入门 | 全章节整理
    目录零.【C语言中的输入和输出函数】sscanf应用场景1:解析用户输入应用场景2:解析文件内容应用场景3:处理网络协议数据应用场景4:字符串解析和数据转换应用场景5:解析复杂的日志数据其他应用场景:scanf 一【编程语言相关】c和cpp二.【数据结构相关】结构体循环定......
  • C++之类和对象2
    文章目录1.类的默认成员函数有哪些2.构造函数3.析构函数4.拷贝构造函数5.赋值运算符重载函数5.1运算符重载5.2赋值运算符重载函数6.取地址运算符重载函数6.1const成员函数6.2普通取地址重载函数与const取地址运算符重载函数1.类的默认成员函数有哪些默认构造函数是......