1、多态
多态(Polymorphism)是面向对象语言的一种特征,让你能够以类似的方式处理不同类型的对象。
1.1、使用虚函数实现多态行为
可通过Fish指针或Fish引用访问Fish对象,这种指针或引用可指向Fish、Carp等对象。但你不需要知道也不关心它们指向的是哪种对象。可以用下面代码所示:

pFish->Swim();
myFish.Swim();

你希望通过这种指针或引用调用Swim()时,如果它们指向的是Tuna对象,则可像Tuna那样游泳,若指向的是Carp对象,则可以像Carp那样游泳,若指向的是Fish,则可像Fish那样游泳。为此,可在基类Fish中将Swim声明为虚函数;

class Base
{virtual ReturnType FunctionName (parameter List);
};
class Derived
{FunctionName (parameter List);
};

通过使用关键字virtual,可确保编译器调用覆盖版本。也就是说,如果Swim()被声明为虚函数,则将参数myFish(其类型为Fish&)设置为一个Tuna对象时,myFish.Swim()将执行Tuna::Swim(),程序如下所示:

#include<iostream>
#include<string>
using namespace std;
class Fish
{
public:virtual void Swim(){cout << "Fish swims!" << endl;}
};
class Tuna :public Fish
{
public:void Swim(){cout << "Tuna swims!" << endl;}
};
class Carp :public Fish
{
public:void Swim(){cout << "Carp swims!" << endl;}
};
void MakeFishSwim(Fish& InputFish)
{InputFish.Swim();
}
int main()
{Tuna myDinner;Carp myLunch;MakeFishSwim(myDinner);MakeFishSwim(myLunch);return 0;
}

输出为:
Tuna swims!
Carp swims!
首先,根本没有调用Fish::Swim() ,因为存在覆盖版本 Tuna::Swim()和 Carp::Swim() ,它们优先于被声明为虚函数的Fish::Swim()。这很重要,它意味着在MakeFishSwim()中,可通过Fish&参数调用派生类定义的Swim(),而无需知道该参数指向的是哪种类型的对象。
这就是多态:将派生类对象视为基类对象,并执行派生类的Swim()实现。
1.2、为什么需要虚构函数
上面的代码如果加入析构函数释放内存,对于使用new在自由储存区中实例化的派生类对象,如果将其赋值给基类指针,并通过该指针调用delete,将不会调用派生类的析构函数,这可能导致资源未释放、内存泄露等问题,必须引起重视。要避免这种问题,可将基类析构函数声明为虚函数。如下面代码所示:

class Fish
{
public:Fish(){cout<<"construct Fish"<<endl;}virtual ~Fish(){cout<<"Destroy Fish"<<endl;}
};

输出还表明,无论Tuna对象是使用new在自由存储区中实例化的,还是以局部变量的方式在栈中实例化的,构造函数和析构函数的调用顺序都相同。
1.3、抽象基类和纯虚函数
不能实例化的基类被称为抽象基类,这样的基类只有一个用途,那就是从它派生出其他类。在 C++中,要创建抽象基类,可声明纯虚函数。
以下述方式声明的虚函数被称为纯虚函数:

class AbstractBase
{
public:virtual void DoSomething()=0;  //纯虚函数
};
该声明告诉编译器, AbstractBase的派生类必须实现方法DoSomething();
class Derived : public AbstractBase
{
public:void DoSomething(){cout<<"Implemented virtual function"<<endl;}
};

AbstractBase类要求Derived类必须提供虚方法DoSomething()的实现。这让基类可指定派生类中方法的名称和特征(Signature),即指定派生类的接口。
虽然不能实例化抽象基类,但可将指针或引用的类型指定为抽象基类。抽象基类提供了一种非常好的机制,让您能够声明所有派生类都必须实现的函数。抽象基类常被简称为ABC。 ABC有助于约束程序的设计。
1.4、使用虚继承解决菱形问题

如图程序所示,如果没有使用虚继承,则会输出:

Animal constructor
Animal constructor
Animal constructor
Platypus constructor

输出表明,由于采用了多继承,且 Platypus 的全部三个基类都是从 Animal 类派生而来的,因此第38行创建Platypus实例时,自动创建了三个Animal实例。Animal 有一个整型成员——Animal::Age,为方便说明问题,将其声明成了公有的。如果您试图通过Platypus 实例访问 Animal::Age(如第 42 行所示),将导致编译错误,因为编译器不知道您要设置Mammal::Animal::Age、Bird::Animal::Age还是Reptile::Animal::Age。更可笑的是,如果您愿意,可以分别设置这三个属性:

