Жасмин Бланшет - QT 4: программирование GUI на С++
Обычно в пространство имен заключаются все объявления заголовочного файла, чтобы гарантировать невозможность попадания идентификаторов, объявленных в этом заголовочном файле, в глобальное пространство имен. Например:
01 #ifndef SOFTWAREINC_RANDOM_H
02 #define SOFTWAREINC_RANDOM_H
03 namespace SoftwareInc
04 {
05 extern int randomNumbers[128];
06 void populateRandomArray();
07 }
08 #endif
(Обратите внимание на то, что мы переименовали препроцессорные макросимволы, используемые для предотвращения многократного включения содержимого заголовочного файла, снижая риск конфликта имен с заголовочным файлом, имеющим такое же имя, но расположенным в другом каталоге.)
Синтаксис пространства имен совпадает с синтаксисом класса, однако в конце не ставится точка с запятой. Ниже приводится новая версия файла random.cpp:
01 #include "random.h"
02 int SoftwareInc::randomNumbers[128];
03 static int seed = 42;
04 static int nextRandomNumber()
05 {
06 seed = 1009 + (seed * 2011);
07 return seed;
08 }
09 void SoftwareInc::populateRandomArray()
10 {
11 for (int i = 0; i < 128; ++i)
12 randomNumbers[i] = nextRandomNumber();
13 }
В отличие от классов, пространства имен можно «повторно открывать» в любое время. Например:
01 namespace Alpha
02 {
03 void alpha1();
04 void alpha2();
05 }
06 namespace Beta
07 {
08 void beta1();
09 }
10 namespace Alpha
11 {
12 void alpha3();
13 }
Это позволяет определять сотни классов, размещенных во многих заголовочных файлах и принадлежащих одному пространству имен. Используя этот прием, стандартная библиотека С++ помещает все свои идентификаторы в пространство имен std. В Qt пространства имен используются для таких подобных глобальным идентификаторов, как Qt::AlignBottom и Qt::yellow. По историческим причинам классы Qt не принадлежат никакому пространству имен, но имеют префикс 'Q'.
Для ссылки на идентификатор, объявленный в другом пространстве имен, указывается префикс в виде имени этого пространства имен (и ::). Можно поступить по-другому — использовать один из следующих трех механизмов, нацеленных на уменьшение количества вводимых символов:
• Можно определить псевдоним пространства имен:
namespace ElPuebloDeLaReinaDeLosAngeles
{
void beverlyHills();
void culverCity();
void malibu();
void santaMonica();
}
namespace LA = ElPuebloDeLaReinaDeLosAngeles;
После определения псевдонима он может использоваться вместо исходного имени.
• Из пространства имен можно импортировать один идентификатор:
int main()
{
using ElPuebloDeLaReinaDeLosAngeles::beverlyHills;
beverlyHills();
}
Объявление using позволяет обращаться к данному идентификатору без указания префикса, состоящего из имени пространства имен.
• Можно импортировать все пространство имен с помощью одной директивы:
int main()
{
using namespace ElPuebloDeLaReinaDeLosAngeles;
santaMonica();
malibu();
}
При таком подходе конфликты имен становятся более вероятными. Если компилятор «жалуется» на двусмысленное имя (например, когда два класса имеют одинаковое имя, определенное в различных пространствах имен), всегда при ссылке на идентификатор его можно уточнить именем пространства имен.
Препроцессор
Препроцессор С++ — это программа, которая обрабатывает исходный файл .cpp, содержащий директивы # (такие, как #include, #ifndef и #endif), и преобразует его файл исходного кода, который не содержит таких директив. Эти директивы предназначены для выполнения простых операций с текстом исходного файла, например для выполнения условной компиляции, включения файла и разворачивания макроса. Обычно препроцессор автоматически вызывается компилятором, однако в большинстве систем предусмотрена возможность непосредственного его вызова (часто для этого используется опция компилятора —E и /E).
• Директива #include разворачивается в содержимое файла, имя которого указывается в угловых скобках (< >) или в двойных кавычках (" "), в зависимости от расположения заголовочного файла в стандартном каталоге или в каталоге текущего проекта. Имя файла может содержать .. и / (этот символ правильно интерпретируется компиляторами Windows как разделитель каталогов). Например:
#include "../shared/globaldefs.h"
• С помощью директивы #define определяется макрос. Каждое появление в тексте программы имени, расположенном после директивы #define, заменяется определенным для него значением. Например, директива
#define PI 3.14159265359
указывает препроцессору на необходимость замены каждого появления в текущей единице компиляции лексемы PI лексемой 3.14159265359. Для предотвращения конфликтов имен с переменными и классами общей практикой стало назначение макросам имен, состоящих только из прописных букв. Можно определять макрос с аргументами:
#define SQUARE(x) ((x) * (x))
Считается хорошим стилем окружение в теле макроса скобками любых параметров, а также всего тела макроса, что позволяет избегать проблем, связанных с приоритетностью операторов. В конце концов нам нужно, чтобы запись 7 * SQUARE(2 + 3) разворачивалась в 7 * ((2 + 3) * (2 + З)), а не в 7 * 2 + 3 * 2 + 3.
Компиляторы С++ обычно позволяют определять макросы в командной строке, используя опцию —D или /D. Например:
CC -DPI=3.14159265359 -с main.cpp
Макросы были очень популярны в прежние дни, когда еще не были введены typedef, перечисления, константы, встраиваемые функции и шаблоны. В наши дни они играют важную роль в предотвращении многократных включений заголовочных файлов.
• Макрос можно отменить в любом месте с помощью директивы #undef:
#undef PI
Эту возможность необходимо использовать, если требуется переопределить макрос, поскольку препроцессор не позволяет определять один и тот же макрос дважды. Эту директиву полезно также применять для управления условной компиляцией.
• Отдельные фрагменты программного кода можно обрабатывать или пропускать при помощи директив #if, #elif, #else и #endif в зависимости от конкретных числовых значений макросов. Например:
#define NO_OPTIM 0
#define OPTIM_FOR_SPEED 1
#define OPTIM_FOR_MEMORY 2
#define OPTIMIZATION OPTIM_FOR_MEMORY
…
#if OPTIMIZATION == OPTIM_FOR_SPEED
typedef int MyInt;
#elif OPTIMIZATION == OPTIM_FOR_MEMORY
typedef short MyInt;
#else
typedef long long MyInt;
#endif
В приведенном выше примере компилятором будет обрабатываться только второе объявление, которое вводит синоним для short. Изменяя определение макроса OPTIMIZATION, мы получим другие программы. Если макрос не определен, он будет иметь значение 0.
Другим оператором условной компиляции является проверка макроса на предмет его определения. Это можно сделать следующим образом, используя оператор defined():
#define OPTIM_FOR_MEMORY
…
#if defined(OPTIM_FOR_SPEED)
typedef int MyInt;
#elif defined(OPTIM_FOR_MEMORY)
typedef short MyInt;
#else
typedef long long MyInt;
#endif
• Ради удобства препроцессор воспринимает #ifdef X и #ifndef X как синонимы #if defined(X) и #if !defined(X). Для пpeдoтвpaщeния мнoгoкpaтныx включeний заголовочного файла мы окружаем его содержимое следующими директивами:
#ifndef MYHEADERFILE_H
#define MYHEADERFILE_H
…
#endif
При первом включении заголовочного файла символ MYHEADERFILE_H оказывается неопределенным, поэтому компилятор обрабатывает программный код, заключенный между директивами #ifndef и #endif. При повторном и последующих включениях заголовочного файла символ MYHEADERFILE_H оказывается определенным, поэтому весь блок #ifndef … #endif пропускается.
• Директива #errоr генерирует на этапе компиляции определенное пользователем сообщение об ошибке. Эта директива часто используется в комбинации с директивами условной компиляции для вывода сообщения о возникновении недопустимого условия. Например:
class UniChar
{
public:
#if BYTE_ORDER == BIG_ENDIAN
uchar row;