Зачем объявлять переменную в одной строке, а присваивать ей в следующей?

101

Я часто вижу в коде C и C ++ следующее соглашение:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

вместо

some_type val = something;
some_type *ptr = &something_else;

Сначала я предполагал, что это привычка, оставшаяся со времен, когда вам приходилось объявлять все локальные переменные в верхней части области видимости. Но я научился не бросать так быстро привычки разработчиков-ветеранов. Итак, есть ли веская причина для объявления в одной строке и последующего назначения?

Джонатан Стерлинг
источник
12
+1 за «Я научился так быстро не отказываться от привычек разработчиков-ветеранов». Это мудрый урок.
подстановочный

Ответы:

92

С

В C89 все объявления должны были находиться в начале области видимости ( { ... }), но это требование было быстро отброшено (сначала с расширениями компилятора, а затем со стандартом).

C ++

Эти примеры не совпадают. some_type val = something;вызывает конструктор копирования, в то время как val = something;вызывает конструктор по умолчанию, а затем operator=функцию. Это различие часто критично.

Привычки

Некоторые люди предпочитают сначала объявлять переменные, а затем определять их, в случае, если они переформатируют свой код позже с объявлениями в одном месте и определением в другом.

Что касается указателей, некоторые люди просто имеют привычку инициализировать каждый указатель NULLили nullptrнезависимо от того, что они делают с этим указателем.

orlp
источник
1
Большое отличие для C ++, спасибо. Как насчет простой C?
Джонатан Стерлинг
13
Тот факт, что MSVC по-прежнему не поддерживает объявления, кроме как в начале блока, когда он компилируется в режиме C, является источником бесконечного раздражения для меня.
Майкл Берр
5
@Michael Burr: Это потому, что MSVC вообще не поддерживает C99.
orlp
3
«some_type val = кое-что; вызывает конструктор копирования»: он может вызывать конструктор копирования, но стандарт позволяет компилятору исключить конструкцию по умолчанию для хранимой копии, конструкцию копии val и уничтожение временной и непосредственно конструировать val, используя some_typeконструктор, принимающий в somethingкачестве единственного аргумента. Это очень интересный и необычный крайний случай в C ++ ... это означает, что существует презумпция семантического значения этих операций.
2
@Aerovistae: для встроенных типов они одинаковы, но не всегда можно сказать о пользовательских типах.
orlp
27

Вы пометили свой вопрос C и C ++ одновременно, в то время как ответ значительно отличается в этих языках.

Во-первых, формулировка названия вашего вопроса неверна (или, точнее, не имеет отношения к самому вопросу). В обоих ваших примерах переменная объявляется и определяется одновременно в одну строку. Разница между вашими примерами в том, что в первом случае переменные либо остаются неинициализированными, либо инициализируются фиктивным значением, а затем ему присваивается значимое значение. Во втором примере переменные инициализируются сразу.

Во-вторых, в языке C ++, как заметил @nightcracker в своем ответе, эти две конструкции семантически различны. Первый опирается на инициализацию, а второй - на присвоение. В C ++ эти операции перегружаются и поэтому могут потенциально привести к различным результатам (хотя можно заметить, что создание неэквивалентных перегрузок инициализации и присваивания не является хорошей идеей).

В оригинальном стандартном языке C (C89 / 90) запрещено объявлять переменные в середине блока, поэтому вы можете увидеть переменные, объявленные неинициализированными (или инициализированные фиктивными значениями) в начале блока, а затем присвоенные значимым значения позже, когда эти значимые значения становятся доступными.

В языке C99 можно объявить переменные в середине блока (как в C ++), что означает, что первый подход необходим только в некоторых конкретных ситуациях, когда инициализатор неизвестен в точке объявления. (Это относится и к C ++).

Муравей
источник
2
@Jonathan Sterling: я читаю твои примеры. Возможно, вам нужно освежить стандартную терминологию языков C и C ++. В частности, о терминах объявления и определения , которые имеют конкретные значения в этих языках. Я повторю это снова: в обоих ваших примерах переменные объявлены и определены в одной строке. В C / C ++ строка some_type val;сразу объявляет и определяет переменную val. Это то, что я имел в виду в своем ответе.
1
Я понимаю, что вы там имеете в виду. Вы определенно правы в том, что объявляете и определяете, что я довольно бессмысленен, как я их использовал. Я надеюсь, что вы примите мои извинения за плохую формулировку и плохо продуманный комментарий.
Джонатан Стерлинг
1
Итак, если согласие заключается в том, что «объявлять» - неправильное слово, я бы предложил, чтобы кто-то с лучшим знанием стандарта, чем я, отредактировал страницу викибук.
Джонатан Стерлинг
2
В любом другом контексте объявление было бы правильным словом, но так как объявление - это четко определенное понятие , с последствиями, в C и C ++ вы не можете использовать его так свободно, как в других контекстах.
orlp
2
@ybungalobill: Вы не правы. Объявление и определение в C / C ++ не являются взаимоисключающими понятиями. На самом деле, определение - это просто конкретная форма декларации . Каждое определение является объявлением одновременно (за редким исключением). Существуют определяющие объявления (то есть определения) и не определяющие объявления. Более того, обычно объявление therm используется все время (даже если оно является определением), за исключением случаев, когда различие между ними является критическим.
13

