typedef - зарезервированное ключевое слово в языках программирования C и C ++. Он используется для создания дополнительного имени ( псевдонима) для другого типа данных, но не создает новый тип, за исключением неясного случая квалифицированного typedef для типа массива, где квалификаторы typedef передаются типу элемента массива. Таким образом, он часто используется для упрощения синтаксиса объявления сложных структур данных, состоящих из типов структур и объединений, но столь же распространен при предоставлении конкретных описательных имен типов для целочисленных типов данных различной длины.
Синтаксис объявления typedef:
typedef
тип-объявление ;Имя нового псевдонима типа следует тому же синтаксису, что и объявление любого другого идентификатора C, поэтому в более подробной форме:
typedef
идентификатор определения типаВ стандартной библиотеке C и в спецификациях POSIX идентификатор для определения typedef часто дополняется суффиксом _t
, например size_t и time_t. Это практикуется в других системах кодирования, хотя POSIX явно резервирует эту практику для типов данных POSIX.
typedef int length;
Это создает тип length
как синоним типа int
.
Объявление typedef может использоваться в качестве документации, указывая значение переменной в контексте программирования, например, оно может включать выражение единицы измерения или количества. Общие объявления,
int current_speed; int high_score; void congratulate(int your_score) { if (your_score gt; high_score) { //... } }
может быть выражено объявлением типов, зависящих от контекста:
typedef int km_per_hour; typedef int points; // `km_per_hour` is synonymous with `int` here, and thus, the compiler treats // our new variables as integers. km_per_hour current_speed; points high_score; void congratulate(points your_score) { if (your_score gt; high_score) { //... } }
Оба раздела кода выполняются одинаково. Однако использование объявлений typedef во втором блоке кода дает понять, что две переменные, представляющие один и тот же тип данных int
, хранят разные или несовместимые данные. Определения в congratulate()
о your_score
указует программист этого current_speed
(или любого другим переменной, не объявленные как points
) не должны быть переданы в качестве аргумента. Это было бы не так очевидно, если бы обе они были объявлены как переменные типа int
данных. Однако это указание только для программиста ; компилятор C / C ++ считает, что обе переменные относятся к типу, int
и не помечает предупреждения о несоответствии типов или ошибки для «неправильных» типов аргументов congratulate(points your_score)
в приведенном ниже фрагменте кода:
void foo() { km_per_hour km100 = 100; congratulate(km100); }
Typedef может использоваться для упрощения объявления составного типа ( структура, объединение ) или типа указателя. Например,
struct MyStruct { int data1; char data2; };
Это определяет тип данных struct MyStruct
. Объявление переменной этого типа в C также требует ключевого слова struct
, но его можно опустить в C ++:
struct MyStruct a;
Объявление typedef устраняет необходимость указания struct
в C. Например, объявление
typedef struct MyStruct newtype;
сводится к:
newtype a;
Объявление структуры и typedef также можно объединить в один оператор:
typedef struct MyStruct { int data1; char data2; } newtype;
Или это можно использовать следующим образом:
typedef struct { int data1; char data2; } newtype;
В C ++, в отличие от C, ключевые слова struct
, class
и enum
являются необязательными в объявлениях переменных, которые отделены от определений, если нет двусмысленности с другим идентификатором:
struct MyStruct x; MyStruct y;
Таким образом, MyStruct
можно использовать везде, где только newtype
можно. Однако обратное неверно; например, методы конструктора для MyStruct
не могут быть названы newtype
.
Печально известный пример, когда даже C ++ нуждается в struct
ключевом слове, - это системный вызов POSIX stat, который использует структуру с тем же именем в своих аргументах:
int stat(const char *filename, struct stat *buf) { //... }
Здесь и C, и C ++ нуждаются в struct
ключевом слове в определении параметра.
Typedef может использоваться для определения нового типа указателя.
typedef int *intptr; intptr ptr; // Same as: // int *ptr;
intptr
это новый псевдоним с типом указателя int *
. Определение intptr ptr;
,, определяет переменную ptr
с типом int *
. Итак, ptr
это указатель, который может указывать на переменную типа int
.
Использование typedef для определения нового типа указателя иногда может привести к путанице. Например:
typedef int *intptr; // Both 'cliff' and 'allen' are of type int*. intptr cliff, allen; // 'cliff2' is of type int*, but 'allen2' is of type int**. intptr cliff2, *allen2; // Same as: // intptr cliff2; // intptr *allen2;
Выше intptr cliff, allen;
означает определение 2 переменных с int*
типом для обеих. Это связано с тем, что тип, определенный с помощью typedef, является типом, а не расширением. Другими словами, intptr
который является int*
типом, украшает и cliff
и allen
. Для intptr cliff2, *allen2;
, то intptr
типа украшает cliff2
и *allen2
. Таким образом, intptr cliff2, *allen2;
эквивалентно двум отдельным определениям intptr cliff2;
и intptr *allen2
. intptr *allen2
означает, что allen2
это указатель, указывающий на память с int*
типом. Вкратце, allen2
имеет тип int**
.
Typedefs также может упростить определения или объявления для типов указателей на структуру. Учти это:
struct Node { int data; struct Node *nextptr; };
Используя typedef, приведенный выше код можно переписать следующим образом:
typedef struct Node Node; struct Node { int data; Node *nextptr; };
В C можно объявить несколько переменных одного и того же типа в одном операторе, даже смешивая структуру с указателем или не указателями. Тем не менее, нужно будет поставить звездочку перед каждой переменной, чтобы обозначить ее как указатель. Далее программист может предположить, что errptr
это действительно файл. Node *
Но опечатка означает, что errptr
это Node
. Это может привести к незначительным синтаксическим ошибкам.
struct Node *startptr, *endptr, *curptr, *prevptr, errptr, *refptr;
Путем определения typedef Node *
гарантируется, что все переменные являются типами указателей на структуру, или, скажем, каждая переменная является типом указателя, указывающим на тип структуры.
typedef struct Node* NodePtr; NodePtr startptr, endptr, curptr, prevptr, errptr, refptr;
int do_math(float arg1, int arg2) { return arg2; } int call_a_func(int (*call_this)(float, int)) { int output = call_this(5.5, 7); return output; } int final_result = call_a_func(amp;do_math);
Предыдущий код можно переписать со спецификациями typedef:
typedef int (*MathFunc)(float, int); int do_math(float arg1, int arg2) { return arg2; } int call_a_func(MathFunc call_this) { int output = call_this(5.5, 7); return output; } int final_result = call_a_func(amp;do_math);
Вот MathFunc
новый псевдоним типа. A MathFunc
- указатель на функцию, которая возвращает целое число и принимает в качестве аргументов число с плавающей запятой, за которым следует целое число.
Когда функция возвращает указатель на функцию, без typedef это может быть еще более запутанным. Ниже приведен прототип функции signal (3) из FreeBSD :
void (*signal(int sig, void (*func)(int)))(int);
Объявление функции выше является загадочным, поскольку оно не ясно показывает, что функция принимает в качестве аргументов или тип, который она возвращает. Начинающий программист может даже предположить, что функция принимает единственный int
аргумент и ничего не возвращает, но на самом деле ей также нужен указатель на функцию и она возвращает другой указатель на функцию. Более чисто можно написать:
typedef void (*sighandler_t)(int); sighandler_t signal(int sig, sighandler_t func);
Typedef также можно использовать для упрощения определения типов массивов. Например,
typedef char arrType[6]; arrType arr = {1, 2, 3, 4, 5, 6}; arrType *pArr; // Same as: // char arr[6] = {1, 2, 3, 4, 5, 6}; // char (*pArr)[6];
Вот arrType
новый псевдоним для char[6]
типа, который представляет собой тип массива с 6 элементами. Для arrType *pArr;
, pArr
это указатель, указывающий на память char[6]
типа.
Typedef создается с использованием синтаксиса определения типа, но может использоваться, как если бы он был создан с использованием синтаксиса приведения типов. ( Приведение типов изменяет тип данных.) Например, в каждой строке после первой строки:
// `funcptr` is a pointer to a function which takes a `double` and returns an `int`. typedef int (*funcptr)(double); // Valid in both C and C++. funcptr x = (funcptr) NULL; // Only valid in C++. funcptr y = funcptr(NULL); funcptr z = static_castlt;funcptrgt;(NULL);
funcptr
используется в левой части для объявления переменной и используется в правой части для приведения значения. Таким образом, typedef может использоваться программистами, которые не хотят выяснять, как преобразовать синтаксис определения в синтаксис приведения типов.
Без typedef, как правило, невозможно взаимозаменяемо использовать синтаксис определения и синтаксис приведения. Например:
void *p = NULL; // This is legal. int (*x)(double) = (int (*)(double)) p; // Left-hand side is not legal. int (*)(double) y = (int (*)(double)) p; // Right-hand side is not legal. int (*z)(double) = (int (*p)(double));
В C ++ имена типов могут быть сложными, а typedef предоставляет механизм для присвоения типу простого имени.
std::vectorlt;std::pairlt;std::string, intgt;gt; values; for (std::vectorlt;std::pairlt;std::string, intgt;gt;::const_iterator i = values.begin(); i != values.end(); ++i) { std::pairlt;std::string, intgt; const amp; t = *i; //... }
а также
typedef std::pairlt;std::string, intgt; value_t; typedef std::vectorlt;value_tgt; values_t; values_t values; for (values_t::const_iterator i = values.begin(); i != values.end(); ++i) { value_t const amp; t = *i; //... }
В C ++ 11 появилась возможность выражать typedef с помощью using
вместо typedef
. Например, два вышеупомянутых typedef могут быть эквивалентно записаны как
using value_t = std::pairlt;std::string, intgt;; using values_t = std::vectorlt;value_tgt;;
C ++ 03 не предоставляет шаблонных определений типов. Например, чтобы stringpairlt;Tgt;
представлять std::pairlt;std::string, Tgt;
для каждого типа T
один не может использовать:
templatelt;typename Tgt; typedef std::pairlt;std::string, Tgt; stringpairlt;Tgt;; // Doesn't work
Однако, если кто-то желает принять stringpairlt;Tgt;::type
вместо stringpairlt;Tgt;
, то можно достичь желаемого результата с помощью typedef внутри неиспользуемого в противном случае шаблонного класса или структуры:
templatelt;typename Tgt; class stringpair { private: // Prevent instantiation of `stringpairlt;Tgt;`. stringpair(); public: // Make `stringpairlt;Tgt;::type` represent `std::pairlt;std::string, Tgt;`. typedef std::pairlt;std::string, Tgt; type; }; // Declare a variable of type `std::pairlt;std::string, intgt;`. stringpairlt;intgt;::type my_pair_of_string_and_int;
В C ++ 11 шаблонные определения типов добавляются со следующим синтаксисом, для которого требуется using
ключевое слово, а не typedef
ключевое слово. (См. Псевдонимы шаблонов. )
template lt;typename Tgt; using stringpair = std::pairlt;std::string, Tgt;; // Declare a variable of type `std::pairlt;std::string, intgt;`. stringpairlt;intgt; my_pair_of_string_and_int;
В SystemVerilog typedef ведет себя точно так же, как в C и C ++.
Во многих функциональных языках со статической типизацией, таких как Haskell, Miranda, OCaml и т. Д., Можно определять синонимы типов, которые аналогичны определениям типов в C. Пример в Haskell:
type PairOfInts = (Int, Int)
В этом примере синоним типа определен PairOfInts
как целочисленный тип.
В Seed7 определение постоянного типа используется для введения синонима типа:
const type: myVector is array integer;
В Swift typealias
ключевое слово используется для создания typedef:
typealias PairOfInts = (Int, Int)
C # содержит функцию, аналогичную typedef или using
синтаксису C ++.
using newType = global::System.Runtime.Interop.Marshal; using otherType = Enums.MyEnumType; using StringListMap = System.Collections.Generic.Dictionarylt;string, System.Collections.Generic.Listlt;stringgt;gt;;
В D ключевое слово alias
позволяет создавать синонимы типа или частичного типа.
struct Foo(T){} alias FooInt = Foo!int; alias Fun = int delegate(int);
Керниган и Ричи указали две причины использования typedef. Во-первых, он предоставляет средства, позволяющие сделать программу более переносимой или более простой в обслуживании. Вместо того, чтобы изменять тип при каждом появлении в исходных файлах программы, нужно изменить только один оператор typedef. size_t и ptrdiff_t в lt;stdlib.hgt; являются такими именами typedef. Во-вторых, определение типа может облегчить понимание сложного определения или объявления.
Некоторые программисты выступают против широкого использования определений типов. Большинство аргументов основано на идее, что typedef просто скрывает фактический тип данных переменной. Например, Грег Кроа-Хартман, хакер ядра Linux и разработчик документации, не рекомендует использовать их для чего-либо, кроме объявления прототипов функций. Он утверждает, что такая практика не только излишне запутывает код, но также может привести к тому, что программисты будут случайно неправильно использовать большие структуры, считая их простыми типами.