C++ 重载运算符 继承 多态 (超详细)

时间:2022-07-28
本文章向大家介绍C++ 重载运算符 继承 多态 (超详细),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

(一)重载运算符:

(1)声明与定义格式 一般是类内声明,类外定义,虽然可以在类内定义,但 写前面堆一堆不好看!!! 类内声明

class Demo
{
		返回值类型 operator  运算符(形参表);
}

类外定义:

返回类型 Demo(类名)::operator运算符(形参表)
{
    函数体
}

(2)双目运算符重载为成员函数 当重载运算符为双目运算符时,形参表中只有一个参数作为右操作数。当前对象作为左操作数,通过this指针隐式传递给函数,一个例子来介绍。 实例: 写到最后突然想起来,用int不能实现浮点数的全部特性0.03就不能实现,所以仅作为一个例子。

class Myfloat
{
	int inter;
	int deci;
public:
    Myfloat(int a,int b):inter(a),deci(b){}
	Myfloat operator+(Myfloat const &temp) const;
	Myfloat operator-(Myfloat const &temp) const;
};
Myfloat Myfloat::operator+(Myfloat const &temp) const
{
    return Myfloat(inter+temp.inter,deci+temp.deci);
}
Myfloat Myfloat::operator-(Myfloat const &temp) const
{
    return Myfloat(inter-temp.inter,deci-temp.deci);
}

现在只是重载了加减号,实现了自定义浮点数的运算,但是还不成熟,咱们一点一点来丰满这个代码,这个类。(3)单目运算符重载为成员函数 此时参数表中没有参数,只有当前对象作为运算符的一个操作数。 实例:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
class Myfloat
{
	int inter;
	int deci;
public:
    Myfloat(int a,int b):inter(a),deci(b){}
	Myfloat operator+(Myfloat const &temp) const;
	Myfloat operator-(Myfloat const &temp) const;
    Myfloat operator--();
    Myfloat operator++();
    Myfloat operator--(int); //补充一个虚操作数,表示前置操作
    Myfloat operator++(int);
};
Myfloat Myfloat::operator+(Myfloat const &temp) const
{
    return Myfloat(inter+temp.inter,deci+temp.deci);
}
Myfloat Myfloat::operator-(Myfloat const &temp) const
{
    return Myfloat(inter-temp.inter,deci-temp.deci);
}
Myfloat Myfloat::operator--()  {return Myfloat(inter--,deci);}
Myfloat Myfloat::operator++()  {return Myfloat(inter++,deci);}
Myfloat Myfloat::operator--(int)  {return Myfloat(--inter,deci);}

要区分前置与后置运算要加一个(需操作数)告诉机器是前置还是后置。

(3) 友元函数重载+重载输入输出流(用的稀烂用的多比较重要) 在左右操作数类型不同时上述重载方式都不能正常使用,这时候就需要两个操作数,在类外重载,因类外不能直接调用,所以要把该函数声明为类的友元。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
class Myfloat
{
	int inter;
	int deci;
public:
    Myfloat(int a,int b):inter(a),deci(b){}
	Myfloat operator+(Myfloat const &temp) const;
	Myfloat operator-(Myfloat const &temp) const;
    Myfloat operator--();
    Myfloat operator++();
    Myfloat operator--(int); //补充一个虚操作数,表示前置操作
    Myfloat operator++(int);
    friend ostream& operator<<(ostream out,Myfloat &w) ;
    friend istream &operator>>(istream in,Myfloat &w);
};
Myfloat Myfloat::operator+(Myfloat const &temp) const{return Myfloat(inter+temp.inter,deci+temp.deci);}
Myfloat Myfloat::operator-(Myfloat const &temp) const{return Myfloat(inter-temp.inter,deci-temp.deci);}
Myfloat Myfloat::operator--()  {return Myfloat(inter--,deci);}
Myfloat Myfloat::operator++()  {return Myfloat(inter++,deci);}
Myfloat Myfloat::operator--(int)  {return Myfloat(--inter,deci);}
Myfloat Myfloat::operator++(int)  {return Myfloat(++inter,deci);}
ostream& operator<<(ostream out,Myfloat &w)
{
    out<<w.inter<<'.'<<w.deci;
}
istream &operator>>(istream in,Myfloat &w)
{
    in>>w.inter>>w.deci;
}

