重载和多态ios
1. 区别:重载是同名但参数不一样,经过参数来肯定调用哪一个函数;函数
多态是同名同参数,经过函数的实际类型决定调用哪一个函数。this
2. 多态的实现(如下将具体分析这三点):spa
① 函数重载3d
② 运算符重载指针
③ 虚函数code
多态从实现的角度来说可分为:编译时的多态和运行时的多态。对象
前者是在编译的过程当中肯定了同名操做的具体操做对象(静态绑定);blog
然后者则是在程序运行过程当中才动态地肯定操做所针对的具体对象(动态绑定)。继承
这种肯定操做地具体对象地过程就是绑定(就是把一条消息和一个对象的方法相结合的过程)。
3. 重写:若子类和父类的某个函数具备相同的函数名,形参列表,且父类中的函数被定义为虚函数,则子类对该函数的实现被称为函数的重写。重写是常见的多态实现方式。
1、 函数重载
1. 概念:两个以上的函数,具备相同的函数名,可是形参的个数或者类型不一样,编译器根据实参和形参的类型及个数的最佳匹配,自动肯定调用哪个函数,也就是函数的重载。简单理解,就是“一物多用”。
注:重载函数的参数个数、参数类型或者参数顺序三者中必须有一个不一样。
·例如:
1 int add(int x, int y); 2 float add(float x, float y);//形参类型不一样 3 4 int add(int x, int y); 5 int add(int x, int y, int z);//形参个数不一样
2.C++容许功能相近的函数在相同的做用域内声明几个相似的同名函数,从而造成重载。编译程序对实参和形参的类型、个数、顺序,来选择调用哪个函数。重载函数经常使用来处理实现功能相似但数据类型不一样的问题。
·如下指出几种错误状况:
int add(int x, int y); int add(int b, int b);//错误,编译器不以形参名来区分函数 int add(int x, int y); void add(int x, int y);//错误,编译器不以返回值来区分函数
1 //当使用具备默认形参值得函数重载形式时,须要注意防止二义性 2 //如下两个函数原型,在编译时便没法区别为不一样的重载形式: 3 4 void fun(int length, int width = 2, int height = 33); 5 void fun(int length); 6 7 //也就是说,当以fun(1)来调用函数fun时,编译器没法肯定应该执行哪一个重载函数
//不要将不一样功能的函数定义为重载函数 int add(int x, int y) { return x + y; } float add(float x, float y) { return x - y; }
·具体实例1(形参类型不一样):
1 #include<iostream> 2 using namespace std; 3 4 int ADD(int left, int right) 5 { 6 return left + right; 7 } 8 9 double ADD(double left, double right) //与第一个函数相比,形参类型不一样 10 { 11 return left + right; 12 } 13 14 int main() 15 { 16 17 cout << ADD(1, 2) << endl; 18 cout << ADD(1.3, 2.3) << endl; 19 return 0; 20 }
·具体实例2(形参个数不一样):
#include<iostream> using namespace std; int ADD(int a, int b) { return a + b; } double ADD(int a, int b,int c) //与第一个函数相比,形参个数不一样 { return a + b+c; } int main() { cout << ADD(1, 2) << endl; cout << ADD(1, 2,3) << endl; return 0; }
2、 运算符重载
·基本知识
1. 概念:运算符重载是对已有的运算符赋予多重含义,是同一个运算符做用域不一样类型的数据时致使不一样的行为。(运算符重载的实质就是函数重载)
2. 运算符重载是针对新类型数据的实际须要,对原有运算符进行适当的改造。
例如:
·使复数类的对象能够用“+”运算符实现加法;
·使时钟类的对象能够用“++”运算符实现时间增长1秒。
3. C++几乎能够重载所有的运算符,并且只可以重载C++中已经有的。不能重载的运算符:“.” “.*” “::” “?:”。
4. 重载以后运算符的优先级和结合性都不会改变。
5. 运算符重载的形式有两种:
·重载为类的非静态成员函数;
·重载为非成员函数。
·具体讲解
1、 重载为类的非静态成员函数
1.定义:
1 返回类型 operator 运算符(形参表) 2 { 3 函数体 4 } 5 参数个数=原操做数个数-1(后置++、--除外)
·返回类型:指定了重载运算符的返回值类型,也就是运算结果类型。
·operator:是定义运算符重载的关键字。
·运算符:是要重载的运算符名称,好比要重载加法运算符,这里就写“+”。
·形参表:给出重载运算符所须要的参数和类型。
2. 重载为成员函数,她就能够自由地访问本类的数据成员。
3. 双目运算符重载:
(1)若是要重载B为类成员函数,使之可以实现表达式oprd1 B oprd2,其中oprd1为A类对象,则B应被重载为A类的成员函数,该函数只有一个形参,形参类型是oprd2所属的类型。
(2)经重载后,表达式oprd1 B oprd2至关于oprd1.operator B(oprd2)。
·实例1(复数加减运算):
1 #include<iostream> 2 using namespace std; 3 4 class Complex { //复数类定义 5 public: //外部接口 6 Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {}//构造函数 7 Complex operator+(const Complex& c2)const;//运算符+重载成员函数 8 Complex operator-(const Complex& c2)const;//运算符-重载成员函数 9 void display()const; //输出复数 10 private: 11 double real; //复数实部 12 double imag; //复数虚部 13 }; 14 Complex Complex::operator+(const Complex& c2)const //重载运算符函数实现 15 { 16 return Complex(this->real + c2.real, this->imag + c2.imag);//建立一个临时无名对象做为返回值,含义:调用Complex构造函数建立一个临时对象并返回它。 17 } 18 Complex Complex::operator-(const Complex& c2)const 19 { 20 return Complex(this->real - c2.real, this->imag - c2.imag); 21 } 22 void Complex::display()const 23 { 24 cout << "(" << real << "," << imag << ")" << endl; 25 } 26 int main() { 27 Complex c1(5, 4), c2(2, 10), c3; 28 cout << "c1="; c1.display(); 29 cout << "c2="; c2.display(); 30 c3 = c2 - c1; 31 cout << "c3=c2-c1"; c3.display();//使用重载运算符完成复数减法 32 c3 = c1 + c2; 33 cout << "c3=c1+c2="; c3.display();//使用重载运算符完成复数加法 34 return 0; 35 }
上面的语句:return Complex(this->real + c2.real, this->imag + c2.imag);
能够改写为:Complex Complex::operator+(const Complex& c2)const {
Complex c(this->real + c2.real, this->imag + c2.imag);
return c;
}
4. 前置单目运算符重载:
(1)若是要重载U为类成员函数,使之可以实现表达式U oprd,其中oprd为A类对象,则U应被重载为A类的成员函数,无形参。
(2)经重载后,表达式U oprd至关于oprd.operator U()。
5. 后置单目运算符++和--重载
(1)若是要重载++和--类成员函数,使之可以实现表达式oprd++或者oprd--,其中oprd为A类对象,则++和--应被重载为A类的成员函数,且具备一个int类型形参。
(2)经重载后,表达式oprd++至关于oprd.operator ++()。
(3)前置单目运算符重载为成员函数时没有形参;后置单目运算符重载为成员函数时须要有一个int型形参,该int型参数在函数体中并未使用,纯粹只是用来区别前置和后置,因此参数表中能够只给出类型名,没有参数名。
·实例2(时钟类):
1 #include<iostream> 2 using namespace std; 3 4 class Clock { 5 public: 6 Clock(int hour = 0, int minute = 0, int second = 0); 7 void showTime()const; 8 Clock& operator++();//前置单目运算符重载 9 Clock operator++(int);//后置单目运算符重载 10 private: 11 int hour, minute, second; 12 }; 13 Clock::Clock(int hour/*=0*/, int minute/*=0*/, int second/*=0*/) 14 { 15 if (0 <= hour && hour < 24 && 0 <= minute && minute < 60 && 0 <= second && second, 60) { 16 this->hour = hour; 17 this->minute = minute; 18 this->second = second; 19 } 20 else 21 cout << "Time error!" << endl; 22 } 23 void Clock::showTime()const { 24 cout << hour << ":" << minute << ":" << second << endl; 25 } 26 Clock& Clock::operator++() {//前置单目运算符重载函数 27 second++; 28 if (second >= 60) { 29 second -= 60; 30 if (minute >= 60) { 31 minute -= 60; 32 hour = (hour + 1) % 24; 33 } 34 } 35 return(*this); 36 } 37 Clock Clock::operator++(int) {//后置单目运算符重载函数 38 Clock old = *this; 39 ++(*this); 40 return old; 41 } 42 int main() { 43 Clock myClock(23, 59, 59); 44 cout << "First time output:"; 45 myClock.showTime(); 46 cout << "Show myClock++:"; 47 (myClock++).showTime(); 48 cout << "Show ++myClock:"; 49 (++myClock).showTime(); 50 return 0; 51 }
2、 重载为非成员函数
1.定义:
1 返回类型 operator 运算符(形参表) 2 { 3 函数体 4 } 5 参数个数=原操做数个数(后置++、--除外)
2. 函数的形参表明依自左至右次序排列的各操做数。
3. 至少应该有一个自定义类型的参数。
4. 若是在运算符的重载函数中须要对操做某类对象的私有成员,能够将此函数声明为该类的友元函数。
5. 双目运算符B重载后:
表达式oprd1 B oprd2至关于operator B(oprd1,oprd2)。
6. 前置单目运算符B重载后:
表达式B oprd至关于operator B(oprd)。
7. 后置单目运算符++和--重载后:
表达式B oprd至关于operator B(oprd,0)。
·实例3(复数加减运算):
1 #include<iostream> 2 using namespace std; 3 4 class Complex { 5 public: 6 Complex(double r=0.0,double i=0.0):real(r),imag(i){} 7 friend Complex operator+(const Complex& c1, const Complex& c2);//运算符+重载 8 friend Complex operator-(const Complex& c1, const Complex& c2);//运算符-重载 9 friend ostream& operator<<(ostream& out, const Complex& c);//运算符<<重载 10 private: 11 double real, imag; 12 }; 13 Complex operator+(const Complex& c1, const Complex& c2) { 14 return Complex(c1.real + c2.real, c1.imag + c2.imag); 15 } 16 Complex operator-(const Complex& c1, const Complex& c2) { 17 return Complex(c1.real - c2.real, c1.imag - c2.imag); 18 } 19 ostream& operator<<(ostream& out, const Complex& c) { 20 out << "(" << c.real << "," << c.imag << ")"; 21 return out; 22 } 23 int main() { 24 Complex c1(5, 4), c2(2, 10), c3; 25 cout << "c1=" << c1 << endl; 26 cout << "c2=" << c2 << endl; 27 c3 = c1 - c2; 28 cout << "c3=c1-c2=" << c3 << endl; 29 c3 = c1 + c2; 30 cout << "c3=c1+c2=" << c3 << endl; 31 return 0; 32 }
1.上面将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操做数是ostream类型的引用,右操做数是Complex类型的引用,这样在执行cout<<c1时,就会调用用operator<<(cout,c1)。该函数把经过第一个参数传入的ostream对象以引用形式返回,用以支持下面形式的输出:
cout<<a<<b;
该输出调用的是:
operator<<(operator<<(cout,a),b);
2.以非成员函数重载,能够直接使用5.0+c1或者c1+5.0,由于Complex的构造函数使得实数能够被隐含转换为Complex类型。而以成员函数重载时,左操做数必须具备Complex类型,不能是实数,只有右操做数能够是实数。
3、虚函数
·基本知识
1. 虚函数的声明:
virtual 函数类型 函数名(形参表);
2. 虚函数声明只能出如今类定义的函数原型声明中,不能在成员函数实现的时候。
3. 虚函数是动态绑定的基础,必须是非静态的成员函数。虚函数通过派生以后,在类族中就能够实现运行过程当中的多态。
4. 若是须要经过基类得指针指向派生类的对象,并访问某个与基类同名的成员,那么首先在基类中将这个同名函数说明为虚函数。这样,经过基类类型的指针,就可使不一样派生类的不一样对象产生不一样的行为,从而实现运行过程当中的多态。
5. 运行时的多态须要知足如下三个条件:
·知足赋值兼容规则;
·要声明虚函数;
·由成员函数来调用或者是经过指针、引用来访问虚函数。(若是是使用对象名来访问虚函数,则绑定在编译过程当中就能够进行(静态绑定),而无须在运行过程当中进行)
6. 虚函数通常不声明为内联函数,由于对虚函数的调用须要动态绑定,而对内联函数的处理是静态的。但将虚函数声明为内联函数也不会引发错误。
7. 构造、静态函数不能是虚函数,析构函数能够是虚函数。
·具体讲解
·实例4:(书例7-3):
1 #include<iostream> 2 using namespace std; 3 4 class Base1 { 5 public: 6 virtual void display()const;//虚函数 7 /*可在该语句前加virtual,能够更清楚地提示这是一个虚函数,也可不加*/ 8 }; 9 void Base1::display()const { 10 cout << "Base1::display()" << endl; 11 } 12 class Base2:public Base1 { 13 public: 14 void display()const;//覆盖基类的虚函数 15 }; 16 void Base2::display()const { 17 cout << "Base2::display()" << endl; 18 } 19 class Derived :public Base2 { 20 public: 21 void display()const;//覆盖基类的虚函数 22 }; 23 void Derived::display()const { 24 cout << "Derived::display()" << endl; 25 } 26 void fun(Base1* ptr) {//参数为指向基类对象的指针 27 ptr->display();//“对象指针->成员名” 28 } 29 int main() { 30 Base1 base1; 31 Base2 base2; 32 Derived derived; 33 fun(&base1);//用Base1对象的指针调用fun函数 34 fun(&base2);//用Base2对象的指针调用fun函数 35 fun(&derived);//用Derived对象的指针调用fun函数 36 return 0; 37 }
1.在本程序中,派生类并无显式给出虚函数声明virtual,这时系统就会遵循如下规则来判断派生类的一个函数成员是否是虚函数。若是肯定为虚函数,这时,派生类的虚函数将覆盖基类的虚函数。
·该函数是否与基类的虚函数有相同的名称
·该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型
·该函数是否与基类的虚函数有相同的返回值或者知足赋值兼容规则的指针、引用型的返回值
2.通常习惯于在派生类的函数中也使用virtual关键字,以加强程序的可读性
3.若是把fun函数中的ptr->display()改成ptr->Base::display(),不管ptr所指向对象的动态类型是什么,最终被调用的老是Base1类的display()函数。在派生类的函数中,有时就能够用“基类名::函数名(…)”来调用基类中被覆盖的函数。
4.在重写继承来的虚函数时,若是函数有默认形参值,不要从新定义不一样的值。虽然虚函数是动态绑定的,但默认形参值是静态绑定的。也就是说,经过一个指向派生类对象的基
类指针,能够访问到派生类的虚函数,但默认形参之却只能来自基类的定义。
例如:将实例4中的fun函数的参数类型设定为Base1而非Base1*,那么3次fun函数的调用中,被执行的都会是Base1::display()。由于基类的对象不能表示派生类的对象
5.只有经过基类的指针或引用调用虚函数时,才会发生动态绑定。
例如:将实例4中的fun函数的参数类型设定为Base1而非Base1*,那么3次fun函数的调用中,被执行的都会是Base1::display()。由于基类的对象不能表示派生类的对象。
1 Derived d; //定义派生类对象 2 Base* ptr = &d;//基类指针ptr能够指向派生类对象 3 Base& ref = d;//基类引用ref能够做为派生类对象的别名 4 Base b = d;//调用Base1的复制构造函数用d构造b,b的类型是Base而非Derived