Різниця між оголошенням змінних до або в циклі?


312

Мені завжди було цікаво, чи взагалі декларування змінної викидання перед циклом, на відміну від повторного всередині циклу, має яку-небудь (продуктивність) різницю? (Абсолютно безглуздо) приклад в Java:

а) оголошення перед циклом:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

б) оголошення (повторно) всередині циклу:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

Хто з них кращий, а чи б ?

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

Редагувати: Мене особливо цікавить справа Java.


Це має значення під час написання коду Java для платформи Android. Google пропонує, щоб для критично важливого для часу коду, щоб оголосити збільшення змінних за межами for-циклу, як ніби всередині for-loop, вона повторно оголошує її щоразу в цьому середовищі. Різниця в продуктивності дуже помітна для дорогих алгоритмів.
AaronCarson

1
@AaronCarson чи не могли б ви надати посилання на цю пропозицію від Google
Віталій Зінченко

Відповіді:


256

Що краще, a чи b ?

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

З точки зору обслуговування, b краще. Декларуйте та ініціалізуйте змінні там же, у найкоротшому обсязі. Не залишайте проміжок між декларацією та ініціалізацією та не забруднюйте простори імен, які вам не потрібно.


5
Замість Double, якщо мова йде про String, все-таки справа "b" краще?
Antoops

3
@Antoops - так, b краще з причин, які не мають нічого спільного з типом даних оголошеної змінної. Чому для Strings це було б інакше?
Даніель Ервікер

215

Ну, я провів ваші приклади A і B по 20 разів кожен, циклічно 100 мільйонів разів. (JVM - 1.5.0)

A: середній час виконання: 0,774 сек

B: середній час виконання: 0,667 сек

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


12
Ви побили мене, я щойно збирався розмістити свої результати для профілювання, я отримав більш-менш те саме і так, на диво, Б швидше дійсно подумав би, якби мені потрібно було зробити ставку на це.
Марк Девідсон

14
Не велике здивування - коли змінна локальна для циклу, її не потрібно зберігати після кожної ітерації, тому вона може залишатися в реєстрі.

142
+1 за те, що насправді тестує його , а не лише думку / теорію, яку ОП міг скласти сам.
MGOwen

3
@GoodPerson, якщо чесно, я хотів би, щоб це було зроблено. Я провів цей тест близько 10 разів на своїй машині для 50 000 000-100 000 000 ітерацій з майже однаковим фрагментом коду (яким я хотів би поділитися з усіма, хто хоче запустити статистику). Відповіді були розділені майже однаково в будь-якому випадку, як правило, з 900-кратною відстані (понад 50-ти ітерацій), що насправді не так багато. Хоча моя перша думка полягає в тому, що це буде "шумом", воно може трохи нахилитися. Ці зусилля здаються мені суто академічними (для більшості застосунків у реальному житті). Я б хотів побачити результат у будь-якому випадку;) Чи хтось згоден?
javatarz

3
Показати результати тестування, не документуючи налаштування, марно. Це особливо актуально в цьому випадку, коли обидва фрагменти коду виробляють однаковий байт-код, тому будь-яка виміряна різниця є лише ознакою недостатнього умови тестування.
Холгер

66

Це залежить від мови та точного використання. Наприклад, у C # 1 це не мало значення. У C # 2, якщо локальна змінна захоплена анонімним методом (або лямбда-виразом у C # 3), це може зробити дуже значну різницю.

Приклад:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Вихід:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

Різниця полягає в тому, що всі дії фіксують одну і ту ж outerзмінну, але кожна має свою окрему innerзмінну.


3
у прикладі B (оригінальне запитання), чи справді він щоразу створює нову змінну? що відбувається в очах стека?
Рой Намір

@Jon, це була помилка в C # 1.0? Чи в ідеалі не повинно Outerбути 9?
nawfal

@nawfal: Я не знаю, що ти маєш на увазі. Виразів лямбда не було 1,0 ... а Зовнішнє - 9. Про яку помилку ви маєте на увазі?
Джон Скіт

@nawfal: Моя думка полягає в тому, що в C # 1.0 не було жодних мовних особливостей, де можна було б відзначити різницю між оголошенням змінної всередині циклу і оголошенням його зовні (якщо вважати, що вони компільовані). Це змінилося в C # 2.0. Немає помилок.
Джон Скіт

@JonSkeet О так, я розумію вас, я зовсім не помітив того, що ви не можете закрити такі змінні в 1.0, моє погано! :)
nawfal

35

Далі - те, що я написав і склав у .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

Це те, що я отримую від .NET Reflector, коли CIL повертається у код.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Тож обидва виглядають однаково після компіляції. У керованих мовах код перетворюється в CL / байт-код, а під час виконання він перетворюється на машинну мову. Так що в машинній мові подвійний може навіть не бути створений на стеку. Це може бути просто регістр, оскільки код відображає, що він є тимчасовою змінноюWriteLine функції. Існує цілий набір правил оптимізації лише для циклів. Тож пересічний хлопець не повинен про це турбуватися, особливо на керованих мовах. Бувають випадки, коли ви можете оптимізувати керуючий код, наприклад, якщо вам доведеться об'єднати велику кількість рядків, використовуючи просто string a; a+=anotherstring[i]vs за допомогоюStringBuilder. Є дуже велика різниця в продуктивності між обома. Існує дуже багато таких випадків, коли компілятор не може оптимізувати ваш код, оскільки він не може з'ясувати, що призначено для більшого обсягу. Але це може значно оптимізувати основні речі для вас.


