C++中的多态及虚函数大总结
多态是C++中很关键的一部分,在面向对象程序设计中的作用尤为突出,其含义是具有多种形式或形态的情形,简单来说,多态:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为。即用一个函数名可以调用不同内容的函数。
多态可分为静态多态与动态多态,静态多态的实现在于静态联编,关联出现在编译阶段而非运行期,用对象名或者类名来限定要调用的函数,称为静态关联或静态联编。常见有三种方法
(1)函数多态(函数与运算符的重载);
(2)宏多态;
(3)模板多态。
而对于动态多态的实现是运行阶段把虚函数和类对象绑定在一起的,即动态联编,动态绑定。具体的说,通过一个指向基类的指针调用虚成员函数的时候,运行时系统将能够根据指针所指向的实际对象调用恰当的成员函数实现。
当编译器使用动态绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字,这样的函数称之为虚函数(virtual functions)。根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用派生类的成员函数。如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。 而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。这样,通过基类类型的指针,就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行过程的多态。可看这个例子:
#include <iostream>
using namespace std;
class A
{
public :
void print( ) { cout << “A::print”<<endl ; }
};
class B:public A
{
public :
void print( ) { cout << “B::print” <<endl; }
};
int main( )
{
A a;
B b;
A *pA = &b;
pA->print( );
return 0;
}
此时输出A::print ,若将A类中的print( )函数声明为virtual,则此时就为动态联编 程序执行结果为: B::print。
注意点1:构造函数和静态成员函数不能是虚函数:静态成员函数不能为虚函数,是因为virtual函数由编译器提供了this指针,而静态成员函数没有this指针,是不受限制于某个对象;构造函数不能为虚函数,是因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。
class A
{
public:
virtual A( ) {}; //error
};
class B
{
public:
virtual static void func( ) {}; //error “virtual”不能和“static”一起使用
};
int main( )
{
B b; //报错
A *a=&b;
return 0;
}
注意点2:派生类对象的指针可以直接赋值给基类指针,如上面中的A *a=&b;*a可以看作一个类A的对象,访问它的public成员。通过强制指针类型转换,可以把a转换成B类的指针: a = &b; aa = static_cast< B * > a。此外指向基类的指针,可以指向它的公有派生的对象,但不能指向私有派生的对象,对于引用也是一样的。
class B
{
public:
virtual void print() { cout<<"Hello B"<<endl; }
};
class D : private B
{
public:
virtual void print() { cout<<"Hello D"<<endl; }
};
int main()
{
D d;
B* pb = &d; //转换存在,无法访问
pb->print();
B& rb = d; //转换存在,无法访问
rb.print();
return 0;
}
注意点3:构造函数中调用virtual函数 ,在构造函数和析构函数中调用虚函数时:他们调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。
class Transaction
{
public:
Transaction( ){ logTransaction( ); }
virtual void logTransaction( ) = 0;
};
class BuyTransaction: public Transaction
{
public:
int buyNum;
virtual void logTransaction( ) { cout<< "This is a BuyTransaction"; }
};
class SellTransaction: public Transaction
{
public:
int sellNum;
virtual void logTransaction( )
{
cout<< "This is a SellTransaction";
}
};
int main( )
{
BuyTransaction b;
SellTransaction s;
}
以上代码应该会有报错提示,
若将基类的Transaction中虚函数logTransaction改为:
virtual void logTransaction( ) { cout<< "This is a Transaction"<<endl; };
程序执行结果为: This is a Transaction
This is a Transaction
注意点4:普通成员函数中调用虚函数,在普通成员函数中调用虚函数,则是动态联编,是多态。
#include <iostream>
using namespace std;
class Base
{
public:
void func1( ) { func2( ); }
void virtual func2( ) { cout << "Base::func2( )" << endl; }
};
class Derived:public Base
{
public:
virtual void func2( ) { cout << "Derived:func2( )" << endl; }
};
int main( )
{
Derived d;
Base * pBase = & d;
pBase->func1( );
return 0;
}
因为,Base类的func1( )为非静态成员函数,编译器会给加一个this指针: 相当于 void func1( ) { this->func2( ); } 编译这个函数的代码的时候,由于func2( )是虚函数,this是基类指针,所以是动态联编。上面这个程序运行到func1函数中时, this指针指向的是d,所以经过动态联编,调用的是Derived::func2( )。
注意点5:虚函数的访问权限,如果基类定义的成员虚函数是私有的,我们来看看会怎么样
class Base{
private:
virtual void func( ) { cout << "Base::func( )" << endl; }
};
class Derived : public Base {
public:
virtual void func( ) { cout << "Derived:func( )" << endl; }
};
int main( )
{
Derived d;
Base *pBase = & d;
pBase->func( ); // 无法访问, private 成员(在“Base”类中声明)
return 0;
}
对于类的private成员 ,只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.所以即使是虚函数,也没办法访问。但是!派生类虚函数的可访问性与继承的方式和虚函数在基类的声明方式有关(public,或private)与派生类声明的方式无关(如public继承,但声明为private,但仍可访问),把上面的public与private互换位置,程序可以正常运行,并输出Derived:func( )。
注意点6:虚函数与友元,先看代码
class A;
class B
{
private:
int x;
void print() { cout<<x<<endl; }
public:
B(int i = 0) { x = i; }
friend class A;
};
class A
{
public:
void func(B b){ b.print(); }
};
class C : public A
{
};
class D: public B
{
public:
D(int i):B(i){}
};
int main()
{
D d(99);
A a;
C c;
a.func(d);
c.func(d);
return 0;
}
程序执行结果为:99 99
由第一个99可知,A是B的友元类,A中的所有成员函数都为B的友元函数,可访问B的私有成员函数。友元类A不是基类B的一部分,更不是派生类D的一部分。从上例看,友元视乎能够被继承,基类的友元函数或友元类能够访问派生类的私有成员。但public继承是一种“is a”的关系,即一个派生类对象可看成一个基类对象。所以,上例中不是基类的友元被继承了,而是派生类被识别为基类了。而第二个99说明一个友元类的派生类,可以通过其基类接口去访问设置其基类为友元类的类的私有成员,也就是说一个类的友元类的派生类,某种意义上还是其友元类。
注意点7:析构函数通常是虚函数。虚析构函数保证了在析构时,避免只调用基类析构函数而不调用派生类析构函数的情况,保证资源正常释放,避免了内存释放。只有当一个类被用来作为基类的时候,才会把析构函数写成虚函数。
以上为个人总结,有不妥的地方欢迎指出。
C++中的多态及虚函数大总结的更多相关文章
- C++中的多态与虚函数的内部实现
1.什么是多态 多态性可以简单概括为“一个接口,多种行为”. 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可 ...
- 详解C++中的多态和虚函数
一.将子类赋值给父类 在C++中经常会出现数据类型的转换,比如 int-float等,这种转换的前提是编译器知道如何对数据进行取舍.类其实也是一种数据类型,也可以发生数据转换,但是这种转换只有在 子类 ...
- 【转载】 C++多继承中重写不同基类中相同原型的虚函数
本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...
- C++多态、虚函数、纯虚函数、抽象类、虚基类
一.C++多态 C++的多态包括静态多态和动态多态.静态多态包括函数重载和泛型编程,动态多态包括虚函数.静态多态是指在编译期间就可以确定,动态多态是指在程序运行时才能确定. 二.虚函数 1.虚函数为类 ...
- C++多态、虚函数、纯虚函数、抽象类
多态 同一函数调用形式(调用形式形同)可以实现不同的操作(执行路径不同),就叫多态. 两种多态: (1)静态多态:分为函数重载和运算符重载,编译时系统就能决定调用哪个函数. (2)动态多态(简称多态) ...
- 转 C++中不能声明为虚函数的有哪些函数
传送门 C++中不能声明为虚函数的有哪些函数 常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数 ...
- C++中的继承与虚函数各种概念
虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...
- C++ 基础语法 快速复习笔记(3)---重载函数,多态,虚函数
1.重载运算符和重载函数: C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它 ...
- 《挑战30天C++入门极限》C++中类的多态与虚函数的使用
C++中类的多态与虚函数的使用 类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持 ...
随机推荐
- redis 5种类型
redis可以不严谨的看成: redis: { name: value, name: value, } value的数据类型: 1.字典 2.列表 3.字符串 4.集合 5.有序集合 注意: redi ...
- 利用Springmvc的AbstractXlsxView下载Excel文件
设计一个模型,在针对多中数据类型进行拓展 public abstract class ExcelView extends AbstractXlsxView { public CellStyle cel ...
- context:component-scan标签的诠释
XML中配置context:component-scan时,spring会自动的扫描这个包下被这些注解标识的类@Component,@Service,@Controller,@Repository,同 ...
- 高通量计算框架HTCondor(二)——环境配置
目录 1. 概述 2. 安装 3. 结果 4. 相关 1. 概述 HTCondor是开源跨平台的分布式计算框架,在其官网上直接提供了源代码和Windows.Linux以及MacOS的安装包.因为平台限 ...
- 造轮子-toast组件的实现(下)
1.解决 toast 中传入 html 的问题,通过假的 slot 来实现 // plugins.js toast.$slots.default = [message] // toast.vue &l ...
- spring.net 基础 1
Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序 1: 在2004年初,Martin Fowler曾经问他网站的读者:当我们谈到控制反转时,"问题是, ...
- n-tier waf 41 project 层真够多
ps: http://waf.codeplex.com/releases/view/618696
- Ubuntu16手动安装OpenStack——nova篇。。转
前言: 本文转自https://www.voidking.com/dev-ubuntu16-manual-openstack-nova/ ,过程非常的详细,作者也说本实验最终失败,因为课程要求我们只要 ...
- JS基础——ATM机终端程序编写(3.0)
利用函数进行代码实现,要点:将每一项操作单独写成一个函数,在需要时进行调用,弄清参数的传递. 创建模拟账户 使用数组创建账户 let user = ["xiaohei", 1234 ...
- JDBC详细说明+使用
JDBC详解 一.相关概念 1.什么是JDBC JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提 ...