Sự khác biệt giữa 'struct' và 'typedef struct' trong C ++?


Câu trả lời:


1202

Trong C ++, chỉ có một sự khác biệt tinh tế. Đó là một sự tiếp quản từ C, trong đó nó tạo ra sự khác biệt.

Tiêu chuẩn ngôn ngữ C ( C89 §3.1.2.3 , C99 §6.2.3C11 §6.2.3 ) bắt buộc các không gian tên riêng biệt cho các loại định danh khác nhau, bao gồm cả định danh thẻ (cho struct/ union/ enum) và số nhận dạng thông thường (cho typedefvà các định danh khác) .

Nếu bạn chỉ nói:

struct Foo { ... };
Foo x;

bạn sẽ gặp lỗi trình biên dịch, vì Foochỉ được xác định trong không gian tên thẻ.

Bạn sẽ phải khai báo nó như sau:

struct Foo x;

Bất cứ khi nào bạn muốn đề cập đến a Foo, bạn luôn phải gọi nó là a struct Foo. Điều này gây khó chịu nhanh chóng, vì vậy bạn có thể thêm typedef:

struct Foo { ... };
typedef struct Foo Foo;

Bây giờ struct Foo(trong không gian tên thẻ) và chỉ đơn giản Foo(trong không gian tên định danh thông thường) cả hai đều đề cập đến cùng một điều và bạn có thể tự do khai báo các đối tượng loại Foomà không cần structtừ khóa.


Xay dung:

typedef struct Foo { ... } Foo;

chỉ là viết tắt cho khai báo và typedef.


Cuối cùng,

typedef struct { ... } Foo;

tuyên bố một cấu trúc ẩn danh và tạo ra typedefcho nó. Do đó, với cấu trúc này, nó không có tên trong không gian tên thẻ, chỉ có một tên trong không gian tên typedef. Điều này có nghĩa là nó cũng không thể được tuyên bố trước. Nếu bạn muốn khai báo chuyển tiếp, bạn phải đặt tên cho nó trong không gian tên thẻ .


Trong C ++, tất cả struct/ union/ enum/ classkhai báo hoạt động giống như chúng được ngầm định typedef, miễn là tên đó không bị ẩn bởi một khai báo khác có cùng tên. Xem câu trả lời của Michael Burr để biết chi tiết đầy đủ.


53
Trong khi những gì bạn nói là đúng, AFAIK, tuyên bố, 'typedef struct {...} Foo;' tạo một bí danh cho một cấu trúc không tên.
dirkgently

25
Bắt tốt, có một sự khác biệt tinh tế giữa "typedef struct Foo {...} Foo;" và "typedef struct {...} Foo;".
Adam Rosenfield 04/03/2016

8
Trong C, các thẻ struct, thẻ union và thẻ liệt kê chia sẻ một không gian tên, thay vì (struct và union) sử dụng hai không gian như đã nêu ở trên; không gian tên được tham chiếu cho tên typedef thực sự là riêng biệt. Điều đó có nghĩa là bạn không thể có cả 'union x {...};' và 'struct x {...};' trong một phạm vi duy nhất.
Jonathan Leffler

10
Ngoài điều không hoàn toàn typedef, một điểm khác biệt giữa hai đoạn mã trong câu hỏi là Foo có thể định nghĩa hàm tạo trong ví dụ đầu tiên, nhưng không phải trong trường hợp thứ hai (vì các lớp ẩn danh không thể định nghĩa hàm tạo hoặc hàm hủy) .
Steve Jessop

1
@Lazer: Có những khác biệt tinh tế, nhưng Adam có nghĩa (như anh ấy nói) rằng bạn có thể sử dụng 'Type var' để khai báo các biến mà không cần typedef.
Fred Nurk

226

Trong bài viết DDJ này , Dan Saks giải thích một khu vực nhỏ nơi các con bọ có thể chui qua nếu bạn không gõ các cấu trúc của bạn (và các lớp!):

Nếu bạn muốn, bạn có thể tưởng tượng rằng C ++ tạo ra một typedef cho mỗi tên thẻ, chẳng hạn như

typedef class string string;

Thật không may, điều này không hoàn toàn chính xác. Tôi ước nó đơn giản, nhưng không phải. C ++ không thể tạo ra các typedefs như vậy cho các cấu trúc, công đoàn hoặc enum mà không đưa ra sự không tương thích với C.

