C/C++中虚函数详解及其作用介绍

目录
  • 概述
  • 使用方法
  • 关联
    • 静态关联
    • 动态关联
  • 案例1
    • 未使用虚函数
    • 使用虚拟类
  • 案例2
  • 总结

概述

虚函数 (virtual function) 指可以被子类继承和覆盖的函数.

使用方法

基类声明成员函数为虚函数的方法:

virtual [类型] 函数名([参数表列])

注: 在类外定义虚函数时, 不需再加 virtual.

虚函数的特点:

  • 提高程序扩充性: 派生类根据需要可以进行函数覆盖
  • 成员函数被声明为虚数后, 其派生类中覆盖函数自动称为虚函数
  • 若虚函数在派生类中未重新定义, 则派生类简单继承其直接基类的虚函数
  • 指向基类的指针, 当指向派生类对象时, 可以嗲用派生类的方法

关联

通过关联 (binding), 我们可以把一个标识符和一个存储地址联系起来, 或者把一个函数名与一个类对象捆绑在一起.

静态关联

静态关联 (static binding) 指通过对象名调用虚函数. 在编译时即可确定其调用的虚函数属于哪一类

动态关联

动态关联 (dynamic binding) 是指通过基类指针与虚函数, 在运行阶段确定关联关系. 动态关联提供动态的多态性, 即运行阶段的多态性.

案例1

未使用虚函数

Square 类:

#ifndef PROJECT6_SQUARE_H
#define PROJECT6_SQUARE_H

class Square {
protected:
    int length;
public:
    Square(int l) : length(l) {};
    int area() const {
        return length *length;
    }
};

#endif //PROJECT6_SQUARE_H

Rectangle 类:

#ifndef PROJECT6_RECTANGLE_H
#define PROJECT6_RECTANGLE_H

#include "Square.h"

class Rectangle : public Square{
private:
    int height;
public:
    Rectangle(int l, int h) : Square(l), height(h) {};
    int area() const {
        return Square::area() * 2 + length * height * 4;  // 两个底加四个边
    }
};

#endif //PROJECT6_RECTANGLE_H

main:

#include <iostream>
#include "Square.h"
#include "Rectangle.h"
using namespace std;

int main() {
    // 创建对象
    Square s1(2), *pt;
    Rectangle r1(3, 3);

    pt = &s1;
    cout << pt->area() << endl;
    pt = &r1;
    cout << pt->area() << endl;

    return 0;
}

输出结果:

4
9 // 输出的是底面积

此时调用的是 Square 类的area()函数.

使用虚拟类

Square 类:

#ifndef PROJECT6_SQUARE_H
#define PROJECT6_SQUARE_H

class Square {
protected:
    int length;
public:
    Square(int l) : length(l) {};
    virtual int area() const {
        return length *length;
    }
};

#endif //PROJECT6_SQUARE_H

Rectangle 类:

#ifndef PROJECT6_RECTANGLE_H
#define PROJECT6_RECTANGLE_H

#include "Square.h"

class Rectangle : public Square{
private:
    int height;
public:
    Rectangle(int l, int h) : Square(l), height(h) {};
    int area() const {
        return Square::area() * 2 + length * height * 4;  // 两个底加四个边
    }
};

#endif //PROJECT6_RECTANGLE_H

main:

#include <iostream>
#include "Square.h"
#include "Rectangle.h"
using namespace std;

int main() {
    // 创建对象
    Square s1(2), *pt;
    Rectangle r1(3, 3);

    pt = &s1;
    cout << pt->area() << endl;
    pt = &r1;
    cout << pt->area() << endl;

    return 0;
}

输出结果:

4
54 // 长方体的面积

此时调用的是 Rectangle 类的area()函数.

案例2

Animal 类:

#ifndef PROJECT6_ANIMAL_H
#define PROJECT6_ANIMAL_H

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void bark(){
        cout << "咋叫?" << endl;
    }
};

