虚方法以及new和delete讲解
先通过一个简单的例子引发的问题来探究虚方法的作用以及完整的解决方案。
下面先讲解一下new和delete的作用。
new和delete
以前我们的做法是:创建一个变量,再把这个变量的地址赋值给一个指针。然后我们就可以利用指针来访问这个变量的值了。
事实上,在C和C++中我们完全可以在没有创建变量的情况下为有关数据分配内存,也就是直接创建一个指针并让它指向新分配的内存块,用法如下:
#include <iostream>
int main()
{
int *pointer = new int;
*pointer = 110;
std::cout << *pointer << std::endl;
delete pointer;
return 0;
}
输出结果为110;
注意最后一步非常必要和关键,这是因为程序不会自动释放内存,程序中的每一个new操作的都必须有一个与之对应的delete操作。
下面再来个例子:
#include <iostream>
#include <string>
class Pet
{
public:
Pet(std::string theName);
void eat();
void sleep();
void play();
protected:
std::string name;
};
class Cat : public Pet
{
public:
Cat(std::string theName);
void climb();
void play();
};
class Dog : public Pet
{
public:
Dog(std::string theName);
void bark();
void play();
};
Pet::Pet(std::string theName)
{
name = theName;
}
void Pet::eat()
{
std::cout << name << "正在吃东西!" << std::endl;
}
void Pet::sleep()
{
std::cout << name << "正在睡觉!" << std::endl;
}
void Pet::play()
{
std::cout << name << "正在玩!" << std::endl;
}
Cat::Cat(std::string theName) : Pet(theName)
{
}
void Cat::climb()
{
std::cout << name << "正在爬树!" << std::endl;
}
void Cat::play()
{
Pet::play();
std::cout << name << "正在玩毛球!" << std::endl;
}
Dog::Dog(std::string theName) : Pet(theName)
{
}
void Dog::bark()
{
std::cout << name << "正在叫!" << std::endl;
}
void Dog::play()
{
Pet::play();
std::cout << name << "正在追猫!" << std::endl;
}
int main()
{
Pet *cat = new Cat("加菲");
Pet *dog = new Dog("欧迪");
cat->eat();
cat->sleep();
cat->play();
dog->eat();
dog->sleep();
dog->play();
delete cat;
delete dog;
return 0;
}
输出结果为:
出现了问题,明明对两个play函数进行了覆盖,输出还是原本的。这里试了一下,把Cat::play()和Dog::play()里面的Pet::play注释掉掉之后,输出还是上面的这个图。
解释:
程序之所以会有这样奇怪的行为,是因为C++的创始人希望用C++生成的代码至少和它的老前辈C一样快。
所以程序在编译的时候,编译器将检查所有的代码,在如何对某个程序进行处理可以对该类型的数据进行何种处理之间寻找一个最佳点。
正是这一项编译时的检查影响了刚才的程序结果:cat和dog在编译时都是Pet类型指针,编译器就认为两个指针调用的play()方法是Pet::play()方法,因为这是执行起来最快的解决方案。
而引发问题的源头就是我们使用了new在程序运行的时候才为dog和cat分配Dog类型和Cat类型的指针。
这些是它们在运行时才分配的类型,和它们在编译时的类型是不一样的!
为了让编译器知道它应该根据这两个指针在运行时的类型而有选择地调用正确的方法(Dog::play()和Cat::play()),我们必须把这些方法声明为虚方法。
声明一个虚方法的语法非常简单,只要在其原型前边加上virtual保留字即可。
virtual void play();
另外,虚方法是继承的,一旦在的基类里把某个方法声明为虚方法,在子类里就不可能再把它声明为一个非虚方法了。
下面就把上面的程序改一下就好了:
#include <iostream>
#include <string>
class Pet
{
public:
Pet(std::string theName);
void eat();
void sleep();
virtual void play();
protected:
std::string name;
};
class Cat : public Pet
{
public:
Cat(std::string theName);
void climb();
void play();
};
class Dog : public Pet
{
public:
Dog(std::string theName);
void bark();
void play();
};
Pet::Pet(std::string theName)
{
name = theName;
}
void Pet::eat()
{
std::cout << name << "正在吃东西!" << std::endl;
}
void Pet::sleep()
{
std::cout << name << "正在睡觉!" << std::endl;
}
void Pet::play()
{
std::cout << name << "正在玩!" << std::endl;
}
Cat::Cat(std::string theName) : Pet(theName)
{
}
void Cat::climb()
{
std::cout << name << "正在爬树!" << std::endl;
}
void Cat::play()
{
Pet::play();
std::cout << name << "正在玩毛球!" << std::endl;
}
Dog::Dog(std::string theName) : Pet(theName)
{
}
void Dog::bark()
{
std::cout << name << "正在叫!" << std::endl;
}
void Dog::play()
{
Pet::play();
std::cout << name << "正在追猫!" << std::endl;
}
int main()
{
Pet *cat = new Cat("加菲");
Pet *dog = new Dog("欧迪");
//Cat cat_2("加菲_2");
//cat_2.play();
cat->eat();
cat->sleep();
cat->play();
dog->eat();
dog->sleep();
dog->play();
delete cat;
delete dog;
return 0;
}
输出结果:
Tips
- 如果拿不准要不要把某个方法声明为虚方法,那么就把她声明为虚方法好了。
- 在基类里如果把所有的方法都声明为虚方法会让最终生成的代码的速度变得稍微慢一些,但好处是可以一劳永逸地确保程序的行为符合你的预期。
- 在实现一个多层次的类继承关系的时候,最低级的基类应该只有虚方法。
- 析构器都是虚方法!从编译的角度看,它们只是普通的方法。如果它们不是虚方法,编译器就会根据它们在编译时的类型而调用在基类里定义的版本(构造器),那样往往会造成内存泄漏。
#include <iostream>
#include <string>
class Pet
{
public:
Pet(std::string theName);
void eat();
void sleep();
void play();
protected:
std::string name;
};
class Cat : public Pet
{
public:
Cat(std::string theName);
void climb();
void play();
};
class Dog : public Pet
{
public:
Dog(std::string theName);
void bark();
void play();
};
Pet::Pet(std::string theName)
{
name = theName;
}
void Pet::eat()
{
std::cout << name << "正在吃东西!" << std::endl;
}
void Pet::sleep()
{
std::cout << name << "正在睡觉!" << std::endl;
}
void Pet::play()
{
std::cout << name << "正在玩!" << std::endl;
}
Cat::Cat(std::string theName) : Pet(theName)
{
}
void Cat::climb()
{
std::cout << name << "正在爬树!" << std::endl;
}
void Cat::play()
{
Pet::play();
std::cout << name << "正在玩毛球!" << std::endl;
}
Dog::Dog(std::string theName) : Pet(theName)
{
}
void Dog::bark()
{
std::cout << name << "正在叫!" << std::endl;
}
void Dog::play()
{
Pet::play();
std::cout << name << "正在追猫!" << std::endl;
}
int main()
{
//Pet *cat = new Cat("加菲");
Pet *dog = new Dog("欧迪");
Cat cat("加菲");
Cat *p_cat = &cat;
p_cat->eat();
p_cat->sleep();
p_cat->play();
dog->eat();
dog->sleep();
dog->play();
//delete cat;
delete dog;
return 0;
}
我又测试了一下,这样又是可以的,反正现在有点晕,先留着慢慢学