Навіщо оголошувати змінну в одному рядку та присвоювати їй у наступному?


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 за "Я навчився не так швидко відмовлятися від звичок розробників-ветеранів". Це мудрий урок, який слід засвоїти.
Wildcard

Відповіді:


92

С

У C89 всі декларації мали бути на початку області ( { ... }), але ця вимога була швидко відмінена (спочатку з розширеннями компілятора, а пізніше зі стандартом).

C ++

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

Звички

Деякі люди вважають за краще спочатку оголошувати змінні та пізніше визначати їх, якщо вони переформатують свій код пізніше з деклараціями в одному місці та визначенням в іншому.

Щодо покажчиків деякі люди просто мають звичку ініціалізувати кожен вказівник на NULLабо nullptr, незалежно від того, що вони роблять із цим вказівником.


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
Отже, якщо консенсус полягає в тому, що «заявити» - це неправильне слово, я б запропонував, щоб хтось із кращими знаннями стандарту, ніж я, редагував сторінку Wikibooks.
Джонатан Стерлінг

2
У будь-якому іншому контексті оголосити це було б правильним словом, але оскільки оголошення є чітко визначеним поняттям , з наслідками, у C та C ++ ви не можете використовувати його так вільно, як ви могли в інших контекстах.
orlp

2
@ybungalobill: Ви помиляєтесь. Декларація та визначення в C / C ++ не є взаємовиключними поняттями. Власне, визначення - це лише конкретна форма декларації . Кожне визначення - це декларація одночасно (за невеликими винятками). Існують визначальні декларації (тобто визначення) та неозначені декларації. Крім того, як правило, терми декларація використовується весь час (навіть якщо це визначення), за винятком випадків , коли контексти відмінність між цими двома критичними.

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 */
}

4

Інші відповіді досить хороші. Існує деяка історія навколо цього в C. У C ++ є різниця між конструктором та оператором призначення.

Я здивований, що ніхто не згадує додатковий момент: тримати декларації окремо від використання змінної іноді може бути набагато читабельніше.

Візуально кажучи, при читанні коду більш звичні артефакти, такі як типи та назви змінних, - це не те, що вискочить у вас. Це твердження, які вас найбільше цікавлять, проводите більшу частину часу, дивлячись на це, і тому є тенденція оглядати решту.

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

Це може здатися трохи протиінтуїтивним, але це один випадок, коли ваш джерело займає більше вертикального простору, може зробити це кращим. Я вважаю це схожим на те, чому ви не повинні писати спаковані рядки, що заповнюють варення, які роблять божевільну кількість арифметики вказівника та присвоєння у тісному вертикальному просторі - тільки тому, що мова дозволяє вам відволіктися від таких речей, не означає, що вам слід робити. це постійно. :-)


2

Для C це було стандартною практикою, оскільки змінні доводилося оголошувати на початку функції, на відміну від C ++, де її можна було оголосити в будь-якому місці в тілі функції, яке слід використовувати згодом. Покажчики були встановлені на 0 або NULL, оскільки він просто переконався, що вказівник не вказує сміття. Інакше я не можу придумати жодної суттєвої переваги, яка змушує когось робити таке.


2

Плюси локалізації визначень змінних та їх осмисленої ініціалізації:

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

  • може бути ефективнішим

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

  • мінімізація сфери змінних, у свою чергу, мінімізує середню кількість змінних одночасно за сферою застосування : це

    • робить його легше подумки відстежувати змінні в обсязі, потоки виконання та затвердження , які можуть вплинути на ці змінні і імпорт їх вартості
    • принаймні, для деяких складних і непрозорих об'єктів, це зменшує використання ресурсів (купа, потоки, спільна пам'ять, дескриптори) програми
  • іноді більш стислим, оскільки ви не повторюєте ім'я змінної у визначенні, а потім у початковому змістовному призначенні

  • необхідний для певних типів, таких як посилання та коли ви хочете, щоб об’єкт був const

Аргументи для групування визначень змінних:

  • Іноді зручно та / або стисло визначити тип ряду змінних:

    the_same_type v1, v2, v3;

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

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

    type v1;
    type v2; type v3;

    Це підкреслює спільність типу та полегшує їх зміни, одночасно дотримуючись змінної на рядок, що полегшує копіювання-вставлення, //коментування тощо.

Як це часто трапляється в програмуванні, хоча в одній практиці в більшості ситуацій може бути чітка емпірична вигода, інша практика дійсно може бути в кращому випадку краще.


Я хотів би, щоб більше мов розрізнило випадок, коли код декларує і встановлює значення змінної, яка ніколи не буде записана в іншому місці, хоча нові змінні можуть використовувати те саме ім'я [тобто, де поведінка буде такою ж, чи використовували б пізніші оператори ту саму змінну чи інший], від тих, де код створює змінну, яка повинна бути записана в декількох місцях. Хоча обидва випадки використання виконуватимуться однаково, знаючи, коли змінні можуть змінюватися, дуже корисно при спробі відстеження помилок.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.