第12章 多态
(本资料qq讨论群112133686)
多态定义为同样的消息被不同类型的对象接收时导致不同的行为,消息指的对类的成员函数的调用,不同的实现就需要不同的行为,也就是说要调用不同的函数。面向对象的多态性可以分为四类,为专用多态:重载多态和通用多态:强制多态、包含多态和函数多态。前面学过的普通函数及类的成员函数重载属于重载多态。
12.1 多态与虚函数
定义一个类,使用保留字virtual可将成员的函数声明为虚函数。虚函数实现了“一个接口,多种方法”的功能,在编写程序时候带来方便,同时由于采用指针或引用操作,程序的执行效率不会降低依然很高。
案例12-1 员工月薪发放(多态)
【案例描述】
多态性是指处理多种类型对象的能力,C++通过虚函数实现多态特性。本实例基类中定义员工类月薪发放的虚函数,没有定义基类怎样计算月薪的,继承基类的销售员类和销售经理类实现虚函数的多态性,具体实现月薪发放的函数。本例效果如图12-1所示。
图12-1 员工月薪发放(多态)
【实现过程】
定义抽象虚拟类公司员工employee,构造函数employee赋值操作,静态数据成员totalno在类外进行初始化,私有成员编号no、姓名namesalary,成员函数display()显示员工月薪发放;定义继承类销售员类saleman和销售经理类manager,具体实现函数pay()和display()。代码如下:
#include<iostream.h>
#include<iostream>
#include<string.h>
class employee //公司员工类定义
{
protected:
int no; //编号
char *name; //姓名
float salary; //工资
static int totalno; //静态数据成员totalno在类外进行初始化
public:
virtual void pay()=0;
virtual void display()=0; //显示员工月薪发放
employee() //构造函数
{
char temname[20];
no=totalno++;
cout<<"职工姓名:";
cin>>temname; //输入姓名
name=new char[strlen(temname)+1]; //申请空间
strcpy(name,temname); //输入的姓名赋值给类成员函数
salary=0;
}
~employee()
{
delete[] name; //释放内存
}
};
int employee::totalno=100; //静态数据成员totalno在类外进行初始化
class saleman:virtual public employee //定义销售员类
{
protected:
float commrate; //销售额提纯系数
float sales; //销售额
public:
saleman(){commrate=0.04f;}
void pay()
{
cout<<name<<"本月销售额:";
cin>>sales;
salary=sales*commrate+680; //680为底薪
}
void display()
{
cout<<"销售员"<<name<<"(编号为"<<no<<")"\
<<")"<<"本月工资:"<<salary<<endl;
}
};
class manager:virtual public employee //定义销售经理类
{
protected:
float monthlypay; //月工资
public:
manager(){monthlypay=8000; }
void pay(){salary=monthlypay;}
void display()
{
cout<<"经理"<<name<<"(编号为"<<no<<")"\
<<")"<<"本月工资:"<<salary<<endl;
}
};
void main()
{
saleman s1; //建立saleman类对象s1
s1.pay();
s1.display(); //输出信息
manager m1; //建立manager类对象m1
m1.pay();
m1.display();
system("pause");
}
【案例分析】
(1)继承多态性可以通过强制多态、重载多态、类型参数化多态、包含多态四种形式来实现。代码中由虚基类employee派生出的类saleman进行成员函数初始化时,直接调用虚基类的构造函数pay()和display()。因此,若将一个类定义为虚基类,则一定要有正确的构造函数供所有派生类调用。用虚基类进行多重派生时,若虚基类没有缺省的构造函数,则在每一个派生类的构造函数中都必须有对虚基类构造函数的调用,且首先调用。
(2)用基指针访问虚函数时,指向其实际派生类对象重新定义的函数,实现动态聚束。s1.display())通过一个对象名访问时,只能静态聚束。即由编译器在编译的时候决定调用哪个函数。
注意:当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型、顺序、个数要一一对应,函数的返回的类型也要相同。
案例12-2 学生的假期生活 (接口)
【案例描述】
纯虚函数在基类中是没有定义具体的操作内容,各派生类根据需要定义自己的具体的功能的版本,带有纯虚函数的类称抽象类,作用通过它为一个类建立一个公共接口,使它们能够有效发挥多态特征。如定义list类,插入元素函数和输出链表的各元素在基类中没有具体内容为纯虚函数,继承子类具体实现。本例效果如图12-2所示。
图12-2 学生的假期生活 (接口)
【实现过程】
程序定义了个链表类list,定义2个纯虚函数store()和retrieve(),为插入元素函数和输出链表的各元素,在主函数中为继承类queue输入3个值,再通过一个判断按照先进先出顺序输出这3个值。其代码如下:
#include <iostream>
#include <cstdlib>
using namespace std;
class list { //list类定义
public:
list *head; //list的头部
list *tail; //list的尾部
list *next; //指向下一个节点的指针
int num; //节点存储的值
list() {
head = tail = next = NULL; //置空
}
virtual void store(int i) = 0; //纯虚函数
virtual int retrieve() = 0;
};
class queue : public list {
public:
void store(int i);
int retrieve(); //输出链表的各个元素
};
void queue::store(int i) //插入元素函数
{
list *item;
item = new queue; //分配内存
if(!item) {
cout << "内存分配错误.\n";
exit(1); //结束程序
}
item->num = i;
// put on end of list
if(tail)
tail->next = item; //下一个节点赋值
tail = item;
item->next = NULL;
if(!head)
head = tail;
}
int queue::retrieve() //输出链表的各元素
{
int i;
list *p;
if(!head) {
cout << "是空链表.\n";
return 0;
}
i = head->num; //头节点赋值给变量i
p = head;
head = head->next;
delete p; //释放内存
return i;
}
int main()
{
char *i1="您认为自己的假期生活是:";
char *j1="您一般和谁渡过假期: ";
char *k1="您每天会花多少时间在读书上: ";
int i,j,k;
list *p; //建立list类指针对象p
queue q_ob; //建立queue类指针对象q_ob
p = &q_ob; //指向queue
cout<<i1<<"1、精彩 2、无聊 3、痛苦"<<endl;
cout<<j1<<"1、家人 2、朋友 3、同学"<<endl;
cout<<k1<<"1、少于半小时 2、一小时左右3、一到三小时"<<endl;
cin>>i>>j>>k;
p->store(i); //插入元素
p->store(j);
p->store(k);
cout << "Queue: ";
int retval1,retval2,retval3;
retval1=p->retrieve(); //输出链表的各元素
retval2=p->retrieve();
retval3=p->retrieve();
if( retval1==1) //用多个if判断输出信息
cout<<"假期生活是精彩 ";
else if(retval1==2) cout<<"假期生活是无聊 ";
else if(retval1==3) cout<<"假期生活是痛苦 ";
if(retval2==1) cout<<"一般和"<<"家人"<<"渡假期 ";
else if(retval2==2) cout<<"一般和"<<"朋友"<<"渡假期 ";
else if(retval3==3) cout<<"一般和"<<"同学"<<"渡假期 ";
if(retval3==1) cout<<"每天会花少于半小时在读书上 ";
else if(retval3==2) cout<<"每天会花一小时左右在读书上 ";
else if(retval3==3) cout<<"每天会花一到三小时在读书上 ";
cout<<endl;system("pause"); return 0;
}
【案例分析】
(1)基类中的store()、retrieve()虚函数没有给出具体功能的实现,只是在派生类中有具体的意义。这时基类中的虚函数只是一个入口,具体的实现目的由不同的派生类中的对象决定,这个虚函数称为纯虚函数。抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,实现动态多态性。
(2)纯虚函数特点:
1、在定义时,不能定义虚函数的实现部分。
2、把函数名赋于0,本质上是将指向函数体的指针值赋为初值0。
3、把至少包含一个纯虚函数的类,称为抽象类。
4、在以抽象类作为基类的派生类中必须有纯虚函数的实现部分,即必须有重载纯虚函数的函数体。否则,这样的派生类也是不能产生对象的。
注意:在没有重新定义这种纯虚函数之前,是不能调用这种函数的。必须在派生类中重载纯虚函数,否则会产生程序的运行错误。
12.2 虚函数的访问
案例12-3 比谁跑得远
【案例描述】
类的继承包含派生方式有:公有派生、保护派生、私有派生,那么派生类中对三种派生方式的权限,三种派生有什么区别呢。本实例举的是公有派生。本实例定义一个基类,然后公有有派生出两个子类,讨论公有和保护成员的访问属性和基类的私有成员直接访问的问题。本实例类公有继承基类,利用基类成员函数计算跑的时间,效果如图12-3所示。
【实现过程】
定义基类base,其中有虚函数disp()为显示信息,getdata()实现取得数据;定义个子类child1和child2继承基类,disp()用数学公式计算跑的距离。代码如下:
#include <iostream>
using namespace std;
class base //基类定义
{
public:
virtual void disp() //虚函数
{
cout<<"hello,base"<<endl;
}
void getdata()
{
cout<<"输入时间:"<<endl;
cin>>hour>>minute>>second; //输入时、分、秒
time=(double)(hour*3600+minute*60+second); //计算总共有多少秒
}
protected:
int hour,minute,second;
double time;
};
class child1:public base //派生类定义
{
public:
void disp() //虚函数的覆盖(对普通函数来说,是隐藏)
{
getdata(); //取得输入的时间
//距离=时间*速度
cout<<"child1跑了"<<time*0.5<<endl;
}
};
class child2:public base
{
public:
void disp() //虚函数的覆盖(对普通函数来说,是隐藏)
{
getdata(); //取得输入时间
cout<<"child2跑了"<<time*0.6<<endl;
}
};
void disp(base * p)
{
p->disp(); //调用disp()
}
int main()
{
base obj_base; //创建一个基类对象
child1 obj_child1; //建立child1类对象obj_child1
child2 obj_child2;
disp(&obj_base);
disp(&obj_child1); //输出距离
disp(&obj_child2);
system("pause");
return 0;
}
【案例分析】
(1)类child1公有派生base。派生并不是简单的扩充,也就是说有可能改变基类的性质。有三种派生方式:公有派生、保护派生、私有派生。默认的是私有派生。公有派生时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问。基类中所有成员在派生类中保持各个成员的访问权限。
(2)私有派生时,基类中公有成员和保护成员在派生类中均变为私有的,在派生类中仍可直接使用这些成员,但基类中的私有成员,在派生类中不可直接使用。保护派生时,基类中公有成员和保护成员在派生类中均变为保护的和私有的,在派生类中仍可直接使用这些成员,基类中的私有成员,在派生类中不可直接使用。
注意:公有派生时,基类中所有成员在派生类中保持各个成员的访问权限。
案例12-4 矩形范围(判断一个点是否超出矩形范围)
【案例描述】
在实际软件开发过程中,可利用面向对象编程的抽象、封装、继承和多态等优点,来不断增加和修改功能,本案例演示需要增加功能的时候在基类定义个虚函数,派生类继承这个函数再实现具体的功能。本例中矩形继承图形抽象基类,通过成员函数判断点是否在矩形区域内,效果如图12-4所示。
图12-4 矩形范围(判断一个点是否超出矩形范围)
【实现过程】
设计图形抽象基类Figure,矩形具有长和宽两种属性,定义个结构Rect,纯虚函数inRect();再定义个矩形类Rectangle继承基类Figure。该类提供成员函数inputRect()和inRect(),分别输入矩形参数,判断点是否在矩形区域内。其代码如下:
#include <iostream>
#include <cmath>
#include<stdlib.h>
using namespace std;
class Figure //图形基类定义
{
protected:
float x; //两个边长x和y
float y;
public:
typedef struct
{ int x; //图形左上角x坐标
int y; //图形左上角Y坐标
int h; //图形的高
int w; //图形的宽
}Rect;
public:
virtual bool inRect(int x ,int y ,Rect const & a) =0;//纯虚函数
virtual void DispName() =0; //纯虚函数,因此Figure类是抽象类,无法声明其对象
};
class Rectangle:public Figure //在抽象类Figure的基础上派生Rectangle矩形类
{
public:
Rectangle(float xp=0,float yp=0) //构造函数
{
x=xp;
y=yp;
}
virtual void DispName() //覆盖实现了虚函数DispName,此处virtual去掉没有影响
{
cout<<"矩形:"<<endl;
}
//输入a的x、y、h、w的值
void inputRect(Rect &a,char const *prompt);
bool inRect(int x ,int y ,Rect const & a);
};
//输入矩形参数
void Rectangle::inputRect(Rect &a,char const *prompt)
{
cout<<prompt<<endl; cout<<"x:"; //prompt为提示
cin>>a.x; //图形左上角x坐标
cout<<"y:"; //图形左上角y坐标
cin>>a.y;
h_w:
cout<<"h:"; //图形的高
cin>>a.h;cout<<"w:";
cin>>a.w; //图形的宽
if (a.h<=0 || a.w<=0)
{
cout<<"h and w must >0\n";
goto h_w;
}}bool Rectangle::inRect(int x ,int y ,Rect const & a)
{ //判断点是否在矩形区域内,原理就是点的x 和 y 同时在矩形的 x y 范围内。
int a_x2=a.x+a.w;
int a_y2=a.y+a.h;
return (x<a_x2 && x>a.x) && (y<a_y2 && y>a.y);
}
int main()
{
Rectangle::Rect a,b;
Figure *pF=NULL; //虽然不能创建Figure类对象,但可声明Figure型的指针
Rectangle r(1.2f,3.6f); //声明一个矩形对象,其边长分别为1.2和3.6
pF=&r; //用矩形对象r的地址为pF赋值
cout<<"已有个点(12,23)\n";
//判断一个点是否超出矩形范围
r.inputRect(a,"输入矩形的x、y坐标、高和宽:");
if(r.inRect(12,23,a))
{ cout<<"点(12,23)在矩形范围内\n"; }
else {cout<<"点超出矩形范围\n";}
system("pause");
return 0;
}
【案例分析】
(1)矩形的4个顶点位于笛卡尔坐标系的第一象限内,定义矩形类Rectangle,继承抽象类Figure,长和宽两种属性属于保护成员protected,protected成员只能通过派生对象访问,不能通过基类对象访问(当然不能是私有继承)。inRect判断一个点是否超出矩形范围,其行为是提供给用户的,所以暴露接口。虚函数Rectangle::inRect()判断一个点是否超出矩形范围,根据点的x和y坐标值同时在矩形的x和 y坐标范围内来判断。
(2)类Rectangle公有继承基类Figure。公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。virtual bool inRect()是纯虚函数,在继承类中实现具体的功能。
注意:在派生类中没有重新定义虚函数时,是与一般的成员函数一样的,当调用这种派生类对象的虚函数时,则调用其基类中的虚函数。
12.3 纯虚函数与抽象类
案例12-5 头文件重定义错误实例
【案例描述】
编程中肯定会用到多个H和CPP文件同时编译和执行,那就避免不了多个H文件或CPP文件会包含在同一个头文件中,包含的头文件定义的常量或变量就会造成编译器就会出现重定义错误,那就需要改进错误。本例效果如图12-5所示。
图12-5 头文件重定义错误实例
【实现过程】
定义基类BASE,定义放在base.h中;定义T描述出租车类定义放在base1.h,定义类J描述学生成绩类base2.h里,这2个类继承基类BASE。代码实现如下:
//base.h代码
//#pragma once
#include <iostream.h>
#include <iostream>
#include<string.h>
//#ifndef BASH_H_
//#define BASH_H_
class BASE //抽象类BASE
{
public:
virtual double count()=0;
};
int line=0;
//#endif
//base1.h代码
#include<iostream>
#include"base.h"
class T:public BASE //出租车类
{
double s,k,p; //s、k与p分别表示起步价格、行驶公里数和每公里单价
public:
T(double s1,double k1,double p1) //构造函数
{
s=s1;k=k1; p=p1;
}
double count()
{
return s+(k-2.5)*p;
} //计算乘客使用出租车的费用
};
//base2.h代码
#include<iostream>
#include"base.h"
class J:public BASE //描述学生成绩类
{
int num;
char name[20]; //表示姓名
double m,c,e; //m、c和e分别表示数学、语文和英语成绩
public:
//构造函数
J(int num1,char name1[],float m1,float c1,float e1)
{
num=num1;
strcpy(name,name1);
m =m1;
c =c1;
e=e1;
}
int getNum(){
return num;
}
char* getName() //取得学生姓名
{
return name;
}
double count() //计算学生的数学、语文和英语的平均成绩
{
return (m+c+e)/3;
}
};
//主程序cpp
#include"base.h"
#include"base1.h"
#include"base2.h"
void main( )
{
T b(120,3,150); //使用出租车的费用
J j(12,"zhangsan",34,65.7,1200); //学生平均成绩
BASE *k;
k =&b;
cout<<"出租车的费用:"<<k-> count()<<endl;
k=&j;
cout<< "学生编号:"<<j.getNum()<<endl<<"学生姓名"<<j.getName()<<endl;
cout<< "学生成绩: "<<k-> count()<<endl;
cout<<"line="<<line<<endl;
system("pause");
}
【案例分析】
C++由于头文件重复包含了所定义的变量或者常量,编译时编译器就会报重复定义的错误。如果碰见这样的问题可以考虑从下面几个方面去解决:
(1)在出现重定义错误的头文件加上:
#ifndef BASH_H_,#define BASH_H_,....(头文件内容) #endif。
注意:如果BASH_H_这个名字已经被使用,将会出现未定义问题,这是保证BASH_H_唯一就可以。在出现重定义错误的头文件加上这一句:#pragma once就可以解决(Visual Studio建立的类都会默认添加这一行),方式2与1其实是一样的,二选一即可。
(2)在多个cpp文件编译时候,如果file1.c定义一个全局变量int a;,file2.c要用这个全局变量,那么加上定义extern int a;就可以使用。当在一个文件中要引用另一个文件中的全局变量或在全局变量定义之前要引用它时,可用extern作说明,相当于扩大全局变量的作用域。
注意:把头文件base.h用‘//’屏蔽代码去掉,程序编译通过。
案例12-6 走迷宫
【案例描述】
本例继续讨论类的多重继承和虚函数的应用,这些在第11章专门的实例讨论过,本实例在此基础上加深理解,同时演示类的这些功能在游戏上的应用。本实例定义类实现一个简单的迷宫环境,用户输入移动方向,走出迷宫,效果如图12-6所示。
图12-6 走迷宫
【实现过程】
程序在头文件定义几个类CRCObject、Imaze、Iplayer、Room、CmazeBuilder、Player。头文件类代码实现如下:
struct Room{ //迷宫环境
string name;
int id;
int eastID;
int westID;
int southID;
int northID;
};
class Maze : public IMaze
{
public:
Maze();
~Maze();
void addRoom(Room room); //往迷宫增加一个房间
Room* getRoom(int i); //根据房间编号参数返回房间的引用
virtual IPlayer *GetPlayer(); //返回操作者
private:
IPlayer* iplayer; //操作者
vector<Room> rooms; //存储房间的向量结构
};
class CMazeBuilder : public CRCObject
{
public:
CMazeBuilder(); //构造函数
~CMazeBuilder(); //析构函数
void BuildRoom(int rId, string rName); //增加一个房间,参数分别是房间号和房间名字
void BuildDoor(int direction, int r1, int r2); //设置房间之间的门,第一个参数0表示南北走向,1表示东西走向,第二、第三参数表示连接起来的两个房间号
IMaze *GetMaze();
private:
Maze *maze;
};
class Player : public IPlayer
{
public:
Player();
Player(Maze* temp);
~Player();
virtual string GetEnvironment(); //返回当前位置信息
virtual bool GoNorth(); //往北行走
virtual bool GoSouth();
virtual bool GoWest();
virtual bool GoEast();
private:
int postion; //当前位置
Maze* maze; //迷宫指针
};
【案例分析】
(1)一个VC++下实现的走迷宫游戏,预设一个简单的迷宫环境,用户输入移动方向走出迷宫。
(2)类Iplayer、Imaze定义纯虚函数,同时采用多种继承,Imaze继承CRCObject。
注意:函数重载在类和对象方面应用比较多,尤其是在类的多态性中。在以后将碰到更多的类型不同的函数重载,尤其是在结合类的继承性和指针类型的不同的函数重载,这些都是以后VC++编程经常要用到的。
12.4 虚函数引入的二义性
案例12-7 虚函数引入的二义性
【案例描述】
解决多重多级继承造成的二义性问题。例如有基类B,从B派生出C和D,然后类F又同时继承了C和D,现在类F的一个对象里面包含了两个基类B的对象,如果F访问自己的从基类B那里继承过来的的数据成员或者函数成员那么编译器就不知道你指的到底是从C那里继承过来的B对象呢还是从D那里继承过来的B对象。本例效果如图12-7所示。
图12-7 虚函数引入的二义性
【实现过程】
虚基类为解决多中继承,如案例描述中,将C和D的继承方式改为虚继承,那么F访问自己从B那里继承过来的成员就不会有二义性问题了,也就是将F对象里的B对象统一为一个,只有一个基类B对象,代码实现如下:
#include <iostream>
using namespace std;
class A
{
public:
int i;
void showa(){cout<<"i="<<i<<endl;}
};
class B:virtual public A //此处采用虚继承
{
public:
int j;
};
class C:virtual public A //此处采用虚继承
{
public:
int k;
};
class D:public B,public C
{
public:
int m;
};
int main()
{
A a;
B b;
C c;
a.i=1;
a.showa();
b.i=2;
b.showa();
c.i=3;
c.showa();
D d;
d.i=4;
d.showa();
//cout << "Hello world!" << endl;
return 0;
}
【案例分析】
从这个案例可以看出B、,C、D从A那里继承过来了,i这个变量并且它们之间不会有任何影响,如果B和C不是虚继承方式的,那么d.i=4;就不能编译通过了。
12.5 重载、覆盖与隐藏
案例12-8 人体健康评估系统
【案例描述】
编程中的很多数据处理可以定义个类,在类中利用一定的算法重载运行符如重载“+”,实现指定的功能如矩阵相加,本例定义个矩阵输入健康评估数据,计算得到需要的输出数据。本例效果如图12-8所示。
图7-18 人体健康评估系统
【实现过程】
定义矩阵类matrix,类中定义矩阵的行、列变量,和存储元素的变量elems,setelem给元素赋值,重载运算符()、+、-和*,实现矩阵的返回元素值、加、减和乘法运行。代码实现如下:
class matrix //定义矩阵类
{
short rows,cols; //矩阵的行、列
double *elems; //存放矩阵中各元素,按行存放
public:
matrix(){}
matrix(short r,short c);
double operator ()(short r,short c); //重载运算符"()",用来返回元素值
void setelem(short r,short c,double v); //给元素赋值
//重载“+”,实现矩阵相加
friend matrix operator +(matrix p,matrix q);
friend matrix operator -(matrix p,matrix q);
friend matrix operator *(matrix p,matrix q);
void print(); };
matrix::matrix(short r,short c) //构造函数
{
rows=r;
cols=c;
elems=new double[r*c];
}
double matrix::operator()(short r,short c) //重载运算符号()
{
return (r>=1 && r<=rows && c>=1 && c<=cols)?
elems[(r-1)*cols+(c-1)]:0.0;
}
//给元素赋值
void matrix::setelem(short r,short c,double v)
{
//在内存中是按照行顺序存放矩阵元素的,所以应对行、列进行换算,
//算出在elems中的下标
if(r>=1 && r<=rows && c>=1 && c<=cols)
elems[(r-1)*cols+(c-1)]=v;
}
matrix operator +(matrix p,matrix q) //重载运算符号+
{
matrix m(p.rows,p.cols);
if(p.rows!=q.rows || p.cols!=q.cols)
return m;
for(int r=1;r<=p.rows;r++)
for(int c=1;c<=p.cols;++c)
m.setelem(r,c,p(r,c)+q(r,c)); //实现矩阵相加
return m; }
matrix operator -(matrix p,matrix q) //重载运算符号-
{
matrix m(p.rows,p.cols);
if(p.rows!=q.rows || p.cols!=q.cols)
return m;
for(int r=1;r<=p.rows;r++)
for(int c=1;c<=p.cols;c++)
m.setelem(r,c,p(r,c)-q(r,c)); //实现矩阵相减
return m; }
matrix operator *(matrix p,matrix q) //重载运算符号*
{
matrix m(p.rows,q.cols);
if(p.cols!=q.rows)
return m;
for(int r=1;r<=p.rows;r++)
for(int c=1;c<=q.cols;c++)
{
m.setelem(r,c,0.0);
for(int i=1;i<=p.cols;i++)
//实现矩阵相乘
m.setelem(r,c,m(r,c)+p(r,i)*q(i,c));
}
return m; }
void matrix::print()
{
for(int r=1;r<=rows;r++)
{
for(int c=1;c<=cols;c++)
cout<<setw(7)<<(*this)(r,c);
//注意:this指针的用法,此句只能写成(*this)(r,c)形式,其他形式错误
//使用*this标识被该重载函数成员正在操作的对象
cout<<endl;
} }
void main()
{
//调用构造函数声明matrix变量a、b和d
matrix a(2,3),b(2,3),c(3,2),d(2,3),e(2,2);
//给元素赋值
a.setelem(1,1,3); a.setelem(1,2,1); a.setelem(1,3,2);
a.setelem(2,1,1); a.setelem(2,2,3); a.setelem(2,3,2);
b.setelem(1,1,2); b.setelem(1,2,1); b.setelem(1,3,1);
b.setelem(2,1,2); b.setelem(2,2,3); b.setelem(2,3,2);
c.setelem(1,1,2); c.setelem(1,2,2); c.setelem(2,1,2);
c.setelem(2,2,2); c.setelem(3,1,1); c.setelem(3,2,1);
//输出信息
cout<<"健康评估输入数据:"<<endl;
cout<<"当前状况:"<<endl; a.print();
cout<<"生活习惯:"<<endl; b.print();
cout<<"体格检查:"<<endl; c.print();
cout<<"健康评估后结果:"<<endl;
d=a+b; cout<<"当前状况+生活习惯:"<<endl; d.print();
d=a-b; cout<<"当前状况-生活习惯:"<<endl; d.print();
e=a*c; cout<<"当前状况*体格检查:"<<endl; e.print();
system("pause");
}
【案例分析】
(1)代码double operator ()重载运算符,与一般函数比较相同之处是:
1、均为类的成员函数;
2、实现同一个功能,当用成员函数实现运算符的重载时,运算符重载函数的参数只能有两种情况:没有参数或带有一个参数。
(2)对于只有一个操作数的运算符(如++),在重载运算符时,通常不能有参数;而对于有两个操作数的运算符,只能带有一个参数。这参数可以是对象,对象的引用,或其他类型的参数。在C++中不允许重载有3个操作数的运算符。
注意:运算符重载函数不能定义为静态的成员函数,因为静态的成员函数中没有this指针。用成员函数实现运算符的重载时,运算符的左操作数为当前对象,并且要用到隐含的this指针。
12.6 本章练习