Khởi tạo cấu trúc C ++


278

Có thể khởi tạo cấu trúc trong C ++ như được chỉ ra dưới đây không

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

Các liên kết ở đâyở đây đề cập rằng chỉ có thể sử dụng kiểu này trong C. Nếu vậy tại sao điều này không thể có trong C ++? Có bất kỳ lý do kỹ thuật cơ bản nào tại sao nó không được triển khai trong C ++, hoặc sử dụng phong cách này là không tốt. Tôi thích sử dụng cách khởi tạo này vì cấu trúc của tôi lớn và phong cách này cho tôi khả năng đọc rõ ràng về giá trị nào được gán cho thành viên nào.

Hãy chia sẻ với tôi nếu có những cách khác để chúng ta có thể đạt được khả năng đọc tương tự.

Tôi đã giới thiệu các liên kết sau trước khi đăng câu hỏi này

  1. C / C ++ cho AIX
  2. Khởi tạo cấu trúc C với biến
  3. Khởi tạo cấu trúc tĩnh với các thẻ trong C ++
  4. C ++ 11 Khởi tạo cấu trúc phù hợp

20
Quan điểm cá nhân về thế giới: bạn không cần kiểu khởi tạo đối tượng này trong C ++ vì thay vào đó bạn nên sử dụng một hàm tạo.
Philip Kendall

7
Vâng, tôi nghĩ về điều đó, nhưng tôi có một loạt các cấu trúc lớn. Nó sẽ dễ dàng và dễ đọc đối với tôi để sử dụng theo cách này. Bạn có bất kỳ phong cách / thực hành tốt về khởi tạo bằng cách sử dụng Trình xây dựng, điều này cũng giúp dễ đọc hơn.
Dinesh PR

2
Bạn đã xem xét thư viện tham số boost, kết hợp với các constructor chưa? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy

18
Không liên quan đến lập trình: địa chỉ này chỉ hoạt động tốt ở Mỹ. Ở Pháp, chúng tôi không có "tỉnh", ở những nơi khác trên thế giới, không có mã bưu chính, mẹ của một người bạn sống trong một ngôi làng nhỏ mà địa chỉ của cô ấy là "Bà X, mã bưu chính tên làng nhỏ "(vâng, không có đường phố). Vì vậy, hãy xem xét cẩn thận địa chỉ hợp lệ là gì đối với thị trường mà bạn sẽ áp dụng địa chỉ này;)
Matthieu M.

5
@MatthieuM. Không có tỉnh nào ở Hoa Kỳ (đây có thể là định dạng của Canada?), Nhưng có những tiểu bang, vùng lãnh thổ và thậm chí cả những ngôi làng nhỏ không bận tâm đến việc đặt tên đường phố. Vì vậy, vấn đề tuân thủ địa chỉ áp dụng ngay cả ở đây.
Tim

Câu trả lời:


165

Nếu bạn muốn làm rõ từng giá trị của trình khởi tạo là gì, chỉ cần chia nó thành nhiều dòng, với một nhận xét trên mỗi dòng:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};

7
Cá nhân tôi thích và giới thiệu phong cách này
Dinesh PR

29
Sự khác biệt giữa việc đó là gì và thực sự sử dụng ký hiệu dấu chấm để truy cập CHÍNH XÁC CHÍNH HÃNG lĩnh vực này, nó không giống như bạn đang tiết kiệm bất kỳ không gian nào nếu đó là điều đáng lo ngại. Tôi thực sự không có được lập trình viên C ++ khi nói đến việc nhất quán và viết mã có thể duy trì được, họ dường như luôn muốn làm điều gì đó khác biệt để làm cho mã của họ nổi bật, mã có nghĩa là phản ánh vấn đề đang được giải quyết. một thành ngữ của riêng mình, nhằm mục đích đáng tin cậy và dễ bảo trì.

5
@ user1043000, đối với một người, trong trường hợp này, thứ tự mà bạn đặt các thành viên của mình có tầm quan trọng cao nhất. Nếu bạn thêm một trường vào giữa cấu trúc của mình, bạn sẽ phải quay lại mã này và tìm vị trí chính xác để chèn khởi tạo mới, rất khó và nhàm chán. Với ký hiệu dấu chấm, bạn chỉ cần đặt phần khởi tạo mới của mình vào cuối danh sách mà không cần bận tâm đến thứ tự. Và ký hiệu dấu chấm sẽ an toàn hơn nếu bạn tình cờ thêm cùng loại (như char*) với một trong những thành viên khác ở trên hoặc bên dưới cấu trúc, vì không có nguy cơ hoán đổi chúng.
Gui13