Я думаю, что это старая привычка, оставшаяся от времен "местной декларации". И поэтому как ответ на ваш вопрос: нет, я не думаю, что есть веская причина. Я никогда не делаю это сам.


источник
4

Я сказал кое-что об этом в своем ответе на вопрос Helium3 .

По сути, я говорю, что это наглядное пособие, чтобы легко увидеть, что изменилось.

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

а также

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}
PMG
источник
4

Другие ответы довольно хороши. В Си есть некоторая история об этом. В C ++ есть разница между конструктором и оператором присваивания.

Я удивлен, что никто не упоминает дополнительный пункт: хранение объявлений отдельно от использования переменной иногда может быть намного более читабельным.

Визуально говоря, при чтении кода более приземленные артефакты, такие как типы и имена переменных, не являются тем, что бросается в глаза. Это заявления, которые вы обычно больше всего интересуете, тратите большую часть времени, уставившись на них, и поэтому есть тенденция взглянуть на все остальное.

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

Это может показаться немного нелогичным, но это один из примеров, когда ваш источник занимает больше вертикального пространства, что может сделать его лучше. Я вижу это как сродни тому, почему вы не должны писать переполненные строки, которые делают сумасшедшие арифметические действия и присваивания указателей в узком вертикальном пространстве - только потому, что язык позволяет вам сойти с рук с такими вещами, не означает, что вы должны делать это все время. :-)

asveikau
источник
2

В C это было стандартной практикой, потому что переменные должны были объявляться в начале функции, в отличие от C ++, где она могла быть объявлена ​​в любом месте тела функции для последующего использования. Указатели были установлены в 0 или NULL, потому что он просто проверял, что указатель не указывает на мусор. В противном случае, я не могу придумать существенного преимущества, которое заставляет любого делать это.

Vite Falcon
источник
2

Плюсы для локализации определений переменных и их значимой инициализации:

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

  • может быть более эффективным

    • позволяет избежать накладных расходов на установку начального значения (конструирование по умолчанию или инициализация некоторого значения часового типа, например, NULL)
    • operator= иногда может быть менее эффективным и требовать временного объекта
    • иногда (особенно для встроенных функций) оптимизатор может устранить некоторые / все недостатки

  • минимизация области видимости переменных в свою очередь минимизирует среднее количество переменных одновременно в области видимости :

    • делает его легче мысленно отслеживать переменные в объеме, потоки выполнения и утверждения , которые могут повлиять на эти переменные и импорт их стоимости
    • по крайней мере для некоторых сложных и непрозрачных объектов, это уменьшает использование ресурсов (куча, потоки, общая память, дескрипторы) программы
  • иногда более кратким, поскольку вы не повторяете имя переменной в определении, а не в первоначальном значимом присваивании

  • необходимо для определенных типов, таких как ссылки и когда вы хотите, чтобы объект const

Аргументы для группировки определений переменных:

  • иногда удобно и / или лаконично выделить тип ряда переменных:

    the_same_type v1, v2, v3;

    (если причина в том, что имя типа слишком длинное или сложное, typedefиногда может быть лучше)

  • иногда желательно сгруппировать переменные независимо от их использования, чтобы подчеркнуть набор переменных (и типов), участвующих в какой-либо операции:

    type v1;
    type v2; type v3;

    Это подчеркивает общность типов и делает их немного более легкими для изменения, но при этом придерживается переменной для каждой строки, что облегчает копирование-вставку, //комментирование и т. Д.

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

Тони
источник
Хотелось бы, чтобы больше языков отличало тот случай, когда код объявляет и устанавливает значение переменной, которая никогда не будет записана в другом месте, хотя новые переменные могут использовать то же имя [т. Е. Где поведение будет одинаковым, независимо от того, будут ли последующие операторы использовать ту же переменную или другой], от тех, где код создает переменную, которая должна быть доступна для записи в нескольких местах. Хотя оба варианта использования будут выполняться одинаково, очень важно знать, когда переменные могут измениться, при попытке отследить ошибки.
суперкат