2009-10-04 41 views
8

Hãy xem xét chương trình sau đây.Lẫn lộn trong đầu ra

#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; 

} 

Đầu ra của chương trình trên là 01010.
Tôi nghĩ kết quả sẽ là 00000 vì giá trị của _bval được khởi tạo 0 (mỗi lần) bởi các nhà xây dựng lớp cơ sở.

Nhưng tại sao đầu ra khác với 00000?
Tôi đang thiếu gì?

Trả lời

7

Câu trả lời ngắn gọn: Trong C++, mảng giá trị không bao giờ đa hình, ngay cả khi nội dung của chúng là và không thể được xử lý như vậy. Tức là, bạn không thể xem một số derived ad[N] như thể đó là base ab[N].

Câu trả lời dài: Lý do cho điều này được chôn sâu trong số học con trỏ của C. Nếu bạn có một số int* pi và tăng nó ++pi, nó sẽ không đơn giản tăng lên đến địa chỉ bộ nhớ tiếp theo. Nếu nó đã làm, nó sẽ không trỏ đến int tiếp theo vì điều này không bắt đầu tại địa chỉ tiếp theo. Vì vậy, thay vào đó, sizeof(int) byte được thêm vào con trỏ. (Ví dụ cụ thể có thể giúp: Trên các kiến ​​trúc có 8bit char loại - char, theo định nghĩa C và C++ xem xét kích thước byte của kiến ​​trúc - và 32bit int loại, int có kích thước 4 byte, như vậy, ++pi sẽ thêm 4 vào địa chỉ con trỏ, để nó trỏ đến int tiếp theo.) Số học tương tự áp dụng cho tất cả các thao tác con trỏ khác. Vì vậy, ví dụ, với int* pi2=pi+1, pi2 sẽ chỉ sizeof(int) byte đằng sau pi, mặc dù pi2-pi sẽ mang lại 1.

Vì vậy, giả sử bạn hiểu đoạn cuối cùng, chúng ta hãy quay trở lại với mảng. Nếu bạn có một mảng derived ad[N], địa chỉ của ad[1]sizeof(derived) byte lớn hơn địa chỉ ad[0].Tuy nhiên, nếu bạn có một base* pb trỏ đến ad[0], tăng nó sẽ làm cho nó điểm sizeof(base) đằng sau địa chỉ của phần tử đầu tiên - trong đó, nếu (như trường hợp trong ví dụ của bạn) sizeof(base) < sizeof(derived), là không phải địa chỉ ad[1], nhưng ở đâu đó ở giữa ad[0].

Điều duy nhất bạn có thể làm để điều trị nội dung mảng như thể nó là tất cả các lớp học cơ sở, là để lặp qua mảng bằng cách sử dụng một derived* và đúc con trỏ này để base*trong vòng lặp:

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; 
} 

(Lưu ý rằng tôi cũng đã thay đổi mã của bạn để sử dụng trình lặp bắt đầu/kết thúc thành ngữ của C++).

+0

Cảm ơn bạn đã chính xác. –

11

p[i] cung cấp cho bạn giá trị tại sizeof(base) * i byte sau p. Vì vậy, p[1] sẽ không cung cấp cho bạn phần tử thứ hai của d, nó sẽ cung cấp cho bạn nửa thứ hai của phần tử đầu tiên.

Nói cách khác: nếu bạn sử dụng con trỏ đến lớp cơ sở để lặp qua một mảng của lớp dẫn xuất, bạn sẽ nhận được kết quả sai nếu lớp dẫn xuất có kích thước lớn hơn lớp cơ sở vì nó sẽ lặp lại các bước của sizeof(baseclass) byte.

+0

Bạn đúng, nếu chúng ta thay đổi 'i <10', chúng ta sẽ thêm' 01' :) – IProblemFactory

+0

Giá trị của _bval là '1'? –

+0

'bval' là biến thành viên duy nhất của lớp b. Vì vậy, để có được 'bval' của một đối tượng' base' bạn lấy giá trị tại 'address_of_the_base_object + 0'. Vì vậy khi p trỏ vào nửa thứ hai của đối tượng 'derived', nó sẽ thực hiện' p + 0' để tính địa chỉ 'bval', trong trường hợp này sẽ trả về địa chỉ' dval' vì 'p 'không thực sự trỏ vào đối tượng' base'. – sepp2k

3

Hãy suy nghĩ về bố cục bộ nhớ của mảng d.

d-> 0101010101

Nơi mỗi cặp 01 tương ứng với một đối tượng có nguồn gốc.

Bây giờ hãy p điểm với nó:

p-> 0101010101

Kể từ khi kích thước của các đối tượng cơ sở là một int. Phân đoạn bộ nhớ đó được coi là 10 đối tượng cơ sở: phân đoạn đầu tiên với _bval 0, giá trị thứ hai có _bval 1, ... v.v.

+0

Gọn gàng! Việc hiển thị bố cục bộ nhớ và giải thích ngữ nghĩa cơ sở của con trỏ rất rõ ràng. +1 :) – legends2k

1

Bên cạnh những gì sepp2k đã nói, bạn không khởi tạo _bval trong hàm tạo lớp dẫn xuất. Bạn nên khởi tạo nó bằng cách sử dụng phương thức khởi tạo base.