6
bình luận của orip. Nếu định nghĩa cấu trúc dữ liệu bị thay đổi và không ai nghĩ sẽ tìm kiếm các khởi tạo hoặc không thể tìm thấy tất cả chúng, hoặc mắc lỗi chỉnh sửa chúng, mọi thứ sẽ sụp đổ.
Edward Falk

3
Hầu hết (nếu không phải tất cả) các cấu trúc POSIX không có thứ tự xác định, chỉ có các thành viên được xác định. (struct timeval){ .seconds = 0, .microseconds = 100 }sẽ luôn là một trăm micro giây, nhưng timeval { 0, 100 }có thể là một trăm GIÂY . Bạn không muốn tìm một cái gì đó như thế một cách khó khăn.
yyny

98

Sau khi câu hỏi của tôi không mang lại kết quả thỏa mãn (vì C ++ không triển khai init dựa trên thẻ cho các cấu trúc), tôi đã sử dụng mẹo mà tôi tìm thấy ở đây: Các thành viên của cấu trúc C ++ được khởi tạo thành 0 theo mặc định?

Đối với bạn, nó sẽ là số tiền để làm điều đó:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

Đây chắc chắn là gần nhất với những gì bạn muốn ban đầu (không có tất cả các trường trừ những trường bạn muốn khởi tạo).


10
Điều này không hoạt động đối với các đối tượng không định hướng tĩnh
user877329

5
static address temp_address = {};sẽ làm việc. Làm đầy nó sau đó là tùy thuộc vào thời gian chạy, vâng. Bạn có thể bỏ qua điều này bằng cách cung cấp một hàm tĩnh thực hiện init cho bạn : static address temp_address = init_my_temp_address();.
Gui13

Trong C ++ 11, init_my_temp_addresscó thể là hàm lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
Ý tưởng tồi, nó vi phạm nguyên tắc RAII.
hxpax

2
Ý tưởng thực sự tồi tệ: thêm một thành viên vào của bạn addressvà bạn sẽ không bao giờ biết về tất cả những nơi tạo ra addressvà bây giờ không khởi tạo thành viên mới của bạn.
myst_doctor

24

Như những người khác đã đề cập đây là chỉ định khởi tạo.

Tính năng này là một phần của C ++ 20


17

Các định danh trường thực sự là cú pháp khởi tạo C. Trong C ++, chỉ cần đưa ra các giá trị theo đúng thứ tự mà không cần tên trường. Thật không may, điều này có nghĩa là bạn cần cung cấp cho tất cả chúng (thực ra bạn có thể bỏ qua các trường có giá trị bằng 0 và kết quả sẽ giống nhau):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 

1
Có, bạn luôn có thể sử dụng khởi tạo cấu trúc liên kết.
texasbruce

3
Có, hiện tại tôi chỉ sử dụng phương pháp này (Sắp xếp cấu trúc khởi tạo). Nhưng tôi cảm thấy khả năng đọc là không tốt. Vì Cấu trúc của tôi lớn, trình khởi tạo có rất nhiều dữ liệu và tôi rất khó theo dõi giá trị nào được gán cho thành viên nào.
Dinesh PR

7
@ DineshP.R. Sau đó viết một constructor!
Ông Lister

2
@MrLister (hoặc bất cứ ai) Có lẽ tôi bị mắc kẹt trong đám mây ngu ngốc vào lúc này, nhưng quan tâm giải thích làm thế nào một nhà xây dựng sẽ tốt hơn nhiều? Dường như với tôi có một chút khác biệt giữa việc cung cấp một loạt các giá trị không tên phụ thuộc đơn hàng vào danh sách khởi tạo hoặc cung cấp một loạt các giá trị không tên phụ thuộc đơn hàng cho nhà xây dựng ...?
yano

1
@yano Thành thật mà nói, tôi không thực sự nhớ lại lý do tại sao tôi nghĩ rằng một nhà xây dựng sẽ là câu trả lời cho vấn đề. Nếu tôi nhớ, tôi sẽ quay lại với bạn.
Mr Lister

13

Tính năng này được gọi là khởi tạo được chỉ định . Nó là một bổ sung cho tiêu chuẩn C99. Tuy nhiên, tính năng này đã bị loại khỏi C ++ 11. Theo Ngôn ngữ lập trình C ++, ấn bản thứ 4, Mục 44.3.3.2 (Tính năng C không được thông qua bởi C ++):

