2011-08-26 28 views
6

Tôi mới tham gia C++ và toàn bộ ý tưởng về lớp học - Tôi vẫn đang đọc một cuốn sách để thử và tìm hiểu. Cuốn sách tôi đang đọc nói rằng khi tôi xây dựng một lớp học, tôi có thể gán giá trị mặc định bằng cách làm này:Xây dựng lớp học với giá trị ban đầu

class foo { 
public: 
    foo(char c, int i); 
private: 
    char exampleChar; 
    int exampleInt; 
}; 

foo::foo(char c, int i): 
exampleChar(c), 
exampleInt(i) 
{} 

Mã này (với tôi) trông rất lộn xộn và không theo quy tắc mà tôi được sử dụng cho các ngôn ngữ khác. Câu hỏi của tôi là, sự khác biệt giữa việc làm ở trên là gì, và điều này (dưới đây, cá nhân tôi nghĩ rằng trông sạch hơn rất nhiều)?

foo::foo(char c, int i) { 
    exampleChar = c; 
    exampleInt = i; 
} 

Sắp xếp của những điều tôi đang suy nghĩ về là: đang có vấn đề hiệu suất/hiệu quả nếu được thực hiện trên quy mô lớn - hoặc là nó giống hệt nhau?

+1

"Mã này ... không tuân theo các quy tắc mà tôi đã quen với các ngôn ngữ khác" Vâng, C++ không phải là một trong những ngôn ngữ khác. Có rất nhiều điều trong C++ khác với "ngôn ngữ khác" (và, có thể, có rất nhiều thứ trong mỗi ngôn ngữ khác khác với những ngôn ngữ khác). –

+1

các câu hỏi rất giống nhau: http://stackoverflow.com/questions/1598967/benefits-of-initialization-lists, http://stackoverflow.com/questions/926752/why-should-i-prefer-to-use-member -initialization-list, http://stackoverflow.com/questions/4589237/c-initialization-lists, và có lẽ nhiều hơn nữa –

Trả lời

10

Cách đầu tiên, bằng cách thực hiện : exampleChar(c), exampleInt(i) được gọi là danh sách bộ khởi tạo.

Nếu bạn làm điều đó cách thứ hai, hai biến là mặc định được xây dựng đầu tiên , sau đó bạn gán cho chúng một giá trị. (Khi cơ thể thực sự của hàm khởi tạo được nhập vào, bất kỳ thứ gì chưa được khởi tạo bởi danh sách khởi tạo được xây dựng mặc định.) Đây là một sự lãng phí thời gian bởi vì bạn chỉ ghi đè lên các giá trị. Đối với các loại nhỏ như int hoặc char, đây không phải là vấn đề lớn, nhưng khi các biến thành viên đó là các loại lớn cần nhiều chu kỳ để xây dựng, bạn chắc chắn muốn sử dụng danh sách trình khởi tạo.

Cách thứ hai sẽ không lãng phí thời gian cho chúng một giá trị mặc định và ghi đè nó - nó sẽ đặt giá trị của chúng trực tiếp đến giá trị bạn cung cấp (hoặc gọi hàm tạo đúng nếu thành viên là đối tượng).

Bạn có thể xem những gì chúng tôi có nghĩa là bằng cách làm này:

class MyClass { 
public: 
    int _i; // our data 

    // default constructor 
    MyClass() : _i(0) { cout << "default constructor"; } 

    // constructor that takes an int 
    MyClass(int i) : _i(i) { cout << "int constructor"; } 

    // assignment operator 
    void operator=(int i) { _i = i; cout << "assignment operator"; } 
}; 

class OtherClass { 
public: 
    MyClass c; 

    OtherClass() { 
     c = 54; 
    } 
}; 

OtherClass oc; 

Bạn sẽ thấy rằng

default constructor 
assignment operator 

được in ra. Đó là hai cuộc gọi hàm, cho các lớp khác, có thể tốn kém.

Nếu bạn thay đổi các nhà xây dựng của OtherClass để

OtherClass() : c(54) { } 

