C++继承中的基类指针、派生类指针和虚函数
在C++中,继承是面向对象程序设计的核心思想之一,它构建了类之间的层次关系,有父类和子类的概念。本文将总结基类指针、派生类指针和虚函数的相关内容,并附带相应的示例代码。
一、基类指针、派生类指针
在C++中,父类指针可以new一个子类对象,但子类指针不能new一个父类对象。父类指针可以调用父类的成员函数,但无法调用子类的成员函数,因为父类指针只能访问父类的成员变量和成员函数。
//定义基类/父类/超类
//Human.h文件
//定义基类/父类/超类
#ifndef _HUMAN_
#define _HUMAN_
#include<iostream>
class Human
{
public:
Human();
virtual ~Human();
Human(int);
public:
//virtual void eat() /*final*/;//声明成虚函数
virtual void eat2() = 0;//纯虚函数,没有函数体只有一个函数声明
public:
int m_Age;//年龄
char m_Name[100];//名字
void funchuman() {};
void funcpub()
{
std::cout << "执行了Human::funcpub()" << std::endl;
};
public:
void samenamefunc();
void samenamefunc(int);
protected:
int m_pro1;
void funcpro()
{
std::cout << "执行了Human::funcpro()" << std::endl;
};
private:
int m_priv1;
void funcpriv() {};
};//类定义或者类声明时大家千万不要忘记末尾的;
#endif // _HUMAN_
//Human.cpp文件 Human.cpp文件 Human.cpp文件
#include"Human.h"
#include<iostream>
//Human类的实现
Human::Human()//不带参数的构造函数(默认构造函数)
{
std::cout << "执行了Human::Human()构造函数" << std::endl;
}
Human::~Human()
{
std::cout << "执行了Human::~Human()析构函数" << std::endl;
}
Human::Human(int abc)//带一个参数的构造函数
{
std::cout << "执行了Human::Human(int abc)构造函数" << std::endl;
}
void Human::samenamefunc()
{
std::cout << "执行了Human::samenamefunc()" << std::endl;
}
void Human::samenamefunc(int)
{
std::cout << "执行了Human::samenamefunc(int)"<<std::endl;
}
//void Human::eat()//声明成虚函数
//{
// std::cout << "人类吃各种粮食" << std::endl;
//}
//Men.h文件 Men.h文件 Men.h文件
//定义Human的子类Men
//定义Human的子类Men
#ifndef _MEN_
#define _MEN_
#include"Human.h"
#include<iostream>
//男人
class Men : public Human//表示Men是Human的子类
//class Men : protected Human
//class Men : private Human
{
public:
Men();
~Men();
public:
void funcmen() {};
void samenamefunc(int);
public:
using Human::samenamefunc;
public:
//virtual void eat() override;
virtual void eat2()
{
std::cout << "Men::eat2()"<<std::endl;
};
};//类定义或者类声明时大家千万不要忘记末尾的;
#endif // _MEN_
//MEN.cpp MEN.cpp MEN.cpp
#include"Men.h"
#include<iostream>
//子类Men的实现
Men::Men()
{
//funcpro();
std::cout << "执行了Men::Men()构造函数"<<std::endl;
}
Men::~Men()
{
std::cout << "执行了Men::~Men()析构函数" << std::endl;
}
void Men::samenamefunc(int)
{
//Human::samenamefunc(12);//强制调用父类的同名函数
//Human::samenamefunc();//强制调用父类的同名函数
std::cout << "执行了void Men::samenamefunc(int)" << std::endl;
}
//void Men::eat()
//{
// std::cout << "男人喜欢吃米饭" << std::endl;
//}
//主文件.cpp 主文件.cpp 主文件.cpp
#include<iostream>
#include"Human.h"
#include"Men.h"
#include"Women.h"
int main()
{
Human* phuman = new Men; // 可以编译
Men* pmen = new Human; // 报错,编译不通过
phuman->funchuman(); // 父类类型指针可以调用父类的成员函数
phuman->funcmen(); // 报错,无法调用子类成员函数
return 0;
}
概念引入:既然父类指针没有办法调用子类的成员函数,那么你为什么还要父类指针new一个子类对象呢?
二、虚函数
虚函数是在父类中声明的,子类可以重写该函数。在调用虚函数时,会动态绑定,即在程序运行时才能确定调用哪个子类的函数。在父类指针调用虚函数时,会调用子类的同名函数。
Human* phuman = new Human;
phuman->eat();//调用的是父类的eat函数,因为phuman是父类指针。
那如何调用Men和Women的eat函数呢?
Men* pmen = new Men;
pmen->eat();//调用子类Men的eat()
Women* pwomen = new Women;
pwomen->eat();//调用子类Women的eat()
以上方法太**麻烦**,有没有一个**解决方法**,我们**只定义一个对象指针**,就能够**调用父类**,以及**各个子类的同名函数**呢?
有:**这个对象指针,它的类型必须是父类类型(Human *phuman)。**
我们如果想通过一个父类指针调用父类,子类中的同名同参的函数的话,对于这个函数的要求:
在父类中,**函数(eat()函数)声明之前必须要加virtual声明函数(eat()函数)成虚函数**
**一旦某个函数(在基类中)被声明成了虚函数,,在派生类(子类)中也被声明成了虚函数**
Human* phuman1 = new Men;
phuman1->eat();//调用的是父类的eat();
eat()声明成虚函数
Human* phuman = new Men;
phuman->eat(); //调用的是子类Men的eat()函数
phuman->Human::eat();//调用父类的eat()函数
delete phuman;
如果未声明成虚函数:
Human* phuman = new Men;//可以编译
phuman->eat();//调用的是父类(Human)的eat()函数,因为phuman是父类指针。
phuman = new Women;
phuman->eat();//调用的是子类Women的eat()函数
delete phuman;
phuman = new Human;
phuman->eat();//调用的是父类Human的eat()函数
delete phuman;
三、override和final关键字
在C++11中,可以在函数声明中加入override关键字,用于说明派生类中的虚函数覆盖了父类的同名函数。如果子类中的虚函数写错了名字或参数,编译器会报错。
**为了避免你在子类类中写错虚函数(如:参数不一致)**,在C++11中,你可以**在函数声明这里增加一个override关键字**,这个关键字,**用在“ 子类 ”中,而且虚函数专用。**
**override**就是用来说明 **派生类** 中的 **虚函数** ,你用了这个关键字之后,编译器就会认为你这个eat是覆盖了父类的同名函数**(只有虚函数才存在子类可以覆盖父类中同名函数的问题)**,那么这个编译器就会在父类中找同名同参的虚函数,如果没找到,编译器就会报错,这样,如果你不小心在子类中把虚函数写错了名字,写错了参数,编译器能够帮助你纠错
final ,final 也是 虚函数 专用,但它是用在“ 父类” 中,如果我们在父类的函数声明中加了final,那么任何尝试覆盖该函数的操作都将引发错误
调用虚函数执行的是**" 动态绑定 " 动态**:表示在我们程序运行的时候才能知道调用了哪个子类的eat()虚函数。
动态的绑到Men上去,绑到Women上去,取决于new的是Men 还是 Wonmen
总结:
动态绑定:运行的时候才决定你的phuman对象绑到到哪个eat()函数上运行;
Men men;
men.eat();//调用肯定是Men;
Women women;
women.eat();//调用的肯定是Wonmen
Human human;
human.eat();//调用的肯定是Human
虚函数一般是用指针来操作,不然虚函数作用体现不出来