Різниця між "struct" і "typedef struct" в C ++?


Відповіді:


1201

У C ++ є лише тонка різниця. Це перехід від C, в якому він має значення.

Стандарт мови C ( C89 §3.1.2.3 , C99 §6.2.3 та C11 §6.2.3 ) наказує окремі простори імен для різних категорій ідентифікаторів, включаючи ідентифікатори тегів (для struct/ union/ enum) та звичайні ідентифікатори (для typedefта інших ідентифікаторів) .

Якщо ви щойно сказали:

struct Foo { ... };
Foo x;

ви отримаєте помилку компілятора, оскільки Fooвона визначена лише в просторі імен тегів.

Вам доведеться оголосити це як:

struct Foo x;

Кожен раз, коли ви хочете звернутися до цього Foo, ви завжди повинні називати його struct Foo. Це стає дратує, тому ви можете додати typedef:

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

Тепер struct Foo(у просторі імен тегів) і просто Foo(у звичайному просторі імен ідентифікаторів) обидва посилаються на одне і те ж, і ви можете вільно оголошувати об'єкти типу Fooбез structключового слова.


Конструкція:

typedef struct Foo { ... } Foo;

- лише абревіатура для декларації та typedef.


Нарешті,

typedef struct { ... } Foo;

оголошує анонімну структуру та створює typedefдля неї. Таким чином, для цієї конструкції він не має імені в просторі імен тегів, а лише ім'я в просторі імен typedef. Це означає, що вона також не може бути оголошена вперед. Якщо ви хочете зробити переадресацію, вам потрібно вказати її ім’я в просторі імен тегів .


У C ++ всі struct/ union/ enum/ classдекларації діють так, як вони неявно typedefвідредаговані, доки ім'я не приховано іншою декларацією з тим же ім'ям. Детальну інформацію див. У відповіді Майкла Берра .


53
Хоча те, що ви говорите, є істинним, AFAIK, твердженням, 'typedef structure {...} Foo;' створює псевдонім для безіменної структури.
несподівано

25
Хороший улов, є "тонка різниця між" typedef struct Foo {...} Foo; " та "typedef structure {...} Foo;".
Адам Розенфілд

8
У C теги Stru, об'єднані теги та теги перерахування ділять один простір імен, а не (struct та union), використовуючи два, як заявлено вище; простір імен, на який посилаються імена typedef, дійсно є окремим. Це означає, що у вас не може бути обоє 'Union x {...};' та 'struct x {...};' в єдиному обсязі.
Джонатан Леффлер

10
Окрім не зовсім типової речі, ще одна різниця між двома фрагментами коду у питанні полягає в тому, що Foo може визначити конструктор у першому прикладі, а не у другому (оскільки анонімні класи не можуть визначати конструкторів чи деструкторів) .
Стів Джессоп

1
@Lazer: Там є тонкими відмінностями, але Адам означає (як він продовжує говорити) , що ви можете використовувати «Type вар» оголошувати змінні без ЬурейеЕ.
Фред Нурк

226

У цій статті про DDJ Дан Сакс пояснює одну невелику область, через яку можуть пролазити помилки, якщо ви не вводите свої структури (та класи!):

Якщо ви хочете, ви можете уявити, що C ++ генерує typedef для кожного імені тегу, наприклад

typedef class string string;

На жаль, це не зовсім точно. Я б хотів, щоб це було так просто, але це не так. C ++ не може генерувати такі typedefs для структур, об'єднань чи перерахунків без введення несумісності з C.

Наприклад, припустимо, що програма C оголошує як функцію, так і структуру з назвою статусу:

int status(); struct status;

Знову ж, це може бути поганою практикою, але це C. У цій програмі статус (сам по собі) посилається на функцію; статус struct відноситься до типу.

Якщо C ++ автоматично генерував typedefs для тегів, тоді, коли ви компілювали цю програму як C ++, компілятор генерував би:

typedef struct status status;

На жаль, назва цього типу суперечить імені функції, і програма не буде компілюватися. Тому C ++ не може просто створити typedef для кожного тегу.

У C ++ теги діють так само, як імена typedef, за винятком того, що програма може оголосити об'єкт, функцію чи нумератор з тим самим іменем та тією самою областю, що і тег. У такому випадку ім'я об'єкта, функції або перелічувача приховує ім'я тега. Програма може посилатися на ім'я тега лише за допомогою клавіш ключових слів, struct, union або enum (відповідно) перед іменем тегу. Ім'я типу, що складається з одного з цих ключових слів, за яким слідує тег, є розробленим специфікатором типу. Наприклад, статус структури та місяць перерахунку - це розроблені специфікатори типу.

