Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Клуб профессиональных программистов :: Выпуск #11




Что такое 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

А все потому, что препроцессор сделал простую текстовую замену :)

А вот вам еще сюрприз с константами и указателями:

typedef string*  p_string;
#define P_STRING string*

const p_string cstr;
const P_STRING CSTR;

Какой тип имеет переменная 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 и его применении.

Если у вас возникает потребность подробного рассмотрения какого-либо вопроса по С++, пишите нам, мы попытаемся вам помочь.

Сергей Малышев (aka Михалыч).

А теперь прощаемся с Вами до следующего выпуска.

                                        С уважением, команда Клуба.




В избранное