(4)赋值运算符重载用于对象数据的复制 用非类A类型的值为类A的对象赋值时(当然,这种情况下我们可以不提供相应的赋值运算符重载函数,而只提供相应的构造函数,如更有重载函数会优先调用重载后的赋值运算符)。 当用类A类型的值为类A的对象赋值,且类A的数据成员中含有指针的情况下,必须显式提供赋值运算符重载函数。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
class Myfloat
{
	int inter;
	int deci;
public:
    Myfloat(int a,int b):inter(a),deci(b){}
	Myfloat operator+(Myfloat const &temp) const;
	Myfloat operator-(Myfloat const &temp) const;
    Myfloat operator--();
    Myfloat operator++();
    Myfloat operator--(int); //补充一个虚操作数,表示前置操作
    Myfloat operator++(int);
    friend ostream& operator<<(ostream out,Myfloat &w) ;
    friend istream &operator>>(istream in,Myfloat &w);
    Myfloat& operator=(const Myfloat &temp ) ;//写或不写都可以,这种如果按照默认的方式一一对应复制,可以不写。
    Myfloat& operator=(const int &w ) ;
};
Myfloat Myfloat::operator+(Myfloat const &temp) const{return Myfloat(inter+temp.inter,deci+temp.deci);}
Myfloat Myfloat::operator-(Myfloat const &temp) const{return Myfloat(inter-temp.inter,deci-temp.deci);}
Myfloat Myfloat::operator--()  {return Myfloat(inter--,deci);}
Myfloat Myfloat::operator++()  {return Myfloat(inter++,deci);}
Myfloat Myfloat::operator--(int)  {return Myfloat(--inter,deci);}
Myfloat Myfloat::operator++(int)  {return Myfloat(++inter,deci);}
ostream& operator<<(ostream out,Myfloat &w)
{
    out<<w.inter<<'.'<<w.deci;
}
istream &operator>>(istream in,Myfloat &w)
{
    in>>w.inter>>w.deci;
}
Myfloat& Myfloat::operator=(const Myfloat &temp)
{
    inter=temp.inter;
    deci=temp.deci;
    return *this;
}
Myfloat& Myfloat::operator=(const int &w)
{
    inter=w;
    return *this;
}






(二)基类与派生类

(1)继承语法形式

class 派生类名:基类名表

{

数据成员和成员函数声明

};

基类类名表构成: 访问控制 基类名1 访问控制 基类名2… 继承多各类时叫做多继承,容易产生二义性,一般不用。

访问控制有三种 public:公有继承 private:私有继承 protected:保护继承 实例

 class People { }
 class Student:public People

(2)派生类的生成过程

  1. 吸收基类成员:除构造和析构函数外
  2. 改造基类成员:通过在派生类中定义同名成员屏蔽基类成员在派生类中直接调用,仍可以基类指针调用同名成员
  3. .添加新成员

(3)派生类特点

  1. 子类拥有父类除了父类构造和析构函数,所有的成员函数和成员变量;
  2. 2.子类就是一种特殊的父类;
  3. 子类对象可以当做父类的对象使用;
  4. 子类可以拥有父类没有的方法和属性。

(4)派生类中的静态数据成员 基类中定义的静态成员,将被所有派生类共享 2、基类初始化: (5)派生类的初始化

派生类构造函数声明格式为: 派生类构造函数(变元表):基类(变元表)、对象成员1(变元表)

构造函数执行顺序:基类——对象成员(类对象成员的初始化)——派生类 //一开始不理解,现在理解了 举个栗子:

class People
{
protected:
    string  name;
    string  xb;
public:
    People(string a,string b):name(a),xb(b){}
};
class Cloth
{
    string color;
    int mysize;
public:
    Cloth(string c,int m):color(c),mysize(m){}
};
class Student:public People
{
    string id;
    Cloth coat;
public:
    Student(string id,string name,string xh,string color,int size) :People(name,xb),coat(color,size),id(id){}
};

执行顺序跟我写的顺序一样,但不是因为按这个顺序写的原因,就像成员变量初始化,也是按这定义顺序初始化,与自己写的初始化顺序无关。 构造函数的执行顺序:基类→对象成员→派生类; (6)派生类构造函数和析构函数的使用原则

