一 、多态性

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++ 中的 多态性的更多相关文章

  1. 【Java_基础】java中的多态性

    方法的重载.重写和动态链接构成了java的多态性. 1.方法的重载 同一个类中多个同名但形参有所差异的方法,在调用时会根据参数的不同做出选择. 2.方法的重写 子类中重新定义了父类的方法,有关方法重写 ...

  2. C#中的多态性

    1.重载(overload) public void Sleep() { Console.WriteLine("Animal睡觉"); } public int Sleep(int ...

  3. Java中的instanceof关键字

    instanceof是Java的一个二元操作符,和==,>,<是同一类东东.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是测试它左边的对象是否是它右边的类的实例,返回boo ...

  4. c++ 继承 虚函数与多态性 重载 覆盖 隐藏

    http://blog.csdn.net/lushujun2011/article/details/6827555 2011.9.27 1) 定义一个对象时,就调用了构造函数.如果一个类中没有定义任何 ...

  5. Java多态性举例说明

    Java多态性的概念也可以被说成“一个接口,多个方法”. (一)相关类 class A ...{ public String show(D obj)...{ return ("A and D ...

  6. PHP面向对象多态性的应用

    多态是面向对象的三大特性中除封装和继承之外的另一重要特性.它展现了动态绑定的功能,也称为“同名异式”.多态的功能可让软件在开发和维护时,达到充分的延伸性.事实上,多态最直接的定义是让具有继承关系的不同 ...

  7. C++学习之路—多态性与虚函数(一)利用虚函数实现动态多态性

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多态性是面向对象程序设计的一个重要特征.顾名思义 ...

  8. java中的instanceof

    instanceof是Java.php的一个二元操作符(运算符),和==,>,<是同一类东西.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是判断其左边对象是否为其右边类的实 ...

  9. C#23种开发模式,陆续完善中

    #region 单例模式 #region 线程非安全单例模式 public class Singleton1 { private Singleton1() { } private static Sin ...

随机推荐

  1. QTextToSpeech Win7奔溃

    在linux下,它是调用speech-dispatcher.在其它不同的平台上,调用各自平台的TTS引擎.所以在使用的时候,要确保本地的TTS引擎是可用的. 本地TTS引擎不可用可能会在声明QText ...

  2. Oracle审计表AUD$处理方法

    Oracle版本:11.2.0,其他版本要测试DBMS_AUDIT_MGMT能否成功 1. 查询表,然后truncate select count(*) from aud$; truncate tab ...

  3. 【Tomcat】本地域名访问配置

    原路径:localhost:8080/jsja 1.把8080端口改为80端口 打开%TOMCAT_HOME%/conf/server.xml <Connector connectionTime ...

  4. 第2课第2节_Java面向对象编程_封装性_P【学习笔记】

    摘要:韦东山android视频学习笔记  面向对象程序的三大特性之封装性:把属性和方法封装在一个整体,同时添加权限访问. 1.封装性的简单程序如下,看一下第19行,如果我们不对age变量进行权限的管控 ...

  5. mybatis generatorConfig.xml生成配置文件及三种运行方式

    https://blog.csdn.net/gavin5033/article/details/83002335 一 ,cmd命令执行配置文件本人工作目录结构(图一) 在自己放配置文件的目录下新建ge ...

  6. 改变jupyter notebook的主题背景

     https://study.163.com/provider/400000000398149/index.htm?share=2&shareId=400000000398149( 欢迎关注博 ...

  7. 网络通信技术中的中继器repeater

    1. repeater的作用 对信号进行再生和还原 2. repeater的优点 延长通讯距离 提高可靠性 增加节点的最大数目 各个网段可以使用不同的通讯速率 3. repeater的缺点 增加了延时 ...

  8. flutte的第一个hello world程序

    用命令行创建项目: flutter create flutterdemo VSCode或者AS连接手机后 输入 flutter run 编译后就可以将默认的代码显示在手机上了 开始写hello wor ...

  9. JFreechart从入门到放弃

    JFreechart从入门到放弃 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 http://www.jfree.org/jfreechart/ 引言 干嘛用的 使用java画图, ...

  10. ABAP DEMO ALV-监听数据修改

    *&---------------------------------------------------------------------* *& Report YDEMO_006 ...