首页 > 编程语言 >C++: 虚函数,一些可能被忽视的细节

C++: 虚函数,一些可能被忽视的细节

时间:2024-03-31 13:56:21浏览次数:31  
标签:析构 函数 多态 C++ 被忽视 细节 Base 纯虚 func

C++: 虚函数,一些可能被忽视的细节

引言:关于C++虚函数,对某些细节的理解不深入,可能导致我们的程序无法按预期结果运行,或是表明我们对其基本原理理解不够透彻。本文详细解答以下几个问题:实现多态,忘记写virtual会怎么样?虚函数的默认参数可以重载吗?纯虚函数真的不能有实现吗?析构函数可以是纯虚函数吗?

1.1 虚函数是什么?

  • 虚函数是在基类中使用关键字virtual声明的函数,它在派生类中可以被重写,且在运行时根据对象的类型来调用相应的函数。

  • 虚函数的作用是实现多态。多态是指同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。多态分为编译时多态和运行时多态,编译时多态是指函数重载,运行时多态是指虚函数。

  • 虚函数动态绑定的实现原理:每个含有虚函数的类都有一个虚函数表,虚函数表中存储着虚函数的地址,当基类指针绑定了类对象后,通过类对象虚表指针指向的虚函数表找到虚函数的地址,然后调用对应的虚函数。

1.2 实现多态,忘记写virtual会怎么样?

如果忘记在派生类中写virtual关键字,那么就不会实现多态,而是静态绑定。因此以下例子中,调用的是基类的函数,而不是派生类的函数。

class Base {
public:
    void func() {
        std::cout << "Base func" << std::endl;
    }
};

class Derived : public Base {
public:
    void func() {
        std::cout << "Derived func" << std::endl;
    }
};

int main() {
    Base *b = new Derived();
    b->func();
    delete b;
    return 0;
}

输出:

Base func

为了避免这种情况发生,C++11中引入了override关键字对需要重写的函数进行声明,这样如果派生类中没有重写基类的函数,编译器就会报错。

1.3 虚函数的默认参数可以重载吗?

虚函数的默认参数不可以重载,因为虚函数的调用是在运行时确定的,而默认参数是在编译时确定的。从设计角度来说,这样做是合理的,如果虚函数的默认参数可以重载,那么在运行时,编译器就需要在运行时选择合适的默认参数,这样就会增加编译器的复杂度。因此,虚函数的默认参数不可以重载。


class Base {
public:
    virtual void func(int i = 0) = 0;
};

void Base::func(int i) {
    std::cout << "Base func: " << i << endl;
}

class Derived : public Base {
public:
    void func(int i = 2) {
        std::cout<< "Derived func: " << i << endl;
    }
};

int main() {
    Base *b = new Derived();
    b->func();
    delete b;
    return 0;
}

输出:

Base func: 0

1.4 纯虚函数是什么?

  • 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法,否则编译失败。

  • 纯虚函数的声明格式为:virtual 函数类型 函数名(参数表) = 0;,其中“= 0”是纯虚函数的标志,它告诉编译系统,该虚函数没有实现。

  • 含有纯虚函数的类是抽象类,抽象类是不能实例化的。

纯虚函数真的不能有实现吗?其实不然,纯虚函数是可以有自己的实现的,但是这个实现是在类外部实现的,而不是在类内部实现的。详见1.5中的代码实例。

1.5 析构函数可以是纯虚函数吗?

我们知道,析构函数是在对象销毁时调用的,而纯虚函数是没有实现的虚函数,含有纯虚函数的类是抽象类,那么,析构函数可以是纯虚函数吗?

程序验证如下:

class Base {
public:
    virtual ~Base() = 0;
};

Base::~Base() {
    std::cout << "Base destructor" << std::endl;
}

