先来介绍两个c++保留字:new和delete
在c或者C++中完全可以在没有创建变量的情况下为有关数据分配内存。也就是直接创建一个指针并让它指向新分配的内存块:
int *pointer = new int;
*pointer = 110;
cout << *pointer;
delete pointer;
new和malloc没什么区别,new只是malloc的进化版本,进行了封装
注意:最后一步非常必要和关键,因为程序不会自动释放内存,程序中的每一个new操作都必须有一个与之对应的delete操作。
例子:
#include <iostream>
#include <string>
using namespace std;
class Pet
{
public:
Pet(string theName);
void eat();
void sleep();
void play();
protected:
string name;
};
class Cat:public Pet
{
public:
Cat(string theName);
void climb();
void play();
};
class Dog:public Pet
{
public:
Dog(string theName);
void bark();
void play();
};
Pet::Pet(string theName)
{
name = theName;
}
void Pet::eat()
{
cout << name << "正在吃!" << endl;
}
void Pet::sleep()
{
cout << name << "正在睡大觉!" << endl;
}
void Pet::play()
{
cout << name << "正在玩儿!" << endl;
}
Cat::Cat(string theName):Pet(theName)
{
}
void Cat::climb()
{
cout << name << "正在爬树!" << endl;
}
void Cat::play()
{
Pet::play(); //对基类中的方法进行覆盖
cout << name << "玩毛线球!" << endl;
}
Dog::Dog(string theName):Pet(theName)
{
}
void Dog::bark()
{
cout << name << "旺旺!" << endl;
}
void Dog::play()
{
Pet::play(); //对基类中的方法进行覆盖
cout << name << "正在追赶那只该死的猫!" << endl;
}
int main()
{
Pet *cat = new Cat("加菲");
Pet *dog = new Dog("欧迪");
cat -> sleep();
cat -> eat();
cat -> play();
dog -> sleep();
dog -> eat();
dog -> play();
delete cat;
delete dog;
return 0;
}
问题:
通过输出结果可以发现,程序与预期的结果不符:我们在Cat和Dog类里对play()方法进行了覆盖,但实际上调用的是Pet::play()方法而不是那两个覆盖的版本。
原因:
在程序编译的时候,编译器将检查所有的代码,在如何对某个数据进行处理和可以对该类型的数据进行何种处理之间寻找到一个最佳点。正是这一项编译时的检查影响了刚才的程序结果:cat和dog在编译时都是Pet类型指针,编译器就认为两个指针调用的paly()方法是Pet::play()方法,因为这是执行起来最快的解决方案。而引发问题的源头就是我们使用了new在程序运行的时候才为dog和cat分配Dog类型和Cat类型的指针。这些是它们在运行时才分配的类型,和它们在编译时的类型是不一样的。为了让编译器知道它应该根据这两个指针在运行时的类型而有选择地调用正确的方法(Dog::play()和Cat::play()),我们必须把这些方法声明为虚方法。
声明虚方法:
virtual void play();
虚方法是继承的,一旦在基类里把某个方法声明为虚方法,在子类里就不可能再把它声明为一个非虚方法了。
因此只需要在Pet类的play()方法前面加上virtual即可
class Pet
{
public:
Pet(string theName);
void eat();
void sleep();
virtual void play();
protected:
string name;
};
Tips:
(1)如果拿不准要不要把某个方法声明为虚方法,那么就把它声明为虚方法好了
(2)在基类里把所有的方法都声明为虚方法会让最终生成的可执行代码的速度变得稍微慢一些,但好处是可以一劳永逸地确保程序的行为符合你的预期
(3)在实现一个多层次的类继承关系的时候,最顶级的基类应该只有虚方法
(4)构造器都是虚方法!从编译的角度看,它们只是普通的方法。如果它们不是虚方法,编译器就会根据它们在编译时的类型而调用那个在基类里定义的版本(构造器),那样往往会导致内存泄露。
别人的理解:
虚方法一般申明在基类,或者相对基类(子类的子类的基类),使用new动态分配内存的时候可以用基类的指针申明子类的实例,而是转而检查子类中是否有这个方法的覆盖,如果有就执行覆盖后的方法,否则执行基类中的方法,这个时候在调用方法的时候,如果基类方法前有virtual字样,那么编译器不会立刻执行基类中的方法,如果没有则直接执行基类中的方法。