Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
238
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

190

Часть I # Введение в програттшрошаиыв на C-i-^

C + + позволяет определять во вложенных циклах переменную, имя которой определено также во внешней, охватывающей области действия. Это обеспечива­ ет взаимодействие имен, определенных во вложенных областях действия. В дан­ ном случае программный элемент, определенный во внешней области действия, станет недоступным во вложенной области. Когда имя используется внутри вло­ женной области действия, оно ссылается на программный элемент, определенный в данной вложенной области. Вне вложенной области действия это имя могло бы тем не менее ссылаться на программный элемент (переменную, тип или параметр), определенный во внешней области действия.

Данные загружены

800123456 1200

800123123 1500

800123333 1800

Остаток на счете в долл. 4500 На счете 800123123 осталось 1500

Рис. 6.2. Вывод программы из листинга 6.2

Чтобы продемонстрировать эффект вложенности, рассмот­ рим листинг 6.2, в нем показана модифицированная версия программного кода, представленного в листинге 6.1. Обе ло­ кальные переменные temp определены в теле функции main() и функции getBalanceO. Другие бесполезные изменения так­ же сделаны для примера: переменные МАХ (фактически, это константа), count и массив Account а[] стали глобальными в области действия файла, а функция printAccounts() была добавлена для вывода номера счета и остатка на счете (на от­ дельной строке) в массиве а[ ]. Сумма остатков на счетах выво­ дится на экран, а затем программа иш,ет конкретный номер счета и выводит остаток средств на этом счете (если находит его). Результат выполнения программы представлен на рис. 6.2.

Листинг 6.2. Демонстрация вложенных областей действия и наложения имен

#inclucle <iostream> using namespace std;

struct Account { long num; double bal; } ;

const int MAX = 5; int count = 0; Account a[MAX];

void

printAccountsO

 

{ for (int

i = 0;

i < count;

i++)

{

double

count

= a [ i ] . b a l ;

 

cout «

a[i].num « " "

« count « endl; } }

int mainO

//максимальный размер набора данных

//число элементов в наборе данных

//глобальные данные для обработки

//глобальный счетчик

//локальный счетчик

typedef int Index;

 

 

 

long nurn[MAX] = {800123456, 800123123, 800123333, -1 }

 

 

long number = 800123123; double total = 0;

// внешняя область действия

while

(true)

 

// конец, если

контрольное значение

{ doi^ble amounts[MAX] = { 1200, 1500, 1800 }

// данные для

загрузки

if (num[count] == -1) break;

// найдено контрольное значение

double number = amounts[count];

// number скрывает внешнее имя number

a[count].num

= num[count];

// загрузка данных

a[count].bal

= number;

 

 

 

count++; }

 

 

 

 

cout «

"Данные загружены\п\п";

 

 

 

printAccountsO;

// глобальный

счетчик

for (Index i = 0; i < count; i++)

{ double count = a[i].bal;

// локальный

счетчик

total += count;

 

 

 

 

 

Глава 6 ^ Управление памштью

191

 

i f

( i == ::count - 1)

 

 

/ / глобальный

счетчик

 

 

 

cout

« "Остаток на счете

в долл." « total «

endl; }

 

 

for

(Index j

= 0; j < count;

j++)

 

 

 

 

i f

(a[j].num == number)

 

 

 

 

 

cout

«

"Ha счете " « number

« "

осталось: долл. " «

a [ j ] . b a l «

endl;

 

return

0;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

Областью действия глобальных переменных будет файл, в котором они опреде­

 

 

 

лены. Любая функция в данном файле может ссылаться на такое имя (если оно

 

 

 

не скрытое), и все эти ссылки будут указывать на одну глобальную переменную.

 

 

 

Например, массив а[] и переменная count в листинге 6.2 ссылаются только на

 

 

 

функцию printAccountsO, а константа МАХ в main() используется только в функ­

 

 

 

ции main(). Чтобы работать с этими именами в printAccountsO, нет никакой

 

 

 

необходимости определять их там. Достаточно глобальных определений.

 

 

 

По существу областью действия глобальных переменных является программа,

 

 

 

а не файл. Если определить имя МАХ, count, а или num как глобальное имя в другом

 

 

 

файле той же программы, то каждый файл компилируется отдельно. При компи­

 

 

 

ляции не проверяется содержимое других файлов. Независимо от того, для чего

 

 

 

