АЙТИШНЫЙ САЙТ ПЕТРА СЕМИЛЕТОВА

ПЕТР СЕМИЛЕТОВ

ПРОСТО С++: УРОК 7

Составные операторы и области видимости

Составной оператор - это несколько выражений, строк программы, заключенных в фигурные скобки, без точки с запятой после замыкающей скобки.

Пример. Где-то в программе у нас объявлена переменная 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.

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

Поддержать курс:

Навигация:

Оглавление

Предыдущий урок

Следующий урок