duckBilledp.Mamal::Animal::Age=25;
duckBilledp.Bird::Animal::Age=25;
duckBilledp.Reptile::Animal::Age=25;

显然,鸭嘴兽应该只有一个Age属性,但您希望Platypus类以公有方式继承 Mammal、Bird 和Reptile。解决方案是使用虚继承。如果派生类可能被用作基类,派生它是最好使用关键字virtual:

class Derived1 : public virtual Base
{//members and funnctions
};
class Derived2 : public virtual Base
{//members and funnctions
};

在继承层次结构中,继承多个从同一个类派生而来的基类时,如果这些基类没有采用虚继承,将导致二义性。这种二义性被称为菱形问题(Diamond Problem)。其中的“菱形”可能源自类图的形状。
注意:C++关键字virtual的含义随上下文而异(我想这样做的目的很可能是为了省事),对其含义总结如下:
在函数声明中,virtual意味着当基类指针指向派生对象时,通过它可调用派生类的相应函数。从Base类派生出Derived1和Derived2类时,如果使用了关键字virtual,则意味着再从Derived1和Derived2派生出Derived3时,每个Derived3实例只包含一个Base实例。也就是说,关键字virtual被用于实现两个不同的概念。
1.5、可将复制构造函数声明为虚函数吗
根本不可能实现虚复制构造函数,因为在基类方法声明中使用关键字virtual时,表示它将被派生类的实现覆盖,这种多态行为是在运行阶段实现的。而构造函数只能创建固定类型的对象,不具备多态性,因此C++不允许使用虚复制构造函数。虽然如此,存在一种不错的解决方案,就是定义自己的克隆函数来实现上述目的:

虚函数Clone模拟了虚复制构造函数,但需要显式地调用,如下面程序所示:

输出为:

Tuna swims fast in the sea
Carp swims slow in the lake
Tuna swims fast in the sea
Carp swims slow in the lake

