虚函数必须定义(纯虚函数除外)

本文详细解析了C++中虚函数的声明与定义规则,包括如何正确使用纯虚函数和虚函数表的工作原理。同时介绍了基类与派生类中虚函数的实现方式,并通过具体示例代码展示了如何避免编译错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 虚函数的声明和定义
具体关于虚函数的知识不做多讲,我在定义一个抽象类时,忘了将一个虚函数声明为 纯虚函数,又没有对其定义, 导致编译报错时报错如下:
undefined reference to `vtable for Fibonacci'
错误提示的很明显,就是无法生成虚函数表。


我们知道,虚函数表(地址)在定义了虚函数的类所实例化的对象内存中的第一个位置,也就是在实例化过程中生成了虚表。这个错误提示在stackflow中最常见的解答就是类中声明了虚函数,却没有定义。

总结一下虚函数声明和定义的规则如下:

类中的virtual函数,要么设为纯虚函数,要么有定义,否则无法生成虚函数表。

虚函数的可以类外定义,但是必须加上类名,类外定义不需要加virtual
声明为纯虚函数,则类为抽象类,无法实例化,进一步强调,想要实例化有虚函数的类,必须对虚函数进行定义
基类定义为虚函数,则子类同名函数也为虚函数,无论是否有virtual关键字修饰(一般声明时加virtual,便于阅读)


凡是基类定义有虚函数,则基类需要定义虚析构函数(根据上一条法则,虚析构函数要么有定义,要么纯虚,一般不设为纯虚,可以定义空白)
虚函数通过虚表实现,虚表是类实例化时生成在对象中的(虚表地址),所以如果一个类能够实例化,则其虚函数必须有定义,如果不想定义虚函数,只能声明为纯虚函数,留给子类定义。

基类为一个抽象类

// num_sequence.h
#ifndef _NUM_SEQUENCE


#define _NUM_SEQUENCE


#include <string>
#include <iostream>


using namespace std;


class num_sequence {


    public:
        num_sequence() {
            cout << " create a num sequence" << endl;
        }


        virtual ~num_sequence() {
            cout << "~num_sequence has been called" << endl;
        }  


        virtual int elem(int pos) const= 0;
        virtual const string what_am_i() const = 0;


        static int max_elems(){
            return  _max_elems;
        }


        virtual ostream& print(ostream& = cout) const = 0;


    protected:
        virtual void gen_elems(int pos) const = 0;      // const指针指向自认为const的一个对象, 这个对象不能通过const指针进行修改, 但可以通过其他方式进行修改


        bool check_integrity(int pos) const{
            if(pos <=0 || pos >_max_elems) {
                cerr << "Invalid position: " << pos << endl;
                return false;
            }
            return true;
        }


        const static int _max_elems = 1024;         // 最大元素个数
};


#endif

子类不是抽象类,则必须实现基类的纯虚函数


子类声明:


// Fibonacci.h


#ifndef _FIBONACCI


#define _FIBONACCI


#include "num_sequence.h"
#include <vector>


using std::vector;


class Fibonacci : public num_sequence {


    public:
        Fibonacci(int len = 1, int beg_pos = 1): _length(len), _beg_pos(beg_pos){
            cout << "create a Fibonacci sequence" << endl;
        }


        virtual int elem(int pos) const ;    // 子类类中声明virtual时, 应当和父类保持精确一致性


        virtual ostream& print(ostream &os=cout) const;


        virtual const string what_am_i() const{
            cout << " I am Fibonacci sequence" << endl;
        }




        int length() const {
            return _length;
        }


        int beg_pos() const{
            return _beg_pos;
        }


        ~Fibonacci() {
            cout << "~Fibonacci has been called" << endl;
        }


    protected:
        virtual void gen_elems(int pos) const;


        int                 _length;    // 长度
        int                 _beg_pos;   // 起始位置
        static vector<unsigned int>  _elems;     // 元素容器


};

#endif

子类虚函数定义:


// Fibonacci.cpp


#include "Fibonacci.h"


vector<unsigned int> Fibonacci::_elems;


// 类外定义virtual函数时, 不需要virtual关键字
int Fibonacci::elem(int pos) const {


    if(!check_integrity(pos))           // 检查pos可用性
        return 0;


    if(pos > _elems.size() )            // 检查当前元素个数是够足够, 当不足时自动生成
        Fibonacci::gen_elems(pos);      // 利用类作用域, 在编译时指明调用函数  


    return _elems[pos - 1];
}


ostream& Fibonacci::print(ostream &os) const {


    int elem_pos = _beg_pos - 1;
    int end_pos = elem_pos + _length;


    if(end_pos > _elems.size()) {
        Fibonacci::gen_elems(end_pos);
    }


    while(elem_pos < end_pos) {
        os << _elems[elem_pos] << "\t";
        ++elem_pos;
    }
    return os;
}


void Fibonacci::gen_elems(int pos) const {


    if(_elems.empty()) {
        _elems.push_back(1);
        _elems.push_back(1);
    }


    if(_elems.size() <= pos) {
        int ix = _elems.size();
        int n_2 = _elems[ix-2];
        int n_1 = _elems[ix-1];


        for(; ix<=pos; ++ix){
            int elem = n_2 + n_1;
            _elems.push_back(elem);
            n_2 = n_1;
            n_1 = elem;
        }
    }

}

http://blog.youkuaiyun.com/gvfdbdf/article/details/51954819


<think>好的,我需要回答用户关于C++虚函数概念及用法的问题。首先,我得回顾一下自己了解的虚函数知识,并参考提供的引用内容,确保回答准确。 用户给出的三个引用中,引用[1]提到虚函数是实现多态的重要机制,允许派生类重写基类函数,通过基类指针或引用调用时选择正确的实现。引用[2]强调了虚函数的核心目的是通过基类访问派生类定义的函数,并提到定义时建议加上virtual关键字。引用[3]则通过代码示例展示了虚函数和普通成员函数的区别,当基类指针指向派生类对象时,虚函数调用的是派生类版本,而普通函数调用基类版本。 首先,我需要明确虚函数的基本概念。虚函数允许在继承体系中的派生类重写基类的方法,实现运行时多态。使用虚函数时,必须通过指针或引用来调用,才能体现多态性。这一点在引用[1]和[2]中都有提到。 接下来是虚函数定义方法。基类中声明函数时使用virtual关键字,如virtual void func();。如果在派生类中重写该函数,可以省略virtual,但建议保留以提高可读性。这点引用[2]的代码示例有展示。 然后是虚函数的作用,主要是实现多态,让程序在运行时根据对象类型决定调用哪个函数。引用[1]和[2]都强调了这一点。此外,纯虚函数和抽象类的概念也需要解释,当虚函数后面加上=0时,成为纯虚函数,包含纯虚函数的类不能实例化,派生类必须实现这些函数。这可能不在现有引用中,但属于虚函数的重要部分,可能需要补充。 还需要注意虚析构函数的问题。如果基类的析构函数不是虚的,当通过基类指针删除派生类对象时,可能导致资源泄漏。这也是常见考点,但引用中没有提到,是否需要包含?用户的问题是关于概念及用法,这部分应该提及,但需要确认是否有必要。 另外,引用[3]中的代码示例展示了基类指针调用虚函数和非虚函数的区别,这可以具体说明虚函数的行为。比如a->print()调用的是基类的print,而a->println()调用派生类的,因为后者是虚函数。这需要详细解释,帮助用户理解多态的实现机制。 可能用户还会混淆虚函数和函数重载的区别。需要明确虚函数是动态绑定,运行时决定,而重载是静态绑定,编译时确定。但用户的问题可能不需要深入这点,但可以简要提及。 现在整理回答的结构:先定义虚函数,再讲解用法,包括声明、重写、多态的实现,接着是纯虚函数和抽象类,最后注意事项如虚析构函数。同时结合引用中的例子,特别是引用[3]的代码,说明虚函数的效果。 需要确保回答符合系统指令:行内公式用$,独立公式单独成段,正确使用LaTeX,中文回答,生成相关问题。检查引用标注是否正确,例如在提到多态时引用[1],在定义虚函数时引用[2],在代码示例处引用[3]。 最后生成相关问题,可能涉及虚函数与多态的关系、虚析构函数的作用、纯虚函数定义虚函数与重写的区别等。</think>### C++虚函数概念及用法详解 #### 一、基本概念 虚函数是C++实现**运行时多态**的核心机制。通过在基类中使用`virtual`关键字声明函数,允许派生类重写该函数。当通过基类指针或引用调用虚函数时,实际调用的是**对象所属派生类**的函数实现[^1]。 #### 二、定义方式 1. **基类声明** 使用`virtual`关键字定义虚函数: ```cpp class Base { public: virtual void func() { cout << "Base::func()"; } }; ``` 2. **派生类重写** 派生类中可省略`virtual`关键字(建议保留): ```cpp class Derived : public Base { public: virtual void func() override { cout << "Derived::func()"; } }; ``` #### 三、核心特性 1. **动态绑定** 通过基类指针/引用调用虚函数时,实际调用由**对象类型**决定: ```cpp Base* obj = new Derived(); obj->func(); // 输出 Derived::func() ``` 若`func()`未声明为虚函数,此处将输出`Base::func()`[^3]。 2. **虚函数表(vtable)** 编译器为含虚函数的类生成虚函数表,存储函数指针,实现动态绑定的核心数据结构。 #### 四、特殊类型 1. **纯虚函数** 定义格式:`virtual void func() = 0;` 包含纯虚函数的类称为**抽象类**,不可实例化: ```cpp class AbstractClass { public: virtual void pureFunc() = 0; // 纯虚函数 }; ``` 2. **虚析构函数** 若存在继承关系,基类必须声明虚析构函数,否则通过基类指针删除派生类对象时**只会调用基类析构函数**,导致内存泄漏: ```cpp class Base { public: virtual ~Base() {} // 虚析构函数 }; ``` #### 五、关键应用场景 1. **实现多态接口** 定义统一接口,不同派生类实现不同行为: ```cpp class Shape { public: virtual double area() = 0; }; class Circle : public Shape { /* 实现area() */ }; class Square : public Shape { /* 实现area() */ }; ``` 2. **回调机制** 通过基类指针实现灵活的函数调用绑定。 #### 六、注意事项 1. **函数签名一致性** 派生类虚函数必须与基类函数**参数列表、返回类型完全一致**(协变返回类型除外)。 2. **性能影响** 虚函数调用比普通函数多一次间接寻址(通过vtable),但现代CPU对此优化较好,影响通常可忽略。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值