Составные операторы и области видимости
Составной оператор - это несколько выражений, строк программы, заключенных в фигурные скобки, без точки с запятой после замыкающей скобки.
Пример. Где-то в программе у нас объявлена переменная x
int x;
Где-то дальше, ей присваивается некое значение. И потом мы проверяем, равно ли x тринадцати:
if (x == 13)
{
x = x + 1;
cout << "увеличили x на единицу!" << endl;
}
После проверки, равен ли x 13, в случае истинности проверки, управление переходит составному оператору - тем выражениям, которые заключены в фигурные скобки после проверки условия. Таких выражений может быть сколько угодно. В нашем случае их всего два - мы увеличиваем x на единицу, добавив к x один, а затем пишем на консоль, “увеличили x на единицу!”. В конце каждого одинарного выражения мы ставим точку с запятой.
Разумеется, внутри такого составного оператора могут быть свои, вложенные проверки и составные операторы. Итак, составной оператор - это набор выражений, блок, который надо выполнить в ответ на какое-то действие. Не одно выражение в ответ, а несколько. Если одно, нам не нужны фигурные скобки, мы бы просто написали:
if (x == 13)
x = x + 1;
Если это для вас еще туманно, проясню. Что будет, если тут
if (x == 13)
{
x = x + 1;
cout << "увеличим x на единицу!" << endl;
}
…не написать фигурные скобки после проверки if, сделать вот так:
if (x == 13)
x = x + 1;
cout << "увеличим x на единицу!" << endl;
А будет вот что. Когда у нас были фигурные скобки, составной оператор, то если X равно 13, выполняется и строка с cout, где мы выводим текст, и строка с прибавлением единицы к иксу. Поскольку они находятся внутри составного оператор, оформлены как единый блок. Если же Х не равно 13, мы ничего не прибавляем и ничего не пишем на консоль.
А когда нет обрамления фигурными скобками, выражения после проверки не сгруппированы, то реакцией на проверку служит только строка, где мы прибавляем к х единицу, а строка с выводом текста выполняется в любом случае.
То есть, даже если х не равно 13, будет выведена строка “увеличим х на единицу”, что не соответствует задуманному и является ошибкой логики программы.
Замечу, что я нарочно написал в столбик, с одинаковым отступом, вторую и третью строки:
if (x == 13)
x = x + 1;
cout << "увеличим X на единицу!" << endl;
Чтобы сбить вас с толку. Отступы в С++ несут чисто декоративное значение, в отличие например от языка программирования Python (Пайтон). Поэтому программисты, чтобы подчеркнуть логику выражений, помочь себе визуально, делают отступы. И правильнее было бы написать вот так:
if (x == 13)
x = x + 1;
cout << "увеличим x на единицу!" << endl;
Сразу становится видно, где реакция на проверку, а где просто часть программы, на том же уровне отступа, что if.
Есть разные стили отступов, я предпочитаю такой:
if (x == 13)
{
x = x + 1;
cout << "увеличим x на единицу!" << endl;
}
А есть иной популярный:
if (x == 13) {
x = x + 1;
cout << "увеличим X на единицу!" << endl;
}
То есть открывающая скобка составного оператора ставится в той же строке, где и проверочная часть, но мне это кажется менее понятным.
Внутри составного оператора нельзя объявлять функции, но можно объявлять переменные. Например:
//если x равно 13
if (x == 13)
{ //то...
x = x + 1; //присваиваем x значение x + 1
cout << "увеличим x на единицу!" << endl; //теперь выводим значение x на консоль
int y = x; //объявляем переменную y и присваиваем ей значение x
cout << "y равно " << y << endl; //выводим на консоль значение y
}
И тут мы сталкиваемся с новым понятием - область действия переменной.
Переменная, кроме особых случаев, о которых я расскажу много позже, не существует вне блока, в котором она объявлена. Не существует вне составного оператора, где она объявлена. Не существует вне функции, где она объявлена.
Пример программы, несколько изменим наш рабочий убогий пример 07-01.cpp:
#include <iostream>
using namespace std;
int sum (int a, int b)
{
int result = a + b;
return result;
}
int main (int argc, char *argv[])
{
cout << "Значение X равно: " << sum (3, 4) << endl;
return 0;
}
У нас тут две функции, вспомогательная sum и главная функция программы, main.
В функции sum мы объявили переменную result.
Эта переменная не видна в функции main. Когда мы обращаемся в функции main к функции sum, то при вызове последней создается переменная result, туда кладется итог сложения a и b, затем значение переменной result возвращается из функции, происходит завершение работы функции sum, и переменная result буквально исчезает, хотя в функции main мы получили ее значение.
Каждая функция, это, по сути, тоже составной оператор.
А вот такой пример, который допустим просто расположен где-то в программе или функции:
if (x == 13)
{
int y = x;
cout << "y равно " << y << endl;
}
int y;
Тут уже набившая оскомину проверка на 13, ответом на которую идет составной оператор, где мы переменной y присваиваем значение x и выводим значение y на консоль.
Затем, вне составного оператора мы объявляем ЕЩЕ одну переменную с таким же именем, y.
Это два РАЗНЫЕ переменные с одним именем. Такое возможно лишь потому, что одна y объявлена внутри составного оператора и вне его не видна. Это две переменные y, лежащие в разных областях видимости.
Сидоров в квартире, и другой Сидоров во дворе. Фамилии одинаковые, но разные люди в разных местах.
Рассмотрим еще одно положение, довольно странное, но возможное (07-02.cpp):
#include <iostream>
using namespace std;
int main (int argc, char *argv[])
{
int x = 13;
if (x == 13)
{
int x = 0;
cout << x << endl;
}
return 0;
}
Тут мы имеем дело тоже с двумя РАЗНЫМИ, но одноименными переменными x. Одна объявлена вне составного оператора и ей присвоено значение 13. Другая объявлена в составном операторе и ей присвоено значение 0. Затем вы пишем на консоль значение переменной х. Но какой x? Внешней или внутренней?
В таких случаях приоритет отдается переменной внутреннего уровня, то есть той, что объявлена внутри составного оператора.
Еще один пример из той же оперы.
#include <iostream>
using namespace std;
int x = 13;
int main (int argc, char *argv[])
{
if (x == 13)
{
int x = 0;
cout << x << endl;
}
return 0;
}
На сей раз переменная х объявлена вне функции main, и вне какой-либо функции вообще. Таким образом она, во-первых, будет видна во всех функциях. Как видим, в первой строке функции main мы обращаемся к х, не объявляя ее. Потому, что она уже объявлена ранее.
Но рассмотрим конструкцию:
if (x == 13)
{
int x = 0;
cout << x << endl;
}
Если вот эта внешняя для всего х равна 13, мы выполняем составной оператор, в которым объявляем новую переменную х и присваиваем ей значение ноль. Выводим на консоль значение х. Какой х, внешней или внутренней? Ответ - внутренней. Выведется 0.
Но что если мы хотим обратиться к той х, что объявлена вне всех функций? В этом случае надо предварить х двумя двоеточиями, вот так:
cout << ::x << endl;
В этом случае выведется, не ноль, а 13, то есть значение внешней переменной х. Этот трюк не сработает, если мы хотим обратиться к другой переменной х, объявленной в той же функции. Это работает только для внефункционных переменных.
Подобные штуки, когда у переменных внутри одной функции одинаковые имена, вообще говоря отвратительный стиль программирования и подобное допускается лишь в некоторых случаях, например в счетчиках циклов - но до этого мы еще не дошли. Поэтому, вы теперь просто знаете, что ТАК МОЖНО, но старайтесь избегать этого. Избегайте любой путанице в коде.
Но что означает ::
когда мы напишем:
cout << ::x << endl;
Помните я рассказывал про пространства имен? Например, есть пространство std, и так далее. Перечитайте урок 4, если забыли.
Мы можем указать пространство имен, написав имя пространства, затем ::
, а затем имя переменной или функции.
А когда мы не пишем перед ::
никакого имени пространства, это значит, что мы обращаемся к общему пространству имен. У него просто нет отдельного имени. На английском это общее пространство именуют global namescape.
Что же, начало этого урока было простым, потом всё запуталось, но вы теперь знаете, как не войти в эти дебри, то бишь чего следует избегать. Пора двигаться дальше.
PayPal: peter.semiletov@gmail.com
биткоин: 1PCo2zznEGMFJey4qFKGQ8CoFK2nzNnJJf