在main()中,第 40~44行声明了一个静态基类指针(Fish*')数组,并各个元素分别设置为新创建的Tuna、Carp、Tuna和Carp对象。注意到myFishes数组能够存储不同类型的对象,这些对象都是从Fish派生而来的。这太酷了,因为为本书前面的大部分数组包含的都是相同类型的数据,如int。如果这还不够酷,您还可以在循环中使用虚函数Fish::Clone将其复制到另一个Fish*'数组(myNewFishes)中,如第48行所示。注意到这里的数组很小,只有4个元素,但即便数组长得多,复制逻辑也差别不大,只需调整循环结束条件即可。第 52 行进行了核实,它通过新数组的每个元素调用虚函数 Swim(),以验证Clone()复制了整个派生类对象。
2、运算符类型和运算符重载
C++运算符从语法层面来看,除使用关键字operator外,运算符与函数几乎没有差别。
return_type operator operator_symbol(……parameter list……)
其中operator_symbol是程序员可以定义的几种运算符之一。C++运算符分为单目运算符和双目运算符。
2.1、单目运算符
在类声明中编写单目前缀递增运算符(++),可采用如下语法:

Date& operator ++ ( )
{//operator implementation codereturn *this;
}

而后缀递增运算符(++)的返回值不同,且有一个输入参数

Date opeator ++ (int)
{//store a copy of the current state of the object,before incrementing dateDate Copy (*this);//operator implementation codereturn Copy;
}

前缀和后缀递减运算符的声明语法与递增运算符类似,只是将声明中的++替换成--。
示例代码如下:

#include<iostream>
#include<string>
using namespace std;
class Date
{
private:int Day;int Month;int Year;
public:Date(int InputDay, int InputMonth, int InputYear):Day(InputDay), Month(InputMonth), Year(InputYear) {};Date& operator ++ (){++Day;return *this;}Date operator ++ (int){Date Copy(Day, Month, Year);++Day;return Copy; }Date& operator -- (){--Day;return *this;}Date operator -- (int){Date Copy(Day, Month, Year);--Day;return Copy;}void DispalyDate(){cout << Day << " / " << Month << " / " << Year << endl;}
};
int main()
{Date Holiday(25, 12, 2011);cout << "The Day Holiday is:" ;Holiday.DispalyDate();++Holiday;cout << "day after:";Holiday.DispalyDate();--Holiday;cout << "day before:";Holiday.DispalyDate();return 0;
}

输出为:

The Day Holiday is:25 / 12 / 2011
day after:26 / 12 / 2011
day before:25 / 12 / 2011

在上面的代码中,如果cout<<Holiday将出现编译错误,因为cout不知如何解读Holiday对象,cout能够很好地显示 const char※,因此,要让 cout能够显示Date对象,只需添加一个返回 const char的运算符:

operator const char*()
{//operator impletmentation that returns a char*
}

所以上述代码可以改为:

#include<sstream>
#include<string>
class Date
{operator const char*(){ostringstream formattedDate;formattedDate<<Day<<" / "<<Month<<" / "<<Year<<endl;DateInString=formattedDate.str();return DateInString.c_str();}
};

就可以直接输出cout<<Holiday。
2.1.2、解除引用运算符和成员选择运算符(->)
智能指针使用了引用运算符和(->)成员选择运算符,需要添加头文件#include<memory>。智能指针类std::unique_ptr实现了上述用法。
2.2、双目运算符
对两个操作数进行操作的运算符称为双目运算符。以全局函数或静态成员函数的方式实现的双目运算符的定义如下:

return_type operator_type(parameter1,parameter2);

以类成员实现的双目运算符如下:

return_type operator_type(parameter);

以类成员的方式实现的双目运算符只接受一个参数,其原因是第二个参数通常是从类属性获得的。
双目加法与双目减法运算符
示例代码如下:

#include<iostream>
#include<string>
using namespace std;
class Date
{
private:int Day,Month,Year;
public:Date(int InputDay, int InputMonth, int InputYear):Day(InputDay), Month(InputMonth), Year(InputYear) {};Date operator + (int DaysToAdd){Date newDate(Day + DaysToAdd, Month, Year);return newDate;}Date operator - (int DayToSub){return Date(Day-DayToSub,Month,Year); }void DispalyDate(){cout << Day << " / " << Month << " / " << Year << endl;}
};
int main()
{Date Holiday(25, 12, 2011);cout << "The Day Holiday is:" ;Holiday.DispalyDate();Date PreviousHoliday(Holiday - 16);cout << "PreviousHoliday is:";PreviousHoliday.DispalyDate();Date NextHoliday(Holiday + 6);cout << "Nextholiday on:";NextHoliday.DispalyDate();return 0;
}

输出为:

The Day Holiday is:25 / 12 / 2011
PreviousHoliday is:9 / 12 / 2011
Nextholiday on:31 / 12 / 2011

运算符提高了类的可用性,但实现的运算符必须合理。
一般运算符都是下面这种代码格式:

void operator += (int DaysToAdd)
{Day+=DaysToAdd;
}

重载等于运算符(==)和不等于运算符(!=)

bool operator == (const Date& compareTo)
{return ((Day==compareTo.Day)&&(Year==compareTo.Year)&&(Month==compareTo.Month));
}
bool operator != (const Date& compareTo)
{return !(this->operator==(compareTo));
}



重载赋值运算符(=)
跟复制构造函数一样,为确保进行深复制,需要提供复制赋值运算符

Classtype& operator = (const Classtype& CopySource)
{if (this != &CopySource)  //protection against copy into self{//Assignment operator impletment}return *this;
}

2.3、下标运算符
下标运算符让您能够像访问数组那样访问类,其典型语法如下:

return_type& operator [] (subscript_type& subscript);

经典代码如下:

const char& operator [] (int Index) const
{if(Index<GetLength())return Buffer[Index];
}

2.4、函数运算符operator
operator()让对象像函数,被称为函数运算符。函数运算符用于标准模板库(STL)中,通常是 STL算法中。其用途包括决策。根据使用的操作数数量,这样的函数对象通常称为单目谓词或双目谓词。
示例代码如下:

#include<iostream>
#include<string>
using namespace std;
class CDisplay
{
public:void operator () (string Input) const{cout << Input << endl;}
};
int main()
{CDisplay mDisplayFuncObject;mDisplayFuncObject("Dispaly this string!");return 0;
}

这个运算符也称为operator()函数,对象CDisplay也称为函数对象或functor。
用于高性能编程的移动构造函数和移动复制函数
移动构造函数和移动赋值运算符乃性能优化功能,属于C++11标准的一部分,旨在避免复制不必要的临时值(当前语句执行完毕后就不再存在的右值)。对于那些管理动态分配资源的类,如动态数组类或字符串类,这很有用。
典型代码如下:

//移动构造函数声明
Myclass(Myclass&& MoveSource)   //重要&&
{PtrResource = MoveSource.PtrResource;MoveSource.PtrResource = NULL;
}
//移动赋值函数
MyClass& operator= (MyClass&& MoveSource)
{if (this != &MoveSource){delete[] PtrResource;PtrResource = MoveSource.PtrResource;MoveSource.PtrResource; = NULL;}
}

C++学习系列笔记(五)相关推荐

  1. Python学习系列(五)(文件操作及其字典)

    Python学习系列(五)(文件操作及其字典) Python学习系列(四)(列表及其函数) 一.文件操作 1,读文件      在以'r'读模式打开文件以后可以调用read函数一次性将文件内容全部读出 ...

  2. [中级]Java命令学习系列(五)——jhat

    转载自 [中级]Java命令学习系列(五)--jhat jhat(Java Heap Analysis Tool),是一个用来分析java的堆情况的命令.之前的文章讲到过,使用jmap可以生成Java ...

  3. 深度学习系列笔记——贰 (基于Tensorflow2 Keras搭建的猫狗大战模型 三)

    深度学习系列笔记--贰 (基于Tensorflow Keras搭建的猫狗大战模型 一) 深度学习系列笔记--贰 (基于Tensorflow Keras搭建的猫狗大战模型 二) 前面两篇博文已经介绍了如 ...

  4. 深度学习系列笔记——贰 (基于Tensorflow Keras搭建的猫狗大战模型 一)

    猫狗大战是著名的竞赛网站kaggle几年前的一个比赛,参赛者得到猫狗各12500张图片,作为训练集,另外还会得到12500张猫和狗的图片,作为验证.最后提交结果至kaggle平台,获得评测分数. 本篇 ...

  5. memcached java 客户端优化,分布式缓存技术memcached学习系列(五)—— memcached java客户端的使用...

    Memcached的客户端简介 我们已经知道,memcached是一套分布式的缓存系统,memcached的服务端只是缓存数据的地方,并不能实现分布式,而memcached的客户端才是实现分布式的地方 ...

  6. WorldWind学习系列十五:如何切割影像和DEM数据及其在WW中的应用配置

    原文转自:http://www.cnblogs.com/wuhenke/archive/2010/01/03/1638499.html WorldWind学习系列十四中我从代码上分析如何加载DEM数据 ...

  7. 管理3.0 学习词汇笔记 五 复杂理论

    词汇管理3.0 学习笔记 五 复杂理论 断言 1.任何复杂问题都有一个清晰,简单但错误的答案.   2.因果律:事情会按计划发生.能计算2061 哈雷重返太阳系,确不能确定下个星期的天气.因素多且关系 ...

  8. 强化学习系列(五):蒙特卡罗方法(Monte Carlo)

    一.前言 在强化学习系列(四):动态规划中,我们介绍了采用DP (动态规划)方法求解environment model 已知的MDP(马尔科夫决策过程),那么当environment model信息不 ...

  9. Java基础学习系列--(五)【迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类】

    第一章 Iterator迭代器 1.1 Iterator接口 在程序开发中,经常需要遍历集合中的所有元素.针对这种需求,JDK专门提供了一个接口java.util.Iterator. 想要遍历Coll ...

最新文章

  1. Xcode 编译有错误却没有任何可查看错误的issues
  2. 购物小票 FoundPrice.java
  3. Android 消息机制 Handler总结
  4. 分布式文件系统研究-fastDFS安装及配置文件说明
  5. Linux manjaro系统安装后无法连接wifi,解决方案
  6. (八)nodejs循序渐进-事件驱动(进阶篇)
  7. pat根据中序遍历和先序遍历_[leetcode/lintcode 题解] 前序遍历和中序遍历树构造二叉树...
  8. webpack4学习之问题一
  9. Tomcat学习总结(19)—— 为什么首选Tomcat作为JavaWeb应用服务器?
  10. 落魄前端,整理给自己的前端知识体系复习大纲(下篇)
  11. 【论文研读】【医学图像分割】【BCRNN】Fine-grained Recurrent Neural Networks for Automatic Prostate Segmentation ...
  12. Kaldi 语音识别基础教程
  13. C#中的IO流操作(FileStream)
  14. 洛谷P1219 八皇后 题解
  15. SQL Server 数据库性能优化(转载)
  16. Python学习笔记之 循环,列表生成式,生成器,迭代器
  17. I2S接口以及Verilog实现数据接收
  18. Xshell使用教程
  19. 小学最简单的计算机微课PPT,小学音乐微课课件
  20. 【Unity】Jay 开发日志(五)——主菜单的创建

热门文章

  1. 问题记录:EntityFramework 一对一关系映射
  2. Java基础学习 -- I/O系统、流
  3. 贪心+单调栈——去除重复字母(Leetcode 316)
  4. 两个局域网如何互联_如何申请使用三大航司的机上wifi服务?
  5. 详述一则DB2 Error Code 1639和SQL State 08001案例诊断和解决方案
  6. 掌握后可为孩子收藏的MySQL入门全套
  7. MySQL-8.0 | 数据字典最强解读
  8. 深入解析:从源码窥探MySQL优化器
  9. 当SQL Server爱上Linux:配置 SQL Server 2017 上的可用性组初体验
  10. keil及iar调试解释