В чем разница между макросом и константой в C ++? - PullRequest
19 голосов
/ 18 июня 2011

Мне задали этот вопрос в техническом интервью:

В чем разница между const и макросом в C ++?

Мой ответ состоял в том, что макрос является директивой препроцессора, и может быть затруднительно отлаживать приложение, если вы используете макрос, поскольку перед компиляцией он заменяется константным выражением, тогда как const может иметь идентификатор типа и легко отлаживать.

Может ли кто-нибудь указать на какое-либо другое различие, и какое следует отдать предпочтение?

EDIT:

Из документации IBM для C ++:

Ниже приведены некоторые различия между #define и квалификатором типа const:

  1. Директива #define может использоваться для создания имени для числовой, символьной или строковой константы, тогда как объект const любого типа может быть объявлен.

  2. Константный объект подчиняется правилам области видимости для переменных, тогда как константа, созданная с использованием #define, - нет. В отличие от объекта const, значение макроса не отображается в промежуточном исходном коде, используемом компилятором, потому что они развернуты внутри строки. Встроенное расширение делает значение макроса недоступным для отладчика.

  3. Макрос может использоваться в константном выражении, таком как привязка к массиву, тогда как объект const не может. (Я думаю, что нам обязательно нужно использовать макрос для определения array_size.

  4. Компилятор не проверяет тип макроса, включая аргументы макроса.

Ответы [ 7 ]

24 голосов
/ 18 июня 2011

Макросы и константы - это далеко не одно и то же, каждый из них иногда соответствует обстоятельствам, и ваш ответ только царапает поверхность разницы. Кроме того, в C ++ есть два разных вида констант.

Константу, определенную с помощью квалификатора const, лучше всего рассматривать как неизменяемую переменную . У него есть все свойства переменной: у нее есть тип, у нее есть размер, у нее есть связь, вы можете взять ее адрес. (Компилятор может оптимизировать некоторые из этих свойств, если ему это удастся: например, константы, адрес которых никогда не используется, могут не передаваться в исполняемый образ. Но это только по милости правила «как если»). ) Единственное, что вы не можете сделать с const датумом, это изменить его значение. Константа, определенная с помощью enum, немного отличается. У него есть тип и размер, но у него нет связи, вы не можете взять его адрес, а его тип уникален. Обе они обрабатываются на 7-й фазе перевода, поэтому они не могут быть чем-либо, кроме lvalue или rvalue. (Прошу прощения за жаргон в предыдущем предложении, но в противном случае мне пришлось бы написать несколько абзацев.)

Макрос имеет гораздо меньше ограничений: он может распространяться на любую последовательность токенов, если общая программа остается правильно сформированной программой. Он не имеет каких-либо свойств переменной. Применение sizeof или & к макросу может или не может сделать что-то полезное, в зависимости от того, к чему расширяется макрос. Макросы иногда задаются для расширения до числовых литералов, и такие макросы иногда рассматриваются как как константы, но это не так: «собственно компилятор» (то есть фаза перевода 7) видит их как числовые литералы .

В настоящее время обычно считается хорошей практикой не использовать макрос, когда константа подойдет. Макросы не подчиняются тем же правилам определения области действия, что и все другие идентификаторы, что может привести к путанице, и если вы используете константу, вы даете больше информации на фазу преобразования 7 и, следовательно, также отладчику. Тем не менее, макросы позволяют вам делать то, что не может быть выполнено другим способом, и если вам нужно выполнить одно из этих действий, не стесняйтесь использовать их. (Макросы, которые увеличивают вес, в этом смысле, как правило, , а не , просто расширяются до числовых литералов, хотя я не собираюсь говорить никогда.)

EDIT: Вот пример макроса, который делает что-то интересное. Он никоим образом не формирует и не формирует константу. Вполне возможно, что можно получить тот же эффект без макроса (если вы знаете тот, в котором нет струнных потоков, мне было бы интересно услышать об этом!), Но я думаю, что это хорошая иллюстрация как силы, так и опасность макросов (для последнего подумайте, что он будет делать, если его использовать вне одного очень специфического контекста ...)

static double elapsed()
{ ... }
#define ELAPSED '[' << std::fixed << std::setprecision(2) << elapsed() << "] "

// usage:
for (vector<string>::iterator f = files.begin(); f != files.end(); f++) {
    cout << ELAPSED << "reading file: " << *f << '\n';
    process_file(*f);
}
12 голосов
/ 18 июня 2011

Следует предпочесть const int sum = 1; над #define sum 1 по ряду причин:

Механизм на основе области действия:

#defines не уважает области видимости, поэтому нет способа создать пространство имен в области класса.В то время как переменные const могут быть определены в классах.

Как избежать странных магических чисел во время ошибок компиляции:

Если вы используете #define, то этозаменяется препроцессором во время предварительной компиляции. Таким образом, если вы получите ошибку во время компиляции, это будет сбивать с толку, потому что сообщение об ошибке не будет ссылаться на имя макроса, но на значение, и оно появится внезапным значением, и это потратит много времениотслеживая это в коде.

Простота отладки:

Также по тем же причинам при отладке #define действительно не поможет.
Чтобы избежать обоихвышеупомянутые ситуации const будет лучшим выбором.

2 голосов
/ 20 июня 2011

