Dlaczego tak często wpisujemy strukturę w C?


406

Widziałem wiele programów składających się ze struktur takich jak ten poniżej

typedef struct 
{
    int i;
    char k;
} elem;

elem user;

Dlaczego jest tak często potrzebny? Czy jest jakiś konkretny powód lub odpowiedni obszar?


14
Bardziej dokładna i precyzyjna odpowiedź: stackoverflow.com/questions/612328/…
AbiusX

Ma wady Myślę, że nie można utworzyć listy linków z anonimową strukturą, ponieważ linia struct * ptrwewnątrz struktury spowoduje błąd
Raghib Ahsan

9
„Bardziej dokładna i precyzyjna odpowiedź” to Różnica między struct i typedef struct w C ++ , a istnieją znaczące różnice między C i C ++ w tym obszarze, co sprawia, że ​​ta odpowiedź nie jest w pełni odpowiednia dla pytania o C.
Jonathan Leffler

To pytanie ma zduplikowane definicje typedef struct vs struct, które również mają gwiezdne odpowiedzi.
Jonathan Leffler

2
OTOH, kernel.org/doc/html/v4.10/process/coding-style.html mówi nam, że nie powinniśmy robić takich typedefs.
glglgl 27.03.18

Odpowiedzi:


453

Jak powiedział Greg Hewgill, typedef oznacza, że ​​nie musisz już pisać structwszędzie. To nie tylko oszczędza naciśnięcia klawiszy, ale także może uczynić kod czystszym, ponieważ zapewnia smidgen więcej abstrakcji.

Rzeczy jak

typedef struct {
  int x, y;
} Point;

Point point_new(int x, int y)
{
  Point a;
  a.x = x;
  a.y = y;
  return a;
}

staje się czystszy, gdy nie musisz widzieć słowa kluczowego „struct” w całym miejscu, wygląda to tak, jakby naprawdę istniał typ o nazwie „Punkt” w twoim języku. Co, jak typedefsądzę , po tym jest.

Zauważ też, że chociaż twój przykład (i mój) pomijał nazywanie struct samego siebie, tak naprawdę nazywanie go jest również przydatne, gdy chcesz podać nieprzejrzysty typ. W nagłówku miałbyś taki kod, na przykład:

typedef struct Point Point;

Point * point_new(int x, int y);

a następnie podaj structdefinicję w pliku implementacji:

struct Point
{
  int x, y;
};

Point * point_new(int x, int y)
{
  Point *p;
  if((p = malloc(sizeof *p)) != NULL)
  {
    p->x = x;
    p->y = y;
  }
  return p;
}

W tym ostatnim przypadku nie można zwrócić punktu według wartości, ponieważ jego definicja jest ukryta przed użytkownikami pliku nagłówka. Jest to na przykład technika szeroko stosowana w GTK + .

AKTUALIZACJA Zauważ, że istnieją również wysoko cenione projekty C, w których użycie typedefukrywania structjest uważane za zły pomysł, jądro Linuksa jest prawdopodobnie najbardziej znanym takim projektem. Zobacz rozdział 5 dokumentu Linux Kernel CodingStyle, aby dowiedzieć się o gniewnych słowach Linusa. :) Chodzi mi o to, że pytanie „powinno” w pytaniu może nie jest w końcu osadzone w kamieniu.


58
Nie należy używać identyfikatorów z podkreśleniem, po których następuje wielka litera, są one zastrzeżone (patrz sekcja 7.1.3 ust. 1). Chociaż jest mało prawdopodobne, aby stanowił duży problem, jest technicznie niezdefiniowanym zachowaniem podczas ich używania (7.1.3 pkt 2).
dreamlax

16
@dreamlax: W przypadku, gdy dla innych nie było to jasne, rozpoczyna się tylko identyfikatorem z podkreśleniem i wielką literą, którego nie powinieneś robić; możesz użyć tego pośrodku identyfikatora.
brianmearns 16.01.12

12
Interesujące jest to, że podany tutaj przykład (w którym typedef uniemożliwia użycie „struct” w całym miejscu) jest w rzeczywistości dłuższy niż ten sam kod bez typedef, ponieważ zapisuje dokładnie jedno użycie słowa „struct”. Smidgen pozyskiwania abstrakcji rzadko porównuje się pozytywnie z dodatkowym zaciemnieniem.
William Pursell

