Выбрать главу

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

Терминология

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

Объявление (declaration) сообщает компилятору имя и тип чего-либо, опуская некоторые детали. Объявления выглядят так:

extern int x; // объявление объекта

std::size_t numDigits(int number); // объявление функции

class Widget; // объявление класса

template<typename T> // объявление шаблона

class GraphNode; // (см. правило 42 о том, что такое «typename»

Заметьте, что я называю целое число x «объектом», несмотря на то что это переменная встроенного типа. Некоторые люди под «объектами» понимают только переменные пользовательских типов, но я не принадлежу к их числу. Также отметим, что функция numDigits() возвращает тип std::size_t, то есть тип size_t из пространства имен std. Это то пространство имен, в котором находится почти все из стандартной библиотеки C++. Однако, поскольку стандартная библиотека C (точнее говоря, С89) также может быть использована в программе на C++, символы, унаследованные от C (такие как size_t), могут существовать в глобальном контексте, внутри std, либо в обоих местах, в зависимости от того, какие заголовочные файлы были включены директивой #include. В этой книге я предполагаю, что с помощью #include включаются заголовочные файлы C++. Вот почему я употребляю std::size_t, а не просто size_t. Когда я упоминаю компоненты стандартной библиотеки вне текста программы, то обычно опускаю ссылку на std, полагая, что вы знаете, что такие вещи, как size_t, vector и cout, находятся в пространстве имен std. В примерах же программ я всегда включаю std, потому что в противном случае код не скомпилируется.

Кстати, size_t – это всего-навсего определенный директивой typedef синоним для некоторых беззнаковых типов, которые в C++ используются для разного рода счетчиков (например, количества символов в строках типа char*, количества элементов в контейнерах STL и т. п.). Это также тип, принимаемый функциями operator[] в векторах (vector), деках (deque) и строках (string). Этому соглашению мы будем следовать и при определении наших собственных функций operator[] в правиле 3.

В любом объявлении функции указывается ее сигнатура, то есть типы параметров и возвращаемого значения. Можно сказать, что сигнатура функции – это ее тип. Так, сигнатурой функции numDigits является std::size_t(int), иными словами, это «функция, принимающая int и возвращающая std::size_t». Официальное определение «сигнатуры» в C++ не включает тип возвращаемого функцией значения, но в этой книге нам будет удобно считать, что он все же является частью сигнатуры.

Определение (definition) сообщает компилятору детали, которые опущены в объявлении. Для объекта определение – это то место, где компилятор выделяет для него память. Для функции или шаблона функции определение содержит тело функции. В определении класса или шаблона класса перечисляются его члены:

int x; // определение объекта

std::size_t numDigits(int number) // определение функции

{ // (эта функция возвращает количество

std::size_t digitsSoFar = 1; // десятичных знаков в своем параметре)

while((number /= 10) != 0) ++digitsSoFar;

return digitsSoFar;

}

class Widget { // определение класса

public:

Widget();

~Widget();

...

};

template<typename T> // определение шаблона

class GraphNode {

public:

GraphNode();

~GraphNode();

...

};

Инициализация (initialization) – это процесс присваивания объекту начального значения. Для объектов пользовательских типов инициализация выполняется конструкторами. Конструктор по умолчанию (default constructor) – это конструктор, который может быть вызван без аргументов. Такой конструктор либо не имеет параметров вовсе, либо имеет значение по умолчанию для каждого параметра:

class A {

public:

A(); // конструктор по умолчанию

};

class B {

public:

explicit B(int x = 0; bool b = true); // конструктор по умолчанию,

}; // см. далее объяснение

// ключевого слова “explicit”

class C {

public:

explicit C(int x); // это не конструктор по

// умолчанию

};

Конструкторы классов B и C объявлены в ключевым словом explicit (явный). Это предотвращает их использование для неявных преобразований типов, хотя не запрещает применения, если преобразование указано явно: