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

Функция populateRandomArrayO объявляется с использованием прототипа. Ука­зывать ключевое слово extern для функций необязательно.

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

ttifndef RAND0M_H ttdefine RANDOMJ

extern int randomNumbers[128];

void populateRandomArrayO;

ttendif

Мы уже видели, как ключевое слово static может использоваться для объявле­ния переменных-членов и функций-членов, которые не привязываются к конкретно­му экземпляру класса, и теперь мы увидели, как можно его использовать для объяв­ления функций и переменных со статической связью. Существует еще одно приме­нение ключевого слова static, о котором следует упомянуть. В С++ можно определить локальную переменную как статическую. Такие переменные инициали­зируются при первом вызове функции и сохраняют свои значения между вызовами функций. Например:

void nextPrimeO {

static int n = 1;

do {

++п;

} while (!isPrime(n)); return n;

}

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

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

Пространства имен позволяют снизить риск конфликта имен в программах С++. Конфликты имен часто возникают в больших программах, использующих несколько библиотек независимых разработчиков. В своей собственной программе вы решаете сами, использовать ли вам или нет пространства имен.

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

#ifndef SOFTWAREINC_RANDOM_H tfdefine SOFTWAREINC_RANDOM_H

namespace Softwarelnc {

extern int randomNumbers[128]; void populateRandomArrayO;

}

ttendif

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

Синтаксис пространства имен совпадает с синтаксисом класса, однако в конце не ставится точка с запятой. Ниже приводится новая версия файла random, срр:

#include "random.h"

int Softwarelnc::randomNumbers[128];

static int seed = 42;

static int nextRandomNumber()

{

seed = 1009 + (seed * 2011); return seed;

}

void Softwarelnc:: populateRandomArrayO {

for (int i = 0; i < 128; ++i)

randomNumbers[i] = nextRandomNumber();

}

В отличие от классов, пространства имен можно «повторно открывать» в любое время. Например:

namespace Alpha {

void alpha1(); void alpha2();

}

namespace Beta {

void beta1();

}

namespace Alpha {

void alpha3();

}

Это позволяет определять сотни классов, размещенных во многих заголовочных файлах и принадлежащих одному пространству имен. Используя этот прием, стан­дартная библиотека С++ помещает все свои идентификаторы в пространство имен std. В Qt пространства имен используются для таких подобных глобальным иденти­фикаторов, как Qt: :AlignBottom и Qt::yellow. По историческим причинам классы Qt не принадлежат никакому пространству имен, но имеют префикс 'Q'.

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

• Можно определить псевдоним пространства имен:

namespace ElPuebloDeLaReinaDeLosAngeles {

void beverlyHillsO; void culverCityO; void malibuO; void santaMonica();

}

namespace LA = ElPuebloDeLaReinaDeLosAngeles;

После определения псевдонима он может использоваться вместо исходного имени.

• Из пространства имен можно импортировать один идентификатор:

int main() {

using ElPuebloDeLaReinaDeLosAngeles::beverlyHills; beverlyHillsO;

}

Объявление using позволяет обращаться к данному идентификатору без указа­ния префикса, состоящего из имени пространства имен.

• Можно импортировать все пространство имен с помощью одной директивы:

int main() {

using namespace ElPuebloDeLaReinaDeLosAngeles;

santaMonicaO; malibuO;

}

При таком подходе конфликты имен становятся более вероятными. Если компи­лятор «жалуется» на двусмысленное имя (например, когда два класса имеют одина­ковое имя, определенное в различных пространствах имен), всегда при ссылке на идентификатор его можно уточнить именем пространства имен.

Препроцессор

Препроцессор С++ - это программа, которая обрабатывает исходный файл . срр , содержащий директивы # (такие, как #include, #ifndef и #endif), и преобразует его файл исходного кода, который не содержит таких директив. Эти директивы предназ­начены для выполнения простых операций с текстом исходного файла, например для выполнения условной компиляции, включения файла и разворачивания макро­са. Обычно препроцессор автоматически вызывается компилятором, однако в боль­шинстве систем предусмотрена возможность непосредственного его вызова (часто для этого используется опция компилятора -Ей /Е).

