означает создание соответствующего неименованного объекта класса Date, которое затем можно соответствующим образом использовать. Рассмотрим пример.
cout << Date(1978, Date::dec, 24);
В
данном случае конструктор класса действует почти как литерал. Это часто удобнее, чем сначала создавать переменную или константу, а затем использовать ее лишь один раз.
А если нас не устраивает копирование по умолчанию? В таком случае мы можем либо определить свое собственное копирование (см. раздел 18.2), либо создать конструктор копирования и закрытый оператор копирующего присваивания (см. раздел 14.2.4).
9.7.3. Конструкторы по умолчанию
Неинициализированные переменные могут быть источником серьезных ошибок. Для того чтобы решить эту проблему, в языке С++ предусмотрено понятие конструктора, гарантирующее, что каждый объект класса будет инициализирован. Например, мы объявили конструктор
Date::Date(int,Month,int)
, чтобы гарантировать, что каждый объект класса
Date
будет правильно проинициализирован. В данном случае это значит, что программист должен предоставить три аргумента соответствующих типов. Рассмотрим пример.
Date d1; // ошибка: нет инициализации
Date d2(1998); // ошибка: слишком мало аргументов
Date d3(1,2,3,4); // ошибка: слишком много аргументов
Date d4(1,"jan",2); // ошибка: неправильный тип аргумента
Date d5(1,Date::jan,2); // OK: используется конструктор с тремя
// аргументами
Date d6 = d5; // OK: используется копирующий конструктор
Обратите внимание на то, что, даже несмотря на то, что мы определили конструктор для класса
Date
, мы по-прежнему можем копировать объекты класса
Date
. Многие классы имеют вполне разумные значения по умолчанию; иначе говоря, для них существует очевидный ответ на вопрос: какое значение следует использовать, если инициализация не выполнена? Рассмотрим пример.
string s1; // значение по умолчанию: пустая строка ""
vector<string> v1; // значение по умолчанию: вектор без элементов
vector<string> v2(10); // вектор, по умолчанию содержащий 10 строк
Все это выглядит вполне разумно и работает в соответствии с указанными комментариями. Это достигается за счет того, что классы
vector
и
string
имеют конструкторы по умолчанию, которые неявно выполняют желательную инициализацию.
Для типа
T
обозначение
T
— значение по умолчанию, определенное конструктором, заданным по умолчанию.
Итак, можно написать следующий код:
string s1 = string; // значение по умолчанию: пустая строка ""
vector<string> v1 = vector<string>; // значение по умолчанию:
// пустой вектор; без элементов
vector<string> v2(10,string); // вектор, по умолчанию содержащий
// 10 строк
Однако мы предпочитаем эквивалентный и более краткий стиль.
string s1; // значение по умолчанию: пустая строка ""
vector<string> v1; // значение по умолчанию: пустой вектор;
// без элементов
vector<string> v2(10); // вектор, по умолчанию содержащий 10 строк
Для встроенных типов, таких как
int
и
double
, конструктор по умолчанию подразумевает значение
0
, так что запись
int
— это просто усложненное представление нуля, а
double
— долгий способ записать число
0.0
.
Опасайтесь ужасных синтаксических проблем, связанных с обозначением при инициализации.
string s2; // функция, не получающая аргументов и возвращающая
// строку
Использование конструктора, заданного по умолчанию, — это не просто вопрос стиля. Представьте себе, что отказались от инициализации объектов класса
string
и
vector
.
string s;
for (int i=0; i<s.size; ++i) // ой: цикл выполняется неопределенное
// количество раз
s[i] = toupper(s[i]); // ой: изменяется содержание
// случайной ячейки памяти
vector<string> v;
v.push_back("bad"); // ой: запись по случайному адресу
Если значения переменных
s
и
v
действительно не определены, то непонятно, сколько элементов они содержат или (при общепринятом способе реализации; см. раздел 17.5) неясно, где эти элементы должны храниться. В результате будут использованы случайные адреса — и это худшее, что может произойти. В принципе без конструктора мы не можем установить инвариант, поскольку не можем гарантировать, что его объекты будут корректными (см. раздел 9.4.3). Мы настаиваем на том, что такие переменные должны быть проинициализированы. В таком случае фрагмент можно было бы переписать следующим образом: