这是一个面试问题,我们写Shape s = new Circle();
,其中Shape
是abstract class
。
s
与Shape * s = new Circle();
这类形状类型的指针有什么区别?
这两句话截然不同,我先说简单(而且正确)的那句:
<标题> 的指针Shape * s = new Circle();
正如您已经提到的,s
在本例中是指向- Shape
的指针。语句声明了s
,并给它赋值了一个Circle
类型的指针。这意味着Circle
是从Shape
派生出来的。
因为c++是一种多态语言,s
现在指向Circle
类型的对象。如果Shape
上的虚函数被Circle
重载,那么对s
上虚函数的调用将被重定向到重载的Circle
上。
一个简单的和不完整的示例(注意在c++中,没有按关键字定义的abstract classes
):
class Shape{
virtual int getX() = 0; //getX() is purely virtual, so Shape is abstract
}
class Circle : public Shape{
int getX() override;
}
Shape * s = new Circle();
int myX = s->getX(); //will call Circle::getX()
这是基本的多态行为,也是c++中面向对象编程的基石之一。还要注意的是,override关键字并不是完全必要的,我个人认为这是一个很好的风格。
<标题>Shape s = new Circle();
这个语句看起来和第一个语句差不多,但是做的事情完全不同。首先,s
现在是实际的对象,这是好的和坏的同时。这很好,因为只要当前复合语句离开,它就会自动销毁,释放它占用的所有内存。
但这很糟糕,因为s
不能再多态了:它被声明为Shape
-Object,没有别的了。使用上面的代码,并且您的前提是Shape
是一个抽象类,这甚至无法工作。抽象类不能被实例化,因为它们有未实现的函数。
如果我们假设它不是抽象的,我们将陷入许多隐式转换漏洞,我只想简短地描述一下:
为了使上面的语句是正确的c++代码,Shape
需要有一个构造函数,该构造函数接受一个类型为Circle*
的参数,即而不是声明为explicit
。这将使Shape s = new Circle();
等同于Shape s = Shape(new Circle());
。执行隐式转换。
这是, 非常坏代码!new Circle()
为Circle
对象分配的内存永远不会被释放,因为它没有名字!这被称为内存泄漏,这就是为什么你几乎不应该在c++中使用裸指针的原因。另一种方法叫做智能指针,你应该尽可能地使用它们。
explicit
(或至少只接受一个参数的构造函数):
explicit Shape(Circle *);
最后,我想鼓励你至少读一本,最好是几本关于c++的书。我刚才解释的都是你应该知道的基本知识,这里不是学习的好地方。c++是一门危险的语言,你可以做很多非常错误的事情,即使它们最初看起来很正常。
标题>标题>new Circle
创建Circle
类型的动态对象并给出指向该对象的指针。该指针可用于初始化指向Circle
或Circle
的任何基类的指针;所以假设Circle
来源于Shape
,第二种形式
Shape * s = new Circle;
给出一个(静态)类型的指针Shape*
,指向一个(动态)类型的对象Circle
。这可以多态地使用,通过Shape
定义的抽象接口与Circle
交互,而不需要知道实际类型是Circle
。
第一个错误的例子
Shape s = new Circle;
尝试创建一个类型为Shape
的对象,用指向Circle
的指针初始化。由于是抽象的,Shape
不能被实例化,除非作为派生类的一部分,所以编译失败。
当您使用new
时,始终记住在完成动态对象时将其delete
,或者更好的是,使用RAII类(如智能指针)来为您处理容易出错的单调乏味。此外,确保Shape
具有虚析构函数,以便可以安全地删除其派生类型。