调用继承的函数和覆盖函数
默认情况下,派生类继承基类中定义的所有行为。在本课程中,我们将更详细地研究如何选择成员函数,以及如何利用它来更改派生类中的行为。
调用基类函数
使用派生类对象调用成员函数时,编译器首先查看派生类中是否存在该成员。如果没有,它开始走向继承链并检查是否已在任何父类中定义该成员。它使用它找到的第一个。
因此,请看下面的示例:
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
void identify() { std::cout << "I am a Base\n"; }
};
class Derived: public Base
{
public:
Derived(int value)
: Base(value)
{
}
};
int main()
{
Base base(5);
base.identify();
Derived derived(7);
derived.identify();
return 0;
}
这打印
I am a Base
I am a Base
当调用derived.identify()时,编译器会查看是否已在Derived类中定义函数identify()。它没有。然后它开始查看继承的类(在本例中是Base)。Base已定义了一个identify()函数,因此它使用了该函数。换句话说,使用了Base :: identify(),因为Derived :: identify()不存在。
这意味着如果基类提供的函数足够,我们可以简单地使用基类行为。
重新定义函数
但是,如果我们在Derived类中定义了Derived :: identify(),则可以使用它。
这意味着我们可以通过在派生类中重新定义函数来使函数与派生类的工作方式不同!
在上面的例子中,如果derived.identify()打印“I am a Derived” ,它会更准确。让我们修改Derived类中的函数identify(),以便在我们使用Derived对象调用函数identify()时返回正确的响应。
要修改基类中定义的函数在派生类中的工作方式,只需重新定义派生类中的函数即可。
class Derived: public Base
{
public:
Derived(int value)
: Base(value)
{
}
int getValue() { return m_value; }
// 这是我们修改过的函数
void identify() { std::cout << "I am a Derived\n"; }
};
以下是与上面相同的示例,使用新的Derived :: Identify()函数:
int main()
{
Base base(5);
base.identify();
Derived derived(7);
derived.identify();
return 0;
}
I am a Base
I am a Derived
请注意,在派生类中重新定义函数时,派生函数不会继承基类中具有相同名称的函数的访问说明符。它使用派生类中定义的任何访问说明符。因此,在基类中定义为私有的函数可以在派生类中重新定义为public,反之亦然!
class Base
{
private:
void print()
{
std::cout << "Base";
}
};
class Derived : public Base
{
public:
void print()
{
std::cout << "Derived ";
}
};
int main()
{
Derived derived;
derived.print(); // 调用derived :: print(),它是公开的
return 0;
}
添加到现有函数
有时我们不想完全替换基类函数,而是希望为其添加其他功能。在上面的例子中,请注意Derived :: identify()完全隐藏了Base :: identify()!这可能不是我们想要的。可以让我们的派生函数调用同名函数的Base版本(为了重用代码),然后为其添加其他功能。
要使派生函数调用同名的基函数,只需执行普通函数调用,但在函数前加上范围限定符(基类名称和两个冒号)。下面的示例重新定义了Derived :: identify(),因此它首先调用Base :: identify(),然后执行自己的其他操作。
class Derived: public Base
{
public:
Derived(int value)
: Base(value)
{
}
int GetValue() { return m_value; }
void identify()
{
Base::identify(); // 首先调用Base :: identify()
std::cout << "I am a Derived\n"; // 然后定义自己的内容
}
};
现在考虑以下示例:
int main()
{
Base base(5);
base.identify();
Derived derived(7);
derived.identify();
return 0;
}
I am a Base
I am a Base
I am a Derived
当derived.identify()被执行时,它解析为Derived::识别()。但是,Derived :: identify()所做的第一件事是调用Base :: identify(),它打印“I am a Base”。当Base :: identify()返回时,Derived :: identify()继续执行并打印“I am a Derived”。
这应该非常简单。为什么我们需要使用范围解析运算符(::)呢?如果我们像这样定义了Derived :: identify():
class Derived: public Base
{
public:
Derived(int value)
: Base(value)
{
}
int GetValue() { return m_value; }
void identify()
{
identify(); // 注意:没有范围解析!
cout << "I am a Derived";
}
};
在没有范围解析限定符的情况下调用函数identify()将默认为当前类中的identify(),它将是Derived :: identify()。这会导致Derived :: identify()调用自身,这将导致无限循环!
尝试在基类中调用友元函数时会遇到一些棘手问题,例如operator <<。由于基类的友元函数实际上不是基类的一部分,因此使用范围解析限定符将不起作用。相反,我们需要一种方法来使Derived类暂时看起来像Base类,以便可以调用函数的正确operator。
幸运的是,使用static_cast很容易做到。这是一个例子:
class Base
{
private:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
friend std::ostream& operator<< (std::ostream &out, const Base &b)
{
out << "In Base\n";
out << b.m_value << '\n';
return out;
}
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
friend std::ostream& operator<< (std::ostream &out, const Derived &d)
{
out << "In Derived\n";
// static_cast将 Derived 转换为到Base对象,因此我们调用operator <<的正确版本
out << static_cast<Base>(d);
return out;
}
};
int main()
{
Derived derived(7);
std::cout << derived;
return 0;
}
因为Derived is-a Base,所以我们可以将Derived对象静态广播到Base中,以便调用使用Base的适当版本的operator <<。
这打印:
In Derived
In Base
7