Czy w C ++ jest jakaś różnica między:
struct Foo { ... };
i:
typedef struct { ... } Foo;
Czy w C ++ jest jakaś różnica między:
struct Foo { ... };
i:
typedef struct { ... } Foo;
Odpowiedzi:
W C ++ istnieje tylko subtelna różnica. Jest to pozostałość po C, w której robi różnicę.
Standard języka C ( C89 §3.1.2.3 , C99 §6.2.3 i C11 §6.2.3 ) nakazuje oddzielne przestrzenie nazw dla różnych kategorii identyfikatorów, w tym identyfikatorów znaczników (dla struct
/ union
/ enum
) i zwykłych identyfikatorów (dla typedef
i innych identyfikatorów) .
Jeśli właśnie powiedziałeś:
struct Foo { ... };
Foo x;
pojawi się błąd kompilatora, ponieważ Foo
jest on zdefiniowany tylko w przestrzeni nazw znaczników.
Musisz to zadeklarować jako:
struct Foo x;
Za każdym razem, gdy chcesz odnieść się do Foo
, zawsze będziesz musiał nazwać to struct Foo
. Szybko to irytuje, więc możesz dodać typedef
:
struct Foo { ... };
typedef struct Foo Foo;
Teraz struct Foo
(w przestrzeni nazw znacznika) i po prostu Foo
(w zwykłej przestrzeni nazw identyfikatora) oba odnoszą się do tej samej rzeczy i można swobodnie deklarować obiekty typu Foo
bez struct
słowa kluczowego.
Konstrukt:
typedef struct Foo { ... } Foo;
jest tylko skrótem od deklaracji i typedef
.
Wreszcie,
typedef struct { ... } Foo;
deklaruje anonimową strukturę i tworzy typedef
dla niej. Tak więc w przypadku tej konstrukcji nie ma ona nazwy w przestrzeni nazw znacznika, tylko nazwa w przestrzeni nazw typedef. Oznacza to, że nie można go również zadeklarować w przód. Jeśli chcesz złożyć deklarację przesyłania dalej, musisz nadać jej nazwę w przestrzeni nazw znaczników .
W C ++ wszystkie struct
deklaracje / union
/ enum
/ class
działają tak, jakby były niejawnie typedef
edytowane, o ile nazwa nie jest ukryta przez inną deklarację o tej samej nazwie. Zobacz szczegółowe informacje Michaela Burra .
W tym artykule DDJ Dan Saks wyjaśnia jeden mały obszar, w którym mogą się pojawiać błędy, jeśli nie wpiszesz własnych struktur (i klas!):
Jeśli chcesz, możesz sobie wyobrazić, że C ++ generuje typedef dla każdej nazwy znacznika, na przykład
typedef class string string;
Niestety nie jest to do końca dokładne. Chciałbym, żeby to było takie proste, ale tak nie jest. C ++ nie może generować takich typedefs dla struktur, związków lub wyliczeń bez wprowadzenia niezgodności z C.
Załóżmy na przykład, że program C deklaruje zarówno funkcję, jak i strukturę o nazwie status:
int status(); struct status;
Ponownie może to być zła praktyka, ale jest to C. W tym programie status (sam w sobie) odnosi się do funkcji; status struktury odnosi się do typu.
Jeśli C ++ automatycznie wygenerowałby typedefs dla tagów, to po skompilowaniu tego programu jako C ++ kompilator wygenerowałby:
typedef struct status status;
Niestety, nazwa tego typu byłaby w konflikcie z nazwą funkcji, a program się nie skompilował. Właśnie dlatego C ++ nie może po prostu wygenerować typedef dla każdego znacznika.
W C ++ znaczniki działają tak jak nazwy typedef, z tym wyjątkiem, że program może zadeklarować obiekt, funkcję lub moduł wyliczający o tej samej nazwie i tym samym zakresie co znacznik. W takim przypadku nazwa obiektu, funkcji lub modułu wyliczającego ukrywa nazwę znacznika. Program może odwoływać się do nazwy znacznika tylko przy użyciu klasy słowa kluczowego, struktury, unii lub wyliczenia (odpowiednio) przed nazwą znacznika. Nazwa typu składająca się z jednego z tych słów kluczowych, po których następuje znacznik, jest specyfikatorem typu opracowanego. Na przykład status struktury i miesiąc wyliczenia są specyfikatorami typu rozwiniętego.
Tak więc program C, który zawiera zarówno:
int status(); struct status;
zachowuje się tak samo po skompilowaniu jako C ++. Sam status nazwy odnosi się do funkcji. Program może odwoływać się do typu tylko za pomocą statusu struktury typu szczegółowego.
Więc w jaki sposób pozwala to na wkradanie się błędów do programów? Rozważ program z Listingu 1 . Ten program definiuje klasę foo z domyślnym konstruktorem oraz operator konwersji, który konwertuje obiekt foo na char const *. Ekspresja
p = foo();
w zasadzie powinien skonstruować obiekt foo i zastosować operator konwersji. Kolejna instrukcja wyjściowa
cout << p << '\n';
powinien wyświetlać klasę foo, ale tak nie jest. Wyświetla funkcję foo.
Ten zaskakujący wynik występuje, ponieważ program zawiera nagłówek lib.h pokazany na Listingu 2 . Ten nagłówek definiuje funkcję o nazwie również foo. Nazwa funkcji foo ukrywa nazwę klasy foo, więc odwołanie do foo w main odnosi się do funkcji, a nie do klasy. main może odnosić się do klasy tylko przy użyciu specyfikatora typu rozwiniętego, jak w
p = class foo();
Aby uniknąć takich nieporozumień w całym programie, dodaj następujący typedef dla nazwy klasy foo:
typedef class foo foo;
bezpośrednio przed definicją klasy lub po niej. Ten typedef powoduje konflikt między nazwą typu foo a nazwą funkcji foo (z biblioteki), który wywoła błąd czasu kompilacji.
Oczywiście nie znam nikogo, kto napisałby te maszynopisy. Wymaga dużej dyscypliny. Ponieważ częstość występowania błędów, takich jak ta z Listingu 1, jest prawdopodobnie niewielka, wielu z nich nigdy nie ma nic przeciwko temu problemowi. Ale jeśli błąd w oprogramowaniu może spowodować obrażenia ciała, powinieneś napisać typedefs bez względu na to, jak mało prawdopodobny jest błąd.
Nie mogę sobie wyobrazić, dlaczego ktokolwiek miałby kiedykolwiek chcieć ukryć nazwę klasy z nazwą funkcji lub obiektu w tym samym zakresie, co klasa. Zasady ukrywania w C były błędem i nie powinny były zostać rozszerzone na klasy w C ++. Rzeczywiście, możesz poprawić błąd, ale wymaga to dodatkowej dyscypliny programistycznej i wysiłku, który nie powinien być konieczny.
Listing 1
i Listing 2
linki są zepsute. Spójrz.
Jeszcze jedna ważna różnica: typedef
nie można zadeklarować w przód. Więc dla typedef
opcji musisz #include
plik zawierający typedef
, co oznacza, że wszystko #include
jest twoje.h
również zawiera ten plik, bez względu na to, czy jest on bezpośrednio potrzebny, czy nie, i tak dalej. Może to zdecydowanie wpłynąć na czas kompilacji większych projektów.
Bez typedef
, w niektórych przypadkach można po prostu dodać do przodu deklarację struct Foo;
w górnej części .h
pliku, a tylko #include
definicji struct w Twoim .cpp
pliku.
Nie ma różnicy, ale subtelne. Spójrz na to w ten sposób: struct Foo
wprowadza nowy typ. Drugi tworzy alias o nazwie Foo (a nie nowy typ) dla typu nienazwanego struct
.
7.1.3 Specyfikator typedef
1 [...]
Nazwa zadeklarowana za pomocą specyfikatora typedef staje się nazwą typedef. W ramach deklaracji nazwa typedef jest składniowo równoważna słowu kluczowemu i nazywa typ powiązany z identyfikatorem w sposób opisany w klauzuli 8. Nazwa typedef jest zatem synonimem innego typu. Typedef-name nie wprowadza nowego typu tak jak deklaracja klasy (9.1) lub deklaracja enum.
8 Jeśli deklaracja typedef definiuje nienazwaną klasę (lub wyliczenie), pierwsza nazwa typedef zadeklarowana przez deklarację jako ten typ klasy (lub typ wyliczeniowy) jest używana do oznaczenia typu klasy (lub typu wyliczeniowego) wyłącznie w celu powiązania ( 3.5). [Przykład:
typedef struct { } *ps, S; // S is the class name for linkage purposes
Więc typedef zawsze jest używany jako symbol zastępczy / synonim innego typu.
Nie można używać deklaracji przesyłania z strukturą typedef.
Sama struktura jest typem anonimowym, więc nie masz rzeczywistej nazwy do przekazania.
typedef struct{
int one;
int two;
}myStruct;
Deklaracja przekazująca taka jak ta nie będzie działać:
struct myStruct; //forward declaration fails
void blah(myStruct* pStruct);
//error C2371: 'myStruct' : redefinition; different basic types
myStruct
żyje w przestrzeni nazw znaczników, a typedef_ed myStruct
żyje w normalnej przestrzeni nazw, w której mieszkają inne identyfikatory, takie jak nazwa funkcji, nazwy zmiennych lokalnych. Więc nie powinno być żadnego konfliktu .. Mogę pokazać ci mój kod, jeśli masz wątpliwości, że jest w nim jakiś błąd.
typedef
deklarację przekazania o typedef
nazwie ed, nie odnosi się ona do struktury bez nazwy. Zamiast tego deklaracja forward deklaruje niepełną strukturę ze znacznikiem myStruct
. Ponadto, bez zobaczenia definicji typedef
, prototyp funkcji wykorzystujący typedef
nazwę ed jest niezgodny z prawem. Musimy więc uwzględnić cały typedef, ilekroć musimy użyć myStruct
do oznaczenia typu. Popraw mnie, jeśli cię źle zrozumiałem. Dzięki.
Ważną różnicą między „strukturą typedef” a „strukturą” w C ++ jest to, że inicjalizacja elementu wbudowanego w „strukturach typedef” nie będzie działać.
// 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
jest inicjowany. Zobacz test w internetowym środowisku IDE Coliru (zainicjowałem go na 42, więc jest bardziej oczywiste niż zero, że przypisanie naprawdę miało miejsce).
Nie ma różnicy w C ++, ale wierzę, że w C pozwoliłoby to zadeklarować wystąpienia struktury Foo bez wyraźnego wykonywania:
struct Foo bar;