Làm thế nào để khai báo một cấu trúc trong tiêu đề được sử dụng bởi nhiều tệp trong c?


115

Nếu tôi có tệp source.c có cấu trúc:

struct a { 
    int i;
    struct b {
        int j;
    }
};

Làm thế nào để cấu trúc này được sử dụng trong một tệp khác (tức là func.c)?

Tôi có nên tạo một tệp tiêu đề mới, khai báo cấu trúc ở đó và đưa tiêu đề đó vào func.ckhông?

Hay tôi nên xác định toàn bộ cấu trúc trong tệp tiêu đề và đưa nó vào cả source.cfunc.c? Làm cách nào để khai báo cấu trúc externtrong cả hai tệp?

Tôi có nên typedefkhông? Nếu vậy, làm thế nào?


Lưu ý rằng định nghĩa cấu trúc không hợp lệ C. Tối thiểu phải có dấu chấm phẩy sau dấu ngoặc nhọn cho struct b, nhưng khi đó cấu trúc của bạn akhai báo một kiểu không được sử dụng (có lẽ bạn nên xác định tên thành viên ksau dấu ngoặc nhọn bên trong và trước dấu chấm phẩy.
Jonathan Leffler

Câu trả lời:


140

nếu cấu trúc này được sử dụng bởi một số tệp func.c khác thì làm thế nào để làm điều đó?

Khi một kiểu được sử dụng trong một tệp (tức là tệp func.c), thì nó phải hiển thị. Cách tồi tệ nhất để làm điều đó là sao chép, dán nó vào mỗi tệp nguồn cần nó.

Cách đúng là đặt nó vào một tệp tiêu đề và bao gồm tệp tiêu đề này bất cứ khi nào cần.

chúng ta sẽ mở một tệp tiêu đề mới và khai báo cấu trúc ở đó và đưa tiêu đề đó vào func.c?

Đây là giải pháp tôi thích hơn, vì nó làm cho mã có tính mô-đun cao. Tôi sẽ viết mã cấu trúc của bạn là:

#ifndef SOME_HEADER_GUARD_WITH_UNIQUE_NAME
#define SOME_HEADER_GUARD_WITH_UNIQUE_NAME

struct a
{ 
    int i;
    struct b
    {
        int j;
    }
};

#endif

Tôi sẽ đặt các hàm sử dụng cấu trúc này trong cùng một tiêu đề (hàm là một phần "ngữ nghĩa" của "giao diện" của nó).

Và thông thường, tôi có thể đặt tên tệp sau tên cấu trúc và sử dụng lại tên đó để chọn định nghĩa bảo vệ tiêu đề.

Nếu bạn cần khai báo một hàm bằng cách sử dụng con trỏ đến cấu trúc, bạn sẽ không cần định nghĩa cấu trúc đầy đủ. Một khai báo chuyển tiếp đơn giản như:

struct a ;

Sẽ là đủ, và nó giảm khớp nối.

hoặc chúng ta có thể xác định cấu trúc tổng trong tệp tiêu đề và bao gồm cấu trúc đó trong cả source.c và func.c không?

Đây là một cách khác, dễ dàng hơn một chút, nhưng ít mô-đun hơn: Một số mã chỉ cần cấu trúc của bạn để hoạt động sẽ vẫn phải bao gồm tất cả các loại.

Trong C ++, điều này có thể dẫn đến sự phức tạp thú vị, nhưng điều này nằm ngoài chủ đề (không có thẻ C ++), vì vậy tôi sẽ không giải thích.

thì làm thế nào để khai báo cấu trúc đó là extern trong cả hai tệp. ?

Có lẽ tôi không hiểu được vấn đề, nhưng Greg Hewgill đã có một câu trả lời rất hay trong bài đăng của mình Làm thế nào để khai báo một cấu trúc trong tiêu đề được sử dụng bởi nhiều tệp trong c? .

chúng ta sẽ đánh máy nó như thế nào?

  • Nếu bạn đang sử dụng C ++ thì không.
  • Nếu bạn đang sử dụng C, bạn nên.

Lý do là việc quản lý struct C có thể là một vấn đề khó khăn: Bạn phải khai báo từ khóa struct ở mọi nơi nó được sử dụng:

struct MyStruct ; /* Forward declaration */

struct MyStruct
{
   /* etc. */
} ;

void doSomething(struct MyStruct * p) /* parameter */
{
   struct MyStruct a ; /* variable */
   /* etc */
}

Mặc dù typedef sẽ cho phép bạn viết nó mà không cần từ khóa struct.

struct MyStructTag ; /* Forward declaration */

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

void doSomething(MyStruct * p) /* parameter */
{
   MyStruct a ; /* variable */
   /* etc */
}

Điều quan trọng là bạn vẫn giữ tên cho cấu trúc. Viết:

typedef struct
{
   /* etc. */
} MyStruct ;

sẽ chỉ tạo một cấu trúc ẩn danh với tên được đánh máy và bạn sẽ không thể khai báo nó. Vì vậy, hãy giữ nguyên định dạng sau:

typedef struct MyStructTag
{
   /* etc. */
} MyStruct ;

Do đó, bạn sẽ có thể sử dụng MyStruct ở mọi nơi bạn muốn để tránh thêm từ khóa struct và vẫn sử dụng MyStructTag khi typedef không hoạt động (tức là khai báo chuyển tiếp)

Biên tập:

Đã sửa chữa giả định sai về khai báo cấu trúc C99, như nhận xét đúng của Jonathan Leffler .

Chỉnh sửa 2018-06-01:

Craig Barnes nhắc chúng ta trong nhận xét của anh ấy rằng bạn không cần phải giữ các tên riêng biệt cho tên "thẻ" struct và tên "typedef" của nó, giống như tôi đã làm ở trên để rõ ràng hơn.

Thật vậy, đoạn mã trên cũng có thể được viết là:

typedef struct MyStruct
{
   /* etc. */
} MyStruct ;

IIRC, đây thực sự là những gì C ++ thực hiện với khai báo cấu trúc đơn giản hơn của nó, đằng sau hậu trường, để giữ cho nó tương thích với C:

// C++ explicit declaration by the user
struct MyStruct
{
   /* etc. */
} ;
// C++ standard then implicitly adds the following line
typedef MyStruct MyStruct;

Quay lại C, tôi đã thấy cả hai cách sử dụng (tên riêng và tên giống nhau) và không có nhược điểm nào mà tôi biết, vì vậy việc sử dụng cùng một tên sẽ giúp việc đọc đơn giản hơn nếu bạn không sử dụng C riêng biệt "không gian tên" cho cấu trúc và các ký hiệu khác .


2
Bạn có thể nhận xét hoặc chỉ ra phần của tiêu chuẩn C99 đảm bảo cho nhận xét "Nếu bạn đang sử dụng C99, thì không" của bạn?
Jonathan Leffler

Bạn đúng. Gần đây tôi đã thử nghiệm một C99 và rất ngạc nhiên khi thấy rằng cấu trúc không định dạng của tôi không được nhận dạng khi sử dụng cách C ++. Tôi đã tìm kiếm các tùy chọn trình biên dịch và sau đó, trong tất cả các tài liệu tiêu chuẩn, tôi có thể đặt tay vào, nhưng không tìm thấy gì có thể giải thích được, tôi tin là có thể ...
paercebal

2
... Vì vậy, dù sao, cảm ơn vì đã nhận xét. Tôi đã sửa nó ngay bây giờ.
paercebal

Không cần sử dụng các tên khác nhau cho structthẻ và typedeftên. C sử dụng một không gian tên khác cho structcác thẻ, vì vậy bạn có thể sử dụng MyStructcho cả hai.
Craig Barnes,

1
@CraigBarnes Bạn nói đúng, nhưng tôi muốn điều đó rõ ràng chỉ bằng cách đọc mã. Nếu tôi được cùng tên, nó có thể có người mới C bối rối về sự cần thiết phải viết tên * hai lần" trong cùng một 'tuyên bố' Tôi sẽ bổ sung thêm một lưu ý nhắc đến bình luận của bạn Cảm ơn..!
paercebal

34

Đối với một định nghĩa cấu trúc được sử dụng trên nhiều tệp nguồn, bạn chắc chắn nên đặt nó trong tệp tiêu đề. Sau đó đưa tệp tiêu đề đó vào bất kỳ tệp nguồn nào cần cấu trúc.

Các externtuyên bố không được sử dụng cho các định nghĩa cấu trúc, nhưng thay vì sử dụng cho khai báo biến (có nghĩa là, một số giá trị dữ liệu với một kiểu cấu trúc mà bạn đã xác định). Nếu bạn muốn sử dụng cùng một biến trên nhiều tệp nguồn, hãy khai báo biến đó như externtrong tệp tiêu đề như:

extern struct a myAValue;

Sau đó, trong một tệp nguồn, hãy xác định biến thực tế:

struct a myAValue;

Nếu bạn quên làm điều này hoặc vô tình xác định nó trong hai tệp nguồn, trình liên kết sẽ cho bạn biết về điều này.


Bạn có thể gặp lỗi trình liên kết hoặc có thể không. Trong C, mô hình liên kết cho phép các định nghĩa 'chung', vì vậy nhiều định nghĩa không có bộ khởi tạo (và có thể có cùng bộ khởi tạo) có thể hoạt động. Nó là một 'phần mở rộng chung'. Tôi tin rằng một số trình biên dịch cũng hỗ trợ các định nghĩa dự kiến ​​(còn thiếu).
Jonathan Leffler 23/10/08

Sau đó, trong một tệp nguồn, hãy xác định biến thực tế:; ... hoặc vô tình xác định nó trong hai tệp nguồn ... :)
Johannes Schaub - litb

Một kỹ thuật mà tôi đã sử dụng cho vấn đề khai báo / định nghĩa là xác định có điều kiện GLOBALexternhoặc không có gì ở đầu tiêu đề và sau đó khai báo các biến là GLOBAL struct a myAValue;. Từ hầu hết các tệp nguồn, bạn sắp xếp #define GLOBAL externphiên bản sẽ được sử dụng ( khai báo các biến) và từ chính xác một tệp nguồn, nó khiến định nghĩa trống được sử dụng để các biến được xác định .
TripeHound

Bạn có thể có tên cấu trúc giống như tên typedef trong C, nhưng không phải trong C ++.
xuhdev

6

Ah:

#ifndef A_H
#define A_H

struct a { 
    int i;
    struct b {
        int j;
    }
};

#endif

đó bạn, bây giờ bạn chỉ cần thêm ah vào các tệp mà bạn muốn sử dụng cấu trúc này.

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.