#endif //PROJECT6_ANIMAL_H

Dog 类:

#ifndef PROJECT6_DOG_H
#define PROJECT6_DOG_H

#include "Animal.h"

class Dog : public Animal{
public:
    void bark() {
        cout << "汪汪!" << endl;
    }
};

#endif //PROJECT6_DOG_H

Cat 类:

#ifndef PROJECT6_CAT_H
#define PROJECT6_CAT_H

#include "Animal.h"

class Cat : public Animal{
public:
    void bark() {
        cout << "喵喵!" << endl;
    }
};

#endif //PROJECT6_CAT_H

Pig 类:

#ifndef PROJECT6_PIG_H
#define PROJECT6_PIG_H

#include "Animal.h"

class Pig : public Animal {
public:
    void bark() {
        cout << "哼哼!" << endl;
    }
};

#endif //PROJECT6_PIG_H

main:

#include <iostream>
#include "Animal.h"
#include "Dog.h"
#include "Cat.h"
#include "Pig.h"
using namespace std;

int main() {
    // 创建对象
    Animal a, *pt;
    Dog d;
    Cat c;
    Pig p;

    pt = &a;
    pt -> bark();  // 调用基类的bark()
    pt = &d;
    pt -> bark();  // 调用狗的bark()
    pt = &c;
    pt -> bark();  // 调用猫的bark()
    pt = &p;
    pt -> bark();  // 调用猪的bark()

    return 0;
}

输出结果:

咋叫?
汪汪!
喵喵!
哼哼!

总结

虚函数只能是类的成员函数, 而不能将类外的普通函数声明为虚函数. 虚函数的作用是允许在派生类中对基类的虚函数重新定义 (函数覆盖), 只能用于类的继承层次结构中.

虚函数能有效减少空间开销. 当一个类带有虚函数时, 编译系统会为该类构造一个虚函数表 (一个指针数组), 用于存放每个虚函数的入口地址.

什么时候应该使用虚函数:

  • 判断成员函数所在的类是不是基类, 非基类无需使用虚函数
  • 成员函数在类被继承后有没有可能被更改的功能, 如果希望修改成员函数功能, 一般在基类中将其声明为虚函数
  • 我们会通过对象名还是基类指针访问成员函数, 如果通过基类指针过引用去访问, 则应当声明为虚函数

有时候在定义虚函数的时候, 我们无需定义其函数体. 它的作用只是定义了一个虚函数名, 具体的功能留给派生类去添加, 也就是纯虚函数. 例如我们在上面的 Animal 类的bark()函数就应该声明为纯虚函数, 因为 Animal 为基类, 定义bark()函数实体并无意义.

(0)

