Cách khởi tạo một cấu trúc theo tiêu chuẩn ngôn ngữ lập trình C


466

Tôi muốn khởi tạo một thành phần cấu trúc, phân tách trong khai báo và khởi tạo. Đây là những gì tôi có:

typedef struct MY_TYPE {
  bool flag;
  short int value;
  double stuff;
} MY_TYPE;

void function(void) {
  MY_TYPE a;
  ...
  a = { true, 15, 0.123 }
}

Đây có phải là cách khai báo và khởi tạo một biến cục bộ MY_TYPEtheo tiêu chuẩn ngôn ngữ lập trình C (C89, C90, C99, C11, v.v.) không? Hoặc có bất cứ điều gì tốt hơn hoặc ít nhất là làm việc?

Cập nhật Tôi đã kết thúc việc có một yếu tố khởi tạo tĩnh nơi tôi đặt mọi phân tầng theo nhu cầu của mình.


3
bạn thực sự nên chấp nhận một câu trả lời tốt hơn, tôi thấy bạn phải sử dụng một số hướng dẫn mã hóa tồi, nhưng bạn vẫn không nên đề xuất với người khác rằng đó là cách đúng đắn để làm điều đó ..
Karoly Horvath

@KarolyHorvath tốt, hầu hết các câu trả lời tốt đều dành riêng cho C99. Có lẽ câu hỏi của tôi là một bản sao của stackoverflow.com/questions/6624975/ trên ?
co rúm

nếu đó là ý định ban đầu của bạn, thì có lẽ là có, nhưng sau đó 1) số phiếu sẽ rất sai lệch. 2) từ lượt truy cập tìm kiếm hàng đầu, đây là lần duy nhất hiển thị theo cách của C99 ..... sẽ tốt hơn khi sử dụng lại trang này để trình diễn C99 ... (dường như mọi người bắt đầu liên kết trang này để hiển thị cách làm điều đó)
Karoly Horvath

10
Điều thú vị là câu trả lời được chấp nhận (và được đánh giá cao) không thực sự trả lời câu hỏi, ngay cả khi được đăng ban đầu. Công cụ khởi tạo được chỉ định không giải quyết vấn đề của OP, đó là tách phần khai báo khỏi phần khởi tạo. Đối với C trước 1999, giải pháp thực sự duy nhất là chỉ định cho từng thành viên; đối với C99 và sau đó, một nghĩa đen, như trong câu trả lời của CesarB, là giải pháp. (Tất nhiên một initializer thực tế, có hoặc không có định danh, sẽ tốt hơn, nhưng dường như OP đã gánh một tiêu chuẩn mã hóa thực sự tồi tệ.)
Keith Thompson

32
Nói một cách chính xác, thuật ngữ "ANSI C" hiện nay đề cập đến tiêu chuẩn ISO 2011, mà ANSI đã áp dụng. Nhưng trong thực tế, thuật ngữ "ANSI C" thường dùng để chỉ tiêu chuẩn 1989 (chính thức lỗi thời). Ví dụ: "gcc -ansi" vẫn thi hành tiêu chuẩn 1989. Vì ISO đã công bố các tiêu chuẩn 1990, 1999 và 2011, tốt nhất nên tránh thuật ngữ "ANSI C" và đề cập đến ngày của tiêu chuẩn nếu có bất kỳ khả năng nhầm lẫn nào.
Keith Thompson

Câu trả lời:


728

Trong (ANSI) C99, bạn có thể sử dụng trình khởi tạo được chỉ định để khởi tạo cấu trúc:

MY_TYPE a = { .flag = true, .value = 123, .stuff = 0.456 };

Chỉnh sửa: Các thành viên khác được khởi tạo là 0: "Các thành viên trường bị bỏ qua được khởi tạo ngầm giống như các đối tượng có thời lượng lưu trữ tĩnh." ( https://gcc.gnu.org/onlinesocs/gcc/Designated-Inits.html )


87
thật sự cảm ơn. bạn thậm chí có thể lồng em: static oxeRay2f ray = {.unitDir = {.x = 1.0f, .y = 0.0f}};
orion elenzil

4
Điều này không trả lời câu hỏi nào cả. OP muốn tách phần khởi tạo khỏi phần khai báo.
osvein

3
C99! = ANSI C - Vì vậy, điều này không hoạt động trong C89, cũng như trong C90.
Tim Wißmann

3
C99 ANSI C, xem này
Cutberto Ocampo

3
@CutbertoOcampo Tôi hiểu chuyện gì đã xảy ra bây giờ. Câu hỏi ban đầu là yêu cầu các giải pháp trong ANSI C, rõ ràng anh ta chỉ muốn câu trả lời cho C89. Vào năm 2016, câu hỏi đã được chỉnh sửa và "ANSI C" đã bị xóa, điều này khiến chúng ta khó hiểu tại sao câu trả lời và bình luận này lại đề cập đến "(ANSI) C99".
Étienne

198

Bạn có thể làm điều đó với một nghĩa đen . Theo trang đó, nó hoạt động trong C99 (cũng được tính là ANSI C ).

MY_TYPE a;

a = (MY_TYPE) { .flag = true, .value = 123, .stuff = 0.456 };
...
a = (MY_TYPE) { .value = 234, .stuff = 1.234, .flag = false };

Các chỉ định trong khởi tạo là tùy chọn; bạn cũng có thể viết:

a = (MY_TYPE) { true,  123, 0.456 };
...
a = (MY_TYPE) { false, 234, 1.234 };

Vì vậy, các công cụ khởi tạo được chỉ định là khi bạn khai báo và định nghĩa cùng một lúc, nhưng nghĩa đen là khi bạn xác định một biến đã được khai báo?
Geremia

@Geremia trước tiên bạn phải xác định cấu trúc trong cả hai trường hợp, nhưng với bộ khởi tạo được chỉ định, bạn làm cho mã dễ đọc hơn và dễ phục hồi hơn đối với các lỗi, trong trường hợp thay đổi cấu trúc.
hoijui

2
Đây là một chút khó khăn. Nhiều lần (một số thành công) Tôi đã cố gắng sử dụng các công cụ khởi tạo được chỉ định. Tuy nhiên, giờ đây đã trở nên rõ ràng (nhờ ví dụ của bạn và @ philant) rằng phải tồn tại một diễn viên nếu trình khởi tạo không được sử dụng tại thời điểm tạo đối tượng. Tuy nhiên, bây giờ tôi cũng đã học được rằng nếu các trình khởi tạo được sử dụng tại một thời điểm khác ngoài việc tạo đối tượng (như trong ví dụ của bạn) thì nó được gọi là một nghĩa đen , không hoàn toàn là một trình khởi tạo được chỉ định . ideone.com/Ze3rnZ
sherrellbc

93

Tôi thấy bạn đã nhận được câu trả lời về ANSI C 99, vì vậy tôi sẽ tiết lộ về ANSI C 89. ANSI C 89 cho phép bạn khởi tạo cấu trúc theo cách này:

typedef struct Item {
    int a;
    float b;
    char* name;
} Item;

int main(void) {
    Item item = { 5, 2.2, "George" };
    return 0;
}

Một điều quan trọng cần nhớ, tại thời điểm bạn khởi tạo ngay cả một đối tượng / biến trong cấu trúc, tất cả các biến khác của nó sẽ được khởi tạo thành giá trị mặc định.

Nếu bạn không khởi tạo các giá trị trong cấu trúc của mình, tất cả các biến sẽ chứa "giá trị rác".

Chúc may mắn!


3
Có thể sử dụng các biến trong khởi tạo?
Cyan

2
@Cyan Nó dành cho các đối tượng có thời gian lưu trữ tự động. Xem 6.7.9 13) trong open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf . Đối với các đối tượng toàn cầu là khá nhiều hạn chế cho chữ. Bạn thậm chí không thể sử dụng các đối tượng toàn cầu khác, ngay cả khi chúng là const.
PSkocik

Vì vậy, sự khác biệt duy nhất với C 99 là bạn không thể chỉ định các chỉ định?
Akaisteph7

42

a = (MYTYPE){ true, 15, 0.123 };

sẽ làm tốt trong C99


1
Tôi thành thật nghĩ rằng câu trả lời là hữu ích nhất, nhưng bất cứ điều gì.
dùng12211554

22

Bạn gần như đã có nó ...

MY_TYPE a = { true,15,0.123 };

Tìm kiếm nhanh trên 'struct khởi tạo c' cho tôi thấy điều này


1
Tôi nghĩ điều này đúng với tiêu chuẩn C, nhưng không đúng với ANSI C (99?). Ngoài ra, tôi bị ràng buộc với các quy tắc mã hóa sẽ không cho phép tôi thực hiện khai báo và khởi tạo cùng một lúc, vì vậy tôi phải tách nó ra và khởi tạo mỗi phân lớp.
co rúm