Ví dụ: giả sử một chương trình C khai báo cả hàm và trạng thái có tên cấu trúc:

int status(); struct status;

Một lần nữa, đây có thể là thực tiễn xấu, nhưng đó là C. Trong chương trình này, trạng thái (tự nó) đề cập đến chức năng; trạng thái cấu trúc đề cập đến các loại.

Nếu C ++ tự động tạo typedefs cho các thẻ, thì khi bạn biên dịch chương trình này thành C ++, trình biên dịch sẽ tạo:

typedef struct status status;

Thật không may, tên loại này sẽ xung đột với tên hàm và chương trình sẽ không biên dịch. Đó là lý do tại sao C ++ không thể tạo ra một typedef cho mỗi thẻ.

Trong C ++, các thẻ hoạt động giống như tên typedef, ngoại trừ việc một chương trình có thể khai báo một đối tượng, hàm hoặc liệt kê có cùng tên và cùng phạm vi với thẻ. Trong trường hợp đó, tên đối tượng, hàm hoặc tên liệt kê ẩn tên thẻ. Chương trình chỉ có thể đề cập đến tên thẻ bằng cách sử dụng lớp từ khóa, struct, union hoặc enum (nếu thích hợp) trước tên thẻ. Một tên loại bao gồm một trong những từ khóa được theo sau bởi một thẻ là một công cụ xác định kiểu công phu. Ví dụ, trạng thái cấu trúc và tháng enum là các chỉ định kiểu xây dựng.

Do đó, một chương trình C chứa cả hai:

int status(); struct status;

hành xử tương tự khi được biên dịch là C ++. Tình trạng tên một mình đề cập đến chức năng. Chương trình chỉ có thể tham chiếu loại bằng cách sử dụng trạng thái cấu trúc chỉ định kiểu công phu.

Vì vậy, làm thế nào điều này cho phép lỗi để len ​​vào các chương trình? Hãy xem xét chương trình trong Liệt kê 1 . Chương trình này định nghĩa một lớp foo với hàm tạo mặc định và toán tử chuyển đổi chuyển đổi một đối tượng foo thành char const *. Cách diễn đạt

p = foo();

trong main nên xây dựng một đối tượng foo và áp dụng toán tử chuyển đổi. Báo cáo đầu ra tiếp theo

cout << p << '\n';

sẽ hiển thị lớp foo, nhưng nó không. Nó hiển thị chức năng foo.

Kết quả đáng ngạc nhiên này xảy ra vì chương trình bao gồm tiêu đề lib.h được hiển thị trong Liệt kê 2 . Tiêu đề này xác định một chức năng cũng được đặt tên là foo. Tên hàm foo ẩn tên lớp foo, vì vậy tham chiếu đến foo trong chính đề cập đến hàm chứ không phải lớp. chính chỉ có thể tham chiếu đến lớp bằng cách sử dụng một công cụ xác định kiểu công phu, như trong

p = class foo();

Cách để tránh sự nhầm lẫn như vậy trong suốt chương trình là thêm typedef sau cho tên lớp foo:

typedef class foo foo;

ngay trước hoặc sau khi định nghĩa lớp. Typedef này gây ra xung đột giữa tên loại foo và tên hàm foo (từ thư viện) sẽ gây ra lỗi thời gian biên dịch.

Tôi biết không có ai thực sự viết những typedefs này như một vấn đề tất nhiên. Nó đòi hỏi rất nhiều kỷ luật. Vì tỷ lệ mắc các lỗi như trong Liệt kê 1 có lẽ là khá nhỏ, nên nhiều người không bao giờ gặp phải vấn đề này. Nhưng nếu một lỗi trong phần mềm của bạn có thể gây thương tích cho cơ thể, thì bạn nên viết các typedefs cho dù lỗi có thể xảy ra như thế nào.

Tôi không thể tưởng tượng được tại sao bất cứ ai cũng muốn ẩn tên lớp bằng một hàm hoặc tên đối tượng trong cùng phạm vi với lớp. Các quy tắc ẩn trong C là một sai lầm và chúng không nên được mở rộng cho các lớp trong C ++. Thật vậy, bạn có thể sửa lỗi, nhưng nó đòi hỏi kỷ luật lập trình thêm và nỗ lực không cần thiết.