Một vài bổ sung cho C99 (so với C89) đã không được chấp nhận trong C ++:

[1] Mảng có độ dài thay đổi (VLAs); sử dụng vector hoặc một số dạng của mảng động

[2] Chỉ định khởi tạo; sử dụng các nhà xây dựng

Ngữ pháp C99 có các trình khởi tạo được chỉ định [Xem ISO / IEC 9899: 2011, Dự thảo của Ủy ban N1570 - ngày 12 tháng 4 năm 2011]

6.7.9 Khởi tạo

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

Mặt khác, C ++ 11 không có bộ khởi tạo được chỉ định [Xem ISO / IEC 14882: 2011, Dự thảo của Ủy ban N3690 - ngày 15 tháng 5 năm 2013]

8,5 Khởi tạo

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Để đạt được hiệu quả tương tự, hãy sử dụng các hàm tạo hoặc danh sách khởi tạo:


9

Bạn chỉ có thể khởi tạo thông qua ctor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};

9
Đây chỉ là trường hợp nếu bạn kiểm soát định nghĩa của struct address. Ngoài ra, các loại POD thường cố ý không có hàm tạo và hàm hủy.
dùng4815162342

7

Bạn thậm chí có thể đóng gói giải pháp của Gui13 vào câu lệnh khởi tạo đơn:

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

Tuyên bố miễn trừ trách nhiệm: Tôi không đề xuất phong cách này


Điều này vẫn nguy hiểm vì nó cho phép bạn thêm một thành viên addressvà mã sẽ vẫn được biên dịch với một triệu địa điểm chỉ khởi tạo năm thành viên ban đầu. Phần tốt nhất của khởi tạo cấu trúc là bạn có thể có tất cả các thành viên constvà nó sẽ buộc bạn phải khởi tạo tất cả chúng
myst_doctor

7

Tôi biết câu hỏi này khá cũ, nhưng tôi đã tìm thấy một cách khác để khởi tạo, sử dụng constexpr và currying:

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

Phương pháp này cũng hoạt động cho các biến tĩnh toàn cầu và thậm chí cả các biến constexpr. Nhược điểm duy nhất là khả năng bảo trì kém: Mỗi khi thành viên khác phải được khởi tạo bằng phương pháp này, tất cả các phương thức khởi tạo thành viên phải được thay đổi.


1
Đây là mô hình xây dựng . Các phương thức thành viên có thể trả về một tham chiếu đến thuộc tính cần sửa đổi thay vì tạo một cấu trúc mới mỗi lần
phuclv

6

Tôi có thể đang thiếu một cái gì đó ở đây, tại sao không:

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}

Tôi đã biên dịch chương trình trên với trình biên dịch MinGW C ++ và trình biên dịch Arduino AVR C ++ và cả hai đều chạy như mong đợi. Lưu ý #include <cstdio>
run_the_race

4
@run_the_race, đây là về những gì mà tiêu chuẩn c ++ nói không phải là hành vi của một trình biên dịch cụ thể. Tuy nhiên, tính năng này có trong c ++ 20.
Jon

Điều này chỉ hoạt động nếu cấu trúc là POD. Vì vậy, nó sẽ ngừng biên dịch nếu bạn thêm một hàm tạo vào nó.
oromoiluig

5

Nó không được thực hiện trong C ++. (Ngoài ra, char*chuỗi? Tôi hy vọng không).

Thông thường nếu bạn có quá nhiều tham số thì đó là mùi mã khá nghiêm trọng. Nhưng thay vào đó, tại sao không chỉ đơn giản là khởi tạo giá trị cấu trúc và sau đó gán từng thành viên?


6
"(còn nữa, char*chuỗi? Tôi hy vọng là không)." - Vâng, đó là một ví dụ C.
Ed S.

Chúng ta không thể sử dụng char * trong C ++? Hiện tại tôi đang sử dụng nó và nó đang hoạt động (có thể tôi đang làm gì đó sai). Giả định của tôi là trình biên dịch sẽ tạo các chuỗi không đổi của "Hamilton" & "Ontario" và gán địa chỉ của chúng cho các thành viên cấu trúc. Thay vào đó, nó có đúng không khi sử dụng const char *?
Dinesh PR

8
Bạn có thể sử dụng char*nhưng const char*an toàn hơn nhiều loại và mọi người chỉ sử dụng std::stringvì nó đáng tin cậy hơn nhiều.
Cún con

Đồng ý. Khi tôi đọc "như được đề cập dưới đây", tôi cho rằng đó là một ví dụ được sao chép từ đâu đó.
Ed S.

