Описания

<!--StartFragment -->
  • Область Видимости

  • Объекты и Адреса (Lvalue)

  • Время Жизни

Прежде чем имя (идентификатор) может быть использовано в C++ программе, он должно быть описано. Это значит, что надо задать его тип, чтобы сообщить компилятору, к какого вида объектам относится имя. Вот несколько примеров, иллюстрирующих разнообразие описаний:

char ch;
int count = 1;
char* name = \"Bjarne\";
struct complex { float re, im; };
complex cvar;
extern complex sqrt(complex);
extern int error_number;
typedef complex point;
float real(complex* p) { return p->re; };
const double pi = 3.1415926535897932385;
struct user;

Как можно видеть из этих примеров, описание может делать больше чем просто ассоциировать тип с именем.

Большинство описаний являются также определениями; то есть они также определяют для имени сущность, к которой оно относится. Для ch, count и cvar этой сущностью является соответствующий объем памяти, который должен использоваться как переменная - эта память будет выделена. Для real это заданная функция. Для constant pi это значение 3.1415926535897932385. Для complex этой сущностью является новый тип. Для point это тип complex, поэтому point становится синонимом complex. Только описания

extern complex sqrt(complex);
extern int error_number;
struct user;

не являются одновременно определениями. Это означает, что объект, к которому они относятся, должен быть определен где-то еще. Код (тело) функции sqrt должен задаваться неким другим описанием, память для переменной error_number типа int должна выделяться неким другим описанием, и какое-то другое описание типа user должно определять, что он из себя представляет. В C++ программе всегда должно быть только одно определение каждого имени, но описаний может быть много, и все описания должны согласовываться с типом объекта, к которому они относятся, поэтому в этом фрагменте есть две ошибки:


int count;
int count; // ошибка: переопределение
exnern int error_number;
exnern int error_number; // ошибка: несоответствие типов


а в этом - ни одной
exnern int error_number;
exnern int error_number;

Некотрые описания задают \"значение\" для сущностей, которые они определяют:

struct complex { float re, im; };
typedef complex point;
float real(complex* p) { return p->re };
const double pi = 3.1415926535897932385;

Для типов, функций и констант \"значение\" неизменно; для неконстантных типов данных начальное значение может впоследствии изменяться:

int count = 1;
char* name = \"Bjarne\";
//...
count = 2;
name = \"Marian\";

Из всех определений только

char ch;

не задает значение. Всякое описание, задающее значение, является определением.

Область Видимости

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

Например:

int x; // глобальное x
f() {
int x; // локальное x прячет глобальное x
x = 1; // присвоить локальному x
{
int x; // прячет первое локальное x
x = 2; // присвоить второму локальному x
}
x = 3; // присвоить первому локальному x
}
int* p = &x; // взять адрес глобального x

Скрытие имен неизбежно при написании больших программ. Однако читающий человек легко может не заметить, что имя скрыто, и некоторые ошибки, возникающие вследствие этого, очень трудно обнаружить, главным образом потому, что они редкие. Значит скрытие имен следует минимизировать. Использование для глобальных переменных имен вроде i или x напрашивается на неприятности.

С помощью применения операции разрешения области видимости :: можно использовать скрытое глобальное имя.

Например:

int x;
f()
{
int x = 1; // скрывает глобальное x
::x = 2; // присваивает глобальному x
}

Но возможности использовать скрытое локальное имя нет.

Область видимости имени начинается в точке описания. Это означает, что имя можно использовать даже для задания его собственного значения.

Например:

int x;
f()
{
int x = x; // извращение
}

Это не является недопустимым, хотя и бессмысленно, и компилятор предупредит, что x "used before set" ("использовано до того, как задано"), если вы попробуете так сделать. Можно, напротив, не применяя операцию ::, использовать одно имя для ссылки на два различных объекта в блоке.

Например:

int x;
f() // извращение
{
int y = x; // глобальное x
int x = 22;
y = x; // локальное x
}

Переменная y инициализируется значением глобального x, 11, а затем ему присваивается значение локальной переменной x, 22.
Имена параметров функции считаются описанными в самом внешнем блоке функции, поэтому

f(int x)
{
int x; // ошибка
}

содержит ошибку, так как x определено дважды в одной и той же области видимости.

Объекты и Адреса (Lvalue)

Можно назначать и использовать переменные, не имеющие имен, и можно осуществлять присваивание выражениям странного вида (например, *p[a+10]=7). Следовательно, есть потребность в имени "нечто в памяти". Вот соответствующая цитата из справочного руководства по C++: "Объект есть область памяти; lvalue есть выражение, ссылающееся на объект". Слово "lvalue" первоначально было придумано для значения "нечто, что может стоять в левой части присваивания". Однако не всякий адрес можно использовать в левой части присваивания; бывают адреса, ссылающиеся на константу.

Время Жизни

Если программист не указал иного, то объект создается, когда встречается его описание, и уничтожается, когда его имя выходит из области видимости, Объекты с глобальными именами создаются и инициализируются один раз (только) и "живут" до завершения программы. Объекты, определенные описанием с ключевым словом static, ведут себя так же. Например *1:
 

 


int a = 1;
void f()
{
int b = 1; // инициализируется при каждом вызове f()
static int c = 1; // инициализируется только один раз
cout << " a = " << a++
<< " b = " << b++
<< " c = " << c++ << "\\n";
}

main()
{
while (a < 4) f();
}
 

 


производит вывод

a = 1 b = 1 c = 1
a = 2 b = 1 c = 2
a = 3 b = 1 c = 3

Не инициализированная явно статическая (static) переменная неявно инициализируется нулем.

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



Опубликовал admin
23 Мар, Вторник 2004г.



Программирование для чайников.