C++多态的详解
C++多态的详解
对C++多态的理解
C++的多态体现在派生类继承基类的关系中,多态的一个很重要的特性就是基类对派生类的调用。这可以体现在三方面:派生类对象赋值给基类对象、派生类引用赋值给基类引用以及派生类指针赋值给基类指针。
一、普通的继承关系示例
接下来我将用代码写一个简单的例子,并通过代码和它的输出结果,来阐述对多态的需求。
1. 普通对象间的赋值例子:
#include<iostream>
class People
{
public:
People(std::string name,int age);
void DisPlay();
protected:
int age;
std::string name;
};
People::People(std::string name,int age)
{
this->name = name;
this->age = age;
}
void People::DisPlay()
{
std::cout<<"my name is "<<name<<", my age is "\
<<age<<", I have no job"<<std::endl;
}
class Programmer:public People
{
private:
std::string myJob;
public:
Programmer(std::string name,int age,\
std::string job):People(name,age),myJob(job){}
void DisPlay();
};
void Programmer::DisPlay()
{
std::cout<<"my name is "<<name<<", my age is "\
<<age<<", my job is "<<myJob<<std::endl;
}
int main()
{
People people("Li Ming",30);
people.DisPlay();
Programmer programmer("David",33,"program farmer");
programmer.DisPlay();
people = programmer;
people.DisPlay();
return 0;
}
在Ubuntu下,使用如果命令编译和运行代码
[email protected]:~/workspace/SalesItem/src$ g++ main.cpp -o Derived
[email protected]:~/workspace/SalesItem/src$ ./Derived
运行结果如下:
my name is Li Ming, my age is 30, I have no job
my name is David, my age is 33, my job is program farmer
my name is David, my age is 33, I have no job
运行结果分析如下:
现象分析:从运行结果来看,最后一行的结果是否让你觉得有点奇怪呢?从输出结果来分析会发现输出的name和age都是派生类Programmer从基类People继承过来的成员,而不是基类自己的成员,结合最后的输出" I have no job“来看,子类对象programmer虽然赋值给了基类对象,但基类对象调用的成员函数DisPlay()仍然是基类自己的,而不是派生类中同名的成员函数。
原理分析:当把派生类对象赋值给基类对象时被称为向上转型,(派生类引用赋值给基类引用以及派生类指针赋值给基类指针也是如此)。在这个过程中,派生类对象会把从基类继承过来的成员变量的值赋值给基类对象对应的成员;而派生类自己的成员变量(不是继承来的)则不会发生赋值行为,毕竟那是派生类自己的变量,因此也不能通过基类对象调用派生类自己的成员变量。
在此过程中,基类调用的仍然是自己的成员函数而不是派生类对应的同名函数。
2.指针的例子:
只改变主函数的代码(记得包含头文件“memory”)
int main()
{
//C++11的智能指针shared_ptr,在堆上分配内存空间
std::shared_ptr<People> autoPtr(new Programmer("David",33,"program farmer"));
People *ptr = new Programmer("David",33,"program farmer");
autoPtr->DisPlay();
ptr->DisPlay();
delete ptr;
return 0;
}
在Ubuntu下,使用如下命令编译和运行代码
[email protected]:~/workspace/SalesItem/src$ g++ main.cpp -o Derived -std=c++11
[email protected]:~/workspace/SalesItem/src$ ./Derived
为了防止编译失败,命令里要有-std=c++11
运行结果如下:
my name is David, my age is 33, I have no job
my name is David, my age is 33, I have no job
运行结果分析如下:
知识铺垫:这段代码里用到了C++11标准的智能指针std::shared_ptr<T> ,它相对于普通指针的优势在于,普通指针指向了从堆上申请的内存空间,以上面程序中的ptr为例,其存储了从堆上申请的内存空间的地址,主函数执行完后会释放栈上的指针ptr,而堆上申请的那片空间却要程序员自己手动释放,否则会造成内存泄漏。如上面代码的delete ptr;就释放了该空间,不会造成内存泄漏。但程序员往往容易忘记释放而导致内存泄漏,而智能指针的优点就在于不用手动释放堆上的空间,当没有指针指向堆上这片空间时,该空间会自动释放。更多关于智能指针的细节在此不再赘述。
原理分析:
和“普通对象间的赋值例子“一样,基类指针指向了派生类对象,那么通过该指针引用成员函数时,引用的仍然是基类的成员函数,而成员变量却是派生类的。
3.引用的例子:
只改变主函数的代码(记得包含头文件“memory”)
int main()
{
Programmer programmer("David",33,"program farmer");
People &people = programmer;
people.DisPlay();
return 0;
}
在Ubuntu下,使用如下命令编译和运行代码
[email protected]:~/workspace/SalesItem/src$ g++ main.cpp -o Derived -std=c++11
[email protected]:~/workspace/SalesItem/src$ ./Derived
运行结果如下:
my name is David, my age is 33, I have no job
原理分析:和上面的例子原理一致,引用的细节在此也不赘述。
二、含虚函数的继承关系示例
从以上普通的继承关系我们可以知道,基类对象调用的是基类自己的成员函数,但成员变量却是派生类从基类继承的成员变量,这多少有点不伦不类,能不能通过基类对象直接调用派生类的同名(包括参数、返回值也相同)函数呢?当然可以,这就是虚函数发挥力量的时候,也是展示C++多态的特性的时候了。
1.含虚函数的指针例子:
#include<iostream>
#include<memory>
class People
{
public:
People(std::string name,int age);
//~People();
virtual void DisPlay();
protected:
int age;
std::string name;
};
People::People(std::string name,int age)
{
this->name = name;
this->age = age;
}
void People::DisPlay()
{
std::cout<<"my name is "<<name<<", my age is "\
<<age<<", I have no job"<<std::endl;
}
class Programmer:public People
{
private:
std::string myJob;
public:
Programmer(std::string name,int age,std::string job):People(name,age),myJob(job){}
//~Programmer();
void DisPlay();
};
void Programmer::DisPlay()
{
std::cout<<"my name is "<<name<<", my age is "\
<<age<<", my job is "<<myJob<<std::endl;
}
int main()
{
//C++11的智能指针shared_ptr,在堆上分配内存空间
std::shared_ptr<People> autoPtr(new Programmer("David",33,"program farmer"));
People *ptr = new Programmer("David",33,"program farmer");
autoPtr->DisPlay();
ptr->DisPlay();
delete ptr;
return 0;
}
在Ubuntu下,使用如下命令编译和运行代码
[email protected]:~/workspace/SalesItem/src$ g++ main.cpp -o Derived -std=c++11
[email protected]:~/workspace/SalesItem/src$ ./Derived
为了防止编译失败,命令里要有-std=c++11
运行结果如下:
my name is David, my age is 33, my job is program farmer
my name is David, my age is 33, my job is program farmer
运行结果分析如下:
原理分析: 如果对智能指针的使用不了解的请看上一章的原理分析。以上代码只在基类的成员函数DisPlay()前添加了一个关键字virtual,使得该函数成了虚函数,它的作用在于,基类指针指向子类对象时,调用的虚函数DisPlay()不再是基类而是派生类的同名(包括参数、返回值也得相同)成员函数。
三、含纯虚函数的继承关系示例
把第二章的代码中的
virtual void DisPlay();
改为
virtual void DisPlay() = 0;
同时删除基类People中该函数的定义部分,只留下一个声明框架。
这样的话,基类People就成了抽象类,具体来说就是只要含有纯虚函数(至少一个)的类,就是抽象类,抽象类是不能定义对象的,但是可以定义该类的指针,继承该类的派生类必须要实现基类中的纯虚函数,也就是定义一个同名(包括参数、返回值也相同)的函数,那么该派生类才能定义对象,否则该派生类也是抽象类。最后的完整代码如下:
#include<iostream>
#include<memory>
class People
{
public:
People(std::string name,int age);
//~People();
virtual void DisPlay() = 0;
protected:
int age;
std::string name;
};
People::People(std::string name,int age)
{
this->name = name;
this->age = age;
}
class Programmer:public People
{
private:
std::string myJob;
public:
Programmer(std::string name,int age,std::string job):People(name,age),myJob(job){}
//~Programmer();
void DisPlay();
};
void Programmer::DisPlay()
{
std::cout<<"my name is "<<name<<", my age is "\
<<age<<", my job is "<<myJob<<std::endl;
}
int main()
{
//C++11的智能指针shared_ptr,在堆上分配内存空间
std::shared_ptr<People> autoPtr(new Programmer("David",33,"program farmer"));
People *ptr = new Programmer("David",33,"program farmer");
autoPtr->DisPlay();
ptr->DisPlay();
delete ptr;
return 0;
}
编译方法和运行结果和第二章完全一样。
四、总结
本文从普通的继承关系到纯虚函数的实现,来阐述C++实现多态的方法,也就是基类的对象、指针或引用来调用派生类对象的同名(参数、返回值也相同)的方法,当基类有非常多的派生类时,并且有同名(参数、返回值也相同)的方法,但是方法的内容不同时,这种方式非常有用。
上一篇: 迭代法计算一元多项式
下一篇: 牛顿迭代法求解多项式方程的近似解