• Директива #include разворачивается в содержимое файла, имя которого указы­вается в угловых скобках (< >) или в двойных кавычках (""), в зависимости от расположения заголовочного файла в стандартном каталоге или в каталоге теку­щего проекта. Имя файла может содержать .. и / (этот символ правильно интер­претируется компиляторами Windows как разделитель каталогов). Например:

include "../snared/globaldefs.h"

• С помощью директивы #def ine определяется макрос. Каждое появление в тексте программы имени, расположенном после директивы #def ine, заменяется опреде­ленным для него значением. Например, директива

tfdefine PI 3.14159265359

указывает препроцессору на необходимость замены каждого появления в текущей единице компиляции лексемы PI лексемой 3.14159265359. Для предотвращения конфликтов имен с переменными и классами общей практикой стало назначение макросам имен, состоящих только из прописных букв. Можно определять макрос с аргументами:

ttdefine SQUARE(x) ((х) * (х))

Считается хорошим стилем окружение в теле макроса скобками любых парамет­ров, а также всего тела макроса, что позволяет избегать проблем, связанных с приори­тетностью операторов. В конце концов нам нужно, чтобы запись 7 * SQUARE(2 + 3) раз­ворачивалась в 7 * ((2 + 3) * (2 + 3)), а не в 7 * 2 + 3 * 2 + 3.

Компиляторы С++ обычно позволяют определять макросы в командной строке, используя опцию -D или /D. Например:

СС -DPI=3.14159265359 -с main.срр

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

• Макрос можно отменить в любом месте с помощью директивы #undef: ttundef PI

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

• Отдельные фрагменты программного кода можно обрабатывать или пропускать при помощи директив #if, #elif, #else и #endif в зависимости от конкретных числовых значений макросов. Например:

ttdefine N0_0PTIM О

ttdefine 0PTIM_F0R_SPEED 1 #define 0PTIM_F0R_MEM0RY 2

ttdefine OPTIMIZATION 0PTIM_F0R_MEMORY

#if OPTIMIZATION == 0PTIM_F0R_SPEED typedef int Mylnt;

ftelif OPTIMIZATION == 0PTIM_F0R_MEM0RY

typedef short Mylnt;

#else

typedef long long Mylnt; #endif

В приведенном выше примере компилятором будет обрабатываться только вто­рое объявление, которое вводит синоним для short. Изменяя определение макроса OPTIMIZATION, мы получим другие программы. Если макрос не определен, он будет иметь значение 0.

Другим оператором условной компиляции является проверка макроса на пред­мет его определения. Это можно сделать следующим образом, используя оператор defined():

#define 0PTIM_F0R_MEM0RY

#if defined(0PTIM_F0R_SPEED)

typedef int Mylnt;

ttelif defined(0PTIM_F0R_MEM0RY)

typedef short Mylnt;

#else

typedef long long Mylnt; #endif

• Ради удобства препроцессор воспринимает #ifdef X и #ifndef X как синонимы #if defined(X) H#if !defined(X). Для предотвращения многократных включений заголовочного файла мы окружаем его содержимое следующими директивами:

tfifndef MYHEADERFILE_H tfdefine MYHEADERFILE_H

ftendif

При первом включении заголовочного файла символ MYHEADERFILE_H оказывает­ся неопределенным, поэтому компилятор обрабатывает программный код, заклю­ченный между директивами #if ndef и tfendif. При повторном и последующих вклю­чениях заголовочного файла символ MYHEADERFILE_H оказывается определенным, по­этому весь блок #if ndef... #endif пропускается.

• Директива #е г го г генерирует на этапе компиляции определенное пользователем сообщение об ошибке. Эта директива часто используется в комбинации с дирек­тивами условной компиляции для вывода сообщения о возникновении недопус­тимого условия. Например:

class UniChar {

public:

#if BYTE_ORDER == BIG_ENDIAN

uchar row;

uchar cell; #elif BYTE_ORDER == LITTLE_ENDIAN

uchar cell;

uchar row; #else

#error "BYTE_ORDER must be BIG_ENDIAN or LITTLE_ENDIAN"

#endif

};

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


<< назад вперед >>