8
Khởi tạo chỉ có thể xảy ra tại điểm khai báo. Đó là ý nghĩa của việc 'khởi tạo'. Mặt khác, bạn đang cho phép một giá trị không xác định là giá trị ban đầu của biến / struct của bạn và sau đó gán giá trị sau.
Kieveli

8
um ... Bạn có quy tắc mã hóa là cố ý xấu? Có lẽ bạn nên giới thiệu anh chàng đã viết cho họ "Khởi tạo tài nguyên" ( en.wikipedia.org/wiki/RAII )
James Curran

Tôi tin tưởng tôi, tôi thực sự, thực sự, không thể thay đổi một chút các quy tắc này vào thời điểm này. Nó cảm thấy khủng khiếp để mã hóa nó. Và có rất nhiều quy tắc khác ngay từ những năm 70, như các tiêu đề mã tĩnh với thông tin quản lý như số Sửa đổi. Thay đổi cho mỗi bản phát hành, ngay cả khi nguồn không thay đổi ...
co rúm

19

Tiêu chuẩn ngôn ngữ lập trình C ISO / IEC 9899: 1999 (thường được gọi là C99) cho phép một người sử dụng bộ khởi tạo được chỉ định để khởi tạo các thành viên của cấu trúc hoặc liên kết như sau:

MY_TYPE a = { .stuff = 0.456, .flag = true, .value = 123 };

Nó được định nghĩa trong paragraph 7, phần 6.7.8 Initializationcủa tiêu chuẩn ISO / IEC 9899: 1999 như:

Nếu một người chỉ định có hình thức
. định danh
sau đó đối tượng hiện tại (được xác định bên dưới) sẽ có cấu trúc hoặc loại kết hợp và định danh sẽ là tên của một thành viên của loại đó.

Lưu ý rằng paragraph 9cùng một phần nói rằng:

Trừ khi có quy định rõ ràng khác, cho các mục đích của phần này, các thành viên không tên của các đối tượng có cấu trúc và loại kết hợp không tham gia khởi tạo. Các thành viên không tên của các đối tượng cấu trúc có giá trị không xác định ngay cả sau khi khởi tạo.

Tuy nhiên, trong triển khai GNU GCC, các thành viên bị bỏ qua được khởi tạo là giá trị phù hợp với kiểu 0 hoặc 0. Như đã nêu trong phần 6.27 Công cụ khởi tạo được chỉ định của tài liệu GNU GCC:

Các thành viên trường bị bỏ qua được khởi tạo ngầm giống như các đối tượng có thời lượng lưu trữ tĩnh.

Trình biên dịch Microsoft Visual C ++ nên hỗ trợ các trình khởi tạo được chỉ định kể từ phiên bản 2013 theo bài đăng trên blog chính thức C ++ Conformance Roadmap . Đoạn văn Initializing unions and structscủa bài viết Khởi tạo tại tài liệu MSDN Visual Studio cho thấy rằng các thành viên chưa được đặt tên được khởi tạo thành các giá trị phù hợp giống như không tương tự như GNU GCC.

Tiêu chuẩn ISO / IEC 9899: 2011 (thường được gọi là C11) đã thay thế ISO / IEC 9899: 1999 giữ lại các bộ khởi tạo được chỉ định trong phần 6.7.9 Initialization. Nó cũng giữ lại paragraph 9không thay đổi.

Mới ISO / IEC 9899: 2018 tiêu chuẩn (thường được gọi là C18) mà đã thay thế ISO / IEC 9899: 2011 vẫn nắm được initializers dưới phần 6.7.9 Initialization. Nó cũng giữ lại paragraph 9không thay đổi.


3
Ban đầu tôi đề nghị đây là chỉnh sửa câu trả lời được chấp nhận, nhưng nó đã bị từ chối. Vì vậy, tôi gửi nó như là một câu trả lời.
PF4Public

Tôi tin rằng "Thành viên chưa được đặt tên" không có nghĩa là "các trường bị bỏ qua", mà là "các thành viên ẩn danh", chẳng hạn như liên minh struct thing { union { char ch; int i; }; };. Tiêu chuẩn sau này nói: "Nếu có ít bộ khởi tạo trong danh sách được đóng dấu ngoặc hơn có các phần tử hoặc thành viên của tổng hợp, hoặc ít ký tự trong một chuỗi ký tự được sử dụng để khởi tạo một mảng có kích thước đã biết so với các phần tử trong mảng, phần còn lại của tổng hợp sẽ được khởi tạo hoàn toàn giống như các đối tượng có thời lượng lưu trữ tĩnh. "
nhấn chìm

