#include <iostream>
using namespace std;

class Base {

    private:
        int x;
        int y;

    public:
        Base(int x, int y) {
            this->x = x;
            this->y = y;
        }

        int getX() {
            return x;
        }

        int getY() {
            return y;
        }

};

class Sub : public Base {

    private:
        int z;

    public:
        Sub(int x, int y, int z) : Base(x, y) {
            // write your code here
            this->z = z;   
        }

        int getZ() {
            return z;
        }

        int calculate() {
            return Base::getX() * Base::getY() * this->getZ();
        }

};

int main() {

    int x, y, z;
    cin >> x;
    cin >> y;
    cin >> z;
    Sub sub(x, y, z);
    cout << sub.calculate() << endl;
    
    return 0;
}

"::"是作用域限定符,作用是指定函数或者变量所属的类

Base::getX() 表示:明确调用「Base 类中定义的 getX () 成员函数」,而不是子类 Sub 自己的

核心作用是避免命名冲突!

---------------------------------

一、先讲核心1:子类如何调用父类构造函数(为什么?怎么调?)

1. 为什么子类必须调用父类构造?

C++ 中子类继承了父类的成员(比如 Base 的 x、y),但子类不能直接初始化父类的私有成员(x、y 是 Base 的 private)。因此:

  • 创建子类对象时,必须先初始化“父类部分的成员”,再初始化子类自己的成员;
  • 父类 Base 只有带参构造函数Base(int x, int y)),没有无参的默认构造函数——如果子类不主动调用父类构造,编译器不知道怎么初始化 x、y,会直接报错。

简单说:子类构造的“第一步”必须是初始化父类,这是继承的语法规则。

2. 子类调用父类构造的语法(核心是「初始化列表」)

你的代码中:

Sub(int x, int y, int z) : Base(x, y) { // 初始化列表中调用父类构造
    this->z = z;   
}

  • : Base(x, y):这是子类构造函数的「初始化列表」,作用是显式调用父类 Base 的带参构造函数,把参数 x、y 传给父类,让父类初始化自己的私有成员 x、y
  • 执行顺序:创建 Sub 对象时,先执行 Base(x, y) 初始化父类的 x、y → 再执行子类构造函数体 this->z = z 初始化子类自己的 z。

3. 具体执行流程(结合 main 函数)

假设 main 中输入 x=2, y=3, z=4

  1. 执行 Sub sub(2,3,4),触发子类 Sub 的构造函数;
  2. 先执行初始化列表 Base(2,3):调用父类构造,把父类的 x=2y=3
  3. 再执行子类构造函数体 this->z = 4:初始化子类的 z=4
  4. 至此,sub 对象的完整数据:父类部分 x=2、y=3,子类部分 z=4

补充:如果父类有默认构造,子类可以不显式调用

如果 Base 有无参构造:

Base() { x=0; y=0; } // 父类默认构造

那么子类构造可以简化为:

Sub(int x, int y, int z) { // 无需初始化列表调用Base,编译器自动调用默认构造
    this->z = z;
}

但你的代码中父类没有默认构造,所以必须显式调用。

二、再讲核心2:Base::getX() 是什么意思?为什么要这样写?

1. :: 是「作用域限定符」,核心作用是“指定函数/变量所属的类”

Base::getX() 表示:明确调用「Base 类中定义的 getX() 成员函数」,而不是子类 Sub 自己的(如果有的话)。

2. 为什么要这样写?(3个核心原因)

原因1:避免“命名冲突”(最关键)

如果子类 Sub 也定义了同名的 getX() 函数(比如):

class Sub : public Base {
public:
    // 子类重定义了getX()
    int getX() { 
        return 0; 
    }
    // ...其他代码
    int calculate() {
        // 此时:getX() → 调用子类自己的getX()(返回0);Base::getX() → 调用父类的getX()(返回父类x)
        return getX() * Base::getY() * this->getZ(); 
    }
};

如果不加 Base::,编译器会优先调用子类自己的 getX(),导致逻辑错误;加了 Base:: 才能精准调用父类的 getX()

原因2:提升代码“可读性”

即使子类没有同名函数,写 Base::getX() 也能让阅读代码的人一眼看出:“这个 getX() 是从父类 Base 继承来的”,尤其在继承体系复杂时(比如多层继承),能避免歧义。

原因3:规范写法(养成好习惯)

在继承场景中,显式用 父类名:: 调用父类成员,是工业级代码的常见规范——避免后续维护时(比如给子类加同名函数),代码逻辑突然出错。

3. 补充:不加 Base:: 行不行?(你的代码中可以,但不推荐)

你的代码中,Sub 没有重定义 getX(),所以:

int calculate() {
    // 不加Base::,编译器会自动找父类的getX(),结果一样
    return getX() * getY() * getZ(); 
}

虽然能运行,但一旦后续给 Sub 加了 getX(),代码逻辑就会变,因此显式写 Base:: 更健壮

三、完整执行流程(结合 calculate() 函数)

还是以输入 x=2, y=3, z=4 为例:

  1. 调用 sub.calculate()
  2. Base::getX() → 调用父类的 getX(),返回父类的 x=2;
  3. Base::getY() → 调用父类的 getY(),返回父类的 y=3;
  4. this->getZ() → 调用子类的 getZ(),返回子类的 z=4;
  5. 计算 2 * 3 * 4 = 24,返回并输出。

四、核心总结

Sub(...) : Base(x,y) {}

子类构造的初始化列表中调用父类构造,初始化父类继承的成员(x、y)

Base::getX()

用作用域限定符

::

指定调用父类的 getX(),避免命名冲突、提升可读性

简单记:

  • 子类调父类构造:必须在初始化列表里(父类无默认构造时),先构父、后构子;
  • 父类名::成员:精准指定用父类的成员,不怕子类重名,代码更稳。