Ошибка повторного объявления переменной цикла for внутри цикла

Рассмотрим этот фрагмент программы на языке C:

for(int i = 0; i < 5; i++)
{
    int i = 10;  // <- Note the local variable

    printf("%d", i); 
}    

Он компилируется без ошибок и при выполнении дает следующий результат:

1010101010

Но если я напишу аналогичный цикл на C++:

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

     std::cout << i; 
}

Сбой компиляции с этой ошибкой:

prog.cc:7:13: error: redeclaration of 'int i'  
     int i = 10;  
         ^  
prog.cc:5:13: note: 'int i' previously declared here  
     for(int i = 0; i < 5; i++)  
             ^   

Почему это происходит?

Это должен быть C или C++?

Eugene Sh. 01.05.2018 21:24

Это C++. Обратите внимание на cout.

Ṃųỻịgǻňạcểơửṩ 01.05.2018 21:25

@Mulliganaceous Я также заметил, что cout не работает согласно OP.

Eugene Sh. 01.05.2018 21:26

Я подозреваю, что OP может компилировать как для C++, так и для C и спрашивать о разных результатах.

Christian Gibbons 01.05.2018 21:26

К вашему сведению, i в int i=0 и int i=10; разные. int i=10; находится только внутри цикла, а не за его пределами. Я не думаю, что это дает ошибку.

Achal 01.05.2018 21:27

Вопрос не так уж и плох. Все дело в области видимости переменной и ее различном поведении в c и C++.

haccks 01.05.2018 21:27
«В то время как в C++ область действия оператора init и область действия оператора совпадают, в C область действия оператора вложена в область действия оператора init: ...» см. Пример чуть ниже цитаты: en.cppreference.com/w/cpp/language/for
Richard Critten 01.05.2018 21:27

Не могли бы вы оставить это так, чтобы оно тоже могло правдоподобно скомпилироваться на c? То есть удалить cout. Здесь похоронен хороший вопрос.

Bathsheba 01.05.2018 21:31

@EricPostpischil Вы видели оригинал, к которому относится мой комментарий?

Eugene Sh. 01.05.2018 21:54

@EugeneSh. Редактирование - моя вина. Я думаю, что я истолковал намерение OP, но вы можете откатиться, если считаете, что я ошибаюсь.

Bob__ 01.05.2018 21:57

@Bob__ В результате вашего редактирования вопрос получил +9 :) Оставим как есть.

Eugene Sh. 01.05.2018 21:58

Честно говоря, я не понимаю сути этого вопроса. Похоже, вы спрашиваете «почему C и C++ делают это по-разному», на что ответ просто ... C и C++ - разные языки. В чем-то они наверняка очень похожи, но они не одинаковы, и ожидание того, что одно будет идеальным подмножеством другого, приведет к разочарованию. С учетом сказанного, я задаю этот вопрос нравиться, и интересно изучить различия, но это не похоже на хороший вопрос о переполнении стека. Если бы он был немного переформулирован, чтобы спросить о C++, на примере C, это могло бы быть лучше.

Fund Monica's Lawsuit 02.05.2018 00:14

Совершенно ясно, о чем идет речь. Голосование за повторное открытие.

Sergey Kalinichenko 02.05.2018 05:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
24
13
1 531
3

Ответы 3

Это связано с тем, что языки C и C++ имеют разные правила повторного объявления переменных в области, вложенной в цикл for:

  • C++ помещает i в область тела цикла, поэтому второй int i = 10 является повторным объявлением, что запрещено.
  • C допускает повторное объявление в рамках цикла for; самая внутренняя переменная "выигрывает"

Вот демонстрация запущенная программа на C и Программа на C++ не компилируется.

Открытие вложенной области внутри тела исправляет ошибку компиляции (демонстрация):

for (int i =0 ; i != 5 ; i++) {
    {
        int i = 10;
        cout << i << endl;
    }
}

Теперь i в заголовке for и int i = 10 находятся в разных областях, поэтому программе разрешено запускаться.

Есть одно сомнение for(int i=0;i<5;i++){ int i=10; cout<<i; } Подпадает ли он под перепланировку или нет. Его код C++, и он не вызывает ошибок.

Achal 01.05.2018 21:31

@achal Это должно привести к ошибке повторного объявления, потому что в C++ цикл for имеет единую область видимости для тела и переменной, объявленной в заголовке.

Sergey Kalinichenko 01.05.2018 21:33

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

Swift - Friday Pie 01.05.2018 22:20

@Быстрый; Это совсем не так.

haccks 01.05.2018 22:42

@Swift: это было так в старом VC++, VC++ 6? не помню

Andriy Tylychko 02.05.2018 15:15

@AndriyTylychko вплоть до TurboC++, QuickC (не C++) и раньше, конечно, теперь люди говорят: «Это не так», потому что clang и gcc реализуют это по-разному и ведут себя иначе, если вы используете соответствующий флаг -std. Это было результатом сомнительного определения в стандарте текст, который определял оператор инициализации в области, которая была выше тела цикла, затем где-то в примечаниях добавлено: но вы не можете использовать те же идентификаторы. Обратите внимание, что C89 не разрешал объявление переменной внутри заголовка for (). Но Компиляторы уже имели это. Беспорядок с циклом for (), инициализатором и виртуальным ключевым словом - исторические проблемы.

Swift - Friday Pie 02.05.2018 19:32

В отличие от C, C++ имеет правило
С ++ 11-§6.5.3 / 1:

The for statement

