
是指同样的消息被不同类型的对象接受时导致完全不同的行为。
从实现的角度来看,多态可以划分为两类:
- 编译时的多态:在编译的过程中确定了同名 *** 作的具体 *** 作对象;
- 运行时的多态:在程序运行过程中才动态的确定 *** 作所针对的具体对象;
联编(绑定):确定 *** 作的具体对象的过程。
- 静态联编:联编在编译和连接时进行。
函数的重载、函数模板的实例化均属于静态联编,优点是程序执行效率高;
- 动态联编:联编在运行时进行。
优点是灵活,运行速度慢一些;
赋予已有运算符新的功能,使他能够用于特定类型执行特定的 *** 作,使同一个运算符作用于不同类型的数据时导致不同的行为的机制称为运算符重载。
通过重载一种特殊的函数—运算符函数实现。
实际上是函数的重载
运算符函数
2、运算符重载规则 1、可重载的运算符operator 运算符名
除以下5个运算符之外,其余全部可以被重载:
2、运算符的重载规则. :成员选择运算符
. * :成员指针运算符
:: :作用域分辨符
?:三目选择运算符
sizeof:计算数据大小运算符
- 重载后运算符的优先级与结合性不会改变;
- 不能改变原运算符 *** 作数的个数;
- 不能重载C++中没有的运算符;
- 不能改变运算符的原有语义;
这样可以自由的访问该类的任何成员
定义格式:
friend 函数返回类型 operator 运算符(形参表)
{
函数体;
}
例子:重载运算符为类的友元函数进行复数类数据运算
使用(a,b)表示一个复数a+bi,复数的运算规则:
(a,b)+(c,d)=(a+c,b+d);
(a,b)-(c,d)=(a-c,b-d);
-(a,b)=(-a,-b);
(a,b)++ =(a+1,b);
#include
using namespace std;
class Complex
{
private:
double real;//实部
double image;//虚部
public:
Complex(double real=0.0,double image=0.0)
{
this->real=real,this->image=image;
}
void display()
{
cout<<"("<<real<<","<<image<<")"<<endl;
}
friend Complex operator +(Complex A,Complex B)
{
return Complex(A.real+B.real,A.image+B.image);
}
friend Complex operator -(Complex A,Complex B)
{
return Complex(A.real-B.real,A.image-B.image);
}
friend Complex operator -(Complex A)
{
return Complex(-A.real,-A.image);
}
friend Complex operator ++(Complex &A)//前置++
{
return Complex(++A.real,A.image);
}
friend Complex operator ++(Complex &A,int)//后置++
{
return Complex(A.real++,A.image);
}
};
int main()
{
Complex A(2,3);
Complex B(4,5);
Complex C;
cout<<"A=", A.display();//display方法中有cout
cout<<"B=", B.display();
C=A+B;
cout<<"c=a+b=",C.display();
C=A-B;
cout<<"c=a-b=",C.display();
C=-A;
cout<<"-a=",C.display();
C=++A;
cout<<"++a=",C.display();
C=A++;
cout<<"A++ =",C.display();
C=A+5;
C.display();
return 0;
}
4、重载为类的成员函数
这样运算函数可以自由地访问本类的数据成员,语法格式:
返回类型 类名 :: operator 运算符(形参表)
{函数体}
在类中定义则类名::可省
此时,函数的参数个数比原来的 *** 作个数少一个,因为通过对象调用该运算符函数时,对象本身充当了运算符函数最左边的 *** 作数,少的 *** 作数就是该对象本身,由this指针指出。
- 双目运算符重载为类的成员函数时,只显式说明一个参数,即右 *** 作数;
- 前置单目运算符不需要说明参数;
- 后置单目运算符要带有一个整型形参,与前置相区别。
#include
using namespace std;
class Complex
{
private:
double real;//实部
double image;//虚部
public:
Complex(double real=0.0,double image=0.0)
{
this->real=real,this->image=image;
}
void display()
{
cout<<"("<<real<<","<<image<<")"<<endl;
}
Complex operator +(Complex B)
{
return Complex(real+B.real,image+B.image);
}
Complex operator -(Complex B)
{
return Complex(real-B.real,image-B.image);
}
Complex operator -()
{
return Complex(-real,-image);
}
Complex operator ++()//前置++
{
return Complex(++real,image);
}
Complex operator ++(int)//后置++
{
return Complex(real++,image);
}
};
int main()
{
Complex A(2,3);
Complex B(4,5);
Complex C;
cout<<"A=", A.display();//display方法中有cout
cout<<"B=", B.display();
C=A+B;
cout<<"c=a+b=",C.display();
C=A-B;
cout<<"c=a-b=",C.display();
C=-A;
cout<<"-a=",C.display();
C=++A;
cout<<"++a=",C.display();
C=A++;
cout<<"A++ =",C.display();
C=A+5;
C.display();
return 0;
}
总结:
- 一般情况下,单目运算符最好重载为类的成员函数,双目运算符重载为类的友元函数;
- 一些双目运算符不能重载为类的友元函数:=,(),[ ],->;
- 类型转换函数只能定义为成员函数;
- 若一个运算符的 *** 作需要修改对象的状态,选择重载为成员函数较好;
- 若所需的 *** 作数有隐式类型转换,只能选用友元函数;
- 当需要重载运算符的运算具有可交换性时,选择重载为友元函数;
- 当运算符函数是一个成员函数时,最左边的 *** 作数必须是运算符类的一个类对象(或者是对该类对象的引用)。
- 如果左边的 *** 作数是一个不同类的对象,或者是一个基本数据类型的对象,该运算符函数必须作为友元函数。
注意:=运算符只能重载为类的成员函数,常常用于深拷贝
#include
using namespace std;
class Complex
{
private:
double real,image;
public:
Complex(double x=0.0,double y=0.0)
{
real=x,image=y;
}
void display()
{
cout<<"("<<real<<","<<image<<")"<<endl;
}
friend Complex operator +(Complex A,Complex B)
{
return Complex(A.real+B.real,A.image+B.image);
}
//=运算符只能重载为类的成员函数
Complex operator =(Complex B)
{
real=B.real,image=B.image;//先分别赋值
//return Complex(real,image);//也可以,需要调用构造函数建立临时对象
return *this;//使用this指针返回对象不需要调用构造函数,会调用拷贝构造函数
//return Complex(B.real,B.image);//错误
}
//重载+=运算符
Complex operator +=(Complex B)
{
real=real+B.real;
image=image+B.image;
return *this;
}
};
int main()
{
Complex A(1,2),B(2,3),C;
C=A+B;
cout<<"c=a+b=", C.display();
C=A;
cout<<"c=a=", C.display();
return 0;
}
2、重载成员指针运算符->
只能重载为类的成员函数
重载成员指针运算符能确保指向类对象的指针总是指向某个有意义的对象(有效内存地址),即创建一个指向对象的指针,否则返回错误信息,这样避免了对空指针、垃圾指针内容的存取。
成员指针运算符的 *** 作数有两个,左边是一个对象指针,右边是一个对象的成员,由于右边的对象成员的类型不能确定,因此只能作为一元运算符重载。
#include
using namespace std;
class Complex
{
private:
double real,image;
public:
Complex(double real=0.0,double image=0.0)
{
real=this->real,image=this->image;
}
void display()
{
cout<<"("<<real<<","<<image<<")"<<endl;
}
};
//复数类指针定义
class PComplex
{
private:
Complex *PC;
public:
PComplex(Complex *PC=NULL)
{
this->PC=PC;
}
Complex * operator ->()
{
static Complex NullComplex(0,0);//避免指针为空
if(PC==NULL)
{
return &NullComplex;
}
return PC;
}
};
int main()
{
PComplex p1;
p1->display();//
Complex c1(1,2);
p1=&c1;
p1->display();
return 0;
}
3、重载下标运算符[ ]
只能重载为类的成员函数
*** 作对象有两个,左边是一个对象指针,[ ]中间是一个作为下标的整型数,因此作为二元运算符重载。
#include
using namespace std;
#include
class String
{
private:
char *str;
int len;
public:
void showStr(){
cout<<"string:"<<str<<",length"<<len<<endl;
}
//构造函数
String(const char *p=NULL)
{
if(p)
{
len=strlen(p);
str=new char[len+1];
strcpy(str,p);
}else {
len=0;
str=NULL;
}
}
//必须要有虚构函数
~String()
{
if(str!=NULL)
{
delete []str;
}
//处理Srtring对象
char &operator [](int n)
{
return *(str+n);
}
//处理const String 对象
const char &operator[](int n)const
{
return *(str+n);
}
};
int main()
{
String s1("12344566");
s1.showStr();
s1[3]='a';
cout<<"s1[3]"<<s1[3]<<endl;
const String s2("abcdef");
cout<<"s2[3]"<<s2[3]<<endl;
return 0;
}
三、虚函数
1、静态联编与动态联编
静态联编根据指针和引用的类型而不是根据实际指向的目标确定调用的函数,有时会导致错误。
动态联编则在程序的运行过程中,根据指针与引用实际指向的目标调用对应的函数,就是在程序运行时才决定如何动作。
虚函数是允许函数调用与函数体之间的联系在运行时才建立,是实现动态联编的基础。
虚函数的定义形式:
virtual 函数返回类型 函数(形参表)
{
}
- 虚函数不能是静态成员函数,也不能是友元函数,因为二者不属于某个对象;
- 内联函数是不能在运行中动态确定位置的;
- 只有类的成员函数才能说明为虚函数,仅适用于具有继承关系的类对象;
- 构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常为虚函数。
要实现动态联编需要满足:
- 应满足类型兼容规则;
- 在基类中定义虚函数,并且在派生类中要重新定义虚函数;
- 要由成员函数或者通过指针、引用访问虚函数;
在基类中尽可能地将成员函数设置为虚函数,除了增加一些资源开销,没有其他坏处。
例子:
#include
using namespace std;
class BaseCalculator {
public:
int m_A;
int m_B;
// write your code here......
//虚函数
virtual int getResult();
};
// 加法计算器类
class AddCalculator : public BaseCalculator {
// write your code here......
virtual int getResult()
{
return m_A+m_B;
}
};
// 减法计算器类
class SubCalculator : public BaseCalculator {
// write your code here......
virtual int getResult()
{
return m_A-m_B;
}
};
int main() {
BaseCalculator* cal = new AddCalculator;
cal->m_A = 10;
cal->m_B = 20;
cout << cal->getResult() << endl;
delete cal;
cal = new SubCalculator;
cal->m_A = 20;
cal->m_B = 10;
cout << cal->getResult() << endl;
delete cal;
return 0;
}
#include
using namespace std;
class Point
{
private:
int x,y;
public:
Point(int x=0,int y=0)
{
this->x=x,this->y=y;
}
virtual double area()
{
return 0.0;
}
};
const double PI=3.14;
class Circle: public Point
{
private:
double r;
public:
Circle(int x,int y,double r):Point(x,y)
{
this->r=r;
}
double area()
{
return PI*r*r;
}
};
int main()
{
Point p1(10,10);
cout<<"p1.area="<<p1.area()<<endl;
Circle c1(10,10,20);
cout<<"c1.area="<<c1.area()<<endl;
Point *p;
p=&c1;//指针调用派生类的虚函数
cout<<"p->area="<<p->area()<<endl;
Point & p2=c1;//引用调用派生类的虚函数
cout<<"p2.area="<<p2.area()<<endl;
return 0;
}
3、虚析构函数
虚析构函数的定义形式
virtual ~ 类名();
当基类的析构函数被声明为虚函数,则派生类的析构函数无论是否使virtual声明都自动成为虚函数。
析构函数被声明为虚函数后,可以确保使用基类类型的指针能够自动调用适当的析构函数对不同对象进行清理。
如果使用基类指针指向由new运算建立的对象,而delete又作用于指向派生类对象的基类指针,就要将基类的析构函数声明为虚析构函数。
#include
using namespace std;
class A
{
public:
A()
{
cout<<"A::A()"<<endl;
}
//虚析构函数
virtual ~A()
{
cout<<"~A()"<<endl;
}
};
class B:public A{
private:
int *ip;
public:
B(int size=0)
{
ip=new int[size];
cout<<"B::B()"<<endl;
}
~B()
{
cout<<"~B()"<<endl;
delete []ip;
}
};
int main()
{
A *b=new B(10);//类型兼容
delete b;
return 0;
}
A::A()
B::B()
~B()
~A()
如果基类的析构函数不是虚函数,结果为
四、抽象类A::A()
B::B()
~A()
即派生类对象中动态分配的内存空间没有被释放,将造成内存泄露
是一种特殊的类,可以通过它多态地使用其中的成员函数,抽象类自身无法实例化。
带有纯虚函数的类称为抽象类,一个抽象类至少具有一个纯虚函数。
是一个在基类中说明的虚函数,但在基类中没有定义具体实现,要求各派类根据实际需要定义函数实现。
其作用是为派生类提供一致的接口。
纯虚函数的定义:
2、抽象类与具体类virtual 函数类型 函数名(参数表)=0;
与虚函数的区别是:=0
抽象类的特点:
- 只能作为其他类的基类使用,不能定义对象,纯虚函数的实现由派生类给出;
- 派生类也可以不给出纯虚函数的定义,继续作为抽象类,如果派生类给出实现,就是一个具体类,可以定义对象。
- 抽象类不能用作参数类型,函数返回值,强制类型转换;
- 可以定义·一个·抽象类的指针和引用,通过抽象类的指针和引用可以指向并访问各派生类成员,这种访问具有多态特征。
#include
using namespace std;
class Shape
{
//只声明不实现,抽象类
public:
virtual double area()const=0;
virtual void show()=0;
};
class Point:public Shape
{
protected:
int x,y;
public:
Point(int x=0,int y=0)
{
this->x=x,this->y=y;
}
void show()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
double area()const
{
return 0;
}
};
const double PI=3.14;
class Circle:public Point
{
protected:
double r;
public:
Circle(int x,int y,double r):Point(x,y)
{
this->r=r;
}
double area()const{
return PI*r*r;
}
void show()
{
cout<<"centre:",Point::show();
cout<<"radius:"<<r<<endl;
}
};
int main()
{
Circle c1(1,2,3);
Shape *p;//抽象类的指针
p=&c1;
p->show();//访问派生类成员函数
cout<<"area:"<<p->area();
return 0;
}
3、对象指针数组
对象指针数组是一个指针数组,这些指针指向对象。
类的兼容性使基类指针可以指向其各类派生类对象,这意味着一个基类类型的指针数组的各个元素可以指向不同的派生类对象,这就是异类对象存储机制。
对象指针数组的使用
设计一个Person类,派生出Teacher、Student类,用一个Person类的指针数组指向Teacher和Student类的对象。
#include
using namespace std;
#include
class Person
{
protected:
char name[20];
public:
Person(char *iname)
{
strcpy(name,iname);
}
virtual void who()=0;
virtual ~Person()
{
cout<<"~person"<<endl;
}
};
class Student:public Person
{
private:
char major[20];
public:
Student(char * iname,char *imajor):Person(iname)
{
strcpy(major,imajor);
}
void who()
{
cout<<name<<","<<major<<endl;
}
~Student()
{
cout<<"~student"<<endl;
}
};
class Teacher:public Person
{
private:
char teah[20];
public:
Teacher(char *iname,char *iteach):Person(iname)
{
strcpy(teah,iteach);
}
void who()
{
cout<<name<<","<<teah<<endl;
}
~Teacher()
{
cout<<"~teacher"<<endl;
}
};
int main()
{
Person *personArr[5];//基类对象指针数组
personArr[0]=new Student("zhangsan","math");
personArr[1]=new Teacher("lisi","computer");
int len=sizeof(personArr)/sizeof(personArr[0]);
for(int i=0;i<len;i++)
{
personArr[i]->who();
delete personArr[i];
}
}
zhangsan,math
~student
~person
lisi,computer
~teacher
~person
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)