14

như Ron Nuni đã nói:

typedef struct Item {
    int a;
    float b;
    char* name;
} Item;

int main(void) {
    Item item = {5, 2.2, "George"};
    return 0;
}

Một điều quan trọng cần nhớ: tại thời điểm bạn khởi tạo ngay cả một đối tượng / biến trong cấu trúc, tất cả các biến khác của nó sẽ được khởi tạo thành giá trị mặc định.

Nếu bạn không khởi tạo các giá trị trong cấu trúc của mình (nghĩa là nếu bạn chỉ khai báo biến đó), tất cả variable.memberssẽ chứa "giá trị rác", chỉ khi khai báo là cục bộ!

Nếu khai báo là toàn cục hoặc tĩnh (như trong trường hợp này), tất cả chưa variable.membersđược khởi tạo sẽ được tự động khởi tạo thành:

  • 0 cho số nguyên và dấu phẩy động
  • '\0'cho char(tất nhiên điều này giống như 0charlà một kiểu số nguyên)
  • NULL cho con trỏ.

Thú vị về địa phương / toàn cầu, điều này cũng áp dụng cho các giá trị thời gian tm struct. Tôi đã có một địa phương và trong một trường hợp DST được bật, trong trường hợp khác thì không, không phải do bất cứ điều gì tôi đã làm. Tôi đã không sử dụng DST (trường tm_ thẩmst), đây là từ thời gian, nhưng khi tôi chuyển đổi sang time_t thì một số giờ đã hết.
Alan Corey

3
void function(void) {
  MY_TYPE a;
  a.flag = true;
  a.value = 15;
  a.stuff = 0.123;
}

24
một khi tôi đã nghe 'khởi tạo khác với chuyển nhượng'. Vì vậy, đây không phải là một nhiệm vụ hơn là khởi tạo?
Hayri Uğur Koltuk

2

Nếu MS chưa cập nhật lên C99, MY_TYPE a = {true, 15,0.123};


2

Tôi tìm thấy một cách khác để khởi tạo cấu trúc.

Cấu trúc:

typedef struct test {
    int num;
    char* str;
} test;

Khởi tạo:

test tt = {
    num: 42,
    str: "nice"
};

Theo tài liệu của GCC, cú pháp này đã lỗi thời kể từ GCC 2.5 .


2

Tôi không thích bất kỳ câu trả lời nào trong số này vì vậy tôi tự đưa ra. Tôi không biết đây có phải là ANSI C hay không, đó chỉ là GCC 4.2.1 ở chế độ mặc định. Tôi không bao giờ có thể nhớ dấu ngoặc đơn vì vậy tôi bắt đầu với một tập hợp con dữ liệu của mình và chiến đấu với các thông báo lỗi trình biên dịch cho đến khi nó tắt. Khả năng đọc là ưu tiên hàng đầu của tôi.

    // in a header:
    typedef unsigned char uchar;

    struct fields {
      uchar num;
      uchar lbl[35];
    };

    // in an actual c file (I have 2 in this case)
    struct fields labels[] = {
      {0,"Package"},
      {1,"Version"},
      {2,"Apport"},
      {3,"Architecture"},
      {4,"Bugs"},
      {5,"Description-md5"},
      {6,"Essential"},
      {7,"Filename"},
      {8,"Ghc-Package"},
      {9,"Gstreamer-Version"},
      {10,"Homepage"},
      {11,"Installed-Size"},
      {12,"MD5sum"},
      {13,"Maintainer"},
      {14,"Modaliases"},
      {15,"Multi-Arch"},
      {16,"Npp-Description"},
      {17,"Npp-File"},
      {18,"Npp-Name"},
      {19,"Origin"}
    };

Dữ liệu có thể bắt đầu cuộc sống dưới dạng một tệp được phân định bằng tab mà bạn tìm kiếm - thay thế để xoa bóp vào một thứ khác. Vâng, đây là công cụ Debian. Vì vậy, một cặp bên ngoài {} (chỉ ra mảng), sau đó một cặp khác cho mỗi cấu trúc bên trong. Với dấu phẩy giữa. Đặt mọi thứ vào một tiêu đề không thực sự cần thiết, nhưng tôi đã có khoảng 50 mục trong cấu trúc của mình vì vậy tôi muốn chúng trong một tệp riêng biệt, cả hai để tránh sự lộn xộn trong mã của tôi và do đó dễ dàng thay thế hơn.