for ( for-init-statement conditionopt ; expressionopt ) statement 

is equivalent to

{
    for-init-statement 
    while ( condition ) {
        statement 
        expression ;
    } 
}

except that names declared in the for-init-statement are in the same declarative-region as those declared in the condition [...]

Это означает, что возможности for-init-statement и statement совпадают. * и приведенный ниже код вызовет ошибку.

for(int i = 0; i < 5; i++){
     int i = 10;    // Invalid. 
     // ...
}

В C,
C11-§6.8.5 / 5:

An iteration statement is a block whose scope is a strict subset of the scope of its enclosing block. The loop body is also a block whose scope is a strict subset of the scope of the iteration statement.

Следовательно, statement имеет собственную область действия, и приведенный выше код действителен и эквивалентен

for(int i = 0; i < 5; i++){
    {
         int i = 10;    // Valid. 
         // ...
    }
}

Suggested reading: n3337: 6.5.1 The while statement/p(2). Same reference can be found in c++17 draft (n4659) in section § 9.5.1 and §9.5.3 .

N3242 - это черновик до C++ 11; безусловно, рекомендуемая литература должна быть последней версией стандартов и / или последней версией проекта.

M.M 02.05.2018 07:23

@ M.M; Под последним вы имеете в виду черновик n4741?

haccks 02.05.2018 07:37

Что-то вроде этого, да. Последний стандарт - N4659

M.M 02.05.2018 07:41

@ M.M; ОК. Обновлено.

haccks 02.05.2018 09:52

Я не думаю, что стандартный язык C++ действительно оправдывает пометку примера в вопросе как ошибки. Фактически существует два уровня, которые отделяют int i=0for-init-statement от int i=10;, содержащегося в теле цикла. Во-первых, заменяющий текст после «эквивалентно» вводит дополнительные фигурные скобки вокруг утверждение, что исключение указано в словах не относится к, поскольку в нем говорится только о том, что for-init-statement и условие (мой курсив) находятся на одном декларативном уровне; он вообще не упоминает тело цикла. (...)

Marc van Leeuwen 02.05.2018 10:17

Во-вторых, в этом примере утверждение (тело цикла) на самом деле является составное заявление (т. Е. Его самые внешние токены являются фигурными скобками), и эти фигурные скобки остаются в тексте замены, который, следовательно, становится конкретно { int i=0; while(i<5) { {int i=10; cout << i << endl;} i++; } }, с явно на два уровня фигурных скобок вокруг i=10 больше, чем около i=0. Даже если истолковать неверно сформулированное исключение как цель эффективно снять один уровень скоб, останется одна пара. Обратите внимание на то, что двойные фигурные скобки вокруг тела цикла заставляют сообщение об ошибке исчезнуть.

Marc van Leeuwen 02.05.2018 10:24

@MarcvanLeeuwen; Я бы сказал, что это плохое чтение текстов в ответ. Я настоятельно рекомендую вам прочитать ответ еще раз сверху вниз, а затем просмотреть разделы §6.5.1 и §6.5.3, чтобы получить полную картину.

haccks 02.05.2018 10:29

Я прочитал эти разделы до того, как написал свои комментарии; почему ты думаешь, что я этого не сделал? Кстати, я также читал в 6.5 «Если подзапрос в операторе итерации является единственным оператором, а не составным оператором, это как если бы оно было переписано в составное выражение, содержащее исходный оператор», что означает, что вы фактически получить пару фигурных скобок, даже если вы написали тело цикла, в котором их нет (но это не применимо в примере), и 6.3 «Составной оператор определяет область действия блока» без каких-либо исключений или модификаций. Таким образом, каждое тело цикла представляет собой область видимости блока отдельный.

Marc van Leeuwen 02.05.2018 10:37

Между прочим, я думаю, что один момент заключается в том, что в §6.5.3 / 1 должен сказано: «... за исключением того, что имена, объявленные в инструкции for-init, находятся в той же декларативной области, что и имена, объявленные в утверждение», что по крайней мере, соответствовало бы намерению отметить пример как ошибку. Это все равно не очень хорошая формулировка, поскольку она должна относиться только к именам, объявленным на самом внешнем уровне внутри утверждение.

Marc van Leeuwen 02.05.2018 10:46

Кроме того, IMHO плохая идея в первую очередь для определять значения X, говоря, что «конструкция X эквивалентна Y, за исключением того, что у нее есть какое-то другое свойство», поскольку тогда нет способа узнать, как X получил это свойство разные. Лучше описать значение X напрямую, чтобы прояснить все детали; возможно, добавьте в качестве комментария, что это почти то же самое, что и Y, и укажите, в чем заключается тонкая разница.

Marc van Leeuwen 02.05.2018 10:54

Это не повторное объявление.

Смотрите внимательно ...

for(int i = 0; i < 7; i++)
{
    printf("i = %d\n", i);
    int i = 5;
    printf("new i = %d\n", i);
}

Вывод приведенного выше кода: -

i = 0
new i = 5
i = 1
new i = 5
i = 2
new i = 5
i = 3
new i = 5
i = 4
new i = 5
i = 5
new i = 5
i = 6
new i = 5

Ясно, что есть два разных i.

Более новый i имеет более локальный охват.

Это ошибка?

Нет

Какая цель?

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

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

Почему нет предупреждающего сообщения?

Используйте gcc file_name.c -Wshadow для компиляции.


Обновлено:Вы также можете локально заблокировать первоначально объявленные переменные, повторно объявив их в циклах for.

Другие вопросы по теме