『C++&C 语言』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)单目运算符重载为成员函数** 此时参数表中没有参数,只有当前对象作为运算符的一个操作数。 实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #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) 友元函数重载+重载输入输出流(用的稀烂==用的多==比较重要) 在左右操作数类型不同时上述重载方式都不能正常使用,这时候就需要两个操作数,在类外重载,因类外不能直接调用,所以要把该函数声明为类的友元。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #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 的数据成员中含有指针的情况下,必须显式提供赋值运算符重载函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #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)继承语法形式 :
1 2 3 4 5 6 7 class 派生类名:基类名表 { 数据成员和成员函数声明 };
基类类名表构成: 访问控制 基类名 1 访问控制 基类名 2…….继承多各类时叫做多继承,容易产生二义性,一般不用。
访问控制有三种 public:公有继承 private:私有继承 protected:保护继承 实例
1 2 class People { }class Student :public People
(2)派生类的生成过程
吸收基类成员:除构造和析构函数外
改造基类成员:通过在派生类中定义同名成员屏蔽基类成员在派生类中直接调用,仍可以基类指针调用同名成员
.添加新成员
(3)派生类特点
子类拥有父类除了父类构造和析构函数,所有的成员函数和成员变量;
2.子类就是一种特殊的父类;
子类对象可以当做父类的对象使用;
子类可以拥有父类没有的方法和属性。
(4)派生类中的静态数据成员 基类中定义的静态成员,将被所有派生类共享 2、基类初始化:(5)派生类的初始化
派生类构造函数声明格式为: 派生类构造函数(变元表):基类(变元表)、对象成员 1(变元表)
构造函数执行顺序:基类——对象成员(类对象成员的初始化)——派生类//一开始不理解,现在理解了 举个栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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)多态的目的:**接口重用(4)静态联编和动态联编分别表示什么? 在编译的时候能够确定对象所调用的成员函数的地址则为静态联编,一般的调用方式; 动态联编:指的是在程序运行的时候动态地进行,根据当时的情况来确定调用哪个同名函数,父类指针指向哪个子类,就调用哪个子类的同名函数,实际上是在运行的时候虚函数的实现;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std ;class Basic { public: void oper1 () { printf ("1\n" ); } virtual void oper2 () { printf ("2\n" ); } };class Direved : public Basic { public: void oper1 () { printf ("3\n" ); } void oper2 () { printf ("4\n" ); } };int main (void ) { Basic a; Direved b; Basic *p = &a; p->oper1(); p->oper2(); p = &b; p->oper1(); p->oper2(); return 0 ; }
运行结果过如下,重点关注是否为虚函数时函数调用的区别。构成虚函数的必要条件: 函数返回类型一样,参数表一样,函数名一样,同时需要关键字 vitrual,缺一不可。
1 2 3 4 5 6 7 8 class Basic { vitrual void p () { 函数体}; }class Direved : public Basic { void p () { 函数体}; }
1 2 3 4 5 6 7 8 class Basic { void p () { 函数体}; }class Direved : public Basic { void p () { 函数体}; }
1 2 3 4 5 6 7 8 class Basic { vitrual void p () { 函数体}; }class Direved : public Basic { void p (int a) { 函数体}; }
1 2 3 4 5 6 7 8 class Basic { vitrual void p () { 函数体}; }class Direved : public Basic { double p () { 函数体}; }
(5)C++纯虚函数 1.纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion()=0
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
1 2 3 4 5 6 Class animals { virtual void mingjiao () ; { } }
为了解决上述问题,引入了纯虚函数的概念
1 2 3 4 Class animals { virtual void mingjiao () =0 ; }
则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。注意含有纯虚函数的类,不能直接声明对象!!!!!!!!
(6)多态性 实现的两种方式 a、编译时多态性:通过重载函数实现 b、运行时多态性:通过虚函数实现。