相关推荐

  • 详解C++纯虚函数与抽象类

    1.虚函数 1.1虚函数简介 虚函数可以毫不夸张的说是C++最重要的特性之一,我们先来看一看虚函数的概念. 在基类的定义中,定义虚函数的一般形式为: virtual 函数返回值类型 虚函数名(形参表) { 函数体 } 为什么说虚函数是C++最重要的特性之一呢,因为虚函数承载着C++中动态联编的作用,也即多态,可以让程序在运行时选择合适的成员函数.虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public.那么:  (1)为什么类的静态成员函数不能为虚函数?  如果定义为虚函数,那么

  • C++ 中的虚函数表及虚函数执行原理详解

    为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚函数的类(或者继承自的类包含了虚函数)都有一个自己的虚函数表.这个表是一个在编译时确定的静态数组.虚函数表包含了指向每个虚函数的函数指针以供类对象调用. 其次,编译器还在基类中定义了一个隐藏指针,我们称为 *__vptr,*__vptr 是在类实例创建时自动设置的,以指向类的虚函数表.*__vptr

  • 一篇文章彻底弄懂C++虚函数的实现机制

    目录 1.虚函数简介 2.虚函数表简介 3.有继承关系的虚函数表剖析 3.1.单继承无虚函数覆盖的情况 3.2.单继承有虚函数覆盖的情况 3.3.多重继承的情况 3.4.多层继承的情况 4.总结 1.虚函数简介 C++中有两种方式实现多态,即重载和覆盖. 重载:是指允许存在多个同名函数,而这些函数的参数表不同(参数个数不同.参数类型不同或者两者都不同). 覆盖:是指子类重新定义父类虚函数的做法,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让

  • C++ 虚函数和纯虚函数的区别分析

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数. 简介 假设我们有下面的类层次: class A { public: virtual void foo() { cout<<"A::foo() is called"<<endl; } }; cl

  • 一文读懂C++ 虚函数 virtual

    探讨 C++ 虚函数 virtual 有无虚函数的对比 C++ 中的虚函数用于解决动态多态问题,虚函数的作用是允许在派生类中重新定义与积累同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数. 首先写两个简单的类,类 B 继承自类 A,即 A 是基类,B 是派生类. class A{ public: void print(){ cout << "A" << endl; } }; class B : public A { public: void

  • C++ 虚函数与纯虚函数的使用与区别

    目录 什么是虚函数: 虚函数的注意事项: 纯虚函数 纯虚函数的注意事项: 虚函数与纯虚函数区别 什么是虚函数: 虚函数 是在基类中使用关键字 virtual 声明的函数,在C++ 语言中虚函数可以继承,当一个成员函数被声明为虚函数之后,其派生类中的同名函数都自动生成为虚函数, 虚函数主要体验C++的多态方面,(多态是参数个数和类型相同而实现功能不同的函数) 为了更好的里面虚函数请看下面的demo #include <iostream> #include <string> using

  • C/C++中虚函数详解及其作用介绍

    目录 概述 使用方法 关联 静态关联 动态关联 案例1 未使用虚函数 使用虚拟类 案例2 总结 概述 虚函数 (virtual function) 指可以被子类继承和覆盖的函数. 使用方法 基类声明成员函数为虚函数的方法: virtual [类型] 函数名([参数表列]) 注: 在类外定义虚函数时, 不需再加 virtual. 虚函数的特点: 提高程序扩充性: 派生类根据需要可以进行函数覆盖 成员函数被声明为虚数后, 其派生类中覆盖函数自动称为虚函数 若虚函数在派生类中未重新定义, 则派生类简单

  • C/C++ 中memset() 函数详解及其作用介绍

    memset 函数是内存赋值函数,用来给某一块内存空间进行赋值的: 包含在<string.h>头文件中,可以用它对一片内存空间逐字节进行初始化: 原型为 : void *memset(void *s, int v, size_t n); 这里s可以是数组名,也可以是指向某一内在空间的指针: v为要填充的值: n为要填充的字节数: 例子: struct data { char num[100]; char name[100]; int n; }; struct data a, b[10]; me

  • C/C++中字符串流详解及其作用介绍

    目录 概述 字符串流 理解字符串流 输出字符串对象 输入字符串流对象 输入输出字符串流对象 案例一 案例二 字符数组 vs 文件 总结 概述 文件流类和字符串流类都是 ostream, istream 和 iostream 类的派生类, 因此对它们的操作方法是基本相同的. 字符串流 文件流 字符串流 概念 文件流是以外存文件为输入输出对象的数据流 字符串流也 称为内存流, 以内存中用户定义的字符数组 (字符串) 为输入输出的对象 相关流类 ifstream, ofstream 和 fstream

  • C/C++中命名空间(namespace)详解及其作用介绍

    目录 概述 命名空间 命名空间的作用 自定义命名空间 命名空间成员的方法 案例 概述 命名空间 (namespace) 可以帮助我们区分不同库中相同名称的函数, 类, 变量等. 使用了命名空间即定义了上下文. 命名空间就是定义了一个范围. 命名空间 为了解决 C++ 标准库中的标识符与程序中的全局标识符之间以及不同库中的所有标识符之间的命名冲突. 标准 C++ 库的所有标识符都定义在一个名为 std 的命名空间中. 在程序中用到 C++ 标准库时, 使用 std 作为限定. 我们在写 "Hell

  • C/C++中数据类型转换详解及其作用介绍

    目录 概述 不同类型数据间的转换 隐式类型转换 强制类型转换 自己声明的类型转换 转换构造函数 类型转换函数 案例 应用 概述 在日常的开发中, 我们经常会用到数据类型转换, 所以我们要对数据类型转换有一定的了解. 不同类型数据间的转换 在 C++ 中, 某些标准类型的数据之间可以自动转换. 隐式类型转换 隐式类型转换: 由 C++ 编译系统自动完成的, 我们无需干预. 例如: int main() { int a = 6; a = a + 3.5; cout << a << en

  • C++中继承(inheritance)详解及其作用介绍

    概述 面向对象程序设计中最重要的一个概念是继承 (inheritance). 继承允许我们依据另一个类来定义一个类, 这使得创建和维护一个应用程序变得更统一. 这样做也达到了重用代码功能和提高执行效率的效果. 类的概念 一个类中包含了若干数据成员和成员函数. 不同的类中的数据成员和成员函数各不相同. 但是有时两个类的内容基本相同. 例如: 继承的概念 继承 (inheritance) 就是在一个已存在的类的基础上建立一个新的类. 已存在的类: 基类 (base class) 或父类 (fathe

  • C++中指针的详解及其作用介绍

    目录 概述 指向对象的指针 指向对象数据成员的指针 this 指针 this 指针的作用 this 指针的实现 概述 指针 (pointer) 是一个变量, 其指为另一个变量的地址. 即内存位置的直接地址. 指向对象的指针 在建立对象时, 编译系统会为每一个对象分配一定的存储空间, 以存放其成员. 我们可以定义一个指针变量, 用来存放对象的指针. 例如: Time time1; Time *p; // 定义指针, 格式: 类名 *对象指针名 p = &time1; // 将指针指向Time类对象

  • C++中友元的详解及其作用介绍

    目录 概述 友元 普通的友元函数 友元成员函数 友元类 总结 概述 类的友元函数 (friend) 是定义在类外部, 但是有权限访问类的所有私有 (private) 成员和保护 (protected) 成员. 友元 我们先来复习一下公有成员和私有成员的概念: 公有成员 (public) : 在类外可以访问 私有成员 (private): 只有本类中的函数可以访问 友元 (friend) 可以访问与其有好友关系的类中的私有成员 (有限制的共享). 友元包括友元函数和友元类: 友元函数: 如果在本类

  • C++中模板(Template)详解及其作用介绍

    目录 概述 函数模板 类模板 模板类外定义成员函数 类库模板 抽象和实例 概述 模板可以帮助我们提高代码的可用性, 可以帮助我们减少开发的代码量和工作量. 函数模板 函数模板 (Function Template) 是一个对函数功能框架的描述. 在具体执行时, 我们可以根据传递的实际参数决定其功能. 例如: int max(int a, int b, int c){ a = a > b ? a:b; a = a > c ? a:c; return a; } long max(long a, l

  • C++中运算符重载详解及其作用介绍

    目录 概述 函数重载 运算符重载 C++ 的运算符 重载运算符的规则 成员函数实现 Complex 加法 运算符重载的方法 多种实现方法 实现 operator+= 三种运算符重载函数 成员函数实现 友元函数实现 输出结果 重载单元运算符 例子 重载二元运算符 例子 重载 I/O 插入运算符 << 提取运算符 >> 总结 概述 运算符重载 (Operator Overloading) 函数重载 重载: 将同一名字重新赋予新的含义. 函数重载: 对一个函数赋予新的含义, 使之实现新功

随机推荐