2009-10-04 18 views
8

次のプログラムを検討してください。出力の混乱

#include<iostream> 
using namespace std; 

class base 
{ 

    public: 
    int _bval; 

    base():_bval(0){} 
}; 

class derived:public base 
{ 

    public: 
    int _dval; 

    derived():base(),_dval(1){} 
}; 

int main() 
{  

    derived d[5]; 
    base *p; 
    p=d; 
    for(int i=0;i<5;++i,++p) 
     cout<<p->_bval; 

} 

上記のプログラムの出力は、Iが_bvalの値は、ベースクラスのコンストラクタによって0(たび)に初期化されたため、出力が00000だろうと思っ01010.
あります。

しかし、なぜ出力が00000と異なるのですか?
私は何が欠けていますか?

答えて

7

短い答え:C++では、値の配列は内容が同じであっても決して多型ではないため、扱うことはできません。つまり、derived ad[N]をあたかもbase ab[N]のように扱うことはできません。

長い答え:この理由は、Cのポインタ演算に深く埋もれているためです。 int* piがあり、それをインクリメントすると++piになると、単に次のメモリアドレスにインクリメントするだけではありません。そうであれば、それは次のアドレスで始まらないため、次のintを指さないでしょう。代わりに、sizeof(int)バイトがポインタに追加されます。 (具体的な例が役に立つかもしれません:8bit charタイプのアーキテクチャで - charは、CとC++がアーキテクチャのバイトサイズとみなすもので、32ビットintタイプの場合、intのサイズは4バイトです。したがって、++piは4ポインタアドレスであり、次のポインタを指すようにします。int)同じ演算が他のすべてのポインタ演算に適用されます。 pi2-piだから

1が得られますが、そのため、例えば、int* pi2=pi+1で、pi2はあなたが最後の段落を理解想定し、pi後ろsizeof(int)バイトを指すようになります、の配列に戻りましょう。配列がderived ad[N]の場合、ad[1]のアドレスはad[0]よりも大きいsizeof(derived)バイトです。ただし、base* pbad[0]を指している場合は、それをインクリメントすると、最初の要素のアドレスの後ろにsizeof(base)の位置になります。これはif(その場合がそうです。あなたの例)sizeof(base) < sizeof(derived)は、ではなく、ad[1]のアドレスですが、ad[0]のどこかにあります。です。あなたはそれがすべての基本クラスであるかのように、配列の内容を処理するために行うことができます

唯一のものは、derived*を使用して、配列を反復処理し、内base*にループを、このポインタをキャストすることです:

derived d[5]; 
derived* begin = d; 
const derived* end = d + sizeof(d)/sizeof(d[0]); // points one beyond the last element 
while(begin != end) 
{ 
    base* pb = begin; 
    cout<< pb->_bval; 
    ++begin; 
} 

(私はあなたのコードをC++のイディオムのbegin/endイテレータを使用するように変更していることに注意してください)

+0

正確であることに感謝します。 –

11

p[i]は、sizeof(base) * iバイトの値をpの後に指定します。だからp[1]dの2番目の要素を与えません。それは最初の要素の後半を与えます。

つまり、基本クラスへのポインタを使用して派生クラスの配列を反復処理すると、派生クラスのサイズが基本クラスよりも大きければ間違った結果になりますsizeof(baseclass)バイトのステップ。

+0

私は 'i <10 'を変更すると正しいでしょう、' 01' :) – IProblemFactory

+0

_bvalの値は '1'ですか? –

+0

'bval'はクラスbの唯一のメンバ変数です。だから、 'base'オブジェクトの' bval'を得るには、 'address_of_the_base_object + 0'で値を取ってください。だから、pが 'derived'オブジェクトの後半を指しているとき、pvalは' bval'のアドレスを計算するために 'p + 0'を行います。この場合、' dval'のアドレスを実際に返します。 'p 'は実際に' base'オブジェクトを指していません。 – sepp2k

3

d配列のメモリレイアウトについて考えてみましょう。

D-> 0101010101 01の各対は1つの派生オブジェクトに対応

今それにP点を聞かせて:

のp> 0101010101

をベースオブジェクトのサイズが1つのint型のものですので。そのメモリセグメントは、10個のベースオブジェクトとみなされます。最初のオブジェクトは_bval 0、2番目のオブジェクトは_bval 1などです。

+0

ニート!メモリレイアウトを表示し、基本ポインタの意味を説明することは非常に明確です。 +1: – legends2k

1

sepp2kによると、派生クラスのコンストラクタでは_bvalを初期化していません。 baseコンストラクタを使用して初期化する必要があります。