c++学习————多态
面向对象的多态性可以分为四类:重载多态,强制多态,包含多态和参数多态。前面两种统称为专用多态,后面两种称为通用多态。
普通函数及类的成员函数的重载,运算符重载都属于重载多态。
强制多态是指将一个变量的类型加以变化,以符合一个函数或者操作的要求,加法运算符在进行浮点数与整数相加时,首先进行类型的强制转换,把整型数变为浮点数再相加的情况。
包含多态是类族中定义于不同类的同名成员函数的多态行为,主要通过虚函数实现。
参数多态与类模板相关联,在使用时必须赋予实际的类型才可以实例化,这样,由类模板实例化的各个类都具有相同的操作,而操作对象的类型却不相同。
一:虚函数
虚函数是实现动态链编的基础,为什么会有虚函数这样得提法呢?
一:父类得引用或指针指向子类的对象,c++中规定,当一个成员函数被声明为虚函数之后,其派生类的同名函数都自动的成为虚函数。因此在派生类重新定义该虚函数时,可以加vitual也可以不加,但是习惯上每一次声明时都加上vitural,使程序更加清晰。另外呢,由于基类中的函数大部分是没啥用的,所以可以声明为纯虚函数,这样父类就变为了一个抽象类。
二:使用虚函数,子类重写的必须与父类的函数名,函数类型,函数参数个数和类型全部与基函数相同。
首先我们在此处查看静态链编:
// 静态链编的问题
#include<iostream>
const double PI=3.14;
using namespace std;
class Figure
{
public:
Figure(){};
double area()
{
return 0.0;
}
};
class Rectangle :public Figure
{
public:
Rectangle(double myl,double myw)
{
l=myl;
w=myw;
}
double area()
{
return l*w;
}
private:
double l,w;
};
class Circle:public Figure
{
public:
Circle(double myr)
{
r=myr;
}
double area()
{
return PI*r*r;
}
private:
double r;
};
int main()
{
Figure fig;
double area;
area=fig.area();
cout<<"area of Figure is "<<area<<endl;
Rectangle rec(3.0,4.0);
area=rec.area();
cout<<"area of Rectangle is "<<area<<endl;
Circle cic(5.0);
area=cic.area();
cout<<"area of cic is "<<area<<endl;
return 0;
}
程序输出的结果为:
静态链编的主要优点就是执行效率高,因为在编译和链接阶段的有关函数调用和执行的具体关系已经确定,但是静态链编的缺点就是程序员必须预测每一种函数的调用。
但是在碰到父类的引用或指针指向子类的对象时,静态链编的缺点就暴露了出来!
// 静态链编的问题
#include<iostream>
const double PI=3.14;
using namespace std;
class Figure
{
public:
Figure(){};
double area()
{
return 0.0;
}
};
class Rectangle :public Figure
{
public:
Rectangle(double myl,double myw)
{
l=myl;
w=myw;
}
double area()
{
return l*w;
}
private:
double l,w;
};
class Circle:public Figure
{
public:
Circle(double myr)
{
r=myr;
}
double area()
{
return PI*r*r;
}
private:
double r;
};
double fun(Figure &fig)
{
return fig.area();
}
int main()
{
Figure fig;
double area;
area=fun(fig);
cout<<"area of Figure is "<<area<<endl;
Rectangle rec(3.0,4.0);
area=fun(rec);
cout<<"area of Rectangle is "<<area<<endl;
Circle cic(5.0);
area=fun(cic);
cout<<"area of cic is "<<area<<endl;
return 0;
}
在程序编译和运行时没错,可是结果不对。这是因为在编译时fun函数只访问到从基类继承的同名成员,这就是静态链编的后果。所以可知编译程序在编译阶段是不能确切的知道调用的函数的,这就需要动态链编。虚函数是实现动态链编的基础。
// 动态链编
#include<iostream>
const double PI=3.14;
using namespace std;
class Figure
{
public:
Figure(){};
virtual double area()
{
return 0.0;
}
};
class Rectangle :public Figure
{
public:
Rectangle(double myl,double myw)
{
l=myl;
w=myw;
}
double area()
{
return l*w;
}
private:
double l,w;
};
class Circle:public Figure
{
public:
Circle(double myr)
{
r=myr;
}
double area()
{
return PI*r*r;
}
private:
double r;
};
double fun(Figure &fig)
{
return fig.area();
}
int main()
{
Figure fig;
double area;
area=fun(fig);
cout<<"area of Figure is "<<area<<endl;
Rectangle rec(3.0,4.0);
area=fun(rec);
cout<<"area of Rectangle is "<<area<<endl;
Circle cic(5.0);
area=fun(cic);
cout<<"area of cic is "<<area<<endl;
return 0;
}
double fun(Figure *fig)
{
return fig->area();
}
int main()
{
Figure fig;
double area;
area=fun(&fig);
cout<<"area of Figure is "<<area<<endl;
Rectangle rec(3.0,4.0);
area=fun(&rec);
cout<<"area of Rectangle is "<<area<<endl;
Circle cic(5.0);
area=fun(&cic);
cout<<"area of cic is "<<area<<endl;
return 0;
}
调用虚函数的步骤:
(1)定义基类指针变量或基类对象的引用。
(2)将对象的地址或派生类对象的地址赋值给该变量,或者用基类或者派生类对象初始化基类的引用。
(3)用“指针->虚函数(实参)”方式或基类对象的引用虚函数(实参)方式调用基类或派生类中的虚函数。
使用虚函数应该注意以下几点:
(1)只有类的成员函数才能说明为虚函数,虚函数的声明只能出现在类的定义中。因为虚函数只适用于有继承关系的类对象。
(3)虚函数不能是静态成员函数,也不能是友元函数。因为静态成员函数和友元函数不属于某个对象。
(2)构造函数不能是虚函数,析构函数可以是虚函数,而且通常声明为虚函数。
虚函数与一般重载函数的区别:
(1)构造函数可以重载,析构函数不可以,正好相反,析构函数可以定义为虚函数,构造函数不可以。
(2)重载函数的调用是以所传递参数的差别来作为调用不同函数的依据,而虚函数是根据对象的不同
(3)重载函数只要求函数有相同的函数名,虚函数的要求每一性质都相同。
(4)重载函数可以是成员函数或友元函数,而虚函数只能是非静态成员函数。
二:纯虚函数
有时候基类就是没有具体作用的,因此我们可以将基类中的函数写为纯虚函数。此时基类就变为了抽象类,抽象类是不可以实例化对象的。
// 动态链编
#include<iostream>
const double PI=3.14;
using namespace std;
class Figure
{
public:
Figure(){};
virtual double area()=0;//纯虚函数
};
class Rectangle :public Figure
{
public:
Rectangle(double myl,double myw)
{
l=myl;
w=myw;
}
double area()
{
return l*w;
}
private:
double l,w;
};
class Circle:public Figure
{
public:
Circle(double myr)
{
r=myr;
}
double area()
{
return PI*r*r;
}
private:
double r;
};
double fun(Figure &fig)
{
return fig.area();
}
int main()
{
double area;
Rectangle rec(3.0,4.0);
area=fun(rec);
cout<<"area of Rectangle is "<<area<<endl;
Circle cic(5.0);
area=fun(cic);
cout<<"area of cic is "<<area<<endl;
return 0;
}
苍蝇再小也是肉,c++编程时一定要注意细节方面的微小错误。构造函数也是函数有完整的结构。
三:运算符重载
运算符重载的目的是将系统以及定义的运算符用于新定义的数据类型,从而使同一个运算符作用于不同类型的数据实现不同类型的行为。
一:运算符重载为成员函数
重载“+或者—”运算符
// 加号运算符重载
#include<iostream>
using namespace std;
class Complex
{
private:
float Real;
float Image;
public:
Complex()
{
Real=0;
Image=0;
}
Complex(float Re,float Im)
{
Real=Re;
Image=Im;
}
Complex & operator+(Complex c);//运算符重载作为成员函数
Complex & operator-(Complex c);//减号运算符重载
void display();
};
Complex &Complex::operator+(Complex c)
{
return Complex(Real+c.Real,Image+c.Image);
}
Complex &Complex::operator-(Complex c)
{
return Complex(Real-c.Real,Image-c.Image);
}
void Complex::display()
{
cout<<Real<<","<<Image<<endl;
}
int main()
{
Complex c1(5.0,10.0),c2(3.0,-2.5),c3,c4;
c3=c1+c2;
c4=c1-c2;
c3.display();
c4.display();
return 0;
}
结果为:
二:运算符重载为友元函数
由于用户自定义的类型也是不可以输出的,所以同时我们可以对输出流也进行运算符重载。
与成员函数定义方法相比,只是在类中声明函数原型前加了一个关键字friend,由于友元运算发重载函数不是该类的成员函数,所以类外定义时不需要加上类名。
// 加号运算符重载
#include<iostream>
using namespace std;
class Complex
{
private:
float Real;
float Image;
public:
Complex()
{
Real=0;
Image=0;
}
Complex(float Re,float Im)
{
Real=Re;
Image=Im;
}
Complex & operator+(Complex c);//运算符重载作为成员函数
Complex & operator-(Complex c);//减号运算符重载
friend ostream & operator<<(ostream & out,Complex & c);
};
Complex &Complex::operator+(Complex c)
{
return Complex(Real+c.Real,Image+c.Image);
}
Complex &Complex::operator-(Complex c)
{
return Complex(Real-c.Real,Image-c.Image);
}
ostream &operator<<(ostream &out,Complex &c)
{
out<<c.Real<<","<<c.Image;
return out;
}
int main()
{
Complex c1(5.0,10.0),c2(3.0,-2.5),c3,c4;
c3=c1+c2;
c4=c1-c2;
cout<<c3<<endl;
cout<<c4<<endl;
return 0;
}