используются имена — для одной цели или разных, компоновидик сообщит о дуб­

 

 

 

лированных определениях. Например, а[]

и num[] могут определяться в другом

 

 

 

файле как скалярные переменные, а не массивы. Их повторное использование

 

 

 

приведет к ошибке. Это относится только к глобальным определениям и не при­

 

 

 

меняется ни к объявлениям, ни к неглобальным определениям. Позднее будут

 

 

 

приведены примеры.

 

 

 

 

 

 

 

Другие области действия C + + (функции или блока), определенные в конкрет­

 

 

 

ном исходном файле, будут вложенными в глобальную область действия файла.

 

 

 

Следовательно, глобальные имена видимы в функциях внутри файла, как любые

 

 

 

внешние имена во вложенных циклах. Если функции сами содержат вложенные

 

 

 

области действия, то имена глобальных переменных будут видимы и в этих облас­

 

 

 

тях. В листинге 6.2 глобальные массивы а[], num[] и индекс count

используются

в теле первого цикла, вложенного в область действия main(). Наличие вложенных (на любую глубину) областей действия не изменяет видимости имен, определен­ ных во внешних областях действия.

Во вложенных областях действия переменные могут определяться с помощью имен из внешних областей действия (которые уже известны во вложенных облас­ тях). Когда такое имя используется в локальной вложенной области действия (функции в файле, в расположенном в функции блоке или в другом блоке), то дан­ ная ссылка указывает на локальное имя. Если такое имя используется во внешней, охватывающей области действия, ее смысл определяется этой внешней областью (локальное имя вне конкретной области действия неизвестно).

В листинге 6.2 функция printAccountsO использует в цикле с условием про­ должения имя count. Это имя ссылается на глобальную переменную count. Внутри цикла имя count ссылается на переменную, определенную в теле цикла, а не в гло­ бальной области действия. Имя во вложенной области действия переопределяет глобальное имя. Иногда говорят не о переопределении имен во вложенных облас­ тях, а о сокрытии имени. Заметим, что имя во вложенной области необязательно определяет переменную того же типа. Это может быть все, что угодно.

Нетрудно написать функцию printAccountsO без использования переменной count. Эта переменная была представлена только для иллюстрации на относитель­ но простом примере концепции области действия имен. Фактически невозможно предложить пример, где повторное использование глобального имени было бы действительно необходимо. Можно всегда обойтись локальным именем, отличным от имени во внешней области действия. Вся прелесть принципа области действия имен в том, что не требуется определять другое имя. Можно использовать то имя.

192

Часть I # Введв-

которое вам нравится, и это имя будет известно в данной области действия, неза­ висимо от того, какие имена действуют во внешней области.

Когда во вложенной области действия переопределяется имя, определенное во внешней, охватывающей области (глобальной или вложенной в другую область), имя из внешней области действия становится недоступным во вложенной области. Переопределение имени из внешней области указывает сопровождагоидему прило­ жение программисту, что в намерения разработчика не входило использование глобального имени в локальной области.

В листинге 6.2 тело первого цикла в функции main() определяет переменную number. Такое же имя number определяется в области действия самой функции main(). Это означает, что в теле цикла number действует локальная переменная типа double, а не внешняя типа int, так как локальное имя переопределяет внеш­ нее. Вне цикла (например, на предпоследней строке в листинге 6.2) имя number ссылается на переменную, определенную в самой функции main().

Аналогично в теле второго цикла функции main() в листинге 6.2 определяется переменная count типа double, переопределяюш.ая глобальную переменную count типа int. Ссылки на имя count в цикле разрешаются компилятором как ссылки на локальную переменную типа double, хотя в условии продолжения цикла это ссылка на глобальную переменную count типа int.

Если во вложенной области действия требуется доступ к глобальному имени, можно использовать операцию C + + глобальной области действия ': :'. Напри­ мер, в листинге 6.2 сумма остатков на счетах выводится внутри, а не вне цикла (что было бы прош,е и естественней). Таким образом, в цикле нужно сравнить ин­ декс i с числом допустимых элементов набора данных. В таком контексте : : count во втором цикле main() ссылается на глобальный объект count, а не на локальный объект.