int j = 0 для (; j <0x3e8; j ++) таким чином оголошується один раз як змінною, а не кожною для циклу. 2) це завдання товстіше за всі інші варіанти. 3) Отже, правило найкращої практики - це будь-яка заява, що не входить до ітерації.
лука

24

Це дітка у VB.NET. Результат Visual Basic не буде ініціалізувати змінну в цьому прикладі:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

Це буде друкувати 0 вперше (для змінних Visual Basic є значення за замовчуванням, коли вони оголошені!), Але iкожен раз після цього.

Якщо ви додасте додаток = 0, ви отримаєте те, що ви можете очікувати:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

1
Я використовую VB.NET протягом багатьох років і не стикався з цим !!
ChrisA

12
Так, неприємно це зрозуміти на практиці.
Майкл Харен

Ось довідка про це від Пола Вікі: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder На жаль @PaulV вирішив кинути свої старі повідомлення в блозі , тому це тепер мертве посилання.
Марк Херд

так, нещодавно наткнувся на це; шукав деякі офіційні документи з цього приводу ...
Ерік Шнайдер

15

Я зробив простий тест:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

проти

for (int i = 0; i < 10; i++) {
    int b = i;
}

Я склав ці коди з gcc - 5.2.0. А потім я розібрав головний () цих двох кодів і ось результат:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

проти

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Який результат є тим самим результатом, як ASM. це не доказ того, що два коди створюють одне і те ж?


3
так, і здорово, що ви це робили, але це повертається до того, що люди говорили про залежність від мови / компілятора. Цікаво, як впливатиме на продуктивність JIT чи інтерпретовану мову.
користувач137717

12

Це залежить від мови - IIRC C # оптимізує це, тому різниці немає, але JavaScript (наприклад) буде виконувати весь шебанг розподілу пам'яті щоразу.


Так, але це не багато. Я провів простий тест з циклом для виконання циклу 100 мільйонів разів, і я виявив, що найбільша різниця на користь оголошення поза циклом - 8 мс. Зазвичай це було більше, як 3-4, і час від часу оголошення за межами циклу виконували WORSE (до 4 мс), але це було нетипово.
користувач137717

11

Я завжди використовую A (а не покладаюся на компілятор) і можу також переписати на:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

Це все ще обмежується intermediateResultрамкою циклу, але не переосмислюється під час кожної ітерації.


12
Ви концептуально хочете, щоб змінна жила протягом тривалості циклу, а не окремо за ітерацією? Я це рідко роблю. Напишіть код, який максимально чітко розкриває ваш намір, якщо у вас немає дуже-дуже вагомої причини зробити інше.
Джон Скіт

4
Ах, приємний компроміс, я ніколи про це не думав! IMO, код стає трохи менш візуально 'зрозумілим'
Рабарберські,

2
@Jon - я не маю поняття, що насправді робить ОП з проміжним значенням. Просто подумав, що це варіант, який варто розглянути.
Триптих

6

На мою думку, b - краща структура. У a, останнє значення intermediateResult прилипає після завершення циклу.

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


sticks around after your loop is finished- хоча це не має значення в такій мові, як Python, де пов'язані імена тримаються навколо, поки функція не закінчиться.
new123456

@ new123456: ОП запитала конкретні Java, навіть якщо це питання було задано дещо загально. Багато мов, що походять з C, мають визначення рівня блоку на рівні блоків: C, C ++, Perl (з myключовим словом), C # та Java, щоб назвати 5, якими я користувався.
Powerlord

Я знаю - це було спостереження, а не критика.
new123456

5

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


5

Як правило, я декларую свої змінні у найбільш можливій області. Отже, якщо ви не використовуєте intermediateResult за межами циклу, я б пішов з B.


5

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

Я віддаю перевагу другому (і намагаюся переконати свого колегу !;-)), прочитавши, що:

  • Це зменшує область змінних до того, де вони потрібні, що добре.
  • Java оптимізується досить, щоб не суттєво змінити продуктивність. IIRC, можливо, друга форма ще швидша.

Так чи інакше, він потрапляє до категорії передчасної оптимізації, які залежать від якості компілятора та / або JVM.


5

Існує різниця в C #, якщо ви використовуєте змінну в лямбда, і т.д.

Зважаючи на те, що вони в основному однакові: Зауважте, що версія b робить читачів набагато очевиднішими, що змінна не може і не може бути використана після циклу. Крім того, версія b набагато легше реконструюється. Складніше витягти тіло циклу у власний метод у версії a. Більше того, версія b запевняє вас, що такої рефакторингу немає побічного ефекту.

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


5

Що ж, ви завжди зможете зробити поле для цього:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

