C++ 中的 多态性
一 、多态性
1.多态性概述:多态是指同样的消息被不同类型的对象接受时导致不同的行为
2.多态实现:编译时的多态:在编译的过程中确定了同名操作的具体对象。
运行时的多态:在程序运行过程中动态地确定操作所针对地具体现象。
这种确定操作的具体对象的过程就是绑定——指计算机程序自身彼此关联的过程。
绑定工作在编译连接阶段完成的情况称为静态绑定;在运行阶段完成的情况称为动态绑定。
3.运算符重载
1)运算符重载概念:对已有的运算符赋予多重含义,是同一个运算符作用于不同类型的数据时导致不同的行为。
2)运算符重载规则:a)C++中的运算符除了少数几个外,全部都可以重载,而且只能重载C++中已经有的运算符
b)重载后运算符的优先级和结合性都不会改变
c)重载功能应与原有功能相类似,不能改变原运算符的操作对象个数,同时至少有一个操作对象是自定义类型
3)重载形式
a)类的非静态成员函数:
返回类型 operator 运算符(形参表){ 函数体 }
b)重载为非成员函数:
返回类型 operator 运算符(形参表){ 函数体 } 【ps:二者在声明时不同】
注意:当运算符重载为类的成员函数时,函数的参数个数比原来的操作数个数要少一个(后置“++”,“--”除外);
当重载为非成员函数时,参数个数与原操作数个数相同
----------------------------------------------------------------------------------------------
a)类的非静态成员函数
oprd1 B oprd2 ,其中oprd1 为A类对象,则应把B重载为A类的成员函数,该函数只有一个形参,形参类型是oprd2 的所属类型;
oprd++ 或 oprd--,其中oprd 为A类的对象,那么运算符就应该重载为A类的成员函数,这时函数要带一个整形(int)形参——用于辨析前置还是后置
用代码实现:
#include<iostream>
using namespace std; class A {
private:
int x;
public:
A(int x = ) :x(x) {}
A operator+(A &x); //重载+
A& operator++(); //前置++
A operator++(int); //后置++
friend ostream & operator<<(ostream &out, const A &a); //重载<<
void show() { cout << x << endl; }
};
A A::operator+(A &x) {
this->x += x.x;
return *this;
}
A& A::operator++() { //前置++
this->x += ;
return *this;
}
A A::operator++(int) { //后置++
A old = *this;
++(*this); //调用前置++函数
return old;
}
ostream &operator<<(ostream &out, const A &a) {
out << a.x << endl;
return out;
}
int main() {
A a(), b();
cout << "a = " << a << endl << "b = " << b << endl;
cout <<"a+b="<< a + b << endl;
cout <<"a++ = "<< a++ << endl;
cout << "after a++ ,a = " << a << endl;
return ;
}
执行结果:
b)重载为非成员函数
对于双目运算符B,实现oprd1 B oprd2 ,其中oprd1和oprd2中只要有一个具有自定义类型,则可以把B重载为非成员函数,该函数形参为oprd1 和 oprd2;
oprd++ 或 oprd--,其中oprd 为自定义类型,那么运算符就可以重载为非成员函数,这时形参1为oprd 另有一个形参2区别前置 后置(int)
我们用上述成员函数的例子来代码实现:
#include<iostream>
using namespace std; class A {
private:
int x;
public:
A(int x = ) :x(x) {}
friend A operator+(A a,A b);
friend A& operator++(A &a);
friend A operator++(A &a, int);
friend ostream & operator<<(ostream &out, const A &a); //重载<<
void show() { cout << x << endl; }
};
A operator+(A a, A b) { //非成员函数的重载+
A x;
x.x = a.x + b.x;
return x;
}
A& operator++(A &a) { //非成员函数的前置++
a.x += ;
return a;
}
A operator++(A &a, int) { //非成员函数的后置++
A old = a;
++a;
return old;
} ostream &operator<<(ostream &out, const A &a) {
out << a.x << endl;
return out;
}
int main() {
A a(), b();
cout << "a = " << a << endl << "b = " << b << endl;
cout <<"a+b="<< a + b << endl;
cout <<"a++ = "<< a++ << endl;
cout << "after a++ ,a = " << a << endl;
return ;
}
执行结果:
特别地:
“<<” 运算符的重载
ostream & operator << (ostream &out,const 类型说明符 &对象) 【注意着色的几个地方 是必要的】
4、虚函数
1)一般虚函数成员的声明语法是: virtual 函数类型 函数名 (形参表);
虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定
我们用代码实现,理解虚函数的用处
代码如下:
#include<iostream>
using namespace std; class Father {
private:
int x;
public:
Father(int x = ) :x(x) {}
virtual void show() { cout << "x of Father is " << x << endl; }
};
class Son :public Father{
private:
int x;
public:
Son(int x = ) :x(x) {}
void show() { cout << "x of Son is " << x << endl; }
};
int main() {
Father f;
f.show();
Son s;
s.show(); return ;
}
执行结果:
可能这样没有什么说服力,因为上述代码中Father 的成员函数show()不加virtual 也可以实现上面这个结果
那我们接着看看下面两个代码的对比
#include<iostream>
using namespace std; class Father {
private:
int x;
public:
Father(int x = ) :x(x) {}
void show() { cout << "x of Father is " << x << endl; } //不加virtual
};
class Son :public Father{
private:
int x;
public:
Son(int x = ) :x(x) {}
void show() { cout << "x of Son is " << x << endl; }
};
int main() {
Father f;
f.show();
Son s;
s.show(); Father* fs = &s;
fs->show();
return ;
}
主要看加粗代码,执行结果:
接着:
#include<iostream>
using namespace std; class Father {
private:
int x;
public:
Father(int x = ) :x(x) {}
virtual void show() { cout << "x of Father is " << x << endl; } //加virtual
};
class Son :public Father{
private:
int x;
public:
Son(int x = ) :x(x) {}
void show() { cout << "x of Son is " << x << endl; }
};
int main() {
Father f;
f.show();
Son s;
s.show(); Father* fs = &s;
fs->show();
return ;
}
依旧看加粗代码,执行结果:
这时我们发现 用基类指针指向派生类的对象时,出现了不一样的地方,使用了virtual 后派生类与基类的同名函数 在派生类中的那个函数彻底覆盖了基类原有的函数。
使用虚函数可以将一个基类函数在继承后,将这个基类函数的执行方式交给派生类掌控,这也是多态性的体现。
当然可以将基类声明为一个抽象类——即内容具有极高的可扩展性,却可以统一一些派生类所共同需要的函数(避免不同派生类取不同名的函数,却做相同的工作 or 基类指针指向它们的对象时,不能调用它们自己的函数)
例如
#include<iostream>
class Shape {
public:
virtual double getArea()const = ;
};
class Circle :public Shape {
private:
double r = ;
public:
double getArea()const {
return r * r*3.14;
}
};
class Rectangle :public Shape {
private:
double l = , w = ;
public:
double getArea()const {
return w;
}
};
void showArea(Shape*ptr) {
std::cout << ptr->getArea() << std::endl;
}
int main() {
Circle c;
Rectangle r;
showArea(&c);
showArea(&r);
return ;
}
执行结果:
正确得到结果
这里我们还是用了纯虚函数即在虚函数后加“=0”可以彻底把这一函数交给派生类去掌控,纯虚函数在基类中可以不实现。
还有在重写继承来的虚函数时,如果函数有默认的形参值,不要重新定义不同的值,举个‘栗子’
代码:
#include<iostream>
using namespace std; class A {
public:
virtual void show(int x = ) {
cout << "x = " << x << endl;
}
};
class B :public A {
public:
void show(int x = ) { //更改形参,原本应该 x = 1
cout <<"x = "<< xx << endl;
}
};
int main() {
B b;
A *ptr = &b;
ptr->show();
return ;
}
更改前:
输出 x = 1;
更改后:
输出 x = 1;
这是因为,虚函数是动态绑定的,但默认形参是静态绑定的,也就是说通过一个指向派生类的基类指针,可以访问到派生类的虚函数,但是默认形参却只能来自基类定义
还有一点,将基类的析构函数申明为虚函数的优点(值得变为习惯)!!!
用代码说明:
#include<iostream>
using namespace std; class Base {
public:
Base() { cout << "Constructing of Base" << endl; }
virtual void fun1() { cout << "fun1 of Base" << endl; }
void fun2() { cout << "fun2 of Base" << endl; }
~Base() { cout << "Destructing of Base" << endl; } //加virtual 派生类可以被析构,不加,则可能造成内存泄漏
};
class Derived:public Base {
public:
Derived() { cout << "Constructing of Derived" << endl; }
void fun1() { cout << "fun1 of Derived" << endl; }
void fun2() { cout << "fun2 of Derived" << endl; }
~Derived() { cout << "Destructing of Derived" << endl; }
};
int main() {
Base*ptr = new Derived; delete ptr; return ;
}
执行结果:
如果基类的析构函数为虚函数:
执行结果:
很明显这样将动态分配的派生类对象的内存也释放了,而如果是第一种情况就容易造成内存泄漏
-------------------------------------------
为什么使用虚函数?
如果派生类需要修改基类的行为(即重写与基类函数同名的函数)就应该在基类中将相应的函数声明为虚函数;
相反的,在基类中不希望被派生类更改的就应该声明为非虚函数。
补充知识:
dynamic_cast 的使用
目的:将执行基类向派生类转换
理解:基类指针本来只能指向基类的成员,哪怕是指向派生类对象,这个指针仍旧只能指向基类的成员(先除去virtual),
现在用dynamic_cast可以转换基类指针
使用条件:1.积累的指针或引用确实绑定到派生类的对象上;2.只有当基类至少含有一个虚函数即基类是多态类
代码实现:
#include<iostream> class base{
public:
int x = ;
virtual ~base() {} //这里定义为虚函数很重要,必须是多态类才可以dynamic_cast
};
class derived :public base {
public:
int y = ;
~derived() {}
};
int main() {
base *ptr = new derived;
if (dynamic_cast<derived*>(ptr) != NULL) { //因为如果转换失败会返回NULL
std::cout << dynamic_cast<derived*>(ptr)->y << std::endl;
}
if (typeid(*ptr) == typeid(derived)) {
std::cout << "" << std::endl;
}
return ;
}
使用typeid 只能判断一个对象是否为某个具体类型,而不会把它的子类型也包括在内。如果要达到判断一个对象是否为某个类型或其子类型的目的,还是用dynamic_cast更方便。
此处知识还推荐:
https://blog.csdn.net/qq_31073871/article/details/79910328
===============================
以上为现阶段的学习,如果有误望指正:)
C++ 中的 多态性的更多相关文章
- 【Java_基础】java中的多态性
方法的重载.重写和动态链接构成了java的多态性. 1.方法的重载 同一个类中多个同名但形参有所差异的方法,在调用时会根据参数的不同做出选择. 2.方法的重写 子类中重新定义了父类的方法,有关方法重写 ...
- C#中的多态性
1.重载(overload) public void Sleep() { Console.WriteLine("Animal睡觉"); } public int Sleep(int ...
- Java中的instanceof关键字
instanceof是Java的一个二元操作符,和==,>,<是同一类东东.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是测试它左边的对象是否是它右边的类的实例,返回boo ...
- c++ 继承 虚函数与多态性 重载 覆盖 隐藏
http://blog.csdn.net/lushujun2011/article/details/6827555 2011.9.27 1) 定义一个对象时,就调用了构造函数.如果一个类中没有定义任何 ...
- Java多态性举例说明
Java多态性的概念也可以被说成“一个接口,多个方法”. (一)相关类 class A ...{ public String show(D obj)...{ return ("A and D ...
- PHP面向对象多态性的应用
多态是面向对象的三大特性中除封装和继承之外的另一重要特性.它展现了动态绑定的功能,也称为“同名异式”.多态的功能可让软件在开发和维护时,达到充分的延伸性.事实上,多态最直接的定义是让具有继承关系的不同 ...
- C++学习之路—多态性与虚函数(一)利用虚函数实现动态多态性
(根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多态性是面向对象程序设计的一个重要特征.顾名思义 ...
- java中的instanceof
instanceof是Java.php的一个二元操作符(运算符),和==,>,<是同一类东西.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是判断其左边对象是否为其右边类的实 ...
- C#23种开发模式,陆续完善中
#region 单例模式 #region 线程非安全单例模式 public class Singleton1 { private Singleton1() { } private static Sin ...
随机推荐
- QTextToSpeech Win7奔溃
在linux下,它是调用speech-dispatcher.在其它不同的平台上,调用各自平台的TTS引擎.所以在使用的时候,要确保本地的TTS引擎是可用的. 本地TTS引擎不可用可能会在声明QText ...
- Oracle审计表AUD$处理方法
Oracle版本:11.2.0,其他版本要测试DBMS_AUDIT_MGMT能否成功 1. 查询表,然后truncate select count(*) from aud$; truncate tab ...
- 【Tomcat】本地域名访问配置
原路径:localhost:8080/jsja 1.把8080端口改为80端口 打开%TOMCAT_HOME%/conf/server.xml <Connector connectionTime ...
- 第2课第2节_Java面向对象编程_封装性_P【学习笔记】
摘要:韦东山android视频学习笔记 面向对象程序的三大特性之封装性:把属性和方法封装在一个整体,同时添加权限访问. 1.封装性的简单程序如下,看一下第19行,如果我们不对age变量进行权限的管控 ...
- mybatis generatorConfig.xml生成配置文件及三种运行方式
https://blog.csdn.net/gavin5033/article/details/83002335 一 ,cmd命令执行配置文件本人工作目录结构(图一) 在自己放配置文件的目录下新建ge ...
- 改变jupyter notebook的主题背景
https://study.163.com/provider/400000000398149/index.htm?share=2&shareId=400000000398149( 欢迎关注博 ...
- 网络通信技术中的中继器repeater
1. repeater的作用 对信号进行再生和还原 2. repeater的优点 延长通讯距离 提高可靠性 增加节点的最大数目 各个网段可以使用不同的通讯速率 3. repeater的缺点 增加了延时 ...
- flutte的第一个hello world程序
用命令行创建项目: flutter create flutterdemo VSCode或者AS连接手机后 输入 flutter run 编译后就可以将默认的代码显示在手机上了 开始写hello wor ...
- JFreechart从入门到放弃
JFreechart从入门到放弃 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 http://www.jfree.org/jfreechart/ 引言 干嘛用的 使用java画图, ...
- ABAP DEMO ALV-监听数据修改
*&---------------------------------------------------------------------* *& Report YDEMO_006 ...