PVOID - Видимость имен

Видимость имен

 

Блоки


 Именованные сущности, такие как переменные, функции и составные типы данных в C++ должны быть объявлены прежде, чем будут использованы. Место объявления в программе влияет на их видимость:

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

 Переменные с областью видимости блока известны как локальные переменные.

 Например, переменная, объявленная в теле функции, является локальной переменной, которая существует и видна до конца функции (т.е. до фигурной скобки }, закрывающей определение функции).

int foo;    // глобальная переменная

int some_function ()
{
  int bar;  // локальная переменная
  bar = 0;
}

int other_function ()
{
  foo = 1;  // ок: foo является глобальной переменной
  bar = 2;  // ошибка: bar не видна из этой функции
}

 В каждой области видимости не может быть двух сущностей, названных одним именем. Например, нельзя создать две переменные с одним и тем же именем в одной и той же области видимости:

int some_function ()
{
  int x;
  x = 0;
  double x;   // неверно: имя уже используется
              // в этой области видимости
  x = 0.0;
}

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

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

// вложенные блоки
#include <iostream>
using namespace std;

int main () {
  int x = 10;
  int y = 20;
  {
    int x;   // ок, внутренний блок.
    x = 50;  // задает значение внутреннего x
    y = 50;  // задает значение (внешнего) y
    cout << "inner block:\n";
    cout << "x: " << x << '\n';
    cout << "y: " << y << '\n';
  }
  cout << "outer block:\n";
  cout << "x: " << x << '\n';
  cout << "y: " << y << '\n';
  return 0;
}
inner block:
x: 50
y: 50
outer block:
x: 10
y: 50

 Обратите внимание, что y не скрыта во внутреннем блоке, и, таким образом, обращение к y по-прежнему означает обращение к внешней переменной.

 Переменные, объявленные в объявлениях перед блоком, такие как параметры функций или переменные, объявленные в циклах и условиях (например, объявленные в for или if), являются локальными для этого блока.

Пространства имен


 Внутри одного блока может существовать только одна сущность с определенным именем. Это редко является проблемой для локальных имен, поскольку блоки обычно относительно короткие, а имена преследуют определенные цели, такие как именование переменной счетчика, аргумента и т.д...

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

 Пространства имен позволяют группировать именованные сущности, которые в противном случае имели бы глобальную область видимости, в более узкие области, давая им область пространства имен. Это позволяет организовать элементы программы в различные логические блоки, ссылаясь на них по именам.
Синтаксис объявления пространства имен таков:

namespace идентификатор
{
  именованные_сущности
}

  Где идентификатор это любой корректный идентификатор, а именованные_сущности это набор переменных, типов и функций, которые заключены в пространство имен. Например:

namespace myNamespace
{
  int a, b;
}

 В этом случае переменные a и b являются обычными переменными, объявленными внутри пространства имен myNamespace.
Доступ к этим переменным можно получить обычным способом внутри пространства имен по их идентификаторам (a или b), но если необходимо обратиться извне пространства имен myNamespace, необходимо использовать оператор разрешения области видимости (::). Например, для доступа к вышеупомянутым переменным извне пространства имен myNamespace, необходима следующая запись:

myNamespace::a
myNamespace::b

  Пространства имен в частности полезны для избежания коллизий имен. Например:

// пространства имен
#include <iostream>
using namespace std;

namespace foo
{
  int value() { return 5; }
}

namespace bar
{
  const double pi = 3.1416;
  double value() { return 2*pi; }
}
5
6.2832
3.1416

 В этом случае есть две функции с одним и тем же именем: value. Одна определена внутри пространства имен foo, а другая в bar. Блягодаря пространствам имен не происходит ошибки многократно определенного символа. Также отметим что обращение к pi происходит без указания пространства имен изнутри пространства имен bar (просто pi), тогда как в обращении из main необходимо указывать пространство имен bar::pi.
Пространства имен можно разделять: два сегмента кода могут быть объявлены в одном и том же пространстве имен:

namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }

 Здесь объявлены три переменные: a и c находятся в пространстве имен foo, тогда как b в пространстве имен bar. Пространства имен могут дяже распространяться на различные единицы трансляции (т.е. на различные файлы исходного кода).

using


 Ключевое слово using вводит имя в текущую декларативную область (такую как блок), таким образом избегая указания полного имени (с учетом пространств имен). Например:

// using
#include <iostream>
using namespace std;

namespace first
{
  int x = 5;
  int y = 10;
}

namespace second
{
  double x = 3.1416;
  double y = 2.7183;
}

int main () {
  using first::x;
  using second::y;
  cout << x << '\n';
  cout << y << '\n';
  cout << first::y << '\n';
  cout << second::x << '\n';
  return 0;
}
5
2.7183
10
3.1416

 Обратите внимание, что в main переменная x (без каких-либо квалификаторов) ссылается на first::x, тогда как y ссылается на second::y, как указано в обявлениях using. Переменные first::y и second::x также доступны, но требуют указания полных имен.
Ключевое слово using также может быть использовано как директива для введения целого пространства имен:

// using
#include <iostream>
using namespace std;

namespace first
{
  int x = 5;
  int y = 10;
}

namespace second
{
  double x = 3.1416;
  double y = 2.7183;
}

int main () {
  using namespace first;
  cout << x << '\n';
  cout << y << '\n';
  cout << second::x << '\n';
  cout << second::y << '\n';
  return 0;
}
5
10
3.1416
2.7183

  В этом случае, после объявления using namespace first, каждое непосредственное использование x и y без указания какого-либо пространства имен также приводит к их поиску в пространстве имен first.
using и using namespace действительны только в том же блоке, в котором они использованы или во всем файле исходного кода, если они используются непосредственно в глобальной области. Например, можно использовать сначала объекты в одного пространства имен, а затем другого,разбив код на различные блоки:

// пример using namespace
#include <iostream>
using namespace std;

namespace first
{
  int x = 5;
}

namespace second
{
  double x = 3.1416;
}

int main () {
  {
    using namespace first;
    cout << x << '\n';
  }
  {
    using namespace second;
    cout << x << '\n';
  }
  return 0;
}
5
3.1416

 

Псевдоним пространства имен


 Существующим пространствам имен могуть быть присвоены псевдонимы с использованием следующего синтаксиса:

namespace новое_имя = текущее_имя;

 

Пространство имен std


 Все сущности (переменные, типы, константы, функции) стандартной библиотеки C++ объявлены в пространстве имен std. Большинство примеров в этом руководстве включают следующую строку:

using namespace std;

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

cout << "Hello world!";

 часто можно видеть:

std::cout << "Hello world!";

 Независимо от того, вводятся ли элементы пространства имен std с объявлением using или полностью квалифицируются при каждом использовании, это никак не меняет поведения или эффективности полученной программы. Это в основном вопрос стиля, хотя для проектов, смешивающих библиотеки, обычно предпочтительнее явная квалификация.

 

Классы хранения


Память для глобальных и переменных, объявленных внутри пространства имен, выделяется на все время жизни программы. Это известно как статическое хранение, которое контрастирует с классом хранения для локальных переменных (объявленных внутри блока). Для них используется так называемое автоматическое хранение. Хранение локальных переменных доступно только до конца блока, в котором они объявлены; после этого эта же память может использоваться для локальной переменной какой-либо другой функции или использоваться иным образом. 

Но есть еще одно существенное различие между переменными со статическим хранением и переменными с автоматическим хранением:

  • Переменные со статическим хранением (такие как глобальные переменные), которые не были явно инициализированы, автоматически инциализируются нулями.
  • Переменные с автоматическим хранением (такие как локальные переменные), которые не были явно инициализированы, остаются неинициализированными, а потому имею неопределенное значение.
// статическое и автоматическое хранение
#include <iostream>
using namespace std;

int x;

int main ()
{
  int y;
  cout << x << '\n';
  cout << y << '\n';
  return 0;
}
0
4285838

 Фактический вывод может отличаться, но только значение переменной x гарантированнно равно нулю. Переменная y может в действительности содержать практически любое значение (включая ноль).