多态的实现
多态性的实现和联编这一概念有关。所谓联编(Binding,绑定)就是把函数名与函数体的程序代码连接(联系)在一起的过程。
联编分成两大类:静态联编和动态联编。
静态联编优点:调用速度快,效率高,但缺乏灵活性;动态联编优点:运行效率低,但增强了程序灵活性。
C++为了兼容C语言仍然是编译型的,采用静态联编。为了实现多态性,利用虚函数机制,可部分地采用动态联编。
多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。
静态联编
普通成员函数重载可表达为两种形式:
基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
直接用基类指针引用基类对象;
直接用派生类指针引用派生类对象;
用基类指针引用一个派生类对象;
用派生类指针引用一个基类对象。
例如:
A * p ; // 指向类型 A 的对象的指针
A A_obj ; // 类型 A 的对象
B B_obj ; // 类型 B 的对象
p = & A_obj ; // p 指向类型 A 的对象
p = & B_obj ; // p 指向类型 B 的对象,它是 A 的派生类
利用 p,可以通过 B_obj 访问所有从 A 类继承的元素 ,但不能用 p访问 B 类自定义的元素 (除非用了显式类型转换)
#include
#include
using namespace std ;
class A_class
{
char name[20] ;
public :
void put_name( char * s ) { strcpy_s( name, s ) ; }
void show_name() { cout << name << "\n" ; }
};
class B_class : public A_class
{
char phone_num[ 20 ] ;
public :
void put_phone( char * num ) { strcpy_s ( phone_num , num ) ; }
void show_phone() { cout << phone_num << "\n" ; }
};
int main()
{
A_class * A_p ;
A_class A_obj ;
B_class B_obj ;
A_p = & A_obj ;
A_p -> put_name( "Wang xiao hua" ) ;
A_p -> show_name() ;
A_p = & B_obj ;
A_p -> put_name( "Chen ming" ) ;
A_p -> show_name() ;
B_obj.put_phone ( "5555_12345678" );
( ( B_class * ) A_p ) -> show_phone() ;
return 0;
}
派生类指针只有经过强制类型转换之后,才能引用基类对象
#include
using namespace std ;
class Date{
public:
Date( int y, int m, int d ) { SetDate( y, m, d ); }
void SetDate( int y, int m, int d ) { year = y ; month = m ; day = d ; }
void Print() { cout << year << '/' << month << '/' << day << "; " ; }
protected :
int year , month , day ;
} ;
class DateTime : public Date
{ public :
DateTime( int y, int m, int d, int h, int mi, int s ) : Date( y, m, d ) { SetTime( h, mi, s ); }
void SetTime( int h, int mi, int s ) { hours = h; minutes = mi; seconds = s; }
void Print()
{ ( ( Date * ) this ) -> Print();
cout << hours << ':' << minutes << ':' << seconds << '\n' ;
}
private:
int hours , minutes , seconds ;
};
int main() {
DateTime dt( 2009, 1, 1, 12, 30, 0 ) ;
dt.Print() ;
return 0;
}
虚函数
根据赋值兼容规则,可以将派生类的地址赋值给基类的指针。
考虑一个问题:
能否用这个指针访问派生类的成员函数?
#include
class Undergraduate
{
public:
void Display()
{
cout<<"Call BaseClass"<<endl;
cout<<"Unergraduate LiMing"<<endl;
}
}
class Master:public Undergraduate
{
public:
void Display()
{
cout<<"Call MasterClass"<<endl;
cout<<"Master WangWei"<<endl;
}
}
class Doctor:public Master
{
public:
void Display()
{
cout<<"Call DoctorClass"<<endl;
cout<<"Doctor ZhangHua"<<endl;
}
};
void main(){
Undergraduate s1,*pointer;
Master s2;
Doctor s3;
pointer=&s1;
pointer->Display();
pointer=&s2;
pointer->Display();
pointer=&s3;
pointer->Display();
return 0;
}
说明:说明:在编译阶段,基类指针对函数的操作只能绑定到基类的成员函数
根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用类(基类或派生类)的成员函数。
如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。
而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。从而实现运行过程的多态。
实现动态联编方式的前提:
●先要声明虚函数
●类之间满足赋值兼容规则
●通过指针与引用来调用虚函数。
冠以关键字 virtual 的成员函数称为虚函数
实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本
/*指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员*/
#include
using namespace std ;
class Base
{
public :
Base(char xx) { x = xx; }
void who() { cout << "Base class: " << x << "\n" ; }
protected:
char x;
} ;
class First_d : public Base
{ public :
First_d(char xx, char yy):Base(xx) { y = yy; }
void who() { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected:
char y;
} ;
class Second_d : public First_d
{
public :
Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
void who() { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected:
char z;
} ;
int main()
{
Base B_obj( 'A' ) ;
First_d F_obj( 'T', 'O' ) ;
Second_d S_obj( 'E', 'N', 'D' ) ;
Base * p ;
p = & B_obj ;
p -> who() ;
p = &F_obj ;
p -> who() ;
p = &S_obj ;
p -> who() ;
F_obj.who() ;
( ( Second_d * ) p ) -> who() ;
return 0;
}
#include
using namespace std ;
class Base
{
public :
Base(char xx) { x = xx; }
virtual () { cout << "Base class: " << x << "\n" ; }
protected:
char x;
} ;
class First_d : public Base
{ public :
First_d(char xx, char yy):Base(xx) { y = yy; }
void who() { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected:
char y;
} ;
class Second_d : public First_d
{
public :
Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
void who() { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected:
char z;
} ;
int main()
{
Base B_obj( 'A' ) ;
First_d F_obj( 'T', 'O' ) ;
Second_d S_obj( 'E', 'N', 'D' ) ;
Base * p ;
p = & B_obj ;
p -> who() ;
p = &F_obj ;
p -> who() ;
p = &S_obj ;
p -> who() ;
F_obj.who() ;
( ( Second_d * ) p ) -> who() ;
return 0;
}
注意:
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
虚函数必须是类的成员函数
不能将友元说明为虚函数,但虚函数可以是另一个类的友元
析构函数可以是虚函数,但构造函数不能是虚函数
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
如果仅仅返回类型不同,C++认为是错误重载
如果函数原型不同,仅函数名相同,丢失虚特性
例:
class base
{ public :
virtual void vf1 ( ) ;
virtual void vf2 ( ) ;
virtual void vf3 ( ) ;
void f ( ) ;
} ;
class derived : public base
{ public :
void vf1 ( ) ; // 虚函数
void vf2 ( int ) ; // 重载,参数不同,虚特性丢失
char vf3 ( ) ; // error,仅返回类型不同
void f ( ) ; // 非虚函数重载
} ;
void g ( )
{
derived d ;
base * bp = & d ; // 基类指针指向派生类对象
bp -> vf1 ( ) ; // 调用 deriver :: vf1 ( )
bp -> vf2 ( ) ; // 调用 base :: vf2 ( )
bp -> f ( ) ; // 调用 base :: f ( )
} ;
虚析构函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
#include
using namespace std ;
class A
{
public:
~A(){ cout << "A::~A() is called.\n" ; }
} ;
class B : public A
{
public:
~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main() {
A *Ap = new B() ;
B *Bp2 = new B() ;
cout << "delete first object:\n" ;
delete Ap;
cout << "delete second object:\n" ;
delete Bp2 ;
return0;
}
#include
using namespace std ;
class A
{
public:
virtual ~A(){ cout << "A::~A() is called.\n" ; }
} ;
class B : public A
{
public:
~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main() {
A *Ap = new B() ;
B *Bp2 = new B() ;
cout << "delete first object:\n" ;
delete Ap;
cout << "delete second object:\n" ;
delete Bp2 ;
return0;
}
说明:
1.派生类应该从它的基类公有派生。
2.必须首先在基类中定义虚函数。
3.派生类对基类中声明虚函数重新定义时,关键字virtual可以不写。
4.一般通过基类指针访问虚函数时才能体现多态性。
5.一个虚函数无论被继承多少次,保持其虚函数特性。
6.虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态函数。
7.构造函数、内联成员函数、静态成员函数不能是虚函数。(虚函数不能以内联的方式进行处理)
8.析构函数可以是虚函数,通常声明为虚函数。
#include
class A
{
public:
virtual double funA(double x)
{ cout<<"funA of class A called."<<endl;
return x*x; }
double funB(double x)
{ return funA(x)/2; }
};
class B:public A
{
public:
virtual double funA(double x)
{ cout<<"funA of class B called."<<endl;
return 2*x*x; }
};
class C:public B
{
public:
virtual double funA(double x)
{ cout<<"funA of class C called."<<endl;
return 3*x*x;
}
};
int main()
{
C objc;
cout<<objc.funB(3)<<endl;
B objb;
cout<<objb.funB(3)<<endl;
return 0;
}
//运行结果:
funA of class C called.
13.5
funA of class B called.
9
#include
#include
using namespace std;
class Animal
{
string name;
public:
Animal(string a_name):name(a_name){}
virtual void show(){}
void show_name()
{
cout<< "The name is "<<name<<".“<<endl;
}
};
class Cat :public Animal
{
string kind;
public:
Cat(string a_name,string a_kind):Animal(a_name),kind(a_kind)
{}
void show();
};
void Cat::show()
{
show_name();
cout<<" It's a "<<kind<<endl;
}
class Dog:public Animal
{
string kind;
public:
Dog(string a_name,string a_kind):Animal(a_name),kind(a_kind)
{}
void show();
};
void Dog::show()
{
show_name();
cout<<" It's a "<<kind<<endl;
}
class Tiger:public Cat
{
public:
Tiger(string a_name,string a_kind):Cat(a_name,a_kind)
{}
};
int main()
{
Animal *p;
Cat cat("Tom","cat");
Dog dog("Jerry","Dog");
Tiger tiger("DuDu","Tiger");
p=&cat;
p->show();
p=&dog;
p->show();
p=&tiger;
p->show();
return 0;
}
纯虚函数和抽象类
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
virtual 类型 函数名(参数表)= 0 ;
#include
using namespace std ;
#include"figure.h"
class figure
{ protected :
double x,y;
public:
void set_dim(double i, double j=0) { x = i ; y = j ; }
virtual void show_area() = 0 ;
};
class triangle : public figure
{
public :
void show_area()
{ cout<<"Triangle with high "<<x<<" and base "<<y <<" has an area of "<<x*0.5*y<<"\n"; }
};
class square : public figure
{ public:
void show_area()
{ cout<<"Square with dimension "<<x<<"*"<<y <<" has an area of "<<x*y<<"\n"; }
};
class circle : public figure
{
public:
void show_area()
{ cout<<"Circle with radius "<<x;
cout<<" has an area of "<<3.14*x*x<<"\n";
}
};
int main()
{
triangle t ; //派生类对象
square s ; circle c;
t.set_dim(10.0,5.0) ;
t.show_area();
s.set_dim(10.0,5.0) ;
s.show_area() ;
c.set_dim(9.0) ;
c.show_area() ;
return 0;
}
//Employee.h
class Employee
{ public:
Employee(const int,const string );
virtual ~Employee();
const string getName() const;
const int getNumber() const;
virtual double earnings() const=0;
virtual void print() const;
protected:
int number; // 编号
string name; // 姓名
};
class Manager : public Employee
{ public:
Manager(const int , const string, double =0.0);
~Manager() { }
void setMonthlySalary(double);
virtual double earnings() const;
virtual void print() const;
private:
double monthlySalary ;
};
class HourlyWorker : public Employee
{ public:
HourlyWorker(const long, const string, double=0.0, int =0 );
~HourlyWorker(){}
void setWage(double);
void setHours(int);
virtual double earnings() const;
virtual void print() const;
private:
double wage;
double hours;
};
class PieceWorker : public Employee
{ public:
PieceWorker(const long , const string, double =0.0, int =0 );
~PieceWorker() { }
void setWage ( double ) ;
void setQuantity ( int ) ;
virtual double earnings() const;
virtual void print() const;
private:
double wagePerPiece;
int quantity;
};
void test1()
{
Manager m1 ( 10135, "Cheng ShaoHua", 1200 ) ;
Manager m2 ( 10201, "Yan HaiFeng");
m2.setMonthlySalary ( 5300 ) ;
HourlyWorker hw1 ( 30712, "Zhao XiaoMing", 5, 8*20 ) ;
HourlyWorker hw2 ( 30649, "Gao DongSheng" ) ;
hw2.setWage ( 4.5 ) ;
hw2.setHours ( 10*30 ) ;
PieceWorker pw1 ( 20382, "Xiu LiWei", 0.5, 2850 ) ;
PieceWorker pw2 ( 20496, "Huang DongLin" ) ;
pw2.setWage ( 0.75 ) ;
pw2.setQuantity ( 1850 ) ;
// 使用抽象类指针,调用派生类版本的函数
Employee *basePtr;
basePtr=&m1; basePtr->print();
basePtr=&m2; basePtr->print();
basePtr=&hw1; basePtr->print();
basePtr=&hw2; basePtr->print();
basePtr=&pw1; basePtr->print();
basePtr=&pw2; basePtr->print();
}
感想:
时间过得好快啊,转瞬间12周的时间已经过去了,在上周我们的C++的理论课也已经结课了。在这12周的学习中,自己学到了很多也收获了很多。上学期虽然学习了Java但自己对一些知识理解并不深刻,只是划水应付了老师布置的作业,并没有学习到特别多的知识。这学期刚上C++这门课程时,感觉老师布置的作业和其他的老师不同,难度不是一个档次。刚开始有点抱怨,后来自己想清楚了,想要学习到真东西就应该这样。
老师通过几次布置作业的形式让我们完成了一个图书馆管理系统,在完成老师作业的过程中自己看了好几遍老师的课件,通过网络查询需要用到的知识。在这个过程中是特别枯燥的,但最后看到自己亲手做出的系统有一种以前没有的成就感,虽然自己做出的东西还不够好。这次虽然老师没有布置新的作业,只是让我们把上次图书管理系统的客户端改成用继承的方式完成。但我知道,面向对象编程有三个特性:封装、继承、多态。只有熟练地掌握并运用这三个特性才能说自己会使用面向对象的编程语言。
从这周开始就开始做课程设计了,课程设计说白了就是利用这学期学习到的知识独立地做出一个系统来。有了做图书管理系统的经验,我相信自己一定会做出一个不错的课程设计的。“行百里者半九十”,相信自己,继续加油!!!