Понятно, что доступ к скрытым глобальным объектам не должен быть простым. Если во вложенной области действия требуется глобальное имя, его нельзя пере­ определять. Кроме того, чтобы избежать конфликта имен, во вложенной области действия можно ввести любое имя. Операция области действия может понадо­ биться при сопровождении программы, когда возникают новые требования к применению уже переопределенного глобального имени, а в первоначальном варианте программы такое использование не предусматривалось.

О с т о р о ж н о ! Операция глобальной области действия : : переопределяет правила действия областей. Для сопровождающего программиста легче придерживаться правил области действия, чем искать эту операцию.

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

Обратите внимание, что операция области действия позволяет обращаться только к глобальным переменным. В программе C + + нет механизма, с помощью которого можно из вложенной области действия обращаться к переменным внеш­ ней области, переопределенным во вложенной.

В листинге 6.2 в теле первого цикла определяется переменная number, скрываю­ щая переменную number, определенную в main(). Это означает, что все ссылки на number в данном цикле являются ссылками на локальную переменную. К определя­ емой в mainO переменной number можно обращаться вне тела цикла (например, в последнем цикле из листинга 6.2).

Внимание Операция области действия позволяет обращаться к глобальному имени. Если во вложенном цикле переопределяется имя,

действующее во внешнем блоке, то C++ не предусматривает способа ссылки на это внешнее имя. Если во вложенном блоке действительно необходимо такое внешнее имя, просто не переопределяйте его.

Глава 6 « Управление памятью

193

Область действия переменных цикла

Определение переменных в заголовке цикла позаимствовано из аналогичных средств языка Ада, но в C + + оно реализуется по-другому, и разными компилято­ рами интерпретируется по-разному. Когда имя переменной цикла используется вне цикла, одни компиляторы сигнализируют об ошибке, другие — нет. Новый стандарт C + + ограничивает область действия переменных цикла телом цикла, следовательно, они не должны использоваться вне цикла. Если в другом цикле в данной области действия то же имя используется для другой переменной цикла, некоторые компиляторы указывают на ошибку (хотя новый стандарт это допускает), другие ее игнорируют. В листинге 6.2 показаны примеры предусмотрительного использования данных средств с учетом переносимости программы: переменные цикла не переопределяют имен из внешних областей действия, не используются вне циклов и не переопределяются в других циклах в той же области действия.

В обш^ем случае лексическая область действия является важным средством. Имена могут просто использоваться повторно (без конфликтов) в независимых областях действия и переопределяться (путем сокрытия внешних имен) во вло­ женных областях. Если области действия объектов с одним и тем же именем явля­ ются вложенными, то последние определенные имена скрывают предыдуидие.

Правила области действия имен помогают избегать конфликтов имен и излиш­ ней координации между программистами, работаюш^ими над одним проектом.

Управление памятью: классы памяти

Лексическая область действия, о которой рассказывалось в предыдущем раз­ деле, является характеристикой программы этапа компиляции. Она определяет сегменты исходного кода программы, где известно конкретное имя. Однако лекси­ ческая область действия не определяет, когда на этапе выполнения программы выделяется память для конкретной переменной и когда эта память освобождается для других целей. Правила распределения памяти на этапе выполнения зависят от другой характеристики определяемых в программе имен — их классов памяти (или экстента).

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

Выполнение программы в C + + всегда начинается с функции main(). Первый оператор в функции main() обычно является первым выполняемым программой оператором. Функция main() вызывает другие функции программы, которые, в свою очередь, вызывают функции. Когда функция-сервер завершает свою работу (вы­ полняет оператор return или достигает закрываюш,ей фигурной скобки в теле функции), управление возвраш^ается к вызвавшей ее клиентской функции. Когда последняя вызванная из main() функция завершается и функция main() достигает закрываюндей фигурной скобки (или оператора return), завершается вся про­ грамма.

До сих пор рассматривались две версии функции main(): одна с возвращаемым типом int, а другая — возвращающая void. Если тип не указывается*, компилятор предполагает, что это функция int (что, конечно, не всегда уместно). Каждая фор­ ма функции main() может использоваться с необязательными параметрами:

void

main(int argc,

char*

argv[])

/ /

аргументы командной строки

{ for

(int

i = 0; i

< argc;

i++)

 

/ /

начало

выполнения программы

cout «

"Аргумент

" « i

« ":

" « argv[i] «

endl;

 

. . . .

}

 

 

 

 

/ /

конец

программы

194 I