Bạn sẽ thấy rằng

int constructor 

được in ra. Chỉ một cuộc gọi so với hai. Đây là cách hiệu quả nhất.

danh sách Initializer cũng là bắt buộc khi bạn

  1. có loại mà không có constructor mặc định. Bạn phải gọi hàm tạo đúng trong danh sách khởi tạo.

  2. có thành viên const mà bạn muốn đưa ra một số giá trị (chứ không phải chỉ có permantently giá trị mặc định

  3. có thành viên tham khảo. Bạn phải sử dụng danh sách khởi tạo bằng các.

tl; dr: làm điều đó bởi vì nó ít nhất là nhanh chóng nhưng không bao giờ chậm hơn so với cách khác, và đôi khi đến nay nhanh hơn.

Được xây dựng trong các loại như intchar, chúng thực sự không được xây dựng; họ chỉ có giá trị của bất kỳ bộ nhớ nào mà họ đã từng xảy ra trước đây.

+0

C++ không gán các giá trị mặc định cho nguyên thủy. Đó là lý do tại sao sử dụng các biến chưa được gán cho bạn một cảnh báo - đó là hành vi không xác định bởi vì nó chứa một giá trị ngẫu nhiên xảy ra trong khối bộ nhớ đó. –

+0

@Yochai bắt tốt, tôi quên đề cập đến điều đó. Đã thêm ghi chú. –

1

Vâng, đây là một câu hỏi thường gặp câu hỏi tiêu biểu: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6

Trong trường hợp của bạn, sử dụng charint không có sự khác biệt.

Nguyên tắc chung: sử dụng danh sách khởi tạo như possibile, có rất ít trường hợp khi bạn có thể thích giao, ví dụ đối với cải thiện khả năng đọc:

MyClass::MyClass 
{ 
    a = b = c = d = e = f = 0; 
} 

là tốt hơn so với

class MyClass::MyClass : a(0), b(0), c(0), d(0), e(0), f(0) { } 
5

Sự khác biệt là trình biên dịch sẽ luôn khởi tạo tất cả các thành viên (theo thứ tự khai báo) trước câu lệnh hàm tạo đầu tiên do người dùng định nghĩa. Trong trường hợp của charint, cả hai loại nguyên thủy, 'khởi tạo' thực sự có nghĩa là 'không khởi tạo' ở đây. Tuy nhiên, nếu bạn có một thành viên với một nhà xây dựng mà hiện một số công việc thực tế, constructor này sẽ được gọi là trả trước - nếu bạn làm

foo::foo() { 
    myComplexMember = MyComplexClass(42); 
} 

trình biên dịch đã đã gọi constructor mặc định MyComplexClass trước khi mã của bạn đã được gọi, đó là một sự lãng phí tài nguyên (và một lỗi trình biên dịch nếu ctor mặc định là không thể truy cập).

Bằng cách sử dụng danh sách khởi tạo, bạn có thể tùy chỉnh khởi tạo mặc định và tránh làm những việc không có gì. Rõ ràng, đây là con đường để đi.

5

Có những thứ bạn có thể làm như vậy mà bạn không thể làm được.

  1. Nếu một trong các thành viên không có hàm tạo mặc định. Đó là cách duy nhất bạn có thể khởi tạo thành viên khi xây dựng. (cùng với lớp cơ sở)

  2. Bạn có thể chỉ định giá trị cho thành viên const.

  3. Bạn có thể đảm bảo trạng thái đã xác định cho lớp trước khi hàm khởi tạo bắt đầu chạy.

  4. Nếu một thành viên là tham chiếu, nó cần phải được khởi tạo trong Danh sách khởi tạo. Bởi vì tài liệu tham khảo là không thay đổi và có thể được khởi tạo một lần duy nhất trong đầu (như const)

1

Nếu các thành viên đã có nhà xây dựng không tầm thường, trong đoạn code dưới đây đầu tiên các nhà thầu mặc định sẽ được gọi, thì nhiệm vụ sẽ là được thực thi, trong khi trong đoạn mã trên chúng sẽ được khởi tạo chỉ một lần. Vì vậy, có, có thể có một vấn đề hiệu suất.

Cũng có một vấn đề thực tế: nếu chúng là const, tham chiếu hoặc không có hàm tạo mặc định, bạn không thể sử dụng phiên bản bên dưới.

1

Có sự khác biệt tinh tế nhưng quan trọng giữa hai tùy chọn này. Những gì bạn có ở trên cùng được gọi là danh sách khởi tạo thành viên. Khi đối tượng được tạo, các thành viên trong danh sách này được khởi tạo cho bất kỳ thứ gì bạn đặt trong dấu ngoặc đơn.

Khi bạn làm phân trong cơ thể constructor, các giá trị là là đầu tiên khởi, và sau đó giao. Tôi sẽ đăng một ví dụ ngắn dưới đây.

Ví dụ 1: Thành viên khởi

class foo 
{ 
public: 
    foo(char c, int i); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i): 
    exampleChar(c), 
    exampleInt(i), 
    exampleBar()  //Here, a bar is being default constructed 
{ 
} 

Ví dụ 2: Constructor Phân

class foo 
{ 
public: 
    foo(char c, int i, Bar b); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i, Bar b): 
    //exampleChar(c), 
    //exampleInt(i), 
    //exampleBar() 
{ 
    exampleChar = c; 
    exampleInt = i; 
    exampleBar = someOtherBar; //Here, a bar is being assigned 
} 

Đây là nơi mà nó trở nên thú vị. Lưu ý rằng exampleBar đang được gán. Phía sau hậu trường, Bar thực sự là lần đầu tiên được xây dựng mặc định, mặc dù bạn không chỉ định điều đó. Hơn nữa, nếu Bar của bạn phức tạp hơn sau đó là một cấu trúc đơn giản, bạn sẽ cần phải chắc chắn thực hiện toán tử gán để bạn có thể khởi tạo nó theo cách này. Hơn nữa, để khởi tạo Bar từ một Bar khác từ danh sách khởi tạo thành viên, bạn phải triển khai hàm tạo bản sao!

Ví dụ 3: Sao chép constructor được sử dụng trong init viên

class foo 
{ 
public: 
    foo(char c, int i, Bar b); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i, Bar b): 
    //exampleChar(c), 
    //exampleInt(i), 
    exampleBar(b)  //Here, a bar is being constructed using the copy constructor of Bar 
{ 
    exampleChar = c; 
    exampleInt = i; 
} 
0

tôi sẽ nhận được vào thói quen sử dụng danh sách khởi động. Họ sẽ không bị các vấn đề khi ai đó thay đổi một char đối với một số đối tượng nơi hàm tạo mặc định được gọi đầu tiên, và cũng cho const đúng đắn cho các giá trị const!

0
foo::foo(char c, int i):exampleChar(c),exampleInt(i){} 

cấu trúc này được gọi là Member Initializer Danh sách trong C++.

khởi thành viên của bạn exampleChar đến một giá trị c & exampleInt-i.


sự khác biệt giữa Khởi tạo và phân công bên trong constructor là gì? &
Lợi thế là gì?

Có sự khác biệt giữa Khởi tạo thành viên bằng cách sử dụng danh sách trình khởi tạo và gán cho nó một giá trị bên trong phần thân của hàm tạo.

Khi bạn khởi tạo trường thông qua danh sách khởi tạo, các hàm tạo sẽ được gọi một lần.

Nếu bạn sử dụng nhiệm vụ thì trường sẽ được khởi tạo lần đầu với các hàm tạo mặc định và sau đó được gán lại (thông qua toán tử gán) với các giá trị thực.

Như bạn thấy có thêm chi phí cho việc tạo bài tập & sau này, điều này có thể đáng kể đối với các lớp do người dùng xác định.

Đối với loại dữ liệu nguyên (mà bạn sử dụng nó) hoặc thành viên nhóm POD không có phí thực tế.

Các vấn đề liên quan