class Derived : public Base {
public:
    ~Derived() {
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Base *b = new Derived();
    delete b;
    return 0;
}

输出:

Derived destructor
Base destructor

结论:析构函数可以是纯虚函数,含有纯虚析构函数的类无法实例化。因为析构函数是在派生类析构函数调用之后才调用基类析构函数,而派生类析构函数在派生类对象销毁时才会调用,而派生类对象的销毁必须要调用基类的析构函数,因此基类析构函数必须要在类外提供定义。

如果不定义纯虚析构函数的实现,则会链接失败,报以下错误。

:(.text$_ZN7DerivedD1Ev[__ZN7DerivedD1Ev]+0x3e): undefined reference to `Base::~Base()'
collect2.exe: error: ld returned 1 exit status

为什么在类外部实现就可以呢?因为含有纯虚函数的类是抽象类,抽象类是不能实例化的,但是抽象类可以有指针和引用,因此,我们可以通过抽象类的指针或引用调用纯虚函数,但是如果纯虚函数没有实现,那么就会出现问题,因此,我们需要在类外部实现纯虚函数。

1.6 纯虚函数可以被显示调用吗?

派生类的成员函数可以通过限定函数id自由调用基类在类外定义的纯虚函数。

class Base {
public:
    virtual void func() = 0;
};

void Base::func() {
    std::cout << "Base func" << std::endl;
}

class Derived : public Base {
public:
    void func() {
        Base::func();
        std::cout << "Derived func" << std::endl;
    }
};

int main() {
    Base *b = new Derived();
    b->func();
    delete b;
    return 0;
}

输出:

Base func
Derived func

参考

标签:析构,函数,多态,C++,被忽视,细节,Base,纯虚,func
From: https://www.cnblogs.com/qiangz/p/18106666

相关文章

  • C++ 中的 volatile 和 atomic
    C++中的volatile和atomic0.TL;DRstd::atomic用于多线程并发场景,有两个典型使用场景:原子操作:对atomic变量的操作(读/写/自增/自减)仿佛受互斥量保护。一般通过特殊的机器指令实现,比使用互斥量更高效限制编译器/硬件对赋值操作的重新排序volatile和多线程并发没有......
  • C++—vector的介绍及使用 && vector的模拟实现
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档目录文章目录前言一、vector的介绍及使用1.1vector的介绍1.2vector的使用1.2.1vector的定义1.2.2vectoriterator的使用1.2.3vector空间增长问题1.2.4 vector增删查改1.2.5 vector迭代器......
  • 矩阵匹配【华为OD机试JAVA&Python&C++&JS题解】
    一.题目-矩阵匹配从一个NM(N<=M)的矩阵中选出N个数,任意两个数字不能在同一行或同一列,求选出来的N个数中第K大的数字的最小值是多少。输入描述:输入矩阵要求:1<=K<=N<=M<=150输入格式:NMKNM矩阵输出描述:N*M的矩阵中可以选出M!/N!种组合数组,每个组合数组中第K大的数中的......
  • 文本统计分析【华为OD机试JAVA&Python&C++&JS题解】
    一.题目-文本统计分析有一个文件,包含以一定规则写作的文本,请统计文件中包含的文本数量规则如下文本以";“分隔,最后一条可以没有”;",但空文本不能算语句,比如"COMMANDA;;"只能算一条语句.注意,无字符/空白字符/制表符都算作"空"文本文本可以跨行,比如下面,......
  • C++ 计数器小全
    1.简介  这篇文章介绍了C++的计数器们,包括:cnt(累加计数器),sum(求和计数器),mul(累乘计数器),last(上一项记录器),flag(状态计数器)  下文中,我就会详细说这6种计数器2.详解(正文)        2-1.cnt详解        cnt的中文名是累加......
  • 【C++】内存管理
    在学习完类与对象后,在学习C++中,接下来需要学习的是内存管理,在本篇博客中将会讲解C++的内存管理。 一.C/C++内存分布在学习内存管理前,我们先来了解一下C/C++的内存分布。 一般来说,内存可以分为四个区域:栈区、堆区、数据段、代码段。栈区:用于存放非静态的局部变量,函......
  • C++与C语言
    C++之所以是C++,和面向过程的C语言相比,它加了一个类,还有一个是模板。 引入C语言这种面向过程的编译语言可以将待解的问题分解成若干个子问题,面向对象程序设计则是建立在结构化程序设计方法的基础上,完全避免了结构化程序设计方法中所存在的问题。程序=数据结构+算法(结构化......
  • 数据结构 —— 线性表的链式存储(链表)(C++)
    目录单链表(有头结点)定义初始化判空销毁清空求表长取值查找插入删除创建头部创建尾部创建本文相关知识:以链式存储结构来实现线性表(C++)如有错误请指正~~谢谢~后面更新循环链表和双向链表单链表(有头结点)以带头结点的单链表为例,操作更加简便!定义首先,为了增强程序的可读性,做出以......
  • 杨辉三角形(c++实现)
    题目下面的图形是著名的杨辉三角形:如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,…给定一个正整数N,请你输出数列中第一次出现N是在第几个数?输入输入一个整数N。输出输出一个整数代......
  • UE4 c++ 通过枚举寻找DataTable中的数据
    DataTable中的数据DataTable中每一行数据是一个结构体在C++代码中定义结构体,然后可以在蓝图中可以创建以此结构体为单元的DataTable枚举变量定义一个头文件来存储枚举变量,然后可以在要使用的文件中利用MyEnumPtr=FindObject<UEnum>(ANY_PACKAGE,TEXT("EGridShapEnum"),tr......