Часть I ш Введение в oporpaiviivinpOBa^

Параметры передаются функции main() из операционной системы iipn ошо^ос программы. Они содержат аргументы командной строки, введенные пользовате­ лем при вызове программы (если они есть). Эти параметры определены как счет­ чик аргументов командной строки (агде) и массив (вектор) строк (argv[]), где каждая строка содержит один из аргументов (ниже рассказывается об указателях).

Часто эти строки представляют собой имена файлов, набранных в командной строке. В приведенном выше примере функция main() использует count аргумен­ тов командной строки и анализирует каждый из аргументов (в данном случае — просто выводит его на экран). В список аргументов командной строки включено имя выполняемого файла программы. Конечно, строка с именем файла программы имеет в массиве нулевой индекс. Например, если выполняемый файл программы называется сору, то команда:

с:\>сору account.сор c:\clata

дает следующие строки:

Agrument О copy

Agrument 1 account.срр

Agrument 2 c:\clata

В ходе выполнения программы память д/ш ее переменных (объектов) может выделяться в трех зарезервированных для программы областях: в фиксированной памяти, области стека и динамически распределяемой области. Пока что мы не будем рассматривать, как происходит управление этими областями памяти в кон­ кретной компьютерной архитектуре. Какой бы ни была переменная (скалярной, массивом, структурой, классом, объединением или перечислением), память для нее выделяется в одной из данных областей при выполнении программы (в зависи­ мости от ее класса памяти).

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

В определениях переменных классы памяти C + + можно задавать с помощью следующих ключевых слов:

auto: назначается по умолчанию для переменных, определяемых как локальные для блока или функции

extern: может применяться к переменным, глобальным в области действия файла

static: может использоваться для глобальных переменных

вобласти действия файла или для локальных переменных, определенных в области действия блока или функции

register: используется для переменных, хранимых

вбыстродействующих регистрах,

ане в памяти с произвольным доступом

Для объектов (переменных) этих классов правила языка определяют механизмы выделения и освобождения памяти: переменные extern и static распределяются в фиксированной области памяти программы, переменные auto — в стеке, а пере­ менные register — в регистрах (если это возможно). Если нет доступных регист­ ров, то данные переменные распределяются в фиксированной области (в случае глобальных переменных) или в стеке программы (если переменные локальные).

Глава 6»Управление памятью | 195 |

Автоматические переменные

Автоматические переменные — это локальные переменные, определенные в блоках или функциях. Спецификатор auto задается по умолчанию и используется редко. Например, функцию printAccounts() в листинге 6.2 можно записать следу­ ющим образом:

void

printAccountsO

 

 

 

{ for (auto

int

i = 0;

i < count; i++)

/ /

глобальная переменная count

{

auto double

count

= getBalance(a[i]);

/ /

локальная переменная count

 

cout «

a[i].nuni «

" " « count « endl;

} }

 

Поскольку программистам обычно не нравится набирать лишнее, они пред­ почитают опускать спецификаторы по умолчанию, если нет явных причин их указывать.

' Память для автоматических переменных выделяется из стека, когда программа достигает открывающей фигурной скобки функции или блока. Если в определение включена инициализация, как в примере с printAccountsO, выделяемая для пере­ менной память инициализируется. Если начальное значение в определении не задается, значение переменной не определено. Скорее всего, она будет содержать значение, оставшееся в данной области памяти от предыдущей переменной. В C++ нет ключевого слова "undefined" (не определено), но его следует прини­ мать всерьез. Если необходимо конкретное значение, инициализируйте перемен­ ную и используйте его, но на неопределенные значения полагаться не следует. Они могут быть совершенно произвольными и различаться при каждом запуске программы, даже когда эксперименты показывают, что они одни и те же. Не дове­ ряйте подобным экспериментам.

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

C++ предоставляет замечательные возможности управления памятью. Они освобождают программиста от обязанностей распределения памяти для отдельных объектов. Для некоторых задач этих методов недостаточно и вместо них приме­ няется динамическое распределение памяти. Как будет показано в данной главе, динамическое распределение памяти — вещь более сложная и способствующая появлению ошибок. Именно поэтому как можно чаще следует использовать авто­ матические переменные.

