Trong C ++ , có sự khác biệt nào giữa:
struct Foo { ... };
và:
typedef struct { ... } Foo;
Trong C ++ , có sự khác biệt nào giữa:
struct Foo { ... };
và:
typedef struct { ... } Foo;
Câu trả lời:
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.3 và C11 §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 typedef
và 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ì Foo
chỉ đượ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 Foo
mà không cần struct
từ 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 typedef
cho 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
/ class
khai 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 đủ.
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.
Listing 1
và Listing 2
các liên kết bị hỏng. Có một cái nhìn.
Một sự khác biệt quan trọng hơn: typedef
s không thể được tuyên bố chuyển tiếp. Vì vậy, đối với typedef
tùy chọn, bạn phải #include
có tệp chứa typedef
, nghĩa là mọi thứ #include
của bạn .h
cũ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 .h
tệp và chỉ #include
định nghĩa cấu trúc trong .cpp
tệp của bạn .
Có là một sự khác biệt, nhưng tinh tế. Nhìn vào nó theo cách này: struct Foo
giớ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 struct
loạ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.
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
myStruct
sống trong không gian tên thẻ và typedef_ed myStruct
số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 đó.
typedef
khai báo chuyển tiếp với typedef
tê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 typedef
tê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.
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; };
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).
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;