Что такое typedef, и чем он отличается от #define?
Часть 1. Чем-то они так похожи...
#define UL unsigned long
typedef unsigned int UI;
UL var_ul=123456; //переменная типа unsigned long
UI var_ui=1234; //переменная типа unsigned int
Уверен, что почти каждый начинающий программист, изучающий С++ (или С), увидев подобные конструкции, задумывался над тем, а что же, собственно, такое #define и typedef? И чем еще отличаются они друг от друга, кроме перестановки идентификаторов местами (странно, но я сам до сих пор иногда путаю порядок подстановки)?
Ну-с... Для начала немного скучной и, наверняка, всем известной теории :)
#define - это директива (команда) препроцессора, а typedef - спецификатор (специальное служебное слово) языка. Это означает, что и применяются они в разных местах и в разное время. #define - на этапе работы препроцессора, т.е. еще до начала компиляции программы, а typedef уже на этапе собственно компиляции. А так, и то и другое, по сути своей, - текстовая подстановка или замена. Но не совсем. :) Есть тонкости, и мы их увидим дальше. :)
Директива #define заменяет представленный идентификатор заранее подготовленной последовательностью символов. Эта директива может размещаться в любом месте текста и действует (в обычном случае) от места размещения и до конца текста (а в необычном - до директивы #undef).
Идентификатор, представленный в typedef, не вводит новый тип данных, это, скорее, новое имя (синоним) для существующего типа. typedef также может появляться в любом месте программы, где потребуется ввести имя типа.
Давайте посмотрим на примере, на чем можно споткнуться, применяя столь (на первый взгляд) похожие конструкции. Особенно любопытные результаты получаются с применением указателей :)
typedef float* float_ptr;
#define FLOAT_PTR float*
float_ptr pf1, pf2; //тут все прекрасно
FLOAT_PTR PF1, PF2; //а вот тут будет не так все радужно, после препроцессора это
//будет эквивалентно вот чему: float* PF1, float PF2;
float *ff;
pf1 = ff;
pf2 = ff;
PF1 = ff; //до сих пор все хорошо
PF2 = ff; //а тут ошибка assignment to 'float' from 'float*' lacks a cast
А все потому, что препроцессор сделал простую текстовую замену :)
А вот вам еще сюрприз с константами и указателями:
Какой тип имеет переменная CSTR? Правильно - const string* (или эквивалентно string const *) - указатель на константную строку.
Так, а каков тип cstr? Напрашивается ответ - что он тоже является указателем на константную строку. Но это неправильно. Ошибка в размышлении о typedef, как о простом текстовом расширении. А это не так. :) Когда мы объявляем const p_string, то const изменяет тип p_string, который является указателем. Поэтому, это определение объявляет, что cstr будет константным указателем на строку. Определение эквивалентно string *const cstr.
Директива #define позволяет сделать такой "фокус":
#define INT_VAR unsigned int
INT_VAR i=123;
...
#define INT_VAR unsigned long
INT_VAR m=456;
И это будет компилироваться и работать. Правда, компилятор выдаст предупреждение о переопределении INT_VAR (что-то типа "INT_VAR redefined" или "Redefinition of INT_VAR is not identical", в зависимости от того, каким компилятором это делается).
А вот после применения директивы #undef компилятор и ругаться уже не будет.
#define INT_VAR unsigned int
INT_VAR i=123;
#undef INT_VAR
...
#define INT_VAR unsigned long
INT_VAR m=456;
С typedef подобный "фокус" и вовсе не пройдет. Компилятор однозначно выругается на ошибку "Error: conflicting types for 'typedef unsigned long INT_VAR' ". А спецификатора (или директивы) untypdef еще никто не придумал. :) То есть, как только некое имя использовалось как имя типа, оно уже не может быть переопределено:
typedef double Money;
class Account
{
public:
Money balance() { return bal; } //используем глобальное определение Money
private:
typedef long double Money; // error: нельзя изменять имя типа Money
Money bal;
// ...
};
Еще одно отличие - это область видимости. Для typedef онa может быть ограничена функцией, классом, или пространством имен.
Вот, наверное, и все о сходстве и различиях typedef и #define. Может, я что-то и упустил...
В следующей части речь пойдет уже только о typedef и его применении.
Если у вас возникает потребность подробного рассмотрения какого-либо вопроса по С++, пишите нам, мы попытаемся вам помочь.