Память, распределяемая для автоматической переменной при другом вызове той же функции (или на очередной итерации того же цикла), может оказаться в другом месте стека и с другим содержимым, поэтому автоматические перемен­ ные нельзя использовать для передачи данных между последовательными вызо­ вами функции или последовательными итерациями цикла. Если переменная не инициализируется, она будет иметь на каждой итерации неопределенное значение. Если значение переменной включает инициализацию, инициализация повторяется при каждом входе в область действия. В примере с printAccountsO память для локальной переменной count выделяется, инициализируется и освобождается на каждой итерации цикла. Кроме того, память выделяется, инициализируется и освобождается при каждом обращении к функции printAccountsO.

Когда памяти для программы достаточно и скорость ее выполнения не кри­ тична, не следует пытаться оптимизировать управление памятью для локальных переменных. Если же ресурсов не хватает, важно хорошо понимать последствия выбора архитектуры программы. Например, в листинге 6.2 массив num[] опреде­ ляется как локальная переменная в функции main(), а массив amounts[] — как локальная переменная в теле первого цикла. Оба этих массива содержат данные

I 196 I

Часть I # Введение в орогрогл^

:тт.в на С-ь-

для загрузки значений в глобальный массив а[]. Определение массивов num[] и amounts[] в разных местах программы — пример разделения того, что должно быть вместе.

Данное решение может также повлиять на производительность. Память для массива num[ ] выделяется только один раз, в начале выполнения функции main(). Память для массива amounts[] выделяется, инициализируется и освобождается столько раз, сколько выполняется тело цикла. Выделение и освобождение памяти не занимает много времени при выполнении программы (это операции с указате­ лем стека), но копирование значений в элементы массива при инициализации — процесс более длительный. Он требует почти столько же времени, сколько копи­ рование данных из массива amounts[] в массив а[]. Было бы хорошо выделить память для массивов num[] и amounts[] в одном месте и делать это только один раз при выполнении программы.

int mainO

{ typedef int Index;

long num[MAX] - { 800123456, 800123123, 800123333, -1 };

double amounts[MAX] = { 1200, 1500, 1800 } ;

// данные для загрузки

long number 800123123; double total = 0;

// внешний цикл

while (true)

 

{ if (num[count] == -1) break;

 

...<...}}

/ / конец mainO

Имена автоматических переменных невидимы вне их области действия, поэто­ му их можно повторно задавать в других областях действия. Связь между адресами памяти для переменных и их именами в различных областях действия отсутствует. Для уменьшения координации между разработчиками это хорошо. Когда в разных областях действия используется глобальная переменная, в каждой области дей­ ствия ссылка идет на один и тот же адрес памяти, поэтому нужно понять, дейст­ вительно ли можно в разных целях задействовать одну ячейку памяти или нужно ввести разные переменные. Применение автоматических переменных упрощает задачу разработчика и программиста, сопровождаюш,его программу.

Согласно правилам области действия, во вложенных блоках имя можно по­ вторно использовать для других объектов. Память для нового объекта с тем же именем выделяется в стеке в ячейке, отличной от ячейки, выделенной для пере­ менной во внешнем цикле. Имя во вложенной области действия скрывает объект, распределенный в стеке ранее (и все еще существующий). Например, в листин­ ге 6.2 переменная number определяется в функции main() и переопределяется

втеле первого цикла функции main(). Вторая переменная number распределяется

встеке в начале итерации цикла и освобождается в конце каждой итерации. Это совершенно разная память (даже на разных итерациях), и она не имеет ничего общего с памятью, выделенной для переменной number в начале функции main(). Поэтому, когда на третьем цикле в main() требуется значение, присвоенное пере­ менной number в начале функции main(), после завершения первого вложенного цикла его можно использовать снова — оно осталось неприкосновенным.

Аналогично, когда функция main() вызывает функцию printAccountsO, для каждой итерации-в printAccountsO в стеке выделяется память для переменной count. Эти области памяти на каждой итерации могут быть разными и не имеют ничего общего с глобальной переменной count в фиксированной области данных.

Если вложенная область памяти не скрывает переменной, определенной во внешней области, имя этой переменной доступно во вложенной области действия. В листинге 6.2 память для переменной total выделяется в начале функции main() и не переопределяется во вложенных областях действия. Когда второй цикл ссылается на total, это будет ссылка на переменную, определенную во внешней области действия.

Глава 6«Управление па1у|ятью

| 197 |

