问题

为什么使用virtual关键字在C++与C#会出现不同的效果?求解答。

回答
在C++和C中,`virtual`关键字都扮演着至关重要的角色,但它们所承载的语义和最终实现的效果却存在着显著的差异,这种差异根植于两种语言不同的设计哲学和底层机制。

C++中的 `virtual`:为继承而生,重塑运行时行为

在C++的世界里,`virtual`关键字的核心目的在于启用多态性,也就是允许基类指针或引用在运行时指向派生类对象,并能够正确调用派生类中重写(override)的成员函数。这是一种强大的机制,使得代码更加灵活和可扩展。

当我们为一个成员函数加上`virtual`关键字时,我们实际上是在告诉编译器:这个函数是可以被派生类重新定义的,并且在通过基类指针或引用调用这个函数时,要进行动态查找,找到实际指向的对象类型所定义的那个版本的函数。

具体来说,当一个类拥有至少一个`virtual`函数时,编译器通常会为该类生成一个虚函数表(vtable)。这个虚函数表是一个隐藏的指针数组,其中包含了指向该类所有`virtual`函数的地址。每个类的对象在创建时,如果该类(或其基类)有`virtual`函数,它会在对象内部持有一个指向其对应虚函数表的指针,通常称为vptr。

当通过基类指针调用一个`virtual`函数时,程序会首先访问对象的vptr,然后通过vptr查找虚函数表,找到对应函数的地址,最后调用该地址处的函数。这个查找过程是在程序运行时进行的,因此被称为动态绑定或后期绑定。

这就意味着,即使我们持有的是一个指向基类对象的指针,但如果这个指针实际上指向的是一个派生类对象,并且我们调用的是一个`virtual`函数,那么最终执行的将是派生类中重写后的函数。这极大地增强了代码的通用性,我们可以在不知道具体派生类类型的情况下,编写通用的算法来处理各种派生对象。

需要注意的是,在C++中,`virtual`关键字是继承的基石。只有当一个函数在基类中被声明为`virtual`后,派生类中的同名函数才能通过重写(override)获得多态行为。如果派生类中的函数与基类中的非`virtual`函数同名,那么它只是隐藏(hide)了基类函数,而不是重写,通过基类指针调用时仍然会执行基类版本。

C中的 `virtual`:声明可被重写,但执行依赖于关键字

C的 `virtual`关键字在概念上与C++有相似之处,它也用于声明允许派生类重写(override)的成员。然而,C在实现多态性的机制上与C++有着本质的区别,并且`virtual`关键字在C中的使用场景和效果也更为细致。

在C中,当一个方法被声明为 `virtual` 时,它仅仅是在声明该方法可以被派生类通过 `override` 关键字进行重写。与C++不同,C的 `virtual` 并不强制为类生成虚函数表。而是当一个类确实有`virtual`方法,或者它的基类有`virtual`方法时,CLR(Common Language Runtime)才会对该类进行优化,可能生成类似虚函数表的结构来支持多态性。

更重要的是,C在调用被声明为 `virtual` 的方法时,其行为并不总是像C++那样自动进行动态查找。在C中,多态性的调用依赖于调用的上下文:

通过基类引用/指针调用 `virtual` 方法: 如果你有一个指向派生类对象的基类引用,然后调用一个在基类中声明为 `virtual` 的方法,那么CLR会进行动态查找,执行派生类中重写后的方法。这与C++的行为是相似的。

通过派生类引用/指针调用 `virtual` 方法: 如果你拥有一个直接指向派生类对象的派生类引用,然后调用一个在该派生类中声明为 `virtual` 的方法,其行为会稍微有所不同。这里需要区分是调用的是基类中的 `virtual` 方法,还是派生类中新声明的 `virtual` 方法。

如果派生类重写(override)了基类的 `virtual` 方法,那么通过派生类引用调用时,会执行派生类中的重写版本。
如果派生类新声明了一个与基类同名的 `virtual` 方法(这在C中是不允许的,因为重写必须使用 `override`),或者更常见的,它声明了一个新的 `virtual` 方法,那么通过派生类引用调用时,会执行的就是这个新声明的 `virtual` 方法。

关键的区别点在于:

1. C++ 的 `virtual` 是强制启用动态绑定的机制,一旦函数被声明为 `virtual`,那么通过基类指针调用就一定会进行运行时查找。
2. C 的 `virtual` 更多的是一个“允许重写”的标记,CLR会根据情况进行优化。更重要的是,C中方法的调用(包括 `virtual` 方法)默认是静态绑定的,除非你显式地使用了`override`关键字来重写基类方法,并且调用是通过基类引用完成时,才会表现出动态绑定的行为。

`new` 关键字在 C 中的作用

C引入了一个 `new` 关键字,用来隐藏基类中具有相同签名的成员(包括方法、属性、事件等),但不启用多态性。如果你在派生类中声明一个新方法,它与基类中的一个方法同名,但没有使用 `override` 关键字,那么它实际上是隐藏了基类方法。即使基类的方法被声明为 `virtual`,通过基类指针调用时执行的仍是基类的方法,而通过派生类指针调用时,则会执行派生类中新声明的那个隐藏方法。

在C中,`virtual` 和 `override` 是相辅相成的。`virtual` 声明了“可以被重写”,而 `override` 则表示“就是要重写”。缺少 `override`,即使方法在派生类中签名相同,也只是隐藏,没有多态性。

总结

简单来说,C++的 `virtual` 是一个声明并强制执行动态绑定的机制,一旦设置,通过基类指针调用就会默认进行运行时查找。而C的 `virtual` 则是一个声明“允许重写”的标记,动态绑定的行为更多地依赖于调用的上下文以及是否使用了`override`关键字。C通过 `new` 关键字提供了隐藏而非多态的选项,也使得方法重写和隐藏的语义更加清晰。正是这种对多态性实现机制的不同侧重,导致了 `virtual` 关键字在两种语言中虽然名称相同,但其带来的具体效果和使用上的要求有所差异。

网友意见

user avatar

简单来说的话就是C#的构造函数不负责构造。是对象构造完了之后再调用的。

要在C++里面模拟的话,你就把要写在构造函数里面的代码写成一个virtual void Initialize()方法,然后构造完了后再去调用这个方法就好了。

user avatar

这件事情和virtual无关,和this的意思有关。C++的版本里,A::A()的时候this指向的是A的类型,只有完成构造了,才有一个完整的动态类型可用。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有