C++学习笔记:08 多态性
基本概念
多态性
具体的讲,在面向对象程序设计中,指同样的方法被不同对象执行时会有不同的执行效果。
多态的实现
绑定机制
绑定是将一个标识符名和一个存储地址联系在一起的过程
静态多态性:编译时的多态通过静态绑定实现,例如 函数的重载
绑定工作在编译连接阶段完成
动态多态性:运行时的多态通过动态绑定实现,例如 虚函数
绑定工作在程序运行阶段完成
运算符重载
函数重载就体现了静态多态性。运算符也是同样的符号经过重载可以作用于不同的数据类型,可以为类重载不同的操作符,使同一个运算符作用于不同类型的数据时导致不同的行为。例如为复数类重载+,-,*,/等基本操作符,实现复数基本运算法则。
C++ 几乎可以重载全部的运算符,而且只能够重载C++中已经有的。不能重载的运算符:
“.”、“.*”、“::”、“?:”
重载之后运算符的优先级和结合性都不会改变
重载为类的非静态成员函数
重载为非成员函数
重载为成员函数
函数类型 operator 运算符(形参)
{
函数定义
}
双目运算符重载规则:
运算所需变量为两个的运算符叫做双目运算符,例如:+,-,*,等等
//B为双目运算符,obj1,obj2为类对象
obj1 B obj2
/*等效于*/
obj1.operator B(obj2)
例如:
#include <iostream>
using namespace std;
class Complex {
private:
double real, image;
public:
Complex(double r = 0., double i = 0.) :real(r), image(i) {}
Complex operator +(const Complex& c2) const; //重载+
Complex operator -(const Complex& c2) const; //重载-
friend ostream& operator <<(ostream& os, const Complex& c2);
};
Complex Complex::operator +(const Complex& c2) const {
return Complex(this->real + c2.real, this->image + c2.image);
}
Complex Complex::operator -(const Complex& c2) const {
return Complex(this->real - c2.real, this->image - c2.image);
}
ostream& operator <<(ostream& os, const Complex& c2) {
os << "(" << c2.real << "," << c2.image << ")" << endl;
return os;
}
int main() {
Complex c1(1, 1), c2(2, 3);
Complex c3 = c1 + c2;
cout << c1 << c2 << c3;
return 0;
}
单目运算符重载
运算所需变量为一个的运算符叫做单目运算符,例如:++,--,等等
前置单目运算符
//U为单目运算符,obj1为类对象
U obj1
/*等效于*/
obj1.operator U()
后单目运算符
与前置相比,为了加以区分,后置单目运算符会具有一个int类型形参
//U为单目运算符,obj1为类对象
U obj1
/*等效于*/
obj1.operator U(0)
例如:
#include <iostream>
using namespace std;
class Count {
private:
int num;
public:
Count(int _num) :num(_num) {}
Count& operator ++();//前置++
Count operator ++(int);//后置++
friend ostream& operator << (ostream& os, const Count c);
};
Count& Count::operator ++ () {
num++;
return *this;
}
Count Count::operator ++ (int) {
Count old = *this;
++(*this); //调用前置“++”运算符
return old; //返回++之前的值
}
ostream& operator<<(ostream& os, const Count c){
os << "Current Num = " << c.num << endl;
return os;
}
int main() {
Count myCount(10);
cout << "myCount : ";
cout << myCount;
cout << "myCount++: ";
cout << myCount++;
cout << "++myCount: ";
cout << ++myCount;
return 0;
}
/*
myCount : Current Num = 10
myCount++: Current Num = 10
++myCount: Current Num = 12
*/
运算符重载为非成员函数
双目运算符的左操作数不是对象,比如,对于以上复数类,需要实现double+Complex类型运算的功能,则需要在类外实现运算符重载函数。
例如:在complex类中,将+与<<重载为类外操作符。
#include <iostream>
using namespace std;
class Complex {
private:
double real, image;
public:
Complex(double r = 0., double i = 0.) :real(r), image(i) {}
friend Complex operator + (double c1, const Complex& c2); //重载类外+
friend ostream& operator <<(ostream& os, const Complex& c2);
};
Complex operator+(double c1, const Complex& c2) {
return Complex(c1 + c2.real, c2.image);
}
ostream& operator <<(ostream& os, const Complex& c2) {
os << "(" << c2.real << "," << c2.image << ")" << endl;
return os;
}
int main() {
double c1 = 10;
Complex c2(1, 1);
Complex c3 = c1 + c2;
cout << "double " << c1 << endl;
cout << "complex " << c2 << "double +complex " << c3;
return 0;
}
/*
double 10
complex (1,1)
double +complex (11,1)
*/
虚函数
通过virtual关键词,让程序能在运行时根据指针指向的实际对象,找到该对象的函数。使用虚函数实现动态绑定,也就是C++的动态多态性。
虚函数特点
用virtual关键字说明的函数
虚函数是实现运行时多态性基础
C++中的虚函数是动态绑定的函数
虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。
一般成员函数可以是虚函数
构造函数不能是虚函数
析构函数可以是虚函数
例如:例子中的fun()函数,传递不同类型的对象,调用特定对象的display()函数
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void display() const { cout << "Base1::display()" << endl; }; //虚函数
};
class Base2 :public Base1 {
public:
virtual void display() const { cout << "Base2::display()" << endl; }
};
class Derived : public Base2 {
public:
virtual void display() const { cout << "Derived::display()" << endl; }
};
void fun(Base1* ptr) {
ptr->display();
}
int main() {
Base1 base1;
Base2 base2;
Derived derived;
fun(&base1);
fun(&base2);
fun(&derived);
derived.Base1::display();
derived.Base2::display();
derived.display();
return 0;
}
/*
Base1::display()
Base2::display()
Derived::display()
Base1::display()
Base2::display()
Derived::display()
*/
虚函数
虚函数的声明
virtual 函数类型 函数名(形参表);
- 虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。
- 在派生类中可以对基类中的成员函数进行覆盖
- 虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。
virtual 关键字
- 派生类可以不显式地用virtual声明虚函数,这时系统就会用以下规则来判断派生类的一个函数成员是不是虚函数:
- 该函数是否与基类的虚函数有相同的名称、参数个数及对应参数类型
- 该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针、引用型的返回值;
- 如果从名称、参数及返回值三个方面检查之后,派生类的函数满足上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数
- 派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。
- 一般习惯于在派生类的函数中也使用virtual关键字,以增加程序的可读性。
虚析构函数
为什么需要虚析构函数?
- 可能通过基类指针删除派生类对象
- 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
例子:虚析构函数
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
Derived() { p = new int(0); }
virtual ~Derived() { cout << "Derived destructor" << endl; delete p; }
private:
int* p;
};
void fun(Base* b) { delete b;}
int main() {
Base* base = new Base();
Base* derived = new Derived();
fun(base);
fun(derived);
return 0;
}
/*
Base destructor
Derived destructor
Base destructo
*/
虚函数的实现
虚表与动态绑定
虚表
每个多态类有一个虚表(virtual table)
虚表中有当前类的各个虚函数的入口地址
每个对象有一个指向当前类的虚表的指针(虚指针vptr)
动态绑定的实现
构造函数中为对象的虚指针赋值
通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
通过该入口地址调用虚函数纯虚类,定义统一的函数接口
抽象类
纯虚函数
纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:
virtual 函数类型 函数名(参数表) = 0;
抽象类
带有纯虚函数的类称为抽象类,纯虚类作为基类,一般用于定义统一的函数对外接口。
class 类名
{
virtual 类型 函数名(参数表)=0;
//其他成员……
}
抽象类作用
抽象类为抽象和设计的目的而声明
对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
注意
抽象类只能作为基类来使用。
不能定义抽象类的对象。(纯虚函数没有函数具体定义,无法创建对象)
例如:
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void display() const = 0; //纯虚函数
};
class Base2 : public Base1 {
public:
virtual void display() const { cout << "Base2::display()" << endl; } //覆盖基类的虚函数
};
class Derived : public Base2 {
public:
virtual void display() const { cout << "Derived::display()" << endl; } //覆盖基类的虚函数
};
void fun(Base1* ptr) { //定义引用的通用接口
ptr->display();
}
int main() {
Base2 base2;
Derived derived;
fun(&base2);
fun(&derived);
return 0;
}
/*
Base2::display()
Derived::display()
*/
override 与 final关键字
override
多态行为的基础:基类声明虚函数,继承类声明一个函数覆盖该虚函数
覆盖要求: 函数签名(signatture)完全一致
函数签名包括:函数名 参数列表 const(通常可能会忽略const,导致虚函数没有被覆盖)
下列程序就仅仅因为疏忽漏写了const,导致多态行为没有如期进行
final:
C++11提供的final,用来避免类被继承,或是基类的函数被改写
//类限定final
struct Base1 final { };
struct Derived1 : Base1 { }; // 编译错误:Base1为final,不允许被继承
//类成员函数限定final
struct Base2 {
virtual void f() final;
};
struct Derived2 : Base2 {
void f(); // 编译错误:Base2::f 为final,不允许被覆盖
};
C++学习笔记:08 多态性的更多相关文章
- 机器学习实战(Machine Learning in Action)学习笔记————08.使用FPgrowth算法来高效发现频繁项集
机器学习实战(Machine Learning in Action)学习笔记————08.使用FPgrowth算法来高效发现频繁项集 关键字:FPgrowth.频繁项集.条件FP树.非监督学习作者:米 ...
- C++ GUI Qt4学习笔记08
C++ GUI Qt4学习笔记08 qtc++signal图形引擎文档 本章介绍Qt的二维图形引擎,Qt的二维图形引擎是基于QPainter类的.<span style="colo ...
- CSS学习笔记08 浮动
从CSS学习笔记05 display属性一文中,我们知道div是块元素,会独占一行,即使div的宽度很小,像下面这样 应用display属性的inline属性可以让div与div共享一行,除了这种方法 ...
- [Golang学习笔记] 08 链表
链表(Linked list)是一种常见数据结构,但并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针. 由于不必须按顺序存储,链表在插入的时候可以达到O(1),比顺序表快得多,但是查 ...
- [原创]java WEB学习笔记08:HttpServletRequest & ServletRequest
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- Bash脚本编程学习笔记08:函数
官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...
- AMQ学习笔记 - 08. Spring-JmsTemplate之发送
概述 JmsTemplate提供了3组*3,共计9个发送用的方法. 发送的方法有3组: 基本的发送 转换并发送 转换.后处理再发送 必需的资源 必需的资源有: javax.jms.Connecti ...
- knockoutJS学习笔记08:表单域绑定
前面的绑定都是用在基本标签上,这章主要讲表单域标签的绑定. 一.value 绑定 绑定标签:input text.textarea. <p>用户名:<input type=" ...
- Python学习笔记08
正则表达式包re match,search,sub re.match(pattern, string, flags=0) re.search(pattern, string, flags=0) r ...
- 【EF学习笔记08】----------加载关联表的数据 显式加载
显式加载 讲解之前,先来看一下我们的数据库结构:班级表 学生表 加载从表集合类型 //显示加载 Console.WriteLine("=========查询集合===========&quo ...
随机推荐
- 重启网络服务 network 出现问题
2021-08-24 地址冲突了,因为想要设置成静态 ip 一直都不对,情急之下就将本地 ip 设置成了虚拟机的 ip,故出现此错误 后将地址改掉,重启网络服务就没有错误了 一开始我设置的虚拟网卡 n ...
- group by分组查询
有如下数据: 一个简单的分组查询的案例 按照部门编号deptno分组,统计每个部门的平均工资. select deptno,avg(sal) avgs from emp group by deptno ...
- C# ASP.NET RAZOR 链接SQLSERVER
@using System.Data.SqlClient; @using System.Data;//必须引用 <html> <body> <h1>Learn Sq ...
- [考试总结]noip模拟40
最近真的是爆炸啊... 到现在还是有不少没改出来.... 所以先写一下 \(T1\) 的题解.... 送花 我们移动右端点,之后我们用线段树维护全局最大值. 之后还要记录上次的位置和上上次的位置. 之 ...
- nRF52832蓝牙iBeacon广播
开发环境 SDK版本:nRF5_SDK_15.0.0 芯片:nRF52832-QFAA 蓝牙iBeacon实现 iBeacon的核心就是广播,不需要进行连接,通过在广播包中插入信息然后广播出去. 广播 ...
- Python - 面向对象编程 - 小实战(2)
需求 小明和小美都爱跑步 小明体重 75 公斤 小美体重 45 公斤 每次跑步会减肥 0.5 公斤 每次吃东西体重增加 1 公斤 需求分析 小明.小美都是一个具体的对象,他们都是人,所以应该抽象成人类 ...
- C#取汉字首字母,汉字全拼
使用类库为 https://gitee.com/kuiyu/dotnetcodes/tree/master/DotNet.Utilities/%E6%B1%89%E5%AD%97%E8%BD%AC%E ...
- SpringMVC基于注解开发
一. 1.配置 适配器的作用就是规定怎么调控制器: 2.使用 controller代码 三.
- golang isPowerOfTwo判断是否是2的幂
iota.go strconv包 func isPowerOfTwo(x int) bool { return x & (x -1) } 了解n&(n-1)的作用如下: n& ...
- 1.24学习总结——HTML常见标签
HTML 标签简写及全称 下表列出了 HTML 标签简写及全称: 标签 英文全称 中文说明 a Anchor 锚 abbr Abbreviation 缩写词 acronym Acronym 取首字母的 ...