В C ++ есть ли разница между:
struct Foo { ... };
а также:
typedef struct { ... } Foo;
В C ++ есть ли разница между:
struct Foo { ... };
а также:
typedef struct { ... } Foo;
Ответы:
В 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;
В любое время, когда вы хотите сослаться на a Foo
, вы всегда должны называть это a 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
« под ред, пока имя не спрятался другой декларации с тем же именем. См . Ответ Майкла Берра для полной информации.
В этой статье о DDJ Дэн Сакс объясняет одну маленькую область, в которой могут появляться ошибки, если вы не наберете определение своих структур (и классов!):
Если вы хотите, вы можете представить, что C ++ генерирует typedef для каждого имени тега, например
typedef class string string;
К сожалению, это не совсем точно. Хотелось бы, чтобы все было так просто, но это не так. C ++ не может генерировать такие определения типов для структур, объединений или перечислений, не вводя несовместимости с C.
Например, предположим, что программа на C объявляет как функцию, так и структуру с именем status:
int status(); struct status;
Опять же, это может быть плохой практикой, но это C. В этой программе статус (сам по себе) относится к функции; Структура состояния относится к типу.
Если C ++ автоматически сгенерировал typedef для тегов, то, когда вы скомпилировали эту программу как C ++, компилятор сгенерирует:
typedef struct status status;
К сожалению, это имя типа будет конфликтовать с именем функции, и программа не будет компилироваться. Вот почему C ++ не может просто сгенерировать typedef для каждого тега.
В C ++ теги действуют так же, как имена typedef, за исключением того, что программа может объявлять объект, функцию или перечислитель с тем же именем и той же областью действия, что и тег. В этом случае имя объекта, функции или перечислителя скрывает имя тега. Программа может ссылаться на имя тега только с помощью ключевого слова class, struct, union или enum (в зависимости от ситуации) перед именем тега. Имя типа, состоящее из одного из этих ключевых слов, за которым следует тег, является подробным спецификатором типа. Например, struct status и enum month являются уточненными спецификаторами типов.
Таким образом, программа на C, которая содержит оба:
int status(); struct status;
ведет себя так же, когда компилируется как C ++. Только название статуса относится к функции. Программа может ссылаться на тип, только используя структуру развитого спецификатора типа.
Так как же это позволяет ошибкам проникать в программы? Рассмотрим программу в листинге 1 . Эта программа определяет класс foo с помощью конструктора по умолчанию и оператора преобразования, который преобразует объект foo в char const *. Выражение
p = foo();
В main следует создать объект foo и применить оператор преобразования. Последующий выходной оператор
cout << p << '\n';
должен отображать класс foo, но это не так. Отображает функцию foo.
Этот неожиданный результат происходит потому, что программа включает заголовок lib.h, показанный в листинге 2 . Этот заголовок определяет функцию также с именем foo. Имя функции foo скрывает имя класса foo, поэтому ссылка на foo в main ссылается на функцию, а не на класс. main может ссылаться на класс только с помощью разработанного спецификатора типа, как в
p = class foo();
Чтобы избежать путаницы во всей программе, добавьте следующий typedef для имени класса foo:
typedef class foo foo;
непосредственно перед или после определения класса. Этот typedef вызывает конфликт между именем типа foo и именем функции foo (из библиотеки), что приведет к ошибке времени компиляции.
Я не знаю никого, кто бы действительно писал эти typedef как само собой разумеющееся. Это требует много дисциплины. Поскольку количество ошибок, таких как в листинге 1 , вероятно, довольно мало, многие из вас никогда не сталкиваются с этой проблемой. Но если ошибка в вашем программном обеспечении может привести к травмам, вы должны написать typedefs независимо от того, насколько вероятна ошибка.
Я не могу представить, почему кто-то захочет скрыть имя класса с именем функции или объекта в той же области, что и класс. Правила сокрытия в C были ошибкой, и их не следовало распространять на классы в C ++. Действительно, вы можете исправить ошибку, но это требует дополнительной дисциплины программирования и усилий, которые не должны быть необходимыми.
Listing 1
и Listing 2
ссылки не работают. Посмотри.
Еще одно важное отличие: typedef
s не может быть объявлено заранее. Таким образом, для этой typedef
опции вы должны указать #include
файл, содержащий typedef
все, что у #include
вас есть, .h
и этот файл, независимо от того, нужен он ему напрямую или нет, и так далее. Это может определенно повлиять на время сборки больших проектов.
Без typedef
, в некоторых случаях вы можете просто добавить предварительное объявление struct Foo;
в верхней части вашего .h
файла, и только #include
определение структуры в вашем .cpp
файле.
Там является разница, но тонкий. Посмотрите на это так: struct Foo
вводит новый тип. Второй создает псевдоним Foo (а не новый тип) для безымянного struct
типа.
7.1.3 Спецификатор typedef
1 [...]
Имя, объявленное с помощью спецификатора typedef, становится typedef-name. В рамках своей декларации typedef-name синтаксически эквивалентно ключевому слову и называет тип, связанный с идентификатором, способом, описанным в разделе 8. Таким образом, typedef-name является синонимом для другого типа. Typedef-name не вводит новый тип, как объявление класса (9.1) или enum.
8 Если объявление typedef определяет безымянный класс (или enum), первое имя typedef-типа, объявленное объявлением как этот тип класса (или тип enum), используется для обозначения типа класса (или типа enum) только для целей связывания ( 3,5). [ Пример:
typedef struct { } *ps, S; // S is the class name for linkage purposes
Таким образом, typedef всегда используется как заполнитель / синоним для другого типа.
Вы не можете использовать предварительное объявление со структурой 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_ed myStruct
живет в обычном пространстве имен, в котором живут другие идентификаторы, такие как имя функции, имена локальных переменных. Поэтому не должно быть никакого конфликта. Я могу показать вам мой код, если вы сомневаетесь, есть ли в нем какая-либо ошибка.
typedef
объявление с typedef
именем ed, это не относится к безымянной структуре. Вместо этого предварительное объявление объявляет неполную структуру с тегом myStruct
. Также, не видя определения typedef
, прототип функции, использующий typedef
имя ed, является недопустимым. Таким образом, мы должны включать весь typedef всякий раз, когда нам нужно использовать myStruct
для обозначения типа. Поправь меня, если я тебя неправильно понял. Спасибо.
Важное различие между «typedef struct» и «struct» в C ++ заключается в том, что встроенная инициализация члена в «typedef structs» не будет работать.
// 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
инициализируется. См. Тест в онлайн-среде Coliru (я инициализировал его как 42, так что это более очевидно, чем с нулем, что назначение действительно имело место).
В C ++ нет никакой разницы, но я верю, что C позволит вам объявлять экземпляры структуры Foo без явного выполнения:
struct Foo bar;