2009-10-04 2 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; 

} 

Выход выше программы 01010.
я подумал, выход будет 00000, так как значение _bval был инициализирован до 0 (каждый раз) с помощью конструктора базового класса.

Но почему выход отличается от 00000?
Что мне не хватает?

ответ

7

Краткий ответ: в C++ массивы значений никогда не являются полиморфными, даже если их содержимое и не может быть обработано так. То есть вы не можете обработать derived ad[N], как если бы это был base ab[N].

Длинный ответ: причина этого глубоко погружена в арифметику указателя на C. Если у вас есть int* pi и увеличьте его ++pi, он не будет просто увеличиваться до следующего адреса памяти. Если бы это было так, это не указывало бы на следующий int, так как это не начинается с следующего адреса. Таким образом, вместо указателя добавляется sizeof(int) байт. (Конкретный пример может помочь: На архитектурах с 8-битными char типами - char, будучи по определению, что C и C++ считают размер байта архитектуры - и 32 бит int типов, int имеет размер 4 байта. Таким образом, ++pi добавит 4 к адрес указателя, так что он указывает на следующий int.) Такая же арифметика применяется ко всем другим операциям указателя. Так, например, с int* pi2=pi+1, pi2 укажут sizeof(int) байт за pi, хотя pi2-pi даст 1.

Таким образом, предполагая, что вы поняли последний абзац, давайте вернемся к массивам. Если у вас есть массив derived ad[N], адрес ad[1] равен sizeof(derived) байт, превышающим адрес .(Это не относится к выравниванию, чтобы не усложнять проблему.) Однако, если у вас есть base* pb, указывающий на , приращение его приведет к тому, что он будет указывать sizeof(base) за адресом первого элемента - который, если (как в случае с ваш пример) sizeof(base) < sizeof(derived), is не адрес 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++.)

+0

Спасибо за то, что вы точны. –

11

p[i] дает вам значение sizeof(base) * i байт после p. Так что p[1] не даст вам второго элемента d, он даст вам вторую половину первого элемента.

Другими словами: если вы используете указатель на базовый класс для итерации по массиву производного класса, вы получите неправильные результаты, если производный класс имеет больший размер, чем базовый класс, потому что он будет итерации в шаги sizeof(baseclass) байтов.

+0

Вы правы, если мы изменим 'i <10'we will se more' 01' :) – IProblemFactory

+0

Но как можно Значение _bval равно '1'? –

+0

'bval' - единственная переменная члена класса b. Итак, чтобы получить 'bval' объекта' base', вы берете значение в 'address_of_the_base_object + 0'. Поэтому, когда p указывает на вторую половину «производного» объекта, он будет делать «p + 0», чтобы вычислить адрес 'bval', который в этом случае фактически вернет адрес' dval', потому что 'p 'на самом деле не указывает на объект' base'. – sepp2k

3

Подумайте о макете памяти массива d.

d-> 0101010101

где каждая пара 01 соответствует одному объекту производного.

Пусть теперь р точку к нему:

p-> 0101010101

Поскольку размер базовых объектов является то, что один междунар. Этот сегмент памяти считается 10 базовыми объектами: первый с _bval 0, второй с _bval 1, ... и т. Д.

+0

Аккуратно! Отображение макета памяти и объяснение семантики базового указателя очень ясны. +1 :) – legends2k

1

Помимо того, что sepp2k сказал, вы не инициализировали _bval в конструкторе производного класса. Вы должны инициализировать его с помощью конструктора base.

Смежные вопросы