3
Không có câu hỏi về khởi tạo mảng cấu trúc! Ý tôi là, câu trả lời của bạn chứa chi phí.
Victor Signaevskyi

Tôi đoán quan điểm của tôi là khai báo struct với các giá trị đã có trong đó so với việc sử dụng = để đặt từng giá trị sau này.
Alan Corey

Nguyên tắc đó áp dụng cho dù đó là một cấu trúc hay một mảng của chúng. Sử dụng = không thực sự khởi tạo, tôi sẽ không nghĩ.
Alan Corey

0

Cấu trúc trong C có thể được khai báo và khởi tạo như thế này:

typedef struct book
{
    char title[10];
    char author[10];
    float price;
} book;

int main() {
    book b1={"DS", "Ajay", 250.0};

    printf("%s \t %s \t %f", b1.title, b1.author, b1.price);

    return 0;
}

0

Tôi đã đọc Tài liệu Microsoft Visual Studio 2015 để khởi tạo các loại tổng hợp , tất cả các hình thức khởi tạo {...}đều được giải thích ở đó, nhưng việc khởi tạo bằng dấu chấm, có tên '' người chỉ định '' không được đề cập ở đó. Nó cũng không hoạt động.

Tiêu chuẩn C99 chương 6.7.8 Khởi tạo giải thích khả năng của người chỉ định, nhưng trong tâm trí tôi không thực sự rõ ràng đối với các cấu trúc phức tạp. Các tiêu chuẩn C99 như pdf .

Trong tâm trí của tôi, nó có thể tốt hơn để

  1. Sử dụng = {0};-initialization cho tất cả dữ liệu tĩnh. Đó là ít nỗ lực cho mã máy.
  2. Sử dụng macro để khởi tạo, ví dụ

    typedef MyStruct_t{ int x, int a, int b; } MyStruct; define INIT_MyStruct(A,B) { 0, A, B}

Macro có thể được điều chỉnh, danh sách đối số của nó có thể độc lập với nội dung cấu trúc thay đổi. Nó là thích hợp nếu ít yếu tố nên được khởi tạo. Nó cũng thích hợp cho cấu trúc lồng nhau. 3. Một hình thức đơn giản là: Khởi tạo trong chương trình con:

void init_MyStruct(MyStruct* thiz, int a, int b) {
  thiz->a = a; thiz->b = b; }

Thường trình này trông giống như ObjectOrients trong C. Sử dụng thiz, không thisbiên dịch nó với C ++!

MyStruct data = {0}; //all is zero!
init_MyStruct(&data, 3, 456);

0

Tôi đã tìm kiếm một cách hay để khởi tạo cấu trúc của mình và tôi đã sử dụng cách dưới đây (C99). Điều này cho phép tôi khởi tạo một cấu trúc đơn hoặc một mảng các cấu trúc giống như các kiểu đơn giản.

typedef struct {
    char *str;
    size_t len;
    jsmntok_t *tok;
    int tsz;
} jsmn_ts;

#define jsmn_ts_default (jsmn_ts){NULL, 0, NULL, 0}

Điều này có thể được sử dụng trong mã như:

jsmn_ts mydata = jsmn_ts_default; /* initialization of a single struct */

jsmn_ts myarray[10] = {jsmn_ts_default, jsmn_ts_default}; /* initialization of
                                                    first 2 structs in the array */

Nhưng điều này không khởi tạo một mảng các cấu trúc thành các giá trị mặc định. Nó khởi tạo cấu trúc đầu tiên trong mảng thành các giá trị được đưa vào jsmn_ts_default, nhưng phần còn lại của các cấu trúc được khởi tạo như thể mảng có thời gian lưu trữ tĩnh, có nghĩa là các thành viên được khởi tạo NULL0. Nhưng nếu bạn thay đổi thành #define jsmn_ts_default (jsmn_ts){"default", sizeof "default", NULL, 0}bạn sẽ thấy rằng chỉ phần tử mảng đầu tiên được khởi tạo như vậy.
ex nihilo

Có, tôi vừa trở lại sau khi thử nghiệm nhiều hơn, muốn chỉnh sửa bài đăng :) btw, hành vi tương tự xảy ra với int a[10] = {1};Chỉ phần tử đầu tiên sẽ là==1
div0man
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.