Формальные параметры функции интерпретируются как автоматические пере­ менные, определенные в области действия функции. Они инициализируются зна­ чениями фактических аргументов в вызове функции. Например, в первой версии примера программы (в листинге 6.1) функция getBalance() инициализирует свой параметр значением a[i] в функции main(). Память для параметров выделяется в стеке, когда начинается выполнение функции, а освобождается, когда при вы­ полнении функции достигается закрывающая фигурная скобка.

В общем случае лучше определять переменную по возможности на самом глубоком уровне вложенности в структуре блоков. Это дает следующие преи­ мущества:

Минимизируется область действия программы, где известно имя,

 

а следовательно, и потенциальные конфликты имен с другими объектами

Память увязывается с переменной в течение минимального

 

интервала времени. В остальное время эту память можно использовать

 

для других целей

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

Внешние переменные

Внешние, или глобальные, переменные — это переменные, определяемые вне любой функции. Как уже упоминалось, их область действия — файл (от точки определения до конца файла). Следовательно, в другом файле нельзя ссылаться на ту же переменную — это имя будет там невидимо. (На самом деле данное препятствие легко преодолеть.) В то же время такое имя можно использовать в другом файле для определения другой внешней переменной. По существу, имена глобальных переменных действуют в масштабе программы (аналогично именам функций C-I- + ).

Память для глобальных переменных распределяется не так, как для автомати­ ческих. Она выделяется из фиксированной области данных в начале выполнения программы, непосредственно перед первым оператором функции fnain(). Адрес памяти ассоциируется с именем переменной до завершения программы и осво-- бождения памяти после выполнения завершающего оператора main().

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

Влистинге 6.2 переменные МАХ, count и а[] определяются как глобальные.

Легко подсчитать общий объем памяти для всех глобальных переменных в программе. Компилятор обрабатывает каждый файл отдельно и вычисляет, сколько пространства необходимо для глобальных переменных, складывая разме­ ры всех глобальных объектов. (Для автоматических переменных это не имеет смысла, поскольку они не существуют в памяти одновременно.) Еще одно преи­ мущество использования глобальных переменных — скорость: поскольку память для глобальной переменной выделяется и освобождается только один раз, а не при каждом вхождении в область действия, такая операция не замедляет выполнения программы (хотя для многих приложений это не важно).

Г^198"

Часть 1 # Введение в програтттровоитв на С^^-

Другое преимущество применения глобальных переменных состоит в меньших требованиях к стеку программы. Необходимый программе размер стека невоз­ можно вычислить точно, а потому всегда есть вероятность нехватки стека. Вот почему важно не увеличивать требований к стеку без веской причины. Например, массив amounts[] в листинге 6.2 определяется как локальный, а массив num[] — как глобальный. Здесь не только разделено то, что должно быть вместе, и не только тратится время на выделение памяти и инициализацию массива на каждой итерации цикла. Для массива amounts[] отводится память в стеке. Первые две операции требуют времени, а третья — дополнительной памяти. Если сделать данный массив глобальной переменной, все три недостатка будут устранены. В данном примере массив содержит только три переменные — и стека вполне хватит, однако многие программисты выделяют в стеке память д/1я больших массивов, не понимая всех последствий.

Еш.е одно преимуш,ество, по крайней мере для некоторых программистов, со­ стоит в возможности обойтись за счет глобальных переменных без параметров функций. Так как областью действия глобальной переменной является весь файл, где она определена, любая функция, определенная в том же файле после глобаль­ ной переменной, может обращаться непосредственно к этой переменной. Напри­ мер, функция printAccountsO в листинге 6.2 прямо обращается к глобальной переменной count и а[] без сложностей (и дополнительного времени) передачи параметров. Другие полагают, что прямой доступ к глобальным переменным затрудняет понимание интерфейса функции сопровождающим программистом. Чтобы понять, какие переменные функция использует и модифицирует, нужно проверить каждую строку исходного кода функции. Как будет показано ниже, па­ раметры можно документировать непосредственно в интерфейсе функции — не потребуется проверки каждой строки кода.

Недостатком глобальной переменной является длительное время существова­ ния. Она функционирует в течение всего времени выполнения программы, что затрудняет использование памяти для других целей. Например, в листинге 6.2 переменные count и а[ ] действуют во всей программе. С другой стороны, массивы num[] и amounts[] необходимы только в теле первого цикла main(), после чего занятую ими память можно освободить для других целей. Именно это происходит с массивом amounts[ ], для которого отводится память в стеке, однако массив num[ ] сохраняется. Его повторное использование требует аккуратного планирования при разработке программы и может значительно затруднить ее сопровождение. Вот почему в данной программе все переменные не определялись как глобальные.

