Статья
Версия для печати
Обсудить на форуме
Что такое 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 Михалыч).
Версия для печати
Обсудить на форуме