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++中类的多态与虚函数的使用 类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持 ...
随机推荐
- Java入门 - 面向对象 - 02.重写与重载
原文地址:http://www.work100.net/training/java-override-overload.html 更多教程:光束云 - 免费课程 重写与重载 序号 文内章节 视频 1 ...
- python函数和lambda表达式学习笔记
1. python函数 不同于其他语言,python支持函数返回多个值 为函数提供说明文档:help(函数名)或者函数名.__doc__ def str_max(str1, str2): ''' 比较 ...
- 01Java语言基础
[实验任务四]: 1.程序设计思想 根据RandomStr.java,随机生成6位字母,在对话框中输出,用户根据随机生成的验证码对应输入,程序根据用户输入的内容与系统随机生成的验证码字符比较,若相等, ...
- AMD R5 2400G插帧教程
最近买的小主机带的是AMD R5 2400G显卡,支持AMD的插帧技术,Sandeepin肯定要体验一把效果. BlueskyFRC 按照网上的教程配置,似乎2400G显卡驱动里没有AMD Fluid ...
- [总结]ACM模拟总结
1.心态一定要稳,千万不要慌. 2.内部交流要多点,说不定就讨论出有用的性质了. 3.题目细节一定要想清楚. 4.一道题绝对不能让多个人来写. 5.英语要好好学.
- Windows10 企业版激活
今天同大家分享一个Windows自带的激活方法(注:适用于win10 企业版 2019长期服务版,其他版本自行测试) 1.首先确保电脑网络通畅(不需要梯子) 2.以管理员方式运行cmd输入: slmg ...
- EXCEL的VBA(宏)编程
EXCEL的VBA编程 杨康需要我完成的需求 第一列是名称 第二列是甲方账户 第三列是甲方金额 第四列是乙方账户 第五列是乙方金额 第六列是true或false 第七列备注 需求 开始时数据对齐的,如 ...
- JSP-导入taglib 出现classNotFound异常
案例 前端登录跳转到指定jsp,报classNoFoundException,原因是页面导入 <%@ taglib uri="http://java.sun.com/jsp/jstl/ ...
- 吐血推荐珍藏的IDEA插件
之前给大家推荐了一些我自己常用的VS Code插件,很多同学表示很受用,并私信我说要再推荐一些IDEA插件.作为一名职业Java程序员/业余js开发者,我平时还是用IDEA比较多,所以也确实珍藏了一些 ...
- winsocket入门学习
参考资料:http://c.biancheng.net/cpp/socket/ http://www.winsocketdotnetworkprogramming.com/ socket 是" ...