Таким чином, програма C, яка містить обидва:

int status(); struct status;

поводиться так само, коли компілюється як C ++. Сам статус імені відноситься до функції. Програма може посилатися на тип лише за допомогою розробленого статусу структури структури-специфікатора.

Тож як це дозволяє помилкам проникати в програми? Розгляньте програму в Лістингу 1 . Ця програма визначає клас foo з конструктором за замовчуванням та оператором перетворення, який перетворює об'єкт foo в char const *. Вираз

p = foo();

в основному слід побудувати об'єкт foo та застосувати оператор перетворення. Подальший висновок виводу

cout << p << '\n';

має відображати клас foo, але це не так. Він відображає функцію foo.

Цей дивовижний результат виникає тому, що програма включає в себе заголовок lib.h, показаний у Лістингу 2 . Цей заголовок визначає функцію, яка також називається foo. Ім'я функції foo приховує ім'я класу foo, тому посилання на foo в основному стосується функції, а не класу. main може посилатися на клас лише за допомогою розробленого специфікатора типу, як у

p = class foo();

Спосіб уникнення такої плутанини у всій програмі полягає в тому, щоб додати наступний typedef для імені класу foo:

typedef class foo foo;

безпосередньо перед або після визначення класу. Цей typedef викликає конфлікт між ім'ям типу foo та ім'ям функції foo (з бібліотеки), що спричинить помилку часу компіляції.

Я не знаю нікого, хто насправді пише ці typedefs як зрозуміло. Це вимагає великої дисципліни. Оскільки частота помилок, таких як одна в Лістингу 1 , мабуть, досить мала, багато хто ніколи не стикаються з цією проблемою. Але якщо помилка у вашому програмному забезпеченні може спричинити тілесні ушкодження, тоді ви повинні написати typedefs незалежно від того, наскільки це малоймовірно помилка.

Я не можу уявити, чому хто-небудь хотів би приховати ім’я класу з функцією або ім'ям об'єкта в тому ж обсязі, що і клас. Правила приховування в C були помилкою, і їх не слід було поширювати на класи в C ++. Дійсно, ви можете виправити помилку, але це вимагає додаткової дисципліни програмування та зусиль, які не повинні бути необхідними.