11
Trong trường hợp bạn thử "class foo ()" và nó không thành công: Trong ISO C ++, "class foo ()" là một cấu trúc bất hợp pháp (bài viết đã được viết '97, trước khi tiêu chuẩn hóa, có vẻ như vậy). Bạn có thể đặt "lớp typedef foo foo;" vào chính, sau đó bạn có thể nói "foo ();" (bởi vì sau đó, tên typedef gần hơn về mặt từ vựng so với tên của hàm). Về mặt cú pháp, trong T (), T phải là một công cụ xác định kiểu đơn giản. loại công thức cụ thể không được phép. Đây vẫn là một câu trả lời tốt, tất nhiên.
Julian Schaub - litb

Listing 1Listing 2các liên kết bị hỏng. Có một cái nhìn.
Prasoon Saurav

3
Nếu bạn sử dụng các quy ước đặt tên khác nhau cho các lớp và hàm, bạn cũng tránh xung đột đặt tên mà không cần thêm typedefs.
zstewart

64

Một sự khác biệt quan trọng hơn: typedefs không thể được tuyên bố chuyển tiếp. Vì vậy, đối với typedeftùy chọn, bạn phải #includecó tệp chứa typedef, nghĩa là mọi thứ #includecủa bạn .hcũng bao gồm tệp đó cho dù nó có cần trực tiếp hay không, v.v. Nó chắc chắn có thể tác động đến thời gian xây dựng của bạn trên các dự án lớn hơn.

Nếu không có typedef, trong một số trường hợp, bạn chỉ có thể thêm một khai báo chuyển tiếp struct Foo;ở đầu .htệp và chỉ #includeđịnh nghĩa cấu trúc trong .cpptệp của bạn .


Tại sao việc phơi bày định nghĩa về cấu trúc lại ảnh hưởng đến thời gian xây dựng? Trình biên dịch có kiểm tra thêm ngay cả khi nó không cần (đưa ra tùy chọn typedef, để trình biên dịch biết định nghĩa) khi thấy một cái gì đó như Foo * nextFoo; ?
Giàu

3
Nó không thực sự kiểm tra thêm, nó chỉ là nhiều mã hơn mà trình biên dịch cần phải xử lý. Đối với mọi tệp cpp gặp typedef ở đâu đó trong chuỗi bao gồm, nó sẽ biên dịch typedef. Trong các dự án lớn hơn, tệp .h chứa typedef có thể dễ dàng được biên dịch hàng trăm lần, mặc dù các tiêu đề được biên dịch trước giúp ích rất nhiều. Nếu bạn có thể thoát khỏi việc sử dụng khai báo chuyển tiếp, thì việc giới hạn bao gồm .h có chứa đặc tả cấu trúc đầy đủ để chỉ mã thực sự quan tâm, do đó tệp bao gồm tương ứng được biên dịch ít thường xuyên hơn.
Joe

xin vui lòng @ người bình luận trước đó (không phải chủ sở hữu của bài đăng). Tôi gần như bỏ lỡ trả lời của bạn. Nhưng nhờ thông tin.
Giàu

Điều này không còn đúng nữa trong C11, nơi bạn có thể gõ cùng một cấu trúc có cùng tên nhiều lần.
michaelmeyer

32

một sự khác biệt, nhưng tinh tế. Nhìn vào nó theo cách này: struct Foogiới thiệu một loại mới. Cái thứ hai tạo ra một bí danh gọi là Foo (và không phải là một loại mới) cho một structloại không tên .

7.1.3 Công cụ xác định typedef

1 [...]

Một tên được khai báo với specifier typedef trở thành một tên typedef. Trong phạm vi khai báo của nó, một tên typedef về mặt cú pháp tương đương với một từ khóa và đặt tên loại được liên kết với mã định danh theo cách được mô tả trong Khoản 8. Do đó, tên typedef là từ đồng nghĩa với loại khác. Một tên typedef không giới thiệu một kiểu mới theo cách khai báo lớp (9.1) hoặc khai báo enum.

8 Nếu khai báo typedef định nghĩa một lớp không tên (hoặc enum), tên typedef đầu tiên được khai báo là loại lớp đó (hoặc loại enum) chỉ được sử dụng để biểu thị loại lớp (hoặc loại enum) cho mục đích liên kết ( 3.5). [ Thí dụ:

typedef struct { } *ps, S; // S is the class name for linkage purposes

Vì vậy, một typedef luôn được sử dụng làm trình giữ chỗ / từ đồng nghĩa cho loại khác.


10

Bạn không thể sử dụng khai báo chuyển tiếp với cấu trúc typedef.

Bản thân cấu trúc là một loại ẩn danh, vì vậy bạn không có tên thật để chuyển tiếp khai báo.

typedef struct{
    int one;
    int two;
}myStruct;

Một tuyên bố chuyển tiếp như thế này sẽ không hoạt động:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types

Tôi không nhận được lỗi thứ hai cho nguyên mẫu hàm. Tại sao nó nói "xác định lại; các loại cơ bản khác nhau"? trình biên dịch không cần biết định nghĩa của mySturation trông như thế nào, phải không? Không có vấn đề được lấy từ một đoạn mã (typedef, hoặc khai báo chuyển tiếp), mySturation biểu thị một kiểu cấu trúc, phải không?
Giàu

@Rich Thật là phàn nàn rằng có sự xung đột về tên. Có một tuyên bố về phía trước nói rằng "hãy tìm một cấu trúc được gọi là mySturation" và sau đó có typedef đang đổi tên một cấu trúc không tên thành "mySturation".
Timoch Yochai

Bạn có nghĩa là đặt cả khai báo typedef và chuyển tiếp trong cùng một tệp? Tôi đã làm, và gcc biên dịch nó tốt. mySturation được hiểu chính xác là cấu trúc không tên. Thẻ myStructsống trong không gian tên thẻ và typedef_ed myStructsống trong không gian tên bình thường nơi các định danh khác như tên hàm, tên biến cục bộ tồn tại. Vì vậy, không nên có bất kỳ xung đột nào..Tôi có thể hiển thị cho bạn mã của tôi nếu bạn nghi ngờ có lỗi nào trong đó.
Giàu

@Rich GCC đưa ra cùng một lỗi, văn bản thay đổi một chút: gcc.godbolt.org/iêu
Yochai Timmer

Tôi nghĩ rằng tôi hiểu rằng khi bạn chỉ có một typedefkhai báo chuyển tiếp với typedeftên ed, không đề cập đến cấu trúc không tên. Thay vào đó, khai báo chuyển tiếp tuyên bố một cấu trúc không đầy đủ với thẻ myStruct. Ngoài ra, không nhìn thấy định nghĩa typedef, nguyên mẫu hàm sử dụng typedeftên ed là không hợp pháp. Do đó, chúng ta phải bao gồm toàn bộ typedef bất cứ khi nào chúng ta cần sử dụng myStructđể biểu thị một loại. Sửa lỗi cho tôi nếu tôi hiểu lầm bạn. Cảm ơn.
Giàu

0

Một sự khác biệt quan trọng giữa 'typedef struct' và 'struct' trong C ++ là việc khởi tạo thành viên nội tuyến trong str typedef structs 'sẽ không hoạt động.

// the 'x' in this struct will NOT be initialised to zero
typedef struct { int x = 0; } Foo;

// the 'x' in this struct WILL be initialised to zero
struct Foo { int x = 0; };

3
Không đúng. Trong cả hai trường hợp xđược khởi tạo. Xem thử nghiệm trong IDE trực tuyến Coliru (Tôi đã khởi tạo nó thành 42 để rõ ràng hơn bằng không khi việc chuyển nhượng đã thực sự diễn ra).
Colin D Bennett

Trên thực tế, tôi đã thử nghiệm nó trong Visual Studio 2013 và nó không được khởi tạo. Đây là một vấn đề chúng tôi gặp phải trong mã sản xuất. Tất cả các trình biên dịch là khác nhau và chỉ phải đáp ứng các tiêu chí nhất định.
dùng2796283

-3

Không có sự khác biệt trong C ++, nhưng tôi tin vào C, nó sẽ cho phép bạn khai báo các thể hiện của struct Foo mà không cần thực hiện rõ ràng:

struct Foo bar;

3
Nhìn vào câu trả lời @ dirkgently của --- có một sự khác biệt, nhưng đó là tinh tế.
Keith Pinson

-3

Struct là để tạo ra một kiểu dữ liệu. Typedef là đặt tên hiệu cho loại dữ liệu.


12
Bạn đã không hiểu câu hỏi.
Mùa đô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.