1. 基类指针
在创建对象时,可以直接使用[类名] [对象名],也可以使用new关键字创建对象,即 [类名] 对象指针=new [类名]。其中前者对象创建在栈上,后者对象创建在堆上。
#include <iostream>
using namespace std;
class Animal
{
};
int main() {
Animal an1; //对象创建在栈上
Animal *an2 = new Animal;//对象创建在堆上
}
2. 虚函数
2.1 父类指针指向子类对象
父类指针可以指向子类对象,而子类指针无法指向父类对象。若此时,父类和子类中存在同名的成员函数,父类指针调用的是父类的成员函数。
#include <iostream>
using namespace std;
class Animal
{
public:
void eat()
{
cout << "动物吃各种食物" << endl;
}
};
class Dog : public Animal
{
public:
void eat()
{
cout << "狗吃骨头" << endl;
}
};
int main() {
Animal *an1 = new Dog; //父类指针指向子类对象
// Dog wangcai = new Animal; //错误,子类指针指向父类对象
an1->eat(); //动物吃各种食物,执行的是父类成员函数
}
2.2 虚函数——使父类指针可以调用子类的同名成员函数
(1)虚函数的条件:
在父类的成员函数声明前加上virtual关键字,则该成员函数被声明为虚函数。
(2)一旦某个函数在父类中被声明为虚函数,那么在所有子类中它都是虚函数。
(3)子类的虚函数声明前可以不用加virtual。
(4)调用哪个类的成员函数与你new的类对象有关。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "动物吃各种食物" << endl;
}
};
class Dog : public Animal
{
public:
void eat()
{
cout << "狗吃骨头" << endl;
}
};
class Cat : public Animal
{
public:
void eat()
{
cout << "猫吃鱼" << endl;
}
};
int main() {
Animal *an1 = new Animal; //父类指针指向父类对象
Animal *an2 = new Dog; //父类指针指向子类(Dog)对象
Animal *an3 = new Cat; //父类指针指向子类(Cat)对象
an1->eat(); //动物吃各种食物,调用父类的虚函数
an2->eat(); //狗吃骨头, 调用子类Dog的虚函数
an3->eat(); //猫吃鱼,调用子类Cat的虚函数
}
2.3 override与final关键字
1. override
为了避免在子类中写错虚函数,在C++11中,可以在虚函数声明中加上override关键字,此时,编译器会在父类中寻找与子类同名的虚函数,并用子类的虚函数将其覆盖,若在父类中没有找到同名的虚函数则报错。
(1)override主要是为了防止程序员在子类中写错虚函数名,此时会自动报错。
(2)override关键字用在子类中,且虚函数专用。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual int eat(int)
{
cout << "动物吃各种食物" << endl;
}
};
class Dog : public Animal
{
public:
virtual void eat() override
{
cout << "狗吃骨头" << endl;
}
};
class Cat : public Animal
{
public:
virtual void eat() override
{
cout << "猫吃鱼" << endl;
}
};
int main() {
Animal *an1 = new Animal; //父类指针指向父类对象
Animal *an2 = new Dog; //父类指针指向子类(Dog)对象
Animal *an3 = new Cat; //父类指针指向子类(Cat)对象
an1->eat(); //报错,父类中不存在与子类同名的成员函数供子类覆盖
an2->eat(); //报错,父类中不存在与子类同名的成员函数供子类覆盖
an3->eat(); //报错,父类中不存在与子类同名的成员函数供子类覆盖
}
2. final
final也是虚函数专用,但其用于父类,如果我们在父类的成员函数中增加了final,则任何尝试覆盖该函数的操作都将引发错误。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() final
{
cout << "动物吃各种食物" << endl;
}
virtual void eat(int)
{
cout << "动物吃各种食物" << endl;
}
};
class Dog : public Animal
{
public:
virtual void eat() override //报错,父类的虚函数含final,不可被覆盖
{
cout << "狗吃骨头" << endl;
}
virtual void eat(int) override //父类的虚函数没有final,可以被覆盖
{
cout << "狗吃骨头" << endl;
}
};
2.4 动态绑定
调用虚函数执行的是动态绑定,即只有程序运行时,才知道调用了哪个子类的虚函数,动态绑定到Dog还是Cat,取决于new的是Dog还是Cat。
3. 多态性
多态性是针对虚函数来说的,当父类指针指向子类对象,系统内部通过查虚函数表来确定执行哪个类的虚函数,这就是运行时期的多态性。
4. 纯虚函数
(1)纯虚函数是在基类中声明的虚函数,但是其在基类中没有定义。
(2)纯虚函数要求任何子类都要定义该虚函数自己的实现方法。
(3)一旦一个类中有纯虚函数,则这个类变成了“抽象类”,不可以生成该类的对象。
(4)抽象类不用来生成对象,主要目的用于统一管理子类对象。
#include <iostream>
using namespace std;
class Animal //抽象类
{
public:
virtual void eat() = 0; //纯虚函数
};
class Dog : public Animal
{
public:
virtual void eat(); //错误, 子类中需要实现父类的纯虚函数
};
class Cat : public Animal
{
public:
virtual void eat() override // 子类中实现父类的纯虚函数
{
cout << "猫吃鱼" << endl;
}
};
int main() {
// Animal *an1 = new Animal; //错误,父类Animal中含有纯虚函数,变为了抽象类,不可用来生成对象。
// Animal an1; //错误,父类Animal中含有纯虚函数,变为了抽象类,不可用来生成对象。
// Animal *an2 = new Dog; //错误,子类中没有实现父类的纯虚函数。
Animal *an3 = new Cat;
// an2->eat(); //狗吃骨头
an3->eat(); //猫吃鱼
}
5. 虚析构
(1)在存在虚函数时,delete父类指针只析构了父类的对象而子类对象没有被析构,会造成内存泄漏。
#include <iostream>
using namespace std;
class Animal //抽象类
{
public:
Animal()
{
cout << "执行Animal()"<< endl;
}
~Animal()
{
cout << "执行~Animal()" << endl;
}
};
class Dog : public Animal
{
public:
Dog()
{
cout << "执行Dog()"<< endl;
}
~Dog()
{
cout << "执行~Dog()" << endl;
}
};
int main() {
Animal *an = new Dog; //执行Animal(), 执行Dog()
delete an; //执行~Animal(), 此时只析构了父类的对象,子类未被析构
}
(2)为了在delete父类指针时能够同时调用父类的析构函数与子类的析构函数,需要把父类的析构函数声明为virtual。
#include <iostream>
using namespace std;
class Animal //抽象类
{
public:
Animal()
{
cout << "执行Animal()"<< endl;
}
virtual ~Animal() //父类中声明为虚析构函数
{
cout << "执行~Animal()" << endl;
}
};
class Dog : public Animal
{
public:
Dog()
{
cout << "执行Dog()"<< endl;
}
~Dog()
{
cout << "执行~Dog()" << endl;
}
};
int main() {
Animal *an = new Dog; //执行Animal(), 执行Dog()
delete an; //执行~Dog(),执行~Animal(),
}
总结:
(1)如果一个类,想要做基类,我们务必要把该类的析构函数写成virtual析构函数,此时delete基类指针时,也会执行子类的析构函数。
(2)虚函数会增加内存开销,类里面定义虚函数,编译器会给这个类增加虚函数表,在这个表里存放虚函数指针。