C++ Virtual函数

本文详细介绍了C++中虚函数的概念及其使用方式,包括如何通过基类访问派生类定义的函数来实现多态,纯虚函数及抽象类的作用,以及为何内联函数、构造函数和静态成员函数不能作为虚函数。

虚函数是C++中用于多态的机制。核心理念就是通过基类访问派生类定义的函数。
基类的析构含糊都必须是virtual的

虚函数只能借助于指针或者引用来达到多态的效果。
前提B类继承与A类 且foo()为虚函数
void bar(A *a)
{
  a->foo();//被调用的是A::foo()还是B::foo() ?
}
如果a指向的是A类的实例,则A::foo()被调用,如果a指向的是B类实例,则指向
B::foo();
在设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。
从设计角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现。通过
这样的方法,就可以将对象的行为抽象化。

虚函数总是在派生类中被改写,这种改写被称为“override”。
override是指派生类重写基类的虚函数。重写的函数必须有一致的参数表和返回值(允许返回值不同)

 

纯虚函数不需要定义其实际操作,它的存在只是为了在派生类中被重新定义,只是为了提供一个多态接口。

只有拥有纯虚函数的类,就是一种抽象类,它是不能被实例化的。

virtual void foo()=0;  //标志一个虚函数为纯虚函数 (=0)

 

补充:如果类B继承了A之后,A是抽象类,如果类B没有改写A中的纯虚函数,那么B本身也就成为一个拥有纯虚函数

的类,也就变成抽象类了。

 

内联函数,构造函数,静态成员函数为什么不能为virtual函数?
重点虚函数是运行期间确定的
1 >   内联函数 
内联函数是在编译时期展开,而虚函数的特性是运行时才动态联编,所以两者矛盾,不能定义内联函数为虚函数 
2 >   构造函数 
构造函数用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成,所以不能将构造函数定义为虚函数 
3 >   静态成员函数 
静态成员函数属于一个类而非某一对象,没有this指针,它无法进行对象的判别

### C++ 中虚函数的行为与调试问题 虚函数机制是 C++ 实现运行时多态的核心机制之一。在对象模型中,每个具有虚函数的类都会有一个虚函数表(vtable),该表中保存了虚函数的地址,对象通过一个指针(vptr)指向其所属类的虚函数表。当调用虚函数时,程序通过 vptr 找到对应的虚函数表,再从中查找函数地址并调用。 在虚函数表中,如果某个函数是纯虚函数(即没有实现的虚函数),传统上 C++ 编译器会为其在虚函数表中放置一个特殊的函数指用以处理错误调用,例如打印“Pure virtual function called”并导致程序崩溃[^1]。这种机制确保在运行时调用未实现的纯虚函数时能立即发现问题。 虚函数的另一个常见问题是虚函数在构造函数或析构函数中的调用行为。在构造派生类对象时,基类构造函数的执行发生在派生类部分之前,此时对象的动态类型仍为基类类型。因此,在基类构造函数中调用虚函数将调用基类的实现,而是派生类的实现。类似地,在析构函数中调用虚函数也将调用当前对象所属类的函数实现,而是更派生类的实现。某些编译器(如 Visual C++ 8.0 和 GCC 4.x)会优化掉运行时多态行为,直接调用当前类的函数实现。如果基类的虚函数是纯虚函数且没有定义,链接器将报告错误[^2]。 在调试虚函数调用时,可能会遇到以下问题: - **虚函数表指针(vptr)未正确初始化**:这通常发生在对象构造尚未完成时调用虚函数,或者在析构完成后再次调用虚函数。 - **虚函数表中的函数指针正确**:可能由于编译器优化、虚函数未实现或虚函数表被破坏导致。 - **断点未命中**:调试器可能无法在虚函数调用链中正确设置断点,尤其是在涉及多态和虚函数表跳转的情况下。 例如,以下代码演示了虚函数的基本行为: ```cpp #include <iostream> using namespace std; class Base { public: virtual void foo() { cout << "Base::foo" << endl; } virtual void bar() = 0; // 纯虚函数 }; class Derived : public Base { public: void foo() override { cout << "Derived::foo" << endl; } void bar() override { cout << "Derived::bar" << endl; } }; int main() { Derived d; Base* b = &d; b->foo(); // 调用 Derived::foo b->bar(); // 调用 Derived::bar return 0; } ``` 在调试上述代码时,如果在 `Base::bar()` 中设置断点,调试器可能会命中该断点,因为该函数是纯虚函数,实际调用的是 `Derived::bar()`。如果在 `Base` 类中没有为 `bar()` 提供实现,程序将无法链接。如果提供了实现,则在构造函数或析构函数中调用 `bar()` 时,会调用 `Base::bar()`,而是派生类的实现[^2]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值