7
@Rerito fyi, strona 166 szkicu C99 , Wszystkie identyfikatory rozpoczynające się znakiem podkreślenia oraz wielką literą lub innym znakiem podkreślenia są zawsze zarezerwowane do dowolnego użytku. I wszystkie stwierdziliśmy ers fi, które rozpoczynają się od znaku podkreślenia są zawsze zarezerwowane do użytku jako identy fi fi le ERS z zakresu zarówno w zwykłych i znaczników przestrzeni nazw.
e2-e4

10
Co ciekawe, wytyczne dotyczące kodowania jądra Linuksa mówią, że powinniśmy być znacznie bardziej konserwatywni w stosowaniu typedefs (sekcja 5): kernel.org/doc/Documentation/CodingStyle
gsingh2011

206

To niesamowite, jak wielu ludzi się myli. PROSZĘ nie wpisuj struktur w C, niepotrzebnie zanieczyszcza globalną przestrzeń nazw, która jest zwykle bardzo zanieczyszczona już w dużych programach C.

Struktury typedef'd bez nazwy znacznika są główną przyczyną niepotrzebnego narzucania relacji porządkowania między plikami nagłówkowymi.

Rozważać:

#ifndef FOO_H
#define FOO_H 1

#define FOO_DEF (0xDEADBABE)

struct bar; /* forward declaration, defined in bar.h*/

struct foo {
  struct bar *bar;
};

#endif

Przy takiej definicji, bez użycia typedefs, możliwe jest, że jednostka kompilandowa będzie zawierać foo.h, aby uzyskać FOO_DEFdefinicję. Jeśli nie spróbuje wyłuskać elementu „bar” foostruktury, wówczas nie będzie potrzeby dołączania pliku „bar.h”.

Ponadto, ponieważ przestrzenie nazw różnią się między nazwami znaczników a nazwami członków, możliwe jest napisanie bardzo czytelnego kodu, takiego jak:

struct foo *foo;

printf("foo->bar = %p", foo->bar);

Ponieważ przestrzenie nazw są oddzielne, nie ma konfliktu w nazwach zmiennych zgodnych z nazwą ich znacznika struct.

Jeśli będę musiał zachować twój kod, usunę twoje strukturalne typy.


37
Bardziej zdumiewające jest to, że 13 miesięcy po udzieleniu tej odpowiedzi jestem pierwszym, który ją poparł! Typedef'ing structs jest jednym z największych nadużyć języka C i nie ma miejsca w dobrze napisanym kodzie. Typedef jest przydatny do usuwania zaciemniających typów wskaźników funkcji skręconych i tak naprawdę nie służy żadnym innym przydatnym celom.
William Pursell,

28
Peter van der Linden również wypowiada się przeciwko strukturom pisma maszynowego w swojej oświecającej książce „Expert C Programming - Deep C Secrets”. Chodzi o to: CHCESZ wiedzieć, że coś jest strukturą lub związkiem, a nie UKRYJ.
Jens

34
Styl kodowania jądra Linuksa wyraźnie zabrania struktur piszących na maszynie. Rozdział 5: Typedefs: „ Błędem jest używanie typedef do struktur i wskaźników”. kernel.org/doc/Documentation/CodingStyle
jasso

63
Jakie dokładnie korzyści przynosi ciągłe wpisywanie „struct”? Mówiąc o zanieczyszczeniu, dlaczego chcesz mieć strukturę i funkcję / zmienną / typedef o tej samej nazwie w globalnej przestrzeni nazw (chyba że jest to typedef dla tej samej funkcji)? Bezpiecznym wzorem jest użycie typedef struct X { ... } X. W ten sposób możesz użyć krótkiego formularza Xdo adresowania struktury w dowolnym miejscu, w którym dostępna jest definicja, ale nadal możesz zadeklarować dalej i użyć w razie struct Xpotrzeby.
Pavel Minaev

7
Osobiście bardzo rzadko, jeśli w ogóle używam typedef, nie powiedziałbym, że inni ludzie nie powinni go używać, to po prostu nie w moim stylu. Lubię widzieć strukturę przed typem zmiennej, więc od razu wiem, że jest strukturą. Argument łatwiejszy do wpisania jest nieco kiepski, ponieważ zmienna, która jest pojedynczą literą, jest również łatwiejsza do wpisania, również z automatycznym uzupełnianiem, jak trudno jest teraz wpisać struct w dowolnym miejscu.
Nathan Day

138

