C++11学习 virtual(虚函数)的用法

Virtual虚函数

  • 在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。
  • 多态性:其含义就是多种形式;将具有继承关系的多种类型称之为多态模型,因为使用者可以使用这种类型的多种形式,但是不需要在意他们之间的差异。归根结底,引用或指针的静态类型与动态类型的不一致是C++语言支持多态性的根本原因。
  • 在派生类中覆盖了某一个虚函数,可以再一次使用virtual关键字指出该函数的性质。如果将一个函数声明为虚函数,那么这个函数在所有的派生类别中都是虚函数。一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须要和被覆盖的函数完全一致。同样,派生类中的虚函数返回的类型必须和基类的类型是一致的,但是这个存在一个例外。当类的虚函数返回的类型是类本身的指针或者引用的时候,上述规则无效。
  • 什么是虚函数呢?虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。 ——摘自MSDN
#include <cstring>
#include <string>
#include <iostream>
#include <stdio.h>
#include <conio.h>
using namespace std;class Parent{public:char data[20];void Function1();virtual void Function2(); //这里的Function2是虚拟函数}parent;void Parent::Function1() {printf("This is parent,function1\n");
}void Parent::Function2() {printf("This is parent,function2\n");
}class Child:public Parent{void Function1();void Function2();
}child;void Child::Function1(){printf("This is child,function1\n");
}void Child::Function2() {printf("This is child,function2\n");
}
int main(int argc,char* argv[]){Parent *p;//定义了一个基类指针if(_getch() == 'c'){p = &child;  // 如果用户输入一个小写的字母c,指向继承类对象}else{p = &parent; // 否则指向一个基类对象}p->Function1();  // 这里编译时会直接给出Parent::Function1()的入口地址p->Function2();  // 注意这里,执行的是哪一个Function2?}

  • 为什么会有第一行的结果呢?因为是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以看到了第一行的结果。
  • 那么第二行的结果又是怎么回事呢?注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。
  • 如果在运行上面的程序时任意输入一个非c的字符,结果如下:

  • 请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2还是继承类中的Function2,这就是虚函数的作用。在MFC中,很多类都是需要继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。
  • PS:一定要注意“静态联翩 ”和“ 动态联编 ”的区别,对于我来说,若没有在VC6.0中亲自去测试,凭自己的感觉,当在键盘中输入“c”时,因为虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实,它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。第二行中调用了子类的function2,完全是因为virtual 的功能,virtual实现了动态联编,它可以在运行时判断指针指向的对象,并自动调用相应的函数。当然,如果执行的是p=&parent; 这一句,该指针很明显的是指向父类,那么肯定调用的是父类的方法 

final和override说明符

  • 派生类如果定义了一个函数与基类的虚函数的名字相同但是形参列表不同,这仍然是一个合法的行为,需要使用关键字override来说明派生类中的虚函数。
  • 如果将某个函数指定为final,则之后任何尝试覆盖该函数的操作都会引发错误。

虚函数和默认实参

  • 虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

回避虚函数的机制

  • 如果希望对于虚函数的调用不是动态的绑定,而是强迫其执行虚函数的某一个特定的版本。可以使用作用域运算符来实现
  • double undiscounted = baseP->Quote::net_price(43);//强行调用基类中定义的函数版本,而不管baseP的动态类型是什么
  • 通常情况下,只有成员函数(或友元函数)中的代码才需要使用作用域运算符来回避虚函数的机制。如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行的时候该调用会被解析成对于派生类的版本自身的调用,从而导致无限的递归。

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/447095.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++ const

函数名称不单单是函数名 const 补充内容 还有 const 参数类型 函数后头const 只能在成员函数如果const对象 但是我没通过const成员函数 就会报错

C++11学习 新特性之 “=default” 、“=delete”

文章目录 1、 default 和delete 概述2、 类与默认函数3、 使用“delete”来限制函数生成4、 “default”使用范围 1、 default 和delete 概述 default、delete 是C11的新特性&#xff0c;分别为&#xff1a;显式缺省(告知编译器生成函数默认的缺省版本)和显式删除(告知编译器…

C++学习 优雅的实现对象到文件的序列化/反序列化 关键字serialize

需要使用到序列化场景的需求 在写代码的过程中&#xff0c;经常会需要把代码层面的对象数据保存到文件&#xff0c;而这些数据会以各种格式存储&#xff0e;例如&#xff1a;json&#xff0c;xml&#xff0c;二进制等等&#xff0e;二进制&#xff0c;相比json&#xff0c;xml…

C++代码注释详解

常用注释语法 注释写在对应的函数或变量前面。JavaDoc类型的多行注释风格如下&#xff1a; /** * 这里为注释. */ 一般注释中有简要注释和详细注释&#xff0c;简要注释有多种标识方式&#xff0c;这里推荐使用brief命令强制说明&#xff0c;例如&#xff1a;/** * brief 这里…

段错误:SIGSEGV

SIGSEGV是在访问内存时发生的错误&#xff0c;它属于内存管理的范畴 SIGSEGV是一个用户态的概念&#xff0c;是操作系统在用户态程序错误访问内存时所做出的处理。 当用户态程序访问&#xff08;访问表示读、写或执行&#xff09;不允许访问的内存时&#xff0c;产生SIGSEGV。 …

web3 0.2.x 和 1.x.x版本之间的差异

版本差异 单位转换 0.2.x web3.fromWei(13144321,ether) 1.x.x web3.utils.fromWei(13144321,ether)1.0以后的版本使用了大量的Promise&#xff0c;可以结合async/await使用&#xff0c;而0.20版本只支持回调函数

如何提高阅读源码的能力并且手撕源码

怎么有效的手撕代码呢&#xff1f; 把代码跑起来把代码一个片段拿出来使用画出代码运行的流程图一行一行的搬运在看源码的情况下写出类似的demo

并发和并行的区别简单介绍

并发和并行 并发是关于正确有效地控制对共享资源的访问 同时完成多个任务。在开始处理其他任务之前&#xff0c;当前任务不需要完成。并发解决了阻塞发生的问题。当任务无法进一步执行&#xff0c;直到外部环境发生变化时才会继续执行。最常见的例子是I/O&#xff0c;其中任务…

手撕源码 alloc

怎么有效的手撕代码呢&#xff1f; gnu gcc 2.9 的 内存池 把代码跑起来把代码一个片段拿出来使用画出代码运行的流程图一行一行的搬运在看源码的情况下写出类似的demo 第三步&#xff1a; 第五步: // 这个头文件包含一个模板类 allocator&#xff0c;用于管理内存的分配、…

Algorand的共识协议及其核心的优势

Algorand 设计的初衷 Algorand 想解决的核心问题是&#xff1a;去中心化网络中低延时&#xff08;Latency&#xff09;和高置信度&#xff08;Confidence&#xff09;之间的矛盾。其中&#xff0c;延时指从发起交易到确认交易所需要的时间&#xff1b;置信度指的是发出的交易不…

手撕源码 SQL解析器 sqlparser

怎么有效的手撕代码呢&#xff1f; 源代码&#xff1a;https://github.com/hyrise/sql-parser 把代码跑起来把代码一个片段拿出来使用画出代码运行的流程图一行一行的搬运在看源码的情况下写出类似的demo

针对Algorand所使用的密码相关技术细节进行介绍

关键概念 VRF: 可验证随机函数。简单来说是&#xff1a;vrf,Proof VRF(sk,seed)&#xff0c;sk为私钥&#xff0c;seed为随机种子&#xff1b;通过Verify(proof,pk,seed)验证vrf的合法性。cryptographic sorition: 根据用户本轮的VRF值&#xff0c;自身的权重以及公开的区块链…

内存池的实现1 :重载

#ifndef KSTD_ALLOCATOR_H_ #define KSTD_ALLOCATOR_H_// 这个头文件包含一个模板类 allocator&#xff0c;用于管理内存的分配、释放&#xff0c;对象的构造、析构 // 暂不支持标准库容器 todo::支持萃取#include <new> // placement new #include <cstddef>…

对于Algorand的介绍

介绍 Algorand具有能耗低、效率高、民主化、分叉概率极低、可拓展性好等优点&#xff0c;旨在解决现有区块链项目存在的“不可能三角”&#xff08;高度可扩展的、安全的、去中心化&#xff09;问题。Algorand由MIT教授、图灵奖得主Silvio Micali发起&#xff0c;拥有MIT区块链…

内存池的实现2 类专用的内存适配器

B类增加了嵌入指针 #include<new> #include<ctime> #include<iostream> #include<cstdio> class A { public:A() {printf("next%p\n", next);};static void* operator new(size_t size);static void operator delete(void* phead);static i…

C++学习 高级编程

C 文件和流 到目前为止&#xff0c;目前使用最为广泛的是 iostream 标准库&#xff0c;它提供了 cin 和 cout 方法分别用于从标准输入读取流和向标准输出写入流。以下将介绍从文件读取流和向文件写入流。这就需要用到 C 中另一个标准库 fstream&#xff0c;它定义了三个新的数…

内存池的实现3 固定大小的allocator单线程内存配置器

如果我们想使内存管理器用于其他大小不同的类该怎么办呢&#xff1f;为每一个类重复管理逻辑显然是对开发时间的不必要浪费。如果我们看一下前面内存管理器的实现&#xff0c;就会明显地看出内存管理逻辑实际上独立于特定的类 有关的是对象的大小一这是内存池模板实现的良好候选…

C++中文版本primer 第二章变量和基本类型 学习笔记

2.2变量 2.2.1 变量定义 列表初始化 定义一个名字为units_sold的int变量并初始化为0 int units_sold 0; int units_sold {0}; int units_sold{0}; int units_sold(0); C11 用花括号来初始化变量&#xff0c;上面这个步骤也称之为列表初始化。这种初始化有一个重要的特点&…

内存池中的嵌入式指针

嵌入式指针 可以union改struct 内存分配后 next指针就没用了 直接作为数据空间比较省内存 因为对指针指向的内存存储的时候 编译器是不管你是什么类型的 &#xff0c;这里有道练习题可以对指针的概念稍微理解一下&#xff1a; #include <iostream> using std::cout; us…