(Первоначально опубликовано для static const против # define - воспроизведение здесь, поскольку кажется, что этот вопрос имеет больше "импульса" ... дайте мне знать, если это неуместно ...)

Плюсы и минусы ко всему, в зависимости от использования:

  • consts
    • правильно определена область действия / идентификатор конфликтных проблем решен хорошо
    • сильный, одиночный, указанный пользователем тип
      • вы можете попытаться "набрать" #define ala #define S std::string("abc"), но константа избегает повторного построения различных временных в каждой точке использования
    • Осложнения одного правила определения
    • может принимать адреса, создавать на них константные ссылки и т. Д.
  • определяет
    • «глобальную» область действия / более подверженную конфликтным использованиям, что может привести ктрудно решаемые проблемы компиляции и неожиданные результаты во время выполнения, а не нормальные сообщения об ошибках;для смягчения этой ситуации требуются:
      • длинные, неясные и / или централизованно координируемые идентификаторы, и доступ к ним не может быть получен от неявного соответствия используемого / текущего / поискового Кенига пространства имен, псевдонимов пространства имен и т. д.
      • использование всех заглавных символов обычно требуется и зарезервировано для определений препроцессора (важное руководство для того, чтобы использование препроцессора масштаба предприятия оставалось управляемым, и какие сторонние библиотеки следует соблюдать), наблюдение за которым подразумевает миграцию существующих констант или перечисленийОпределение определяет изменение капитализации (и, следовательно, влияет на код клиента).(Лично я пишу с заглавной буквы первую букву перечислений, но не констант, так что я все равно попаду сюда - может быть, пора переосмыслить это.)
    • возможно больше операций времени компиляции: строкабуквальная конкатенация, строковое преобразование (принимая его размер)
      • Недостатком является то, что с учетом #define X "x" и некоторого использования клиента ala "pre" X "post" у вас возникнут проблемы, если вы хотите или вам нужно сделать X изменяемой во время выполнения переменной, а нечем константа, тогда как этот переход легче от const char* или const std::string, поскольку они уже вынуждают пользователя включать операции конкатенации.
    • не может использовать sizeof непосредственно для определенногочисловая константа
    • нетипизированный (GCC не предупреждает, если сравнивать с unsigned)
    • некоторые цепочки компилятора / компоновщика / отладчика могут не представлять идентификатор, поэтому вы будете вынуждены смотреть на "магию"числа "(строки, что угодно ...)
    • не может принимать адрес
    • замененное значение не обязательно должно быть допустимым (или дискретным) в контексте, где #definОн создается, так как он оценивается в каждой точке использования, поэтому вы можете ссылаться на еще не объявленные объекты, зависеть от «реализации», которую не нужно предварительно включать, создавать «константы», такие как { 1, 2 }, которые могут бытьиспользуется для инициализации массивов, или #define MICROSECONDS *1E-6 и т. д. ( определенно не рекомендует это!)
    • некоторые специальные вещи, такие как __FILE__ и __LINE__, могут быть включены в подстановку макросов
  • перечисления
    • возможно только для целочисленных значений
    • правильно определена область действия / конфликт идентификаторов обработан красиво
    • строго типизировано, но для большихДостаточно размера int со знаком или без знака, который вы не можете контролировать (на C ++ 03)
    • не может взять адрес
    • Более строгие ограничения использования (например, увеличение - template <typename T> void f(T t) { cout << ++t; } выиграноне компилировать)
    • тип каждой константы, взятой из вмещающего перечисления, поэтому template <typename T> void f(T) получают отчетливые экземпляры при передаче одного и того же числового значения из разных перечислений, каждый из которых отличается от любого фактического экземпляра f (int).
    • даже с typeof, нельзя ожидать, что numeric_limits предоставит полезную информацию
    • имя типа enum может появляться в различных местах в RTTI, сообщениях компилятора и т. Д. - возможно, полезно, возможно, запутывание

Как правило, я использую consts и считаю их наиболее профессиональным вариантом для общего пользования (хотя другие имеют простоту, привлекательную для этого старого ленивого программиста).

2 голосов
/ 18 июня 2011

Другое отличие состоит в том, что переменная const имеет память и на нее может ссылаться указатель.Макрос - это просто автозаполнение , которое произойдет до компиляции, поэтому имя теряется во время компиляции.

Кроме того, макрос может быть просто больше, чем константа.Это может быть выражение или что-нибудь синтаксически правильное, даже целое определение функции.

Макросы используются для отображения вариантов программирования, например, размера стека;в то время как cosnt используется для отображения констант реального мира, таких как значение Pi или е.

0 голосов
/ 29 сентября 2014

define может быть переопределено, но const будет причиной ошибки компиляции:

образец: источник: main.cpp

#define int_constance 4
#define int_constance 8 // ok, compiler will warning ( redefine macro)

const int a = 2;
const int a = 4; // redefine -> error

int main(int argc, char** argv)
{
   std::cout << int_constance ; // if remove second #define line, output will be 8

   return 0;
}
0 голосов
/ 18 июня 2011

Макросы не относятся к области видимости, и имя макроса может быть недоступно для символического отладчика. Дэн Сакс имеет довольно полную статью об относительных достоинствах макросов (нет), константных объектов и констант перечисления. Как и Стивен Дьюхерст , Сакс предпочитает константы перечисления для целочисленных значений, поскольку они не занимают места хранения (точнее, константы перечисления не имеют ни длительности хранения, ни связи).

0 голосов
/ 18 июня 2011

Макрос всегда имеет тип, например, #define FIVE 5 имеет тип int.

Преимуществом переменной const над макросом может быть использование памяти: при макросе значение может иметьбудет дублироваться везде, где он используется, переменная const не будет дублироваться в памяти.(но я не уверен в этой разнице)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...