覆盖虚函数和继承
Overriding virtual functions and inheritance
我无法完全理解 C++ 中的重写虚函数,以及当这些函数被 Bjarne Stroustrup 使用 C++ 阅读 PPP 时究竟发生了什么,他提供了以下示例来展示重写和虚函数函数:
struct B {
virtual void f() const { cout<<"B::f; }
void g() const { cout << "B::g"; } //not virtual
};
struct D:B {
void f() const { cout<<"D::f"; } //overrides B::f
void g() { cout<<"D::g"; }
};
struct DD:D {
void f() { cout<<"DD::f"; }
void g() const { cout<<"DD::g";}
};
void call(const B& b) {
//a D is kind of B, so call() can accept D
//a DD is kind of D and a D is a kind of B, so call() can accept a DD
b.f();
b.g();
}
int main() {
B b;
D d;
DD dd;
call(b);
call(d);
call(dd);
b.f();
b.g();
d.f();
d.g();
dd.f();
dd.g();
}
输出:B::f B::g D::f B::g D::f B::g B::f B::g D::f D::g DD::f DD::g
我明白 call(b) 是如何直接输出 B::f B::g
的。
现在call(d)
。我不太明白为什么,但似乎 call()
可以将 B
的派生 类 作为参数。行。因此在 call()
中,b.f()
变为 d.f()
,因为 D::f
覆盖了 B::f
。实际上输出显示 D::f
。但是 D::g
不会覆盖 B::g
并且由于我无法理解的原因,在我看来 D::g
在执行 call(d)
时没有效果 - 输出 B::g
在这种情况下。
接下来,我们执行 call(dd)
输出 D::f B::g
。应用与上面相同的逻辑(?)很明显 DD::f
不会覆盖 D::f
- 不是 const - 并且 DD::g
不会覆盖 D::g
和 B::g
因为两者都不是 virtual
.
接下来发生的事情让我很困惑。 b.f()
、 b.g()
、 d.f()
、 d.g()
、 dd.f()
、 dd.g()
的每个单独调用输出结果,就好像覆盖根本不存在一样!
例如,d.g()
怎么会在几秒钟前 d.g()
在 call() 输出 B::g
时输出 D::g
?
另外,dd.f()
怎么会输出DD::f
when in dd.f()
in call()
output D::f
?
可以肯定地说我在这里遗漏了一些重要的东西,为此我需要帮助。
请耐心阅读本文以获得问题的答案。
继承是一个概念,其中 class 的 object 继承另一个 class 的 object 的属性和行为。
基本介绍
- Parent class 也称为基础 class 或超级 class.
- Child class 也称为派生 class 或 subclass.
- 派生class的object可以通过基class引用。
例如
#include <iostream>
using namespace std;
class Base {};
class Derived : public Base {};
int main() {
Base *b = new Derived(); // This is completely valid
return 0;
}
C++ 中的方法覆盖
举个基本的例子
#include <iostream>
using namespace std;
class Base {
public:
void display() { cout << "Base display called\n"; }
};
class Derived : public Base {
public:
void display() { cout << "Derived display called\n"; }
};
int main() {
Base b;
b.display();
Derived d;
d.display();
Base *bptr = &d;
bptr->display();
return 0;
}
输出:
Base display called
Derived display called
Base display called
现在,根据上面的示例,您可能已经猜到派生的 class 会覆盖基数 class,但事实并非如此。输出(第 3 行)显示基 class 函数被调用,因为该函数不是虚拟的。
C++ 中的虚函数
您可以通过在函数开头添加 "virtual" 关键字来使 class 的任何函数成为虚拟函数。
让我们考虑虚函数示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void display() { cout << "Base display called\n"; }
};
class Derived : public Base {
public:
void display() { cout << "Derived display called\n"; }
};
int main() {
Base b;
b.display();
Derived d;
d.display();
Base *bptr = &d;
bptr->display();
return 0;
}
输出:
Base display called
Derived display called
Derived display called
通过上面的例子(输出的第3行),很明显可以通过C++中的虚函数机制实现方法重写
将函数设为虚函数有什么效果?
普通函数和虚函数的区别在于普通函数在编译时解析,也称为静态绑定,而虚函数在运行时解析,也称为动态绑定或后期绑定。调用哪个方法(基础class 显示或派生class 显示方法)在运行 时解决,因为基础class 显示功能是虚拟的。您可以通过阅读 v-table.
深入了解虚函数机制
问题的答案
呼叫(d)。我不太明白为什么,但似乎 call() 可以将 B 的派生 classes 作为参数。
- 派生的classobject可以被基引用class
但是 D::g 不会覆盖 B::g 并且出于我无法理解的原因。
- 因为 g() 在基 class 中不是虚拟的。只能覆盖虚函数。 v-table 仅具有虚函数的条目,因此它们可以在 运行 时被覆盖。
呼叫(dd)
- 由于 DD 中的 f() 是一个 non-const 函数,因此 f() (non-const DD 中的 f() )不是 parent class D。由于它被 Base class B 引用,调用 b.f() 将调用被 D 覆盖的 const f()。因此 D::f 被打印出来.
- 如果 f() 是 DD 中的 const 方法,那么会发生以下情况:
当调用 f() 时,首先在基 class B 中搜索该函数,
由于 f() 是虚拟的,因此使用 v-table 指针,解决了派生 class D 中被覆盖的 f() 函数的问题。但是由于f()在classD中不是virtual,所以无法解析DD中重写的f()。因此 D::f 被打印出来。
但是对于g()来说,baseclass本身并没有virtual g(),所以他们的derivedclasses中的重写函数是无法解析的。因此,B::g 得到打印。
上面的多态性之所以发生,是因为派生的classes被它们的baseclasses(parentclasses引用了,但是在最后的电话中,没有这样的事情。所有 object 都由它们各自的 class 引用,因此它们的适当方法被调用。
考虑这个问题的一个基本逻辑是,函数首先在引用 class 中查找,如果它是虚拟的,那么函数将在派生的 classes 中搜索(如果被引用的 object 是 child class)。如果派生 classes 重写,那么将调用派生方法,否则将调用基方法。您可以进一步将应用于基础 class 的概念扩展到派生的 class 以及检查函数是否为虚函数以及函数是否为虚函数以及 object是派生的(派生的child,基的grandchild)等等。
希望这能说明问题。
例如,d.g() 怎么会输出 D::g 而就在几秒钟前 d.g() 在 call() 输出 B::g ?
当您将派生对象作为指针或基类 class 的引用传递时,您将保留其多态属性。但是,这并不意味着您仍然可以访问派生 class 的非虚函数。
考虑这个例子,
B *bp;
bp = &d;
bp->f();
bp->g();
当bp->g()
执行时,会调用b
的g
函数。
我无法完全理解 C++ 中的重写虚函数,以及当这些函数被 Bjarne Stroustrup 使用 C++ 阅读 PPP 时究竟发生了什么,他提供了以下示例来展示重写和虚函数函数:
struct B {
virtual void f() const { cout<<"B::f; }
void g() const { cout << "B::g"; } //not virtual
};
struct D:B {
void f() const { cout<<"D::f"; } //overrides B::f
void g() { cout<<"D::g"; }
};
struct DD:D {
void f() { cout<<"DD::f"; }
void g() const { cout<<"DD::g";}
};
void call(const B& b) {
//a D is kind of B, so call() can accept D
//a DD is kind of D and a D is a kind of B, so call() can accept a DD
b.f();
b.g();
}
int main() {
B b;
D d;
DD dd;
call(b);
call(d);
call(dd);
b.f();
b.g();
d.f();
d.g();
dd.f();
dd.g();
}
输出:B::f B::g D::f B::g D::f B::g B::f B::g D::f D::g DD::f DD::g
我明白 call(b) 是如何直接输出 B::f B::g
的。
现在call(d)
。我不太明白为什么,但似乎 call()
可以将 B
的派生 类 作为参数。行。因此在 call()
中,b.f()
变为 d.f()
,因为 D::f
覆盖了 B::f
。实际上输出显示 D::f
。但是 D::g
不会覆盖 B::g
并且由于我无法理解的原因,在我看来 D::g
在执行 call(d)
时没有效果 - 输出 B::g
在这种情况下。
接下来,我们执行 call(dd)
输出 D::f B::g
。应用与上面相同的逻辑(?)很明显 DD::f
不会覆盖 D::f
- 不是 const - 并且 DD::g
不会覆盖 D::g
和 B::g
因为两者都不是 virtual
.
接下来发生的事情让我很困惑。 b.f()
、 b.g()
、 d.f()
、 d.g()
、 dd.f()
、 dd.g()
的每个单独调用输出结果,就好像覆盖根本不存在一样!
例如,d.g()
怎么会在几秒钟前 d.g()
在 call() 输出 B::g
时输出 D::g
?
另外,dd.f()
怎么会输出DD::f
when in dd.f()
in call()
output D::f
?
可以肯定地说我在这里遗漏了一些重要的东西,为此我需要帮助。
请耐心阅读本文以获得问题的答案。
继承是一个概念,其中 class 的 object 继承另一个 class 的 object 的属性和行为。
基本介绍
- Parent class 也称为基础 class 或超级 class.
- Child class 也称为派生 class 或 subclass.
- 派生class的object可以通过基class引用。
例如
#include <iostream>
using namespace std;
class Base {};
class Derived : public Base {};
int main() {
Base *b = new Derived(); // This is completely valid
return 0;
}
C++ 中的方法覆盖
举个基本的例子
#include <iostream>
using namespace std;
class Base {
public:
void display() { cout << "Base display called\n"; }
};
class Derived : public Base {
public:
void display() { cout << "Derived display called\n"; }
};
int main() {
Base b;
b.display();
Derived d;
d.display();
Base *bptr = &d;
bptr->display();
return 0;
}
输出:
Base display called
Derived display called
Base display called
现在,根据上面的示例,您可能已经猜到派生的 class 会覆盖基数 class,但事实并非如此。输出(第 3 行)显示基 class 函数被调用,因为该函数不是虚拟的。
C++ 中的虚函数
您可以通过在函数开头添加 "virtual" 关键字来使 class 的任何函数成为虚拟函数。
让我们考虑虚函数示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void display() { cout << "Base display called\n"; }
};
class Derived : public Base {
public:
void display() { cout << "Derived display called\n"; }
};
int main() {
Base b;
b.display();
Derived d;
d.display();
Base *bptr = &d;
bptr->display();
return 0;
}
输出:
Base display called
Derived display called
Derived display called
通过上面的例子(输出的第3行),很明显可以通过C++中的虚函数机制实现方法重写
将函数设为虚函数有什么效果?
普通函数和虚函数的区别在于普通函数在编译时解析,也称为静态绑定,而虚函数在运行时解析,也称为动态绑定或后期绑定。调用哪个方法(基础class 显示或派生class 显示方法)在运行 时解决,因为基础class 显示功能是虚拟的。您可以通过阅读 v-table.
深入了解虚函数机制问题的答案
呼叫(d)。我不太明白为什么,但似乎 call() 可以将 B 的派生 classes 作为参数。
- 派生的classobject可以被基引用class
但是 D::g 不会覆盖 B::g 并且出于我无法理解的原因。
- 因为 g() 在基 class 中不是虚拟的。只能覆盖虚函数。 v-table 仅具有虚函数的条目,因此它们可以在 运行 时被覆盖。
呼叫(dd)
- 由于 DD 中的 f() 是一个 non-const 函数,因此 f() (non-const DD 中的 f() )不是 parent class D。由于它被 Base class B 引用,调用 b.f() 将调用被 D 覆盖的 const f()。因此 D::f 被打印出来.
- 如果 f() 是 DD 中的 const 方法,那么会发生以下情况:
当调用 f() 时,首先在基 class B 中搜索该函数, 由于 f() 是虚拟的,因此使用 v-table 指针,解决了派生 class D 中被覆盖的 f() 函数的问题。但是由于f()在classD中不是virtual,所以无法解析DD中重写的f()。因此 D::f 被打印出来。
但是对于g()来说,baseclass本身并没有virtual g(),所以他们的derivedclasses中的重写函数是无法解析的。因此,B::g 得到打印。
上面的多态性之所以发生,是因为派生的classes被它们的baseclasses(parentclasses引用了,但是在最后的电话中,没有这样的事情。所有 object 都由它们各自的 class 引用,因此它们的适当方法被调用。
考虑这个问题的一个基本逻辑是,函数首先在引用 class 中查找,如果它是虚拟的,那么函数将在派生的 classes 中搜索(如果被引用的 object 是 child class)。如果派生 classes 重写,那么将调用派生方法,否则将调用基方法。您可以进一步将应用于基础 class 的概念扩展到派生的 class 以及检查函数是否为虚函数以及函数是否为虚函数以及 object是派生的(派生的child,基的grandchild)等等。
希望这能说明问题。
例如,d.g() 怎么会输出 D::g 而就在几秒钟前 d.g() 在 call() 输出 B::g ?
当您将派生对象作为指针或基类 class 的引用传递时,您将保留其多态属性。但是,这并不意味着您仍然可以访问派生 class 的非虚函数。
考虑这个例子,
B *bp;
bp = &d;
bp->f();
bp->g();
当bp->g()
执行时,会调用b
的g
函数。