0



首先看看下面的代码(在这个代码中shape是基类,line是派生类)

void drawshapes(shape sarray[],int size)
{
    for(int i=0;i< size; i++)
        sarray[i].draw();
}

main()
{
   line larray[10];
   larray[0]=line(p1,p2);//assuming that we have a point class
   larray[1]=line(p2,p3);
   ..........
   drawshapes(larray,10);
}


当我们编译这个程序时,首先会调用 shape 的 draw 方法,然后程序终止。为什么它终止?为什么我们不能在没有基类指针或引用的情况下实现多态性这是什么技术原因?如果我们试图用对象数组实现多态性,编译器会做什么?请通过示例以易于理解的方式进行解释。我将非常感激。

4

4 回答 4

5

首先:您混合了两个概念:多态性和值与引用语义。

运行时多态性

多态性有多种形式。根据您使用的运行时,其他选项可用。

解释型语言(如 Ruby、Python、Javascript 等)允许“鸭子类型”:如果一个对象只有一个名为 的方法foo,您可以调用它。通常这些语言会进行垃圾收集,因此指针与对象的概念并不太相关。

C++ 有不同的观点:允许多态性,但以更严格的方式。强制一个通用的基类(可能是抽象的)允许编译器检查你的代码的语义:这样编译器可以确保你真的是指foo实现预期接口的方法,而不是一些混杂的foos。

这种多态性是通过使用一个virtual函数来实现的:一个指向函数的指针,它可能因实现而异。的调用者foo首先必须查找函数指针的值,然后跳转到该函数。

到目前为止,对于多态性。

遏制

现在进行遏制:如果您在 C++ 中创建一个对象数组line,这些对象在内存中彼此相邻;它们包含在 value中。将数组传递给函数时,被调用函数只能接收相同类型的数组。否则,在数组中迈出一步,sizeof(shape)我们最终会在 a 的中间line

为了解决这个问题,您可以“通过引用”包含对象 - 在 C++ 中,我们为此使用指针。

编译时多态性

但是还有另一种实现多态功能的方法:模板。您可以drawshapes使用模板参数编写函数,该参数说明您正在使用哪种类型的对象:

template< typename tShape, size_t N > 
void drawshapes( tShape (&aShapes)[N] ) {
    for( tShape* shape=aShapes; shape != aShapes+N; ++shape ) {
        shape->draw();
    }
}

(注意:有 stl 函数可以简化这一点,但这超出了问题的范围。

std::for_each( shapes, shapes+10, mem_fun_ref( &shape::draw ) );

)

于 2010-02-03T08:24:59.063 回答
5

您正在提出一个问题并提供一个失败但出于不同原因的代码示例。从你的问题的措辞:

为什么多态性需要引用/指针?

struct base {
   virtual void f();
};
struct derived : public base {
   virtual void f();
};
void call1( base b ) {
   b.f(); // base::f
}
void call2( base &b ) {
   b.f(); // derived::f
}
int main() {
   derived d;
   call1(d);
   call2(d);
}

base当您使用按值传递语义(或将派生元素存储在基本容器中)时,您正在创建 type元素的 type副本derived。这称为slicing,因为它类似于您有一个derived对象并且您只从中切片/剪切base子对象的事实。在示例中,call1不适d用于 main 中的对象,而是使用临时类型base, 并被base::f调用。

在该call2方法中,您传递对base对象的引用。当编译器call2(d)在 main 中看到时,它将创建base对子对象的引用d并将其传递给函数。base该函数对指向类型对象的类型引用执行操作derived,并将调用derived::f. 指针也是如此,当你base *进入一个derived对象时,对象仍然是derived.

为什么我不能将指针容器传递给采用derived指针容器的函数base

_很明显,如果derivedbase,一个容器derived 就是一个容器base

不。 的容器derived不是 的容器base。那会破坏类型系统。下面是使用容器derived作为base破坏类型系统的对象容器的最简单示例。

void f( std::vector<base*> & v )
{
   v.push_back( new base );
   v.push_back( new another_derived );
}
int main() {
   std::vector<derived*> v;
   f( v ); // error!!!
}

如果语言允许标记有错误的行,那么它将允许应用程序将非类型元素插入derived*容器中,这将意味着很多麻烦......

但问题是关于值类型的容器......

当你有值类型的容器时,元素会被复制到容器中。将一个类型的元素插入一个类型derived的容器中,会在对象中创建一个类型的base子对象的副本。这与上面的切片相同。除了语言限制之外,还有一个很好的理由,当你有一个对象容器时,你就有空间来容纳元素。您不能将较大的对象存储到同一个容器中。否则编译器甚至不知道为每个元素保留多少空间(如果我们以后扩展一个更大的类型呢?)。basederivedbasebase

在其他语言中,这似乎实际上是允许的(Java),但事实并非如此。唯一的变化是语法。当您使用Java 时,您实际上是在用 C++String array[]编写等价物。string *array[]所有非原始类型都是语言中的引用,并且您没有*在语法中添加的事实并不意味着容器保存String 的实例,容器保存对字符串的引用,这些引用与 c++ 指针的关系比 c++ 引用更相关.

于 2010-02-03T08:38:05.373 回答
3

您不能传递线数组而不是形状数组。您必须使用指针数组。发生这种情况是因为当函数尝试访问第二个成员时,它会执行 *(sarray + sizeof(shape)) 而不是 *(sarray + sizeof(line)),这将是访问数组第二个元素的正确方法的线。

于 2010-02-03T08:06:10.053 回答
1

你想要这样的东西:

void drawshapes(shape *sarray[],int size)
{
   for(int i=0;i< size; i++)
      sarray[i]->draw();
}

main()
{
   shape *larray[10];
   larray[0] = new line(p1,p2);//assuming that we have a point class
   larray[1] = new line(p2,p3);
   ..........
   drawshapes(larray, 10);
   // clean-up memory
   ...
}
于 2010-02-03T08:23:50.683 回答