Переменная, объявленная в цикле for, является локальной переменной?


133

Я довольно давно использую C #, но так и не понял следующего:

 public static void Main()
 {
     for (int i = 0; i < 5; i++)
     {

     }

     int i = 4;  //cannot declare as 'i' is declared in child scope                
     int A = i;  //cannot assign as 'i' does not exist in this context
 }

Так почему я не могу использовать значение 'i' вне блока for, если оно не позволяет мне объявлять переменную с этим именем?

Я думал, что переменная итератора, используемая циклом for, действительна только в своей области видимости.


8
Поскольку область внешнего блока включает область цикла for
V4Vendetta

3
Один из способов, которым я об этом думаю (а некоторые руководства по стилю кода требуют этого, особенно для языков с динамической типизацией), состоит в том, что все переменные, объявленные в области действия, могут быть объявлены в начале этой области, то есть ваша функция может быть переписанаint i, A; for(int i = 0; i < 5; i++){ } i=4; A=i
Keith

2
@ V4Vendetta: Это скорее наоборот. Внутренний блок - это черный ящик с родительским блоком.
Себастьян Мах

4
Помимо технических причин, почему это невозможно, почему это (или вариант этого) было бы разумно сделать ?! Очевидно, он служит другой цели, поэтому дайте ему другое имя.
Дэйв

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

Ответы:


119

Причина, по которой вам не разрешено определять переменную с тем же именем как в цикле for, так и вне цикла for, заключается в том, что переменные во внешней области действительны во внутренней области. Это означает, что в цикле for будет две переменные «i», если это будет разрешено.

Смотрите: Области MSDN

В частности:

Область действия локальной переменной, объявленной в объявлении локальной переменной (раздел 8.5.1), - это блок, в котором происходит объявление.

и

Областью видимости локальной переменной, объявленной в инициализаторе for оператора for (раздел 8.8.3), является инициализатор for, условие for, итератор for и содержащийся оператор оператора for.

А также: объявления локальных переменных (раздел 8.5.1 спецификации C #)

В частности:

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

(Акцент мой.)

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

Причина, по которой вам не разрешено делать, int A = i;заключается в том, что int iэта функция предназначена только для использования внутри forцикла. Таким образом, он больше не доступен вне forцикла.

Как вы можете видеть, обе эти проблемы являются результатом определения области; первая проблема ( int i = 4;) приведет к двум iпеременным в пределах forцикла. Принимая во внимание, int A = i;что приведет к доступу к переменной, которая выходит за рамки.

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

public static void Main()
{
    int i;

    for (i = 0; i < 5; i++)
    {

    }

    // 'i' is only declared in the method scope now, 
    // no longer in the child scope -> valid.
    i = 4;

    // 'i' is declared in the method's scope -> valid. 
    int A = i;
}

РЕДАКТИРОВАТЬ :

Конечно, можно изменить компилятор C #, чтобы этот код компилировался вполне корректно. Ведь это действительно так:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}

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

public static void Main()
{
    int i = 4;

    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
    }

    for (int i = 5; i > 0; i--)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(i);
}

Подумайте о возможной ошибке здесь, последняя iраспечатывает 0 или 4? Это очень маленький пример, за которым довольно легко следить и отслеживать, но он определенно намного менее удобен в обслуживании и читаем, чем объявление внешнего iпод другим именем.

NB:

Пожалуйста, обратите внимание, что области видимости C # отличаются от областей видимости C ++ . В C ++ переменные находятся только в области видимости от того, где они объявлены, до конца блока. Что сделало бы ваш код допустимой конструкцией в C ++.


Имеет смысл, я думаю, что во внутренней iпеременной должна быть ошибка ; это кажется мне более очевидным.
Джордж Дакетт

Но область действия для команды и ее {}? Или это означает, что его родитель {}?
Джон V

2
Хорошо, если я объявляю 'A' после оператора for, он недопустим в цикле for, как объявлено позже. Вот почему я не понимаю, почему нельзя использовать одно и то же имя.
Джон V

9
Возможно, в этом ответе для полноты следует указать, что правила области действия C # отличаются от правил для C ++ в этом случае. В C ++ переменные находятся только в области видимости от того, где они объявлены, до конца блока (см. Msdn.microsoft.com/en-us/library/b7kfh662(v=vs.80).aspx ).
AAT