Имя переменной, определенной в исходном коде как глобальная, известно в любой области действия, вложенной в данный файл. Можно обращаться к гло­ бальной переменной из любого места в файле. Например, в листинге 6.2 пере­ менная count используется в main() как счетчик цикла, МАХ применяется для определения массивов а[ ], num[ ] и amounts[ ]. На глобальный массив nuni[ ] имеет-' ся ссылка в main(), а на глобальный массив а[ ] — в функции main() и в функции printAccountsO.

Как уже упоминалось, во вложенной области действия глобальное имя может переопределяться (скрываться). Память при таком переопределении берется из стека, а не из фиксированной области памяти, и имя будет ссылаться на локаль­ ную автоматическую переменную, а не на глобальную. В листинге 6.2 функция printAccountsO, как и второй цикл в фain(), используют имя count для автомати­ ческой переменной. Когда к автоматической переменной применяется операция области действия ': :', эта переменная ссылается на ячейку памяти в фиксирован­ ной области, а не на ячейку памяти в стеке (: : count в листинге 6.2).

Если локальная переменная count определяется в одной из функций в другом файле программы из листинга 6.2, то это не приведет к проблемам, так как облас­ ти действия независимы. Такая функция будет ссылаться на ячейку памяти в стеке. Если же в другом файле определяется глобальная переменная count (а это попу­ лярное имя — короткое и выразительное), то возникнут ошибки при компоновке.

Глава 6 ^ Управление па1^1ятыо Г^199""!'

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

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

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

Предположим, что программа из листинга 6.2 претерпела дальнейшее разви­ тие и разбита на большее число функций и эти функции должны быть размещены в разных файлах, чтобы с ними могли работать другие программисты. Допустим, вместо поиска конкретного номера счета в конце функции main() нужно вызвать функцию printAverage(), которая использует вычисляемую в main() сумму остат­ ков на счетах, определяет среднее и выводит его на экран. Вместо применения в операторе cout литерального значения используется переменная caption[], со­ держащая текст "Средний остаток на счетах в долл." (распространенный метод, облегчающий локализацию программы). Кроме того, функция printAverageO вызывает функцию printCaption(), использующую переменную caption[]. Этот простой пример достаточно легко понять, но введение в нем дополнительных функций требует обсудить вопросы, имеющие важное значение при разработке больших программ.

Для реализации функций printAverageO и printCaptionO в другом исходном файле потребуются:

Исходный файл, вызывающий функцию printAverageO,

т.е. файл с функцией main(), где известно, что printAverage —

имя функции, определенной в другом файле

• В файле, где реализованы функции printAverageO и printCaptionO, должны быть известны глобальные переменные count и caption[], определенные в другом файле

Данные загружены

800123456 1200

800123123 1500

800123333 1800

Данные обработаны

Средний остаток насчетах в долл. 1500

Рис. 6 . 3 . Вывод программы из листинга 6.3 и листинга 6.4

В листинге 6.3 приведена модифицированная про­ грамма из листинга 6.2, решающая эти проблемы. Функ­ ция printAccountsO упрощена, устранен тип Index, массив amounts[] определяется вслед за массивом num[] (как уже говорилось, эти объекты должны быть вместе), функция printAverageO вызывается в конце функции main(). Добавлен глобальный массив caption[] с заго­ ловком для печати среднего остатка на счетах. В листин­ ге 6.4 показан второй файл, где реализованы функции printAverageO и printCaptionO. Вывод программы представлен на рис. 6.3.

Как видно в листинге 6.3, первая проблема решается добавлением к исходному файлу прототипа функции printAverageO с предшествующим ключевым словом extern:

extern void printAverage(double);

/ / определяется

в другом месте

Если ключевое слово extern в объявлении функции

опущено, компилятор

и компоновщик все равно будут искать где-либо эту функцию. Некоторые про­ граммисты предпочитают использовать в программах C-I-+ ключевое слово extern, чтобы избежать проблем переносимости:

void printAverage(double);

/ / все равно определяется в другом месте

Соседние файлы в предмете Программирование на C++