Таким чином ви оголошуєте змінну лише один раз, і вона помре, коли ви вийдете з циклу.


4

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

for(;;) {
  Object o = new Object();
}

Тоді потрібно не тільки створити об’єкт для кожної ітерації, але і потрібно виділити нове посилання для кожного об'єкта. Здається, що якщо сміттєзбірник повільний, то у вас буде купа звисаючих посилань, які потрібно прибрати.

Однак якщо у вас це є:

Object o;
for(;;) {
  o = new Object();
}

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


3
Нова посилання не виділяється для кожного об'єкта, навіть якщо посилання оголошено в циклі 'for'. У BOTH випадках: 1) 'o' - це локальна змінна, і простір стека виділяється один раз для неї на початку функції. 2) У кожній ітерації створюється новий Об'єкт. Тож різниці в продуктивності немає. Для організації коду, читабельності та ремонтопридатності краще оголошувати посилання в циклі.
Ajoy Bhatia

1
Хоча я не можу говорити на Java, у .NET посилання не "виділяється" для кожного об'єкта в першому прикладі. У стеку є один запис для цієї локальної змінної (методу). Для ваших прикладів створена ІР однакова.
Джессі К. Слікер

3

Я думаю, це залежить від укладача і важко дати загальну відповідь.


3

Моя практика така:

  • якщо тип змінної простий (int, double, ...), я віддаю перевагу варіант b (всередині).
    Причина: зменшення області застосування змінної.

  • якщо тип змінної не простий (якийсь classабо struct), я віддаю перевагу варіант a (зовні).
    Причина: зменшення кількості дзвінків ctor-dtor.


1

З точки зору продуктивності, зовні (набагато) краще.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

Я виконував обидві функції в 1 мільярд разів кожна. назовні () зайняло 65 мілісекунд. всередині () зайняло 1,5 секунди.


2
Повинна була тоді налагодити неоптимізовану компіляцію, так?
Томаш Пржиходзький

int j = 0 для (; j <0x3e8; j ++) таким чином оголошується один раз як змінною, а не кожною для циклу. 2) це завдання товстіше за всі інші варіанти. 3) Таким чином, правилом найкращої практики є будь-яка заява поза ітерацією.
лука

1

Я протестував JS з Node 4.0.0, якщо когось цікавить. Оголошення поза циклом призвело до поліпшення продуктивності приблизно в 0,5 мс в середньому за 1000 випробувань із 100 мільйонами повторень циклу за випробування. Тож я скажу, що вперед і напишу це найчитабельнішим / ремонтованим способом, який є B, imo. Я поставив би свій код у скрипці, але я використав модуль Node-продуктивності. Ось код:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

0

A) - це безпечна ставка, ніж B) ......... Уявіть, якщо ви ініціалізуєте структуру в циклі, а не 'int' або 'float', то що?

подібно до

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

Ви, звичайно, зіткнетеся з проблемами із витоком пам’яті !. Отже, я вважаю, що "A" - більш безпечна ставка, тоді як "B" є вразливим для накопичення пам'яті esp, що працює в бібліотеках із закритим джерелом.


0

Це цікаве питання. З мого досвіду є остаточне питання, яке слід враховувати, коли ви обговорюєте цю справу щодо коду:

Чи є якась причина, чому змінна повинна бути глобальною?

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

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


1
хаха, я не погоджуюся з стількох причин ... Однак, голосування не було ... Я поважаю ваше право вибору
Грантлі

0

це краща форма

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) таким чином оголошується один раз як змінна, так і не кожна для циклу. 2) це завдання товстіше за всі інші варіанти. 3) Таким чином, правилом найкращої практики є будь-яка заява поза ітерацією.


0

Спробував те ж саме в Go і порівняв вихід компілятора, використовуючи go tool compile -S допомогою go 1.9.4

Нульова різниця, відповідно до виходу асемблера.


0

У мене це саме те питання було давно. Тож я протестував ще простіший фрагмент коду.

Висновок: Для таких випадків є NO різниця в продуктивності.

Зовнішній чохол

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Корпус всередині петлі

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Я перевірив складений файл на декомпіляторі IntelliJ і для обох випадків отримав те саме Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

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

Зовнішній чохол

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Корпус всередині петлі

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Якщо ви звернете увагу, тільки Slotпризначене iі intermediateResultв LocalVariableTableобміняна в якості продукту порядку їх появи. Така ж різниця у слоті відображена в інших рядках коду.

  • Жодна додаткова операція не виконується
  • intermediateResult як і раніше є локальною змінною в обох випадках, тому різниці в часі доступу немає.

БОНУС

Компілятори роблять тону оптимізації, погляньте, що відбувається в цьому випадку.

Нульовий випадок роботи

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

Нульова робота декомпілювалася

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

-1

Навіть якщо я знаю, що мій компілятор досить розумний, я не люблю покладатися на нього, і буду використовувати a) варіант.

Б) варіант має сенс для мене лише у тому випадку, якщо вам відчайдушно потрібно зробити проміжний результат недоступним після корпусу циклу. Але я не можу собі уявити такої відчайдушної ситуації ...

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.