基类的构造函数和析构函数不能继承 派生类是否定义析构函数与所属基类无关 如果基类没有定义构造函数或是定义无参构造函数,派生类可以不定义构造函数。 如果基类无无参构造函数,派生类必须定义构造函数 如果派生类基类为连续基类继承,每个派生类只负责直接基类的构造

(7)派生类析构函数

与构造函数执行顺序相反,派生-----对象-----基类

(8)赋值兼容原则 这个规则可以简述为能放基类的地方,放派生类一定可以使用,在程序中需要使用基类对象的地方都可以用公有派生类的对象代替。 例:

class Base{};
class Drived{};
Base demo1,Drived demo2; 
demo1=demo2;   //派生类对象可以赋值给基类对象:
Base&Bdemo3=demo2; //派生类对象可以初始化基类引用;
Base *Bpo=&demo2;//派生类对象可以赋给指向基类对象的指针;//多态实现的方法

主要是派生类中一定包含基类中所有成员,在使用中,一定可以找到对应成员。

赋值兼容应注意的问题:

  1. 指向基类的指针可以指向公有派生类的对象,但不允许指向它的私有派生类的对象。
  2. 允许将一个声明为指向基类的指针指向其公有派生类对象,但是不能将一个声明为指向派生类对象的指针指向基类对象。
  3. 声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类的定义的成员。

可以理解为派生类完全包含基类,指向基类的任何成员,都可以在公有派生类中找到对应的成员对象与之对应,如果是私有继承,能找到但是不能访问。但是派生类中有的对象,基类中不一定会有,所以不能这么操作。







(三)虚函数与多态:

(1)多态的概念: 一个接口,多种使用方法 (2)封装的作用: 封装可以是得代码模块化;继承可以扩展已经存在的代码,都是为了代代码重用; **(3)多态的目的:**接口重用 (4)静态联编和动态联编分别表示什么? 在编译的时候能够确定对象所调用的成员函数的地址则为静态联编,一般的调用方式; 动态联编:指的是在程序运行的时候动态地进行,根据当时的情况来确定调用哪个同名函数,父类指针指向哪个子类,就调用哪个子类的同名函数,实际上是在运行的时候虚函数的实现;

#include<iostream>
using namespace std;

class Basic
{
public:
	void oper1()
	{
		printf("1n");
	}
	virtual void oper2()
	{
		printf("2n");
	}
};
class Direved : public Basic
{
public:
	void oper1()
	{
		printf("3n");
	}
	void oper2()
	{
		printf("4n");
	}
};
int main(void)
{
	Basic a;
	Direved b;
	Basic *p = &a; //   定义一个基类指针	
	p->oper1();  //基类的oper1
	p->oper2();  //基类的oper2
	p = &b;  //基类指针指向了派生类
	p->oper1();  //不是虚函数,仍时基类oper1
	p->oper2();  //是虚函数构成多态,是派生类的oper2
	return 0;
}

运行结果过如下,重点关注是否为虚函数时函数调用的区别。

构成虚函数的必要条件: 函数返回类型一样,参数表一样,函数名一样,同时需要关键字vitrual,缺一不可。

class Basic
{
	vitrual  void p(){ 函数体};
}
class Direved :public Basic //构成虚函数的多态
{
	void p(){ 函数体};
}
class Basic
{
   void p(){ 函数体};
}
class Direved :public Basic //不构成虚函数
{
	void p(){ 函数体};
}
class Basic
{
   vitrual  void p(){ 函数体};
}
class Direved :public Basic //不构成虚函数 
{
	void p(int a){ 函数体};
}
class Basic
{
   vitrual  void p(){ 函数体};
}
class Direved :public Basic //不构成虚函数 
{
	double p(){ 函数体};
}

(5)C++纯虚函数 1.纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

virtual void funtion()=0 

1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

Class animals
{
	virtual void mingjiao() ; //动物基类,到底是什么动物呢,该怎么鸣叫?当无法合理的给予基类定义虚函数时,常用纯虚函数。
	{
	}
}

为了解决上述问题,引入了纯虚函数的概念

Class animals
{
  virtual void mingjiao()=0;
}

则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。 注意含有纯虚函数的类,不能直接声明对象!!!!!!!!

(6)多态性 实现的两种方式 a、编译时多态性:通过重载函数实现 b、运行时多态性:通过虚函数实现。