2
Обратите внимание, что Java использует промежуточный подход между C ++ и C #: поскольку это пример OP будет действителен в Java, но если внешнее iопределение было перемещено до цикла for, внутреннее iопределение будет помечено как недопустимое.
Никола Мусатти

29

Ответ Дж. Коммера правильный: вкратце, недопустимо, чтобы локальная переменная была объявлена ​​в пространстве объявления локальной переменной, которое перекрывает другое пространство объявления локальной переменной , имеющее локальную переменную с тем же именем.

Здесь также нарушается дополнительное правило C #. Дополнительное правило заключается в том, что недопустимо использовать простое имя для ссылки на две разные сущности в двух разных перекрывающихся пространствах объявления локальных переменных. Так что не только ваш пример незаконен, но и незаконен:

class C
{
    int x;
    void M()
    {
        int y = x;
        if(whatever)
        {
            int x = 123;

Потому что теперь простое имя «x» используется внутри пространства объявления локальной переменной «y» для обозначения двух разных вещей - «this.x» и локального «x».

См. Http://blogs.msdn.com/b/ericlippert/archive/tags/simple+names/ для более подробного анализа этих проблем.


2
Кроме того, интересно отметить, что ваш примерный код будет скомпилирован, когда вы внесете изменениеint y = this.x;
Фил

4
@Phil: правильно. this.xэто не простое имя .
Эрик Липперт

13

Существует способ объявления и использования iвнутри метода после цикла:

static void Main()
{
    for (int i = 0; i < 5; i++)
    {

    }

    {
        int i = 4;
        int A = i;
    }
}

Вы можете сделать это в Java (это может происходить из C, я не уверен). Конечно, это немного беспорядочно из-за имени переменной.


Да, это происходит из C / C ++.
Бранко Димитриевич

1
Я не знал этого раньше. Опрятная языковая особенность!

@MathiasLykkegaardLorenzen да
Крис С

7

Если бы вы объявили , i прежде чем ваш forцикл, вы думаете , он должен оставаться действительным , чтобы объявить его внутри цикла?

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

Что касается неспособности сделать int A=i;, это просто потому, что iсуществует только в forцикле, как и должно быть.


7

В дополнение к ответу Дж. Коммера (+1 к слову). В стандарте для NET-области есть следующее:

block Если вы объявляете переменную в конструкции блока, такой как оператор If, область действия этой переменной будет только до конца блока. Срок службы до окончания процедуры.

Процедура Если вы объявляете переменную в процедуре, но вне любого оператора If, область действия остается до End Sub или End Function. Время жизни переменной до окончания процедур.

Таким образом, int i, помеченный внутри заголовка цикла for, будет в области видимости только во время блока цикла for, НО его время жизни длится до завершения Main()кода.


5

Самый простой способ подумать об этом - переместить внешнюю декларацию I над циклом. Это должно стать очевидным тогда.

В любом случае это один и тот же прицел, поэтому это невозможно.


4

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

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


2

Ответ Коммера технически верен. Позвольте мне перефразировать это с яркой метафорой слепого экрана.

Между блоком for и окружающим внешним блоком имеется односторонний слепой экран, так что код внутри блока for может видеть внешний код, но код во внешнем блоке не может видеть код внутри.

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

Так что либо ты этого не видишь, либо ты C #!


0

Посмотрите на это так же, как если бы вы могли объявить intв usingблоке:

using (int i = 0) {
  // i is in scope here
}
// here, i is out of scope

Однако, поскольку intне реализует IDisposable, это не может быть сделано. Это может помочь кому-то визуализировать, как intпеременная помещается в частную область видимости.

Другим способом было бы сказать,

if (true) {
  int i = 0;
  // i is in scope here
}
// here, i is out of scope

Надеюсь, это поможет визуализировать происходящее.

Мне очень нравится эта функция, так как объявление intвнутри forцикла делает код красивым и сжатым.


WTF? Если вы собираетесь пометить NEG, попросите объяснить, почему.
jp2code

Не downvoter, и я думаю, что сравнение с usingвполне нормально, хотя семантика довольно различна (возможно, поэтому она была отклонена). Обратите внимание, что if (true)это избыточно. Удалите его, и у вас будет блок обзора.
Abel
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.