Объявление (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 (явный). Это предотвращает их использование для неявных преобразований типов, хотя не запрещает применения, если преобразование указано явно:

void doSomething(B bObject); // функция принимает объект типа B

B bObj1; // объект типа B

doSomething(bObj1); // нормально, B передается doSomething

B bObj(28); // нормально, создает B из целого 28

// (параметр bool по умолчанию true)

doSomething(28); // ошибка! doSomething принимает B,

// а не int, и не существует неявного

// преобразования из int в B

doSomething(B(28)); // нормально, используется конструктор

// B для явного преобразования (приведения)

// int в B (см. в правиле 27 информацию

// о приведении типов)

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

Обратите внимание, что в предшествующем примере приведение выделено. Я и дальше буду использовать такое выделение, чтобы подчеркнуть важность излагаемого материала. (Также я выделяю номера глав, но это только потому, что мне кажется, это выглядит симпатично.)

Конструктор копирования (copy constructor) используется для инициализации объекта значением другого объекта того же самого типа, а копирующий оператор присваивания (copy assignment operator) применяется для копирования значения одного объекта в другой – того же типа:

class Widget {

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату
×