Ze starego artykułu Dana Saksa ( http://www.ddj.com/cpp/184403396?pgno=3 ):


Zasady języka C dla nazewnictwa struktur są trochę ekscentryczne, ale są całkiem nieszkodliwe. Jednak po rozszerzeniu na klasy w C ++ te same reguły otwierają małe pęknięcia, przez które mogą się czołgać błędy.

W C nazwa pojawia się w

struct s
    {
    ...
    };

jest tagiem. Nazwa znacznika nie jest nazwą typu. Biorąc pod uwagę powyższą definicję, deklaracje takie jak

s x;    /* error in C */
s *p;   /* error in C */

są błędami w C. Musisz je zapisać jako

struct s x;     /* OK */
struct s *p;    /* OK */

Nazwy związków i wyliczenia są również znacznikami, a nie typami.

W C znaczniki różnią się od wszystkich innych nazw (dla funkcji, typów, zmiennych i stałych wyliczenia). Kompilatory C utrzymują znaczniki w tabeli symboli, która koncepcyjnie, jeśli nie jest fizycznie oddzielona od tabeli, która zawiera wszystkie inne nazwy. Dlatego możliwe jest, że program C ma zarówno znacznik, jak i inną nazwę o tej samej pisowni w tym samym zakresie. Na przykład,

struct s s;

jest poprawną deklaracją deklarującą zmienną typu struct s. To może nie być dobra praktyka, ale kompilatory C muszą to zaakceptować. Nigdy nie widziałem uzasadnienia, dlaczego C został zaprojektowany w ten sposób. Zawsze myślałem, że to pomyłka, ale jest.

Wielu programistów (w tym naprawdę) wolą myśleć o nazwach struktur jako nazwach typów, więc definiują alias znacznika za pomocą typedef. Na przykład definiowanie

struct s
    {
    ...
    };
typedef struct s S;

pozwala używać S zamiast struct s, jak w

S x;
S *p;

Program nie może używać S jako nazwy zarówno typu, jak i zmiennej (lub funkcji lub stałej wyliczenia):

S S;    // error

To jest dobre.

Nazwa znacznika w definicji struct, union lub enum jest opcjonalna. Wielu programistów składa definicję struktury na typedef i całkowicie rezygnuje ze znacznika, jak w:

typedef struct
    {
    ...
    } S;

W linkowanym artykule jest także dyskusja na temat tego, jak zachowanie C ++, które nie wymaga, typedefmoże powodować subtelne problemy z ukrywaniem nazw. Aby zapobiec tym problemom, dobrym pomysłem jest także dla typedeftwoich klas i struktur w C ++, nawet jeśli na pierwszy rzut oka wydaje się to niepotrzebne. W C ++ typedefukrywanie nazwy staje się błędem, o którym mówi kompilator, a nie ukrytym źródłem potencjalnych problemów.


2
Jednym z przykładów, w których nazwa znacznika jest taka sama jak nazwa nieoznaczona, jest program (POSIX lub Unix) z int stat(const char *restrict path, struct stat *restrict buf)funkcją. Tam masz funkcję statw zwykłej przestrzeni nazw i struct statprzestrzeni nazw znaczników.
Jonathan Leffler

2
twoje oświadczenie, SS; // error .... IS WRONG Działa dobrze. Mam na myśli stwierdzenie, że „nie możemy mieć takiej samej nazwy dla tagu typedef i var” jest NIEPRAWIDŁOWY ... proszę sprawdzić
eRaisedToX

63

Używanie typedefznaku unika konieczności pisania za structkażdym razem, gdy deklarujesz zmienną tego typu:

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct

2
ok, nie mamy tego problemu w C ++. Dlaczego więc nikt nie usunie tej usterki z kompilatora C i nie zmieni jej w C ++. Ok C ++ ma kilka różnych obszarów aplikacji, a więc ma zaawansowane funkcje. Ale nie możemy odziedziczyć niektórych z nich w C bez zmiany oryginalny C?
Manoj Doubts

4
Manoj, nazwa znacznika („struct foo”) jest niezbędna, gdy musisz zdefiniować strukturę, która się do niej odwołuje. np. wskaźnik „następny” na połączonej liście. Co więcej, kompilator implementuje standard i właśnie tak mówi standard.
Michael Carman

41
To nie jest usterka w kompilatorze C, to część projektu. Zmienili to dla C ++, co myślę, że to ułatwia, ale to nie znaczy, że zachowanie C jest złe.
Herms

5
niestety wielu „programistów” definiuje strukturę, a następnie wpisuje ją z jakąś „niepowiązaną” nazwą (np. struct myStruct ...; typedef struct myStruct susan *; W prawie wszystkich przypadkach typedef prowadzi do zaśmiecania kodu, ukrywając rzeczywistą definicję zmienna / parametr i źle prowadzi wszystkich, w tym oryginalnego autora kodu
user3629249

1
@ user3629249 Zgadzam się z tobą, że wspomniany styl programowania jest okropny, ale nie jest to powód do oczerniania typedefstruktur w ogóle. Ty też możesz to zrobić typedef struct foo foo;. Oczywiście structsłowo kluczowe nie jest więcej wymagane, co może być przydatną wskazówką, że alias typu, na który patrzymy, jest aliasem dla struktury, ale ogólnie nie jest zły. Rozważ także przypadek, w którym identyfikator wynikowego aliasu typu typedefwskazuje, że jest to alias dla struktury, np typedef struct foo foo_struct;. :
RobertS obsługuje Monikę Cellio

38

Kolejny dobry powód, aby zawsze wpisywać wyliczenia i struktury wynika z tego problemu:

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

struct StructDef
{
  enum EnuumDef MyEnum;
  unsigned int MyVar;
} MyStruct;

Czy zauważyłeś literówkę w EnumDef w struct ( Enu u mDef)? Kompiluje się bez błędu (lub ostrzeżenia) i jest (w zależności od dosłownej interpretacji standardu C) poprawny. Problem polega na tym, że właśnie utworzyłem nową (pustą) definicję wyliczenia w mojej strukturze. Nie używam (zgodnie z zamierzeniami) poprzedniej definicji EnumDef.

W przypadku typdef podobne typy literówek spowodowałyby błędy kompilatora przy użyciu nieznanego typu:

typedef 
{
  FIRST_ITEM,
  SECOND_ITEM
} EnumDef;

typedef struct
{
  EnuumDef MyEnum; /* compiler error (unknown type) */
  unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */

Zalecałbym ZAWSZE wpisywanie struktur i wyliczeń.

Nie tylko, aby zaoszczędzić trochę pisania (nie ma sensu;)), ale dlatego, że jest bezpieczniejszy.


1
Co gorsza, Twoja literówka może pokrywać się z innym znacznikiem. W przypadku struktury może to spowodować poprawną kompilację całego programu i niezdefiniowane zachowanie środowiska wykonawczego.
MM

3
ta definicja: „typedef {FIRST_ITEM, SECOND_ITEM} EnumDef;” nie definiuje wyliczenia. Napisałem setki ogromnych programów i miałem nieszczęście, aby wykonać konserwację programów napisanych przez innych. Z trudnego doświadczenia, użycie typedef na strukturze prowadzi tylko do problemów. Mamy nadzieję, że programista nie jest tak upośledzony, że ma problemy z wpisaniem pełnej definicji, kiedy deklaruje instancję struktury. C nie jest językiem podstawowym, więc wpisywanie kolejnych znaków nie ma negatywnego wpływu na działanie programu.
user3629249

2
Dla tych, którzy odczuwają wstręt do pisania więcej niż absolutna minimalna liczba znaków, proponuję dołączyć do grupy, która próbuje pisać aplikacje z minimalną liczbą naciśnięć klawiszy. Po prostu nie używaj ich nowych „umiejętności” w środowisku pracy, szczególnie w środowisku pracy, które rygorystycznie wykonuje recenzje użytkowników
user3629249

Zauważ, że zwykle nie zaleca się używania enumjako typu, ponieważ wiele kompilatorów emituje dziwne ostrzeżenia, gdy używa się ich „źle”. Na przykład zainicjowanie enumna 0 może dać ostrzeżenie „stała całkowita nie w wyliczeniu”. Przekazanie do przodu an enumrównież nie jest dozwolone. Zamiast tego należy użyć int(lub unsigned int).
yyny

5
Ten przykład się nie kompiluje i nie powinienem się tego spodziewać. Kompilowanie debugowania / test.o test.c: 10: 17: błąd: pole ma niekompletny typ „enum EnuumDef” enum EnuumDef MyEnum; ^ test.c: 10: 8: uwaga: przednia deklaracja „enum EnuumDef” enum EnuumDef MyEnum; Wygenerowano błąd ^ 1. gnuc, ze std = c99.
natersoz

30

Styl kodowania jądra Linuksa Rozdział 5 podaje świetne zalety i wady (głównie wady) korzystania typedef.

Nie używaj takich rzeczy jak „vps_t”.

Jest to błędem użycie typedef dla struktur i wskaźników. Gdy zobaczysz a

vps_t a;

co to znaczy w źródle?

Przeciwnie, jeśli to mówi

struct virtual_container *a;

możesz właściwie powiedzieć, co to jest „a”.

Wiele osób uważa, że ​​typedefs „poprawia czytelność”. Skąd. Są przydatne tylko w przypadku:

(a) całkowicie nieprzezroczyste obiekty (gdzie typedef jest aktywnie używany do ukrycia tego, co jest przedmiotem).

Przykład: „pte_t” itp. Nieprzezroczyste obiekty, do których można uzyskać dostęp tylko za pomocą odpowiednich funkcji akcesora.

UWAGA! Nieprzezroczystość i „funkcje akcesora” same w sobie nie są dobre. Powodem, dla którego je mamy dla takich rzeczy jak pte_t itp., Jest to, że naprawdę tam jest absolutnie zero przenośnych informacji.

(b) Wyczyść typy liczb całkowitych, w których abstrakcja pomaga uniknąć nieporozumień, czy jest to „int” czy „long”.

u8 / u16 / u32 są idealnie dobranymi czcionkami, chociaż pasują do kategorii (d) lepiej niż tutaj.

UWAGA! Znowu - musi być ku temu powód . Jeśli coś jest „niepodpisane długo”, nie ma powodu, aby to robić

typedef unsigned long myflags_t;

ale jeśli istnieje wyraźny powód, dla którego w pewnych okolicznościach może to być „bez znaku int”, a w innych konfiguracjach może być „bez znaku długi”, to zdecydowanie skorzystaj z pisma maszynowego.

(c) gdy używasz rzadkiego, aby dosłownie utworzyć nowy typ do sprawdzania typu.

(d) Nowe typy, które są identyczne ze standardowymi typami C99, w pewnych wyjątkowych okolicznościach.

Chociaż oczy i mózg przyzwyczają się do krótkiego czasu przyzwyczajenia się do standardowych typów, takich jak „uint32_t”, niektórzy i tak sprzeciwiają się ich użyciu.

Dlatego specyficzne dla Linuksa typy „u8 / u16 / u32 / u64” i ich podpisane odpowiedniki, które są identyczne ze standardowymi typami, są dozwolone - chociaż nie są obowiązkowe w nowym kodzie.

Podczas edycji istniejącego kodu, który już korzysta z jednego lub drugiego zestawu typów, powinieneś dostosować się do istniejących wyborów w tym kodzie.

(e) Rodzaje bezpieczne do użytku w przestrzeni użytkownika.

W niektórych strukturach widocznych dla przestrzeni użytkownika nie możemy wymagać typów C99 i nie możemy użyć powyższego formularza „u32”. Dlatego używamy __u32 i podobnych typów we wszystkich strukturach współdzielonych z przestrzenią użytkownika.

Być może są też inne przypadki, ale zasadą powinno być NIGDY NIE NIGDY używać typedef, chyba że możesz wyraźnie dopasować jedną z tych reguł.

Zasadniczo wskaźnik lub struktura, która ma elementy, do których można uzyskać bezpośredni dostęp, nigdy nie powinna być czcionką maszynową.


4
„Nieprzezroczystość i„ funkcje akcesora ”same w sobie nie są dobre”. Czy ktoś może wyjaśnić, dlaczego? Myślę, że ukrywanie informacji i enkapsulacja byłoby bardzo dobrym pomysłem.
Yawar,

5
@Yawar Właśnie przeczytałem ten dokument i miałem dokładnie tę samą myśl. Jasne, C nie jest zorientowany obiektowo, ale abstrakcja to wciąż coś.
Baldrickk,

12

Okazuje się, że są plusy i minusy. Przydatnym źródłem informacji jest przełomowa książka „Expert C Programming” ( rozdział 3 ). W skrócie, w C masz wiele przestrzeni nazw: tagi, typy, nazwy członków i identyfikatory . typedefwprowadza alias dla typu i lokalizuje go w przestrzeni nazw znaczników. Mianowicie,

typedef struct Tag{
...members...
}Type;

definiuje dwie rzeczy. Jeden znacznik w przestrzeni nazw znaczników i jeden Wpisz w przestrzeni nazw typów. Więc możesz zrobić jedno Type myTypei drugie struct Tag myTagType. Deklaracje takie jak struct Type myTypelub Tag myTagTypesą nielegalne. Ponadto w deklaracji takiej jak ta:

typedef Type *Type_ptr;

definiujemy wskaźnik do naszego typu. Więc jeśli zadeklarujemy:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;

następnie var1, var2i myTagType1są wskaźnikami do Type, ale myTagType2nie.

W wyżej wspomnianej książce wspomina, że ​​struktury do pisania na maszynie nie są zbyt przydatne, ponieważ ratuje programistę przed pisaniem słowa struct. Mam jednak sprzeciw, podobnie jak wielu innych programistów C. Chociaż czasami okazuje się, że niektóre nazwy są zaciemnione (dlatego nie jest wskazane w dużych bazach kodu, takich jak jądro), kiedy chcesz zaimplementować polimorfizm w C, bardzo pomaga tutaj poszukać szczegółów . Przykład:

typedef struct MyWriter_t{
    MyPipe super;
    MyQueue relative;
    uint32_t flags;
...
}MyWriter;

możesz to zrobić:

void my_writer_func(MyPipe *s)
{
    MyWriter *self = (MyWriter *) s;
    uint32_t myFlags = self->flags;
...
}

Możesz więc uzyskać dostęp do zewnętrznego elementu ( flags) przez wewnętrzną strukturę ( MyPipe) poprzez rzutowanie. Dla mnie jest mniej mylące, aby przesyłać cały typ niż robić za (struct MyWriter_ *) s;każdym razem, gdy chcesz wykonać taką funkcjonalność. W takich przypadkach krótkie odwoływanie się jest bardzo ważne, szczególnie jeśli mocno stosujesz tę technikę w kodzie.

Wreszcie ostatnim aspektem typedeftypów ed jest brak możliwości ich rozszerzenia, w przeciwieństwie do makr. Jeśli na przykład masz:

#define X char[10] or
typedef char Y[10]

możesz wtedy zadeklarować

unsigned X x; but not
unsigned Y y;

Tak naprawdę nie dbamy o to dla struktur, ponieważ nie dotyczy to specyfikatorów pamięci ( volatilei const).


1
MyPipe *s; MyWriter *self = (MyWriter *) s;i właśnie złamałeś ścisłe aliasing.
Jonathon Reinhart

@JonathonReinhart Przykładem może być wspomnienie, jak można tego uniknąć, na przykład, jak działa bardzo dobrze obsadzony GTK +: bugzilla.gnome.org/show_bug.cgi?id=140722 / mail.gnome.org/archives/gtk -devel-list / 2004-April / msg00196.html
underscore_d

„typedef wprowadza alias dla typu i lokalizuje go w przestrzeni nazw znaczników. Mianowicie„ typedef struct Tag{ ...members... }Type; ”definiuje dwie rzeczy” nie ma sensu. jeśli typedef definiuje tagi, to tutaj „Type” powinno być również tagiem. Prawda jest definicja określa tagów 2 i 1 typ (lub 2 typów oraz 1 znacznik nie pewny.) struct Tag, TagA Type. struct Tagjest zdecydowanie typem. Tagjest tagiem. ale zamieszanie polega na tym, czy Typejest to tag, czy typ
Qwertyzw

12

Nie sądzę, aby deklaracje typu forward były nawet możliwe w przypadku typedef. Użycie struct, enum i union pozwala na przekazywanie deklaracji, gdy zależności (o których wiadomo) są dwukierunkowe.

Styl: użycie typedef w C ++ ma całkiem sens. Może to być prawie konieczne w przypadku szablonów, które wymagają wielu i / lub zmiennych parametrów. Typedef pomaga zachować prostą nazwę.

Nie w języku programowania C. Użycie typedef najczęściej nie służy celowi, ale zaciemnia użycie struktury danych. Ponieważ tylko liczba klawiszy {{((6), enum (4), union (5)} jest użyta do zadeklarowania typu danych, prawie nie ma zastosowania do aliasingu struktury. Czy ten typ danych jest związkiem czy strukturą? Korzystanie z prostej deklaracji bez czcionek pozwala od razu wiedzieć, jaki to typ.

Zwróć uwagę, jak Linux jest napisany ze ścisłym unikaniem tego nonsensownego aliasingu, jaki przynosi typedef. Rezultatem jest minimalistyczny i czysty styl.


10
Clean nie powtarzałby się structwszędzie ... Typedef tworzy nowe typy. Czego używasz? Rodzaje Nie obchodzi nas, czy jest to struktura, związek, czy wyliczenie, dlatego właśnie to wpisaliśmy.
GManNickG

13
Nie, dbamy o to, czy jest to struktura, czy związek, w przeciwieństwie do wyliczenia lub jakiegoś rodzaju atomu. Nie można wymusić struktury na liczbę całkowitą lub wskaźnik (lub jakikolwiek inny typ, jeśli chodzi o to), co jest czasem wszystkim, co trzeba przechowywać w jakimś kontekście. Posiadanie słów kluczowych „struct” lub „union” poprawia lokalizację rozumowania. Nikt nie mówi, że musisz wiedzieć, co jest w strukturze.
Bernd Jendrissek

1
@BerndJendrissek: Struktury i związki różnią się od innych typów, ale czy kod klienta powinien dbać o to, która z tych dwóch rzeczy (struct lub union) jest czymś podobnym FILE?
supercat

4
PLIK @supercat to dobre użycie typedef. Wydaje mi się, że typedef jest nadużywany , a nie że jest to błąd językowy. IMHO używające typedef do wszystkiego jest zapachem kodu „spekulatywnej nadgeneralności”. Zauważ, że deklarujesz zmienne jako PLIK * foo, nigdy jako PLIK foo. Dla mnie to ma znaczenie.
Bernd Jendrissek

2
@supercat: „Gdyby zmienne identyfikujące plik były typu FILE, a nie PLIK * ...” Ale taka właśnie dwuznaczność umożliwia typedefs! Jesteśmy przyzwyczajeni do otwierania PLIKU *, więc nie martwimy się tym, ale za każdym razem, gdy dodajesz typedef, wprowadzasz jeszcze jeden narzut poznawczy: czy ten interfejs API chce foo_t args czy foo_t *? Jawne przenoszenie „struktury” razem poprawia lokalizację rozumowania, choć kosztem kilku dodatkowych znaków na definicję funkcji.
Bernd Jendrissek

4

Zacznijmy od podstaw i pracujmy dalej.

Oto przykład definicji struktury:

struct point
  {
    int x, y;
  };

Tutaj nazwa pointjest opcjonalna.

Strukturę można zadeklarować podczas jej definiowania lub później.

Deklaracja podczas definicji

struct point
  {
    int x, y;
  } first_point, second_point;

Deklaracja po definicji

struct point
  {
    int x, y;
  };
struct point first_point, second_point;

Teraz uważnie zwróć uwagę na ostatni przypadek powyżej; musisz napisać, struct pointaby zadeklarować Struktury tego typu, jeśli zdecydujesz się utworzyć ten typ w późniejszym punkcie kodu.

Enter typedef. Jeśli zamierzasz później utworzyć nową Strukturę (Struktura jest niestandardowym typem danych) w swoim programie przy użyciu tego samego schematu, użycie typedefjej podczas definicji może być dobrym pomysłem, ponieważ możesz zaoszczędzić trochę pisania na maszynie.

typedef struct point
  {
    int x, y;
  } Points;

Points first_point, second_point;

Ostrzegając przy określaniu typu niestandardowego

Nic nie stoi na przeszkodzie, aby używać sufiksu _t na końcu nazwy typu niestandardowego, ale standard POSIX zastrzega użycie sufiksu _t do oznaczania standardowych nazw typów bibliotek.


3

nazwa, którą (opcjonalnie) nadajesz struktury, nazywa się nazwą znacznika i, jak zauważono, sama w sobie nie jest typem. Aby przejść do tego typu, wymagany jest prefiks struct.

Poza GTK +, nie jestem pewien, czy zmienna jest używana tak często jak typedef do typu struct, więc w C ++, który jest rozpoznawany, możesz pominąć słowo kluczowe struct i użyć również zmiennej jako nazwy typu:


    struct MyStruct
    {
      int i;
    };

    // The following is legal in C++:
    MyStruct obj;
    obj.i = 7;


1

Typedef nie zapewni współzależnego zestawu struktur danych. Tego nie można zrobić z typdef:

struct bar;
struct foo;

struct foo {
    struct bar *b;
};

struct bar {
    struct foo *f;
};

Oczywiście zawsze możesz dodać:

typedef struct foo foo_t;
typedef struct bar bar_t;

Jaki jest tego sens?


1

A> typdef pomaga w znaczeniu i dokumentacji programu, umożliwiając tworzenie bardziej znaczących synonimów dla typów danych . Ponadto pomagają sparametryzować program pod kątem problemów związanych z przenośnością (K&R, str. 147, C prog lang).

B> struktura definiuje typ . Struktury umożliwiają wygodne grupowanie kolekcji zmiennych dla wygody obsługi (K&R, pg127, C prog lang.) Jako pojedynczej jednostki

C> typowanie struktury jest wyjaśnione w A powyżej.

D> Dla mnie struktury są niestandardowymi typami lub kontenerami, kolekcjami lub przestrzeniami nazw lub typami złożonymi, podczas gdy typdef to tylko sposób na stworzenie większej liczby pseudonimów.


0

Okazuje się, że w C99 wymagany jest typedef. Jest przestarzały, ale wiele narzędzi (ala HackRank) używa c99 jako czystej implementacji C. I tam wymagany jest typedef.

Nie twierdzę, że powinni się zmienić (może mieć dwie opcje C), jeśli wymaganie się zmieni, ci z nas, którzy przygotowują się do wywiadów na stronie, byliby SOL.


2
„Okazuje się, że typedefwymagany jest C99 ”. Co masz na myśli?
Julien Lopez

Pytanie dotyczy C, nie C++. W Ctypedefs są „wymagane” (i najprawdopodobniej zawsze będą). 'Wymagane' jak w, nie będziesz w stanie zadeklarować zmiennej, Point varName;a typ będzie synonimem struct Point;bez typedef struct Point Point;.
yyny

0

W języku programowania „C” słowo kluczowe „typedef” służy do zadeklarowania nowej nazwy dla jakiegoś obiektu (struct, tablica, funkcja… typ enum). Na przykład użyję „struct-s”. W „C” często deklarujemy „struct” poza funkcją „main”. Na przykład:

struct complex{ int real_part, img_part }COMPLEX;

main(){

 struct KOMPLEKS number; // number type is now a struct type
 number.real_part = 3;
 number.img_part = -1;
 printf("Number: %d.%d i \n",number.real_part, number.img_part);

}

Za każdym razem, gdy decyduję się na użycie typu struktury, będę potrzebować tego słowa kluczowego „struct” coś „„ nazwa ”.„ Typedef ”po prostu zmieni nazwę tego typu i będę mógł używać tej nowej nazwy w moim programie za każdym razem, gdy chcę. Nasz kod będzie więc:

typedef struct complex{int real_part, img_part; }COMPLEX;
//now COMPLEX is the new name for this structure and if I want to use it without
// a keyword like in the first example 'struct complex number'.

main(){

COMPLEX number; // number is now the same type as in the first example
number.real_part = 1;
number.img)part = 5;
printf("%d %d \n", number.real_part, number.img_part);

}

Jeśli masz jakiś obiekt lokalny (struct, tablica, wartościowy), który będzie używany w całym programie, możesz po prostu nadać mu nazwę, używając „typedef”.


-2

W ogóle w języku C struct / union / enum są makropoleceniami przetwarzanymi przez preprocesor języka C (nie myl się z preprocesorem, który traktuje „#include” i inne)

więc :

struct a
{
   int i;
};

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

Struktura b jest wydana jako coś takiego:

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

i tak w czasie kompilacji ewoluuje na stosie jako coś w rodzaju: b: int ai int i int j

dlatego też trudno jest mieć samoreferencyjne struktury, rundę preprocesora C w pętli deklaracji, która nie może się zakończyć.

typedef są specyfikatorem typu, co oznacza, że ​​przetwarzany jest tylko przez kompilator C i może on robić, co chce, w celu optymalizacji implementacji kodu asemblera. Nie wydaje też członka typu par głupio, tak jak preprocesor robi ze strukturami, ale używa bardziej złożonego referencyjnego algorytmu konstrukcji, więc konstrukcja taka jak:

typedef struct a A; //anticipated declaration for member declaration

typedef struct a //Implemented declaration
{
    A* b; // member declaration
}A;

jest dozwolone i w pełni funkcjonalne. Ta implementacja daje również dostęp do konwersji typu kompilatora i usuwa niektóre efekty błędów, gdy wątek wykonania opuszcza pole aplikacji funkcji inicjalizacji.

Oznacza to, że w C typedefs są bardziej zbliżone do klasy C ++ niż samotne struktury.


3
Nietrudno jest mieć struktury samoreferencyjne. struct foo {struct foo * next; int rzecz; }
Bernd Jendrissek

4
...co? Mówiąc preprocesor do opisania rozdzielczości structsi typedefs jest na tyle złe, ale reszta twojego pisania jest tak skomplikowane, że trudno dostać jakąkolwiek wiadomość od niego. Mogę jednak powiedzieć, że twój pomysł, że non- typedefd structnie może być zadeklarowany w przód ani użyty jako element nieprzezroczysty (wskaźnik), jest całkowicie fałszywy. W pierwszym przykładzie struct bmoże w prosty sposób zawierać struct a *, nie jest typedefwymagane. Twierdzenia, które structsą jedynie głupimi fragmentami makroekspresji i które typedefdają im rewolucyjne nowe moce, są boleśnie niepoprawne
podkreślenie_d
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.