11
У випадку, якщо ви спробуєте "class foo ()", і це не вдається: В ISO C ++ "class foo ()" є незаконною конструкцією (стаття була написана '97, перед стандартизацією, здається). Ви можете поставити "typedef class foo foo;" в основному, тоді ви можете сказати "foo ();" (оскільки тоді ім'я typedef лексично ближче, ніж ім'я функції). Синтаксично в T () T повинен бути специфікатором простого типу. розроблені специфікатори типу не дозволяються. Але це, звичайно, хороша відповідь.
Йоханнес Шауб - ліб

Listing 1і Listing 2посилання порушені. Гляньте.
Prasoon Saurav

3
Якщо ви використовуєте різні умови іменування для класів і функцій, ви також уникаєте конфлікту імен, не потребуючи додавання додаткових типів.
zstewart

64

Ще одна важлива відмінність: typedefs не може бути оголошено вперед. Отже, для typedefпараметра ви повинні мати #includeфайл, що містить typedef, тобто все, що є #includeу вас, .hтакож включає цей файл, незалежно від того, потрібен він чи ні, і так далі. Це однозначно може вплинути на час вашої збірки на більші проекти.

Без цього typedefв деяких випадках ви можете просто додати попереднє оголошення struct Foo;вгорі вашого .hфайлу та лише #includeвизначення структури у вашому .cppфайлі.


Чому викриття визначення структури впливає на час побудови? Чи робить компілятор додаткові перевірки, навіть якщо цього не потрібно (враховуючи параметр typedef, щоб компілятор знав визначення), коли бачить щось на зразок Foo * nextFoo; ?
Багатий

3
Це не дуже додаткова перевірка, це просто більше коду, з яким повинен працювати компілятор. Для кожного файлу cpp, який стикається з цим typedef десь у його складі, є ланцюг, його компілювання typedef. У великих проектах файл .h, що містить typedef, може легко скластись у сотні разів, хоча попередньо складені заголовки дуже допомагають. Якщо ви можете піти з використання прямої декларації, тоді простіше обмежити включення .h, що містить повну специфікацію структури, лише кодом, який дійсно хвилює, і тому відповідний файл включення збирається рідше.
Джо

будь ласка @ попередній коментатор (крім власника допису). Я майже не пропустив ур відповідь. Але дякую за інформацію.
Багатий

Це вже не вірно в C11, де ви можете кілька разів набрати одне і те ж структуру з тим самим іменем.
michaelmeyer

32

Там є різниця, але тонкий. Подивіться на це так: struct Fooвводить новий тип. Другий створює псевдонім під назвою Foo (а не новий тип) для безіменного structтипу.

7.1.3 Специфікатор typedef

1 [...]

Ім'я, оголошене за допомогою специфікатора typedef, стає іменем typedef. В межах декларації ім'я typedef є синтаксично еквівалентним ключовому слову та називає тип, пов'язаний з ідентифікатором, у спосіб, описаний у пункті 8. Ім'я typedef, таким чином, є синонімом іншого типу. Ім'я typedef не вводить новий тип так, як це робить оголошення класу (9.1) або enum.

8 Якщо декларація typedef визначає неназваний клас (або enum), перше ім'я typedef, оголошене декларацією, є типом класу (або enum type), що використовується для позначення типу класу (або типу enum) лише для цілей зв'язку ( 3.5). [Приклад:

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

Отже, typedef завжди використовується як заповнювач / синонім іншого типу.


10

Ви не можете використовувати переадресацію із структурою typedef.

Сама структура анонімного типу, тому у вас немає власного імені для переадресації.

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

Попередня декларація, як ця робота:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

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

Я не отримую другу помилку для прототипу функції. Чому на ньому написано "переосмислення; різні основні типи"? компілятору не потрібно знати, як виглядає визначення myStruct, правда? Незалежно від узятого з фрагмента коду (typedef один або прямого оголошення), myStruct позначає тип структури, правда?
Багатий

@Rich Скаржиться на те, що існує конфлікт імен. Існує пряма декларація, що говорить "шукайте структуру під назвою myStruct", а потім є typedef, який перейменовує безіменну структуру в "myStruct".
Йочай Тіммер

Ви маєте на увазі помістити як typedef, так і переадресацію в один файл? Я це зробив, і gcc склав це чудово. myStruct правильно трактується як безіменна структура. Тег myStructживе в просторі імен тегів, а typedef_ed myStructживе в звичайному просторі імен, де живуть інші ідентифікатори, такі як ім'я функції, імена локальних змінних. Отже, конфлікту не повинно бути.
Багатий

@ Rich GCC дає ту саму помилку, текст дещо змінюється: gcc.godbolt.org/…
Timmer

Я думаю, я розумію, що, якщо у вас є лише typedefпереадресація декларації з typedefім'ям ed, не посилається на безіменну структуру. Натомість пряма декларація оголошує неповну структуру з тегом myStruct. Крім того, не бачачи визначення typedef, прототип функції, що використовує typedefім'я ed, не є законним. Таким чином, ми повинні включати весь typedef, коли нам потрібно використовувати myStructдля позначення типу. Виправте мене, якщо я неправильно зрозумів u. Дякую.
Багатий

0

Важлива відмінність 'typedef structure' від 'struct' у C ++ полягає в тому, що ініціалізація вбудованих членів у структурах 'typedef' не буде працювати.

// 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
Неправда. В обох випадках xініціалізується. Дивіться тест в Coliru online IDE (я ініціалізував його до 42, тому очевидно, ніж з нуля, що призначення дійсно відбулося).
Колін Д Беннетт

Власне, я тестував його в Visual Studio 2013, і він не був ініціалізований. З цією проблемою ми зіткнулися у виробничому коді. Усі компілятори різні та мають відповідати лише певним критеріям.
користувач2796283

-3

У C ++ різниці немає, але я вважаю, що це дозволить вам оголосити екземпляри структури Foo, не роблячи явного:

struct Foo bar;

3
Подивіться на відповідь @ dirkgently --- є різниця, але це тонко.
Кіт Пінсон

-3

Структура полягає у створенні типу даних. Typedef - це встановити псевдонім для типу даних.


12
Ви не зрозуміли питання.
Зимовий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.