第 8章 多态性
8.1 运算符重载
8.2 运算符重载为类的成员函数
8.3 运算符重载为类的友元函数
8.4 虚函数
8.1 运算符重载
8.1.1 问题的提出例 6.3的复数类
#include "iostream.h"
class CComplex
{
private:
double real;
double imag;
public:
CComplex(double r,double i);
void Print();
CComplex Add(CComplex c);
CComplex Sub(CComplex c);
};
第 8章 多态性
CComplex CComplex::Add(CComplex c)
{
CComplex temp;
temp.real = real + c.real;
temp.imag = imag + c.imag;
return temp;
}
CComplex CComplex::Sub(CComplex c)
{
CComplex temp;
temp.real = real - c.real;
temp.imag = imag - c.imag;
return temp;
}
8.1 运算符重载
8.1.1 问题的提出(续一)
void main(void)
{
CComplex a(1,2),b(3.0,4.0),c,d;
c = a.Add(b);
d = a.Sub(b);
cout << "c = ";
c.Print();
cout << "d = ";
d.Print();
}
第 8章 多态性复数加减法只能调用成员函数实现,
不能使用符号,+”和,-”,可以通过重载,+”、,-”运算符,实现如
c=a+b这样的调用方式运算符重载:运算符重载的实质就是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时,产生不同的行为。运算符重载的实质就是函数重载。
例 8.1 用运算符实现复数的加减运算
#include "iostream.h"
class CComplex
{
private:
double real;
double imag;
public:
CComplex(double r=0,double i=0);
void Print();
CComplex operator +(CComplex c);
CComplex operator -(CComplex c);
};
CComplex::CComplex (double r,double i)
{
real = r;
imag = i;
}
第 8章 多态性例 8.1 (续一)
void CComplex::Print()
{
cout << "(" << real << "," << imag << ")" << endl;
}
CComplex CComplex::operator +(CComplex c)
{
CComplex temp;
temp.real = real + c.real;
temp.imag = imag + c.imag;
return temp;
}
CComplex CComplex::operator -(CComplex c)
{
CComplex temp;
temp.real = real - c.real;
temp.imag = imag - c.imag;
return temp;
}
第 8章 多态性例 8.1 (续二)
void main(void)
{
CComplex a(1,2),b(3.0,4.0),c,d;
c = a+b;
d = a-b;
cout << "c = ";
c.Print();
cout << "d = ";
d.Print();
}
第 8章 多态性该语句相当于对函数 operator +(CComplex c)
的调用:,c=a.operator +(b)”,实现两个复数的加法运算。
程序运行结果为:
c = (4,6)
d = (-2,-2)
8.1 运算符重载
8.1.2 运算符重载的格式与规则
1,运算符重载的格式
运算符重载为类的成员函数
运算符重载为类的友元函数运算符重载的为类的成员函数,在类中声明的格式为:
函数类型 operator 运算符(参数表);
定义该函数的格式:
函数类型 类名,:operator 运算符(参数表)
{
函数体;
}
也可以将重载运算符函数的定义直接写在类中。
第 8章 多态性
8.1 运算符重载
8.1.2 运算符重载的格式与规则(续)
2,运算符重载的规则
( 1)除,.”、,*”、,::”、,?:”和,sizeof”等几个运算符不能重载外,C++中几乎所有的运算符都可以重载。
( 2)运算符被重载后,其优先级和结合性不会改变。
( 3)不能改变运算符操作对象的个数。
第 8章 多态性返 回
8.2 运算符重载为类的成员函数
8.2.1 双目运算符重载双目运算符,如果 重载为类的成员函数,其 参数为一个,即比运算对象少一个。
例 8.2 复数的乘法运算,在上例的基础上添加乘法运算符重载函数。复数类乘法运算的定义如下:
( a+bi) *( x+yi) = a*x-b*y + (a*y + b*x)i
第 8章 多态性例 8.2 复数乘法运算源程序
#include "iostream.h"
class CComplex
{
private:
double real;
double imag;
public:
CComplex(double r=0,double i=0);
void Print();
CComplex operator +(CComplex c);
CComplex operator -(CComplex c);
CComplex operator *(CComplex c);
};
CComplex::CComplex (double r,double i)
{
real = r;
imag = i;
}
第 8章 多态性例 8.2 (续一)
void CComplex::Print()
{
cout << "(" << real << "," << imag << ")" << endl;
}
CComplex CComplex::operator +(CComplex c)
{
CComplex temp;
temp.real = real + c.real;
temp.imag = imag + c.imag;
return temp;
}
CComplex CComplex::operator -(CComplex c)
{
CComplex temp;
temp.real = real - c.real;
temp.imag = imag - c.imag;
return temp;
}
第 8章 多态性例 8.2 (续二)
CComplex CComplex::operator *(CComplex c)
{
CComplex temp;
temp.real = real * c.real - imag * c.imag;
temp.imag = real * c.imag + imag * c.real;
return temp;
}
void main(void)
{
CComplex a(1,2),b(3.0,4.0),c,d,e,f;
c = a+b;
d = a-b;
e = a*b;
f = a+1;
cout << "c = ";
c.Print();
cout << "d = ";
d.Print();
cout << "e = ";
e.Print();
cout << "f = ";
f.Print();
}
第 8章 多态性程序运行结果为:
c = (4,6)
d = (-2,-2)
e = (-5,10)
f = (2,2)
总结,设有双目运算符 B,如果要重载 B 为类的成员函数,使之能够实现表达式 oprd1 B oprd2,其中
oprd1 为 A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。
经重载后,表达式 oprd1 B
oprd2 相当于 oprd1.operator
B(oprd2),注意重载双目运算符只需要一个参数。
8.2 运算符重载为类的成员函数
8.2.2 单目运算符重载单目运算符,如果 重载为类的成员函数,不需要参数 。
为区分前置和后置运算符,C++规定:
对于 前置单目运算符,重载函数 没有参数对于 后置单目运算符,重载函数 有一个整型参数,这个整型参数没有其他用途,只是用于区分前置运算与后置运算。
第 8章 多态性例 8.3 定义一个 CInt类,类中只有一个数据成员 i,两个运算符,++”的重载函数,一个没有参数,实现的是前置运算符重载,另一个有一个整型参数,实现后置运算符重载。
#include "iostream.h"
class CInt
{
private:
int i;
public:
CInt(int a=0);
void Print();
CInt operator ++();
CInt operator ++(int);
};
CInt::CInt (int a)
{
i = a;
}
第 8章 多态性
void CInt::Print()
{
cout << "i=" << i << endl;
}
CInt CInt::operator ++()
{
CInt temp;
temp.i = ++i;
return temp;
}
CInt CInt::operator ++(int)
{
CInt temp;
temp.i = i++;
return temp;
}
例 8.3 (续)
void main(void)
{
CInt a(5),b(5),c,d;
c = a++;
d = ++b;
cout << "a,";
a.Print();
cout << "b,";
b.Print();
cout << "c,";
c.Print();
cout << "d,";
d.Print();
}
第 8章 多态性程序运行结果为:
a,i=6
b,i=6
c,i=5
d,i=6
8.2 运算符重载为类的成员函数
8.2.3 赋值运算符重载如果类中只包含简单数据类型的数据成员,则使用 C++提供的赋值运算符,=”就可以实现将一个对象赋给另一个对象。如前面复数类的对象,就可以将一个对象直接赋给另一个对象。但如果类的数据成员比较复杂(如含有指针),这样直接赋值就会产生问题,我们必须重载赋值运算符,=”才能正确使用,=”。
第 8章 多态性例 8.4 类 A只有一个数据成员 str,是一个字符指针,在构造函数中为 str申请存储空间并赋值,在析构函数中释放内存。
#include "iostream.h"
#include "string.h"
class A
{
private:
char *str;
public:
A(char *s="no data");
~A();
void print();
};
A::A (char *s)
{
int len = strlen(s);
str = new char[len+1];
strcpy(str,s);
}
第 8章 多态性例 8.4 (续)
A::~A ()
{
if(str)
delete []str;
}
void A::print()
{
cout << str << endl;
}
void main(void)
{
A *p = new A("AAAA");
A a1;
a1=*p;
a1.print();
delete p;
a1.print();
}
第 8章 多态性
strp str a1
AAAA
该语句只是将 p所指向的对象数据成员
str赋给对象 a1的数据成员 str,即两个对象的 str指向了同一个单元调用析构函数,同时将 str所指向的单元释放了,
再执行 a1.print()时,就会出现错误。
例 8.5 带有重载赋值运算符的 A类
#include "iostream.h"
#include "string.h"
class A
{
private:
char *str;
public:
A(char *s="no data");
~A();
A &operator =(A &a);
void print();
};
A::A (char *s)
{
int len = strlen(s);
str = new char[len+1];
strcpy(str,s);
}
第 8章 多态性例 8.5 (续一)
A::~A ()
{
if(str)
delete []str;
}
A &A::operator =(A &a)
{
int len = strlen(a.str);
if(str)
delete []str; //先释放,再根据实际需要重新申请
str = new char[len+1];
strcpy(str,a.str);
return *this;
}
void A::print()
{
cout << str << endl;
}
第 8章 多态性例 8.5 (续二)
void main(void)
{
A *p = new A("AAAA");
A a1;
a1=*p;
a1.print();
delete p;
a1.print();
}
第 8章 多态性
strp str a1
AAAA AAAA
程序运行结果为:
AAAA
AAAA 返 回
8.3 运算符重载为类的友元函数
8.3.1 问题的提出
( 1)复数与复数相加( c1,c2,c3是复数类的对象)。
c3=c1+c2;
( 2)一个复数与一个实数的加法运算
c3=c1+10.8;
( 3)一个实数与一个复数相加就会出现错误。
c3= 10.8 + c1;
因为加号左边的运算对象是实数,C++试图将加号右边的运算对象解释为实数,但 C++无法将一个复数转换为一个实数,从而产生错误。
将运算符重载为友元函数可以解决这个问题。
第 8章 多态性
8.3 运算符重载为类的友元函数
8.3.2 运算符重载为友元函数类中的 声明,
friend 函数类型 operator 运算符(参数表);
运算符重载函数的 定义形式,
函数类型 operator 运算符(参数表)
{
函数体;
}
第 8章 多态性例 8.6 用友元函数实现复数类加减运算符的重载
#include "iostream.h"
class CComplex
{
private:
double real;
double imag;
public:
CComplex(double r=0,double i=0);
void Print();
friend CComplex operator +(CComplex c1,CComplex c2);
friend CComplex operator -(CComplex c1,CComplex c2);
};
CComplex::CComplex (double r,double i)
{
real = r;
imag = i;
}
第 8章 多态性例 8.6 (续一)
void CComplex::Print()
{
cout << "(" << real << "," << imag << ")" << endl;
}
CComplex operator +(CComplex c1,CComplex c2)
{
CComplex temp;
temp.real = c1.real + c2.real;
temp.imag = c1.imag + c2.imag;
return temp;
}
CComplex operator -(CComplex c1,CComplex c2)
{
CComplex temp;
temp.real = c1.real - c2.real;
temp.imag = c1.imag - c2.imag;
return temp;
}
第 8章 多态性例 8.6 (续二)
void main(void)
{
CComplex a(1,2),b(3.0,4.0),c,d,e;
c = a+b;
d = b-10;
e = 20+a;
cout << "c = ";
c.Print();
cout << "d = ";
d.Print();
cout << "e = ";
e.Print();
}
第 8章 多态性相当于函数调用,c=operator+(a,b)”
程序运行结果为:
c = ( 4,6)
d = ( -7,4)
e = ( 21,2)
总结,设有双目运算符 B,如果要重载 B 为类的友元函数,则该友元函数也有两个参数,分别为运算符的两个运算对象。重载后,表达式
oprd1 B oprd2 等同于调用函数
operator B(oprd1,oprd2 )。
单目运算符也可以重载为类的友元函数,该友元函数有一个参数。
返 回
8.4 虚函数
8.4.1 用虚函数实现动态多态回顾例 7.6
void main()
{
CShape *ps[3];
CShape s("Red");
CPoint p1(10,10),p2(100,100),p3(50,50);
CLine l(p1,p2,"Green");
CCircle c(p3,20,"Black");
ps[0] = &s;
ps[1] = &l;
ps[2] = &c;
for(int i=0; i<3; i++)
ps[i]->Draw();
}
第 8章 多态性程序运行结果为:
Draw a Shape,The color is Red
Draw a Shape,The color is Green
Draw a Shape,The color is Red Black
虽然父类的指针可以指向子类的对象,但调用的函数 Draw()都是父类 CShape的成员函数为了能通过基类的指针调用派生类的成员函数,可以使用虚函数的方法,即把成员函数 Draw()声明为虚函数。
例 8.7 用虚函数实现动态多态
#include <iostream.h>
#include <string.h>
class CPoint
{
private:
int X;
int Y;
public:
CPoint(int x=0,int y=0)
{
X=x;
Y=y;
}
CPoint(CPoint &p)
{
X=p.X;
Y=p.Y;
}
int GetX()
{
return X;
}
int GetY()
{
return Y;
}
};
第 8章 多态性例 8.7 (续一)
class CShape
{
private:
char Color[10];
public:
CShape(char *c)
{
strcpy(Color,c);
}
virtual void Draw()
{
cout << "Draw a Shape,The color is " << Color << endl;
}
void PrintColor()
{
cout << Color << endl;
}
};
第 8章 多态性例 8.7 (续二)
class CLine:public CShape
{
private:
CPoint Start;
CPoint End;
public:
CLine(CPoint s,CPoint e,char *c):CShape(c),Start(s),End(e)
{}
virtual void Draw()
{
cout << "Draw a Line from (" << Start.GetX() << "," << Start.GetY();
cout << ") to ("<< End.GetX() << "," << End.GetY() << "),with color ";
PrintColor();
}
};
第 8章 多态性例 8.7 (续三)
class CCircle:public CShape
{
private:
CPoint Center;
int Radius;
public:
CCircle(CPoint ctr,int r,char *c):CShape(c),Center(ctr)
{
Radius = r;
}
virtual void Draw()
{
cout << "Draw a Circle at center (" << Center.GetX() << "," ;
cout << Center.GetY()<< ") with radius " << Radius << " and color ";
PrintColor();
}
};
第 8章 多态性例 8.7 (续四)
void main()
{
CShape *ps[3];
CShape s("Red");
CPoint p1(10,10),p2(100,100),p3(50,50);
CLine l(p1,p2,"Green");
CCircle c(p3,20,"Black");
ps[0] = &s;
ps[1] = &l;
ps[2] = &c;
for(int i=0; i<3; i++)
ps[i]->Draw();
}
第 8章 多态性程序运行结果为:
Draw a Shape,The color is Red
Draw a Line from (10,10) to (100,100),with color Green
Draw a Circle at center (50,50) with radius 20 and color Black
8.4 虚函数
8.4.1 用虚函数实现动态多态(续)
总结:
( 1)将成员函数声明为 虚函数,在函数原型前加关键字 virtual,
如果成员函数的定义直接写在类中,也在前面加关键字 virtual。
( 2)将成员函数声明为虚函数后,再将基类指针指向派生类对象,在程序运行时,就会根据指针指向的具体对象来调用各自的虚函数,称之为 动态多态 。
( 3)如果基类的成员函数是虚函数,在其派生类中,原型相同的函数自动成为虚函数 。
第 8章 多态性
8.4 虚函数
8.4.2 用虚函数实现动态多态的机制为了实现 动态多态,编译器对每个包含虚函数的类创建一个虚函数地址 表,并设置一个 虚函数地址指针 指向这个对象的虚函数地址表。使用基类指针对虚函数调用时,通过这个虚函数地址指针,在虚函数地址表中查找函数地址。
由于 包含虚函数的类,有一个虚函数地址指针,与没有虚函数的类相比,含有虚函数类的对象所占用的 存储空间要多一个指针所占用的内存 。
第 8章 多态性例 8.8 含有虚函数类的对象所占用的存储空间。
#include <iostream.h>
class A
{
private:
int a;
public:
virtual void func(){}
};
class B,public A
{
private:
int b;
public:
virtual void func(){}
virtual void func1(){}
};
void main(void)
{
cout << "sizeof(A)=" << sizeof(A) << endl;
cout << "sizeof(B)=" << sizeof(B) << endl;
}
第 8章 多态性程序运行结果为:
sizeof(A)=8
sizeof(B)=12
8.4 虚函数
8.4.2 用虚函数实现动态多态的机制(续)
每个 含有虚函数类的对象 都有一个 虚函数地址指针,指向该类虚函数表,如果用基类的指针调用虚函数,在编译时,并不能确定这个指针的具体指向,而是在运行时,根据指针所指向的具体对象 (基类的对象或其派生类的对象),虚函数地址指针才有一个确定的值,即相应类的这个虚函数的地址。从而实现动态多态。
第 8章 多态性
8.4 虚函数
8.4.3 虚析构函数析构函数也可以定义为虚函数,如果基类的析构函数定义为虚析构函数,则派生类的析构函数就会自动成为虚析构函数。
如果 基类的指针指向派生类对象,当用 delete删除这个对象时,
若析构函数不是虚函数,就要 调用基类的析构函数,而 不会调用派生类的析构函数 。如果为基类和派生类的对象分配了动态内存,
或者为派生类的对象成员分配了动态内存,这时释放的只是基类中动态分配的内存,而派生类中动态分配的内存未被释放,因此一般应将析构函数定义为虚析构函数 。
第 8章 多态性例 8.9 定义职员类 CEmployee,数据成员有姓名(字符指针型数据)和年龄,由 CEmployee类派生出教师类 CTeacher,
增加数据成员教学简历(字符指针型数据)。
#include <iostream.h>
#include <String.h>
class CEmployee
{
private:
char *name;
int age;
public:
CEmployee(char *n,int a);
virtual ~CEmployee();
};
CEmployee::CEmployee(char *n,int a)
{
name=new char[strlen(n)+ 1 ];
strcpy(name,n);
age = a;
}
第 8章 多态性例 8.9 (续一)
CEmployee::~CEmployee()
{
cout << "Destruct CEmployee" << endl;
if(name)
{
delete []name;
}
}
class CTeacher,public CEmployee
{
private:
char *mainCourse;
public:
CTeacher(char *n,char *course,int a);
virtual ~CTeacher(); //由于基类已定义虚析构函数,此处也可不加 virtual
};
CTeacher::CTeacher(char *n,char * course,int a),CEmployee(n,a)
{
mainCourse = new char[strlen(course)+1];
strcpy(mainCourse,course);
}
第 8章 多态性例 8.9 (续二)
CTeacher::~CTeacher()
{
cout << "Destruct CTeacher" << endl;
if(mainCourse)
delete []mainCourse;
}
void main(void)
{
CEmployee *p[3];
p[0] = new CEmployee("Name1",20);
p[1] = new CTeacher("Name2","C for 2 years,C++ 3 years",26);
p[2] = new CTeacher("Name3","Data structure for 2 years,C++ 3 years",30);
for(int i=0; i<3; i++)
delete p[i];
}
第 8章 多态性程序运行结果为:
Destruct CEmployee
Destruct CTeacher
Destruct CEmployee
Destruct CTeacher
Destruct CEmployee
若不定义为虚析构函数,程序运行结果为:
Destruct CEmployee
Destruct CEmployee
Destruct CEmployee
由于未调用 CTeacher类析构函数,导致成员 mainCourse
空间未被释放
8.4 虚函数
8.4.4 纯虚函数与抽象类
纯虚函数 是不必定义函数体的特殊虚函数,纯虚函数的声明格式为:
virtual 函数类型 函数名(参数表) = 0;
含有 纯虚函数 的类称为 抽象类 。抽象类常常用作派生类的基类。
如果派生类继承了抽象类的纯虚函数,却没有重新定义原型相同且带函数体的虚函数,或者派生类定义了基类所没有定义的纯虚函数,则派生类仍然是抽象类,在多层派生的过程中,如果到某个派生类为止,所有纯虚函数都已全部重新定义,则该派生类就成为非抽象类 。
不能定义抽象类的对象,但可以声明抽象类的指针和引用 。
第 8章 多态性例 8.10 将例题 8.7中 CShape类的函数 Draw()声明为纯虚函数,其它类保持不变。
class CShape
{
private:
char Color[10];
public:
CShape(char *c)
{
strcpy(Color,c);
}
virtual void Draw()= 0;
void PrintColor()
{
cout << Color << endl;
}
};
第 8章 多态性
void main()
{
CShape *ps[3];
CPoint p1(10,10),
p2(100,100),p3(50,50);
CLine l(p1,p2,"Green");
CCircle c(p3,20,"Black");
ps[1] = &l;
ps[2] = &c;
for(int i=1; i<3; i++)
ps[i]->Draw();
}
程序运行结果为:
Draw a Line from (10,10) to (100,100),with color Green
Draw a Circle at center (50,50) with radius 20 and color Black
不能定义 CShape类的对象,
但可以定义 CShape类的指针返 回谢 谢!