2

Tôi tìm thấy cách làm này cho các biến toàn cục, không yêu cầu sửa đổi định nghĩa cấu trúc ban đầu:

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

sau đó khai báo biến của một kiểu mới được kế thừa từ kiểu cấu trúc ban đầu và sử dụng hàm tạo để khởi tạo các trường:

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

Không hoàn toàn thanh lịch như phong cách C mặc dù ...

Đối với một biến cục bộ, nó yêu cầu một bộ nhớ bổ sung (this, 0, sizeof (* this)) ở đầu hàm tạo, vì vậy rõ ràng nó không tệ hơn và câu trả lời của @ gui13 là phù hợp hơn.

(Lưu ý rằng 'temp_address' là một biến loại 'temp_address', tuy nhiên loại mới này kế thừa từ 'địa chỉ' và có thể được sử dụng ở mọi nơi nơi 'địa chỉ' được mong đợi, vì vậy nó ổn.)


1

Tôi đã phải đối mặt với một vấn đề tương tự ngày hôm nay, nơi tôi có một cấu trúc mà tôi muốn điền vào dữ liệu thử nghiệm sẽ được chuyển qua làm đối số cho hàm tôi đang kiểm tra. Tôi muốn có một vectơ của các cấu trúc này và đang tìm kiếm một phương thức một lớp để khởi tạo mỗi cấu trúc.

Tôi đã kết thúc với một hàm tạo trong cấu trúc, mà tôi tin rằng nó cũng được đề xuất trong một vài câu trả lời cho câu hỏi của bạn.

Có lẽ thực tế xấu khi có các đối số cho hàm tạo có cùng tên với các biến thành viên công khai, yêu cầu sử dụng thiscon trỏ. Ai đó có thể đề nghị chỉnh sửa nếu có cách tốt hơn.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

Mà tôi đã sử dụng trong hàm thử nghiệm của mình để gọi hàm đang được thử nghiệm với các đối số khác nhau như thế này:

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}

1

Có thể, nhưng chỉ khi cấu trúc bạn đang khởi tạo là cấu trúc POD (dữ liệu cũ đơn giản). Nó không thể chứa bất kỳ phương thức, hàm tạo hoặc thậm chí các giá trị mặc định.


1

Trong C ++, các bộ khởi tạo kiểu C đã được thay thế bởi các hàm tạo mà theo thời gian biên dịch có thể đảm bảo rằng chỉ các khởi tạo hợp lệ được thực hiện (tức là sau khi khởi tạo các thành viên đối tượng là nhất quán).

Đó là một thực hành tốt, nhưng đôi khi một khởi tạo trước là tiện dụng, như trong ví dụ của bạn. OOP giải quyết điều này bằng các lớp trừu tượng hoặc các mẫu thiết kế sáng tạo .

Theo tôi, sử dụng cách bảo mật này sẽ giết chết sự đơn giản và đôi khi sự đánh đổi bảo mật có thể quá tốn kém, vì mã đơn giản không cần thiết kế tinh vi để duy trì.

Là một giải pháp thay thế, tôi đề nghị xác định các macro sử dụng lambdas để đơn giản hóa việc khởi tạo để trông gần giống như kiểu C:

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

Macro ADDRESS mở rộng thành

[] { address _={}; /* definition... */ ; return _; }()

tạo ra và gọi lambda. Các tham số macro cũng được phân tách bằng dấu phẩy, vì vậy bạn cần đặt trình khởi tạo vào dấu ngoặc và gọi như

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

Bạn cũng có thể viết trình khởi tạo macro tổng quát

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

nhưng sau đó cuộc gọi hơi kém đẹp

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

tuy nhiên, bạn có thể xác định macro ADDRESS bằng cách sử dụng macro INIT chung một cách dễ dàng

#define ADDRESS(x) INIT(address,x)


0

Lấy cảm hứng từ câu trả lời thực sự gọn gàng này: ( https://stackoverflow.com/a/49572324/4808079 )

Bạn có thể đóng lamba:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

Hoặc, nếu bạn muốn rất lạ mắt

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

Có một số nhược điểm liên quan đến điều này, chủ yếu là phải làm với các thành viên chưa được khởi tạo. Từ những gì các câu trả lời được liên kết nhận xét, nó biên dịch hiệu quả, mặc dù tôi chưa kiểm tra nó.

Nhìn chung, tôi chỉ nghĩ rằng đó là một cách tiếp cận gọn gàng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.