编译器的行为与Cpp中继承问题的探究

文章向导
问题引入
代码实现
Bug分析(error: no matching function for call to…) 与编译器的行为
修正后的代码

一、问题引入

     ~~~~          ~~~    实现如下图所示几个类,它们之间存在一定的组合或继承关系:
在这里插入图片描述
二、代码实现

#include <iostream>
#include <sstream>

using namespace std;

class Object
{
protected:
    string mName;
    string mInfo;
public:
    Object()
    {
        mName = "Object";
        mInfo = "";
    }
    string name()
    {
        return mName;
    }
    string info()
    {
        return mInfo;
    }
};

class Point : public Object
{
private:
    int mX;
    int mY;
public:
    Point(int x, int y)
    {
        mX = x;
        mY = y;
        mName = "Point";

        ostringstream oss;
        oss << "P(" << mX << ", " << mY << ")";
        mInfo = oss.str();
    }

    int x()
    {
        return mX;
    }
    int y()
    {
        return mY;
    }
};

class Line : public Object
{
private:
    Point mP1;
    Point mP2;
public:
    Line(Point p1, Point p2)
    {
        mP1 = p1;
        mP2 = p2;
        mName = "Line";

        ostringstream oss;
        oss << "Line from" << mP1.info() << " to " << mP2.info();
        mInfo = oss.str();
    }
};

int main()
{   
    Object o;
    Point p1(1,2);
    Point p2(3,4);
    Line l(p1, p2);

    cout << o.name() << endl;
    cout << o.info() << endl;
    cout << p1.name() << endl;
    cout << p1.info() << endl;
    cout << l.name() << endl;
    cout << l.info() << endl;

    return 0;
}

[程序结果]
主角登场
     ~~~~          ~~~    如上图所示,编译后并未按预期打印出三个对象的信息,反而编译器提示了一堆错误信息。别急!这也是咱们今天需要分析的主角:error: no matching function for call to …。通过它以理解编译器的行为,从而指导后续面向对象程序的设计。

三、Bug分析?

     ~~~~          ~~~    根据编译器的提示信息,笔者将涉及到的相关代码都整合在一起(位于下图),以便对照分析和理解。
在这里插入图片描述

     ~~~~          ~~~    编译器提示我们:此时没办法匹配到无参构造函数Point(),因为候选者只有含参构造Point(int, int)以及拷贝构造Point(cosnt Point&)。Wait? ①为啥要提供无参构造? ②自己也没有编写拷贝构造的相应代码吖,为啥提示已存在Point(cosnt Point&)?

     ~~~~          ~~~    要回答上面两个疑问,首先需理解编译器在类中的默认行为有哪些,这里有一些规则需要读者理解并记忆:
在这里插入图片描述

  • 当类中不存在构造函数时,编译器会默认提供一个无参构造函数(其函数体为空)
  • 当类中不存在拷贝构造函数时,编译器会默认提供一个浅拷贝构造函数
  • 编译器为每个类默认重载了赋值操作符(但实现的是浅拷贝)
  • 编译器会默认为类提供一个析构函数,但当类中自定义了构造函数,且构造函数中使用了系统资源(如内存申请,文件打开等),则需要自定义析构函数

     ~~~~          ~~~    有了上述几条规则,疑问②自然也就解决。可疑问①应如何理解并解决呢? 此时我们不妨按照之前的错误提示,将代码修改为(为Point类添加无参构造函数,其函数体为空):
在这里插入图片描述
[程序结果]
在这里插入图片描述

     ~~~~          ~~~    从打印结果来看,问题貌似已经得到了解决。可事实果真如此吗? 笔者继续将代码做一丁点改动(去掉刚才添加的无参构造函数,并对main函数中的p1做出调整):
在这里插入图片描述
[程序结果]
在这里插入图片描述

     ~~~~          ~~~    此时,类似的错误信息反而在main函数中也出现了。聚焦至Point p1;这条语句(该句确实没有给p1提供初始化时的参数,自然也就需要调用无参构造函数),大家已经快接近疑问①的真相了。

     ~~~~          ~~~    重新回到最初的这张图,并将视线聚焦于Line类中的成员对象mP1和mP2,需注意的是此时书写的Point mP1;Point mP2只是成员对象的声明,而非定义。只有在Line构造函数执行时,这两个成员对象才会被真正定义出来。可现在面临的现状是:我们是以无参的形式来声明mP1, mP2,而此时可选的构造函数仅有Point(int, int)和Point(const Point&),故自然就会提示“no matching function for call to…”这样的错误信息。
在这里插入图片描述
四、修正后的代码

推荐的方式:
在这里插入图片描述在这里插入图片描述
     ~~~~          ~~~    该方式不仅解决了Line类中成员对象mP1和mP2的声明问题,同时也解决了main函数中类似于Point p1;这样的书写形式所引发的构造问题。
     ~~~~          ~~~    另外,需要注意的是,在类中初始化和赋值两者的底层效率是不一样的(前者直接用相应值来初始化数据成员,后者则先初始化再赋值),故建议读者用初始化列表来初始化成员变量。同时,对于本问题也可以提炼出一个重要的结论当成员变量属于某种类类型且该类没有定义默认构造函数时,必须将该成员初始化(可利用初始化列表的方式)

参阅资料
狄泰软件学院C++深度解析教程
primer cpp 第5版
C++ 标准库 第2版

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值