C++程序设计教程
第 11讲,虚函数( I)
关于多态性
? 多态性( polymorphism)
Shape
TwoDimShape ThreeDimShape
Circle
Square
Triangle
Sphere
Cube
Tetrahedron
例 7-10:人员信息管理
? class employee {
protected,
char name[20]; // 姓名
int individualEmpNo; // 个人编号
int grade; // 级别
float accumPay; // 月薪
static int employeeNo; // 本公司职员目前的最大值
public,
employee(); // 构造函数
virtual ~employee(); // 析构函数
void Pay() {} // 计算月薪函数 函数为空
void GetIndividualEmpNo(); // 获取个人编号
float GetAccumPay(); // 获取月薪
… …
};
例 7-10:人员信息管理
int employee::employeeNo = 1000;
// 员工编号基数为 1000
employee::employee()
{
individualEmpNo = employeeNo++;
// 新员工编号为目前最大号加 1
grade = 1;
accumPay = 0.0f;
}
int employee::GetIndividualEmpNo() // 获取个人编号
{ return individualEmpNo; }
float employee:,GetAccumPay() // 获取月薪
{ return accumPay; }
例 7-10:人员信息管理
class technician, public employee
// 技术人员类
{ private,
float hourlyRate;
int workHours;
public,
technician();
void SetWorkHours(int wh);
void Pay();
};
technician::technician()
{ hourlyRate = 100; workHours = 200; }
void technician::Pay()
{ // 计算月薪,按小时计算
accumPay = hourlyRate * workHours;
}
例 7-10:人员信息管理
class salesman, public employee // 推销员类
{ private,
float CommRate;
float sales;
public,
salesman();
void SetSales(float);
void Pay();
};
void salesman::salesman()
{ CommRate = 0.04; sales = 100000; } // 设置销售额
void salesman::Pay()
{ accumPay = sales * CommRate;
// 计算月薪,按提成比例计算
}
例 7-10:人员信息管理
class manager, public employee // 推销员类
{
private,
float MonthlyPay;
public,
manager ();
void SetSales(float);
void Pay();
};
manager::manager()
{ MonthlyPay = 8000; }
void manager::Pay()
{ accumPay = MonthlyPay ;
// 计算月薪,按提成比例计算
}
int main()
{ manager m1;
technician t1;
salesman s1;
employee *emp[3] = {&m1,&t1,&s1};
for(int i=0;i<3;i++)
{
emp[i]->pay();
cout <<,编号”
<< emp[i]->GetIndividualEmpNo()
<<,本月工资”
<< emp[i]->GetAccumPay() << endl;
}
}
编号 1000 本月工资 8000
编号 1001 本月工资 20000
编号 1002 本月工资 4000
预想的结果
编号 1000 本月工资 0
编号 1001 本月工资 0
编号 1002 本月工资 0
实际输出
int main()
{ manager m1;
technician t1;
salesman s1;
employee *emp[3] = {&m1,&t1,&s1};
for(int i=0;i<3;i++)
{
emp[i]->Pay();
cout <<,编号”
<< emp[i]->GetIndividualEmpNo()
<<,本月工资”
<< emp[i]->GetAccumPay() << endl;
}
}
基类的指针 派生类的地址
基类的函数
§ 8.3 虚函数
? 虚函数的目的是告诉编译器,对于指向基类的指针
而言,如果它派生类中有同名的函数(被重载),
则优先执行派生类中的该函数。
Employee
Pay();
Manager
Pay();
基
类
指
针
Employee
virtual Pay();
Manager
virtual Pay();
基
类
指
针
基类指针只能访问基类的函数 基类指针可以访问派生类的函数
例 7-10:人员信息管理
? class employee {
protected,
char name[20]; // 姓名
int individualEmpNo; // 个人编号
int grade; // 级别
float accumPay; // 月薪
static int employeeNo; // 本公司职员目前的最大值
public,
employee(); // 构造函数
virtual ~employee(); // 析构函数
virtual void Pay() {} // 设为需函数
void GetIndividualEmpNo(); // 获取个人编号
float GetAccumPay(); // 获取月薪
… …
};
int main()
{ manager m1;
technician t1;
salesman s1;
employee *emp[3] = {&m1,&t1,&s1};
for(int i=0;i<3;i++)
{
emp[i]->pay();
cout <<,编号”
<< emp[i]->GetIndividualEmpNo()
<<,本月工资”
<< emp[i]->GetAccumPay() << endl;
}
}
编号 1000 本月工资 8000
编号 1001 本月工资 20000
编号 1002 本月工资 4000
实际输出
静态绑定与动态绑定
? 虚函数是为派生类重载基类的函数而设定的。
? 虚函数的设置导致重载函数指向的不确定性。(所
谓虚函数由此得名)
? 静态绑定 static binding
? 在编译的时候就被确定的虚函数
? 动态绑定 dynamic binding
? 在运行时才被确定的虚函数
? 如果 manager m; m.Pay(); 这是静态绑定!
? 如果 manager m; Employee* pe = &m;
? pe->Pay(); 这是动态绑定!
class B0 //基类 B0声明
{public,//外部接口
virtual void display() //虚成员函数
{cout<<"B0::display()"<<endl;}
};
class B1,public B0 //公有派生
{ public,
void display()
{ cout<<"B1::display()"<<endl; }
};
class D1,public B1 //公有派生
{ public,
void display()
{ cout<<"D1::display()"<<endl; }
};
例 8-4:虚函数成员
void fun(B0 *ptr) //普通函数
{ ptr->display(); }
int main() //主函数
{ B0 b0,*p; //声明基类对象和指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p=&b0;
fun(p); //调用基类 B0函数成员
p=&b1;
fun(p); //调用派生类 B1函数成员
p=&d1;
fun(p); //调用派生类 D1函数成员
}
编译时无法确定是
调用哪个 display,所
以需要动态绑定
绑定到 b0
绑定到 b1
绑定到 d1 运行结果,
B0::display()
B1::display()
D1::display()
§ 8.3.2 虚析构函数
? 何时需要虚析构函数?
? 当你可能通过基类指针删除派生类对象时
? 如果你打算允许其他人通过基类指针调用对
象的析构函数(通过 delete这样做是正常
的),并且被析构的对象是有重要的析构函
数的派生类的对象,就需要让基类的析构函
数成为虚拟的。
class Base
{public,
~Base() //析构函数
{cout<<“Base destructor"<<endl;}
};
class Derived, public Base
{ public,
Derived();
~Derived();
private,
int* i_pointer;
};
例 8-5:虚析构函数举例
Derived::Derived()
{ i_pointer = new int(0); }
Derived::~Derived()
{
cout <<,Derived destructor” << endl;
delete i_pointer;
};
例 8-5:虚析构函数举例
void fun(Base* b)
{ delete b; }
int main()
{
Base* b = new Derived();
fun(b);
}
送进去的是基
类 Base的指针
运行时输出信息为,
Base destructor
如果将基类 Base的析构函数说明为 virtual,则输出,
Derived destructor
Base destructor
? 构造的过程是从基类的构造函数到派生类派生类的
构造函数。
? 而析构时则应先调用派生类析构函数,然后再调用
基类的析构函数。
? 如,
class Base
{ int* a;
public,Base() { a = new int[1]; }
virtual ~Base() { delete[] a; }
};
class Derived, public Base
{ public,
Derived() { a[0] = new int[10]; }
~Derived() { delete[] a[0]; }
}
Base Derived
Derived Base
先进后出的压栈方式
§ 8.4 抽象类
? 抽象类就是不能实例化,只能做基类的类。
所以称为 抽象基类。 abstruct base class
? 抽象类的唯一用途就是为其他类提供合适的
基类。
? 例如前面的例子中的 Employee,它不是一
个具体的职员,只是从 manager,salesman,
technician 等具体雇员中抽象出来的概念。
所以可以将其设为抽象类。
? class Employee { virtual void Pay()=0; }
纯虚函数的声明 只要有一个纯虚函数的类就是抽象基类,纯虚函数必须被派生类实现 !
class B0 //基类 B0声明
{public,//外部接口
virtual void display()=0; //纯虚成员函数
};
class B1,public B0 //公有派生
{ public,
void display()
{ cout<<"B1::display()"<<endl; }
};
class D1,public B1 //公有派生
{ public,
void display()
{ cout<<"D1::display()"<<endl; }
};
例 8-6:抽象类举例
void fun(B0 *ptr) //普通函数
{ ptr->display(); }
int main() //主函数
{ B0 b0,*p; //声明基类对象和指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p=&b1;
fun(p); //调用派生类 B1函数成员
p=&d1;
fun(p); //调用派生类 D1函数成员
}
运行结果,
B1::display()
D1::display()
抽象类不能声明
实例,只能声明
指针。
?
第 11讲,虚函数( I)
关于多态性
? 多态性( polymorphism)
Shape
TwoDimShape ThreeDimShape
Circle
Square
Triangle
Sphere
Cube
Tetrahedron
例 7-10:人员信息管理
? class employee {
protected,
char name[20]; // 姓名
int individualEmpNo; // 个人编号
int grade; // 级别
float accumPay; // 月薪
static int employeeNo; // 本公司职员目前的最大值
public,
employee(); // 构造函数
virtual ~employee(); // 析构函数
void Pay() {} // 计算月薪函数 函数为空
void GetIndividualEmpNo(); // 获取个人编号
float GetAccumPay(); // 获取月薪
… …
};
例 7-10:人员信息管理
int employee::employeeNo = 1000;
// 员工编号基数为 1000
employee::employee()
{
individualEmpNo = employeeNo++;
// 新员工编号为目前最大号加 1
grade = 1;
accumPay = 0.0f;
}
int employee::GetIndividualEmpNo() // 获取个人编号
{ return individualEmpNo; }
float employee:,GetAccumPay() // 获取月薪
{ return accumPay; }
例 7-10:人员信息管理
class technician, public employee
// 技术人员类
{ private,
float hourlyRate;
int workHours;
public,
technician();
void SetWorkHours(int wh);
void Pay();
};
technician::technician()
{ hourlyRate = 100; workHours = 200; }
void technician::Pay()
{ // 计算月薪,按小时计算
accumPay = hourlyRate * workHours;
}
例 7-10:人员信息管理
class salesman, public employee // 推销员类
{ private,
float CommRate;
float sales;
public,
salesman();
void SetSales(float);
void Pay();
};
void salesman::salesman()
{ CommRate = 0.04; sales = 100000; } // 设置销售额
void salesman::Pay()
{ accumPay = sales * CommRate;
// 计算月薪,按提成比例计算
}
例 7-10:人员信息管理
class manager, public employee // 推销员类
{
private,
float MonthlyPay;
public,
manager ();
void SetSales(float);
void Pay();
};
manager::manager()
{ MonthlyPay = 8000; }
void manager::Pay()
{ accumPay = MonthlyPay ;
// 计算月薪,按提成比例计算
}
int main()
{ manager m1;
technician t1;
salesman s1;
employee *emp[3] = {&m1,&t1,&s1};
for(int i=0;i<3;i++)
{
emp[i]->pay();
cout <<,编号”
<< emp[i]->GetIndividualEmpNo()
<<,本月工资”
<< emp[i]->GetAccumPay() << endl;
}
}
编号 1000 本月工资 8000
编号 1001 本月工资 20000
编号 1002 本月工资 4000
预想的结果
编号 1000 本月工资 0
编号 1001 本月工资 0
编号 1002 本月工资 0
实际输出
int main()
{ manager m1;
technician t1;
salesman s1;
employee *emp[3] = {&m1,&t1,&s1};
for(int i=0;i<3;i++)
{
emp[i]->Pay();
cout <<,编号”
<< emp[i]->GetIndividualEmpNo()
<<,本月工资”
<< emp[i]->GetAccumPay() << endl;
}
}
基类的指针 派生类的地址
基类的函数
§ 8.3 虚函数
? 虚函数的目的是告诉编译器,对于指向基类的指针
而言,如果它派生类中有同名的函数(被重载),
则优先执行派生类中的该函数。
Employee
Pay();
Manager
Pay();
基
类
指
针
Employee
virtual Pay();
Manager
virtual Pay();
基
类
指
针
基类指针只能访问基类的函数 基类指针可以访问派生类的函数
例 7-10:人员信息管理
? class employee {
protected,
char name[20]; // 姓名
int individualEmpNo; // 个人编号
int grade; // 级别
float accumPay; // 月薪
static int employeeNo; // 本公司职员目前的最大值
public,
employee(); // 构造函数
virtual ~employee(); // 析构函数
virtual void Pay() {} // 设为需函数
void GetIndividualEmpNo(); // 获取个人编号
float GetAccumPay(); // 获取月薪
… …
};
int main()
{ manager m1;
technician t1;
salesman s1;
employee *emp[3] = {&m1,&t1,&s1};
for(int i=0;i<3;i++)
{
emp[i]->pay();
cout <<,编号”
<< emp[i]->GetIndividualEmpNo()
<<,本月工资”
<< emp[i]->GetAccumPay() << endl;
}
}
编号 1000 本月工资 8000
编号 1001 本月工资 20000
编号 1002 本月工资 4000
实际输出
静态绑定与动态绑定
? 虚函数是为派生类重载基类的函数而设定的。
? 虚函数的设置导致重载函数指向的不确定性。(所
谓虚函数由此得名)
? 静态绑定 static binding
? 在编译的时候就被确定的虚函数
? 动态绑定 dynamic binding
? 在运行时才被确定的虚函数
? 如果 manager m; m.Pay(); 这是静态绑定!
? 如果 manager m; Employee* pe = &m;
? pe->Pay(); 这是动态绑定!
class B0 //基类 B0声明
{public,//外部接口
virtual void display() //虚成员函数
{cout<<"B0::display()"<<endl;}
};
class B1,public B0 //公有派生
{ public,
void display()
{ cout<<"B1::display()"<<endl; }
};
class D1,public B1 //公有派生
{ public,
void display()
{ cout<<"D1::display()"<<endl; }
};
例 8-4:虚函数成员
void fun(B0 *ptr) //普通函数
{ ptr->display(); }
int main() //主函数
{ B0 b0,*p; //声明基类对象和指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p=&b0;
fun(p); //调用基类 B0函数成员
p=&b1;
fun(p); //调用派生类 B1函数成员
p=&d1;
fun(p); //调用派生类 D1函数成员
}
编译时无法确定是
调用哪个 display,所
以需要动态绑定
绑定到 b0
绑定到 b1
绑定到 d1 运行结果,
B0::display()
B1::display()
D1::display()
§ 8.3.2 虚析构函数
? 何时需要虚析构函数?
? 当你可能通过基类指针删除派生类对象时
? 如果你打算允许其他人通过基类指针调用对
象的析构函数(通过 delete这样做是正常
的),并且被析构的对象是有重要的析构函
数的派生类的对象,就需要让基类的析构函
数成为虚拟的。
class Base
{public,
~Base() //析构函数
{cout<<“Base destructor"<<endl;}
};
class Derived, public Base
{ public,
Derived();
~Derived();
private,
int* i_pointer;
};
例 8-5:虚析构函数举例
Derived::Derived()
{ i_pointer = new int(0); }
Derived::~Derived()
{
cout <<,Derived destructor” << endl;
delete i_pointer;
};
例 8-5:虚析构函数举例
void fun(Base* b)
{ delete b; }
int main()
{
Base* b = new Derived();
fun(b);
}
送进去的是基
类 Base的指针
运行时输出信息为,
Base destructor
如果将基类 Base的析构函数说明为 virtual,则输出,
Derived destructor
Base destructor
? 构造的过程是从基类的构造函数到派生类派生类的
构造函数。
? 而析构时则应先调用派生类析构函数,然后再调用
基类的析构函数。
? 如,
class Base
{ int* a;
public,Base() { a = new int[1]; }
virtual ~Base() { delete[] a; }
};
class Derived, public Base
{ public,
Derived() { a[0] = new int[10]; }
~Derived() { delete[] a[0]; }
}
Base Derived
Derived Base
先进后出的压栈方式
§ 8.4 抽象类
? 抽象类就是不能实例化,只能做基类的类。
所以称为 抽象基类。 abstruct base class
? 抽象类的唯一用途就是为其他类提供合适的
基类。
? 例如前面的例子中的 Employee,它不是一
个具体的职员,只是从 manager,salesman,
technician 等具体雇员中抽象出来的概念。
所以可以将其设为抽象类。
? class Employee { virtual void Pay()=0; }
纯虚函数的声明 只要有一个纯虚函数的类就是抽象基类,纯虚函数必须被派生类实现 !
class B0 //基类 B0声明
{public,//外部接口
virtual void display()=0; //纯虚成员函数
};
class B1,public B0 //公有派生
{ public,
void display()
{ cout<<"B1::display()"<<endl; }
};
class D1,public B1 //公有派生
{ public,
void display()
{ cout<<"D1::display()"<<endl; }
};
例 8-6:抽象类举例
void fun(B0 *ptr) //普通函数
{ ptr->display(); }
int main() //主函数
{ B0 b0,*p; //声明基类对象和指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p=&b1;
fun(p); //调用派生类 B1函数成员
p=&d1;
fun(p); //调用派生类 D1函数成员
}
运行结果,
B1::display()
D1::display()
抽象类不能声明
实例,只能声明
指针。
?