Литмир - Электронная Библиотека
A
A

class Year { // год в диапазоне [min:max)

  static const int min = 1800;

  static const int max = 2200;

public:

  class Invalid { };

  Year(int x) : y(x) { if (x<min || max<=x) throw Invalid(); }

  int year() { return y; }

private:

  int y;

};

class Date {

public:

  enum Month {

    jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec

  };

  Date(Year y, Month m, int d); // проверка даты и инициализация

  // ...

private:

  Year y;

  Month m;

  int d; // день

};

Теперь получаем фрагмент кода.

Date dx1(Year(1998),4,3);          // ошибка: 2-й аргумент — не Month

Date dx2(Year(1998),4,Date::mar);  // ошибка: 2-й аргумент — не Month

Date dx2(4, Date::mar,Year(1998)); // ошибка: 1-й аргумент — не Year

Date dx2(Date::mar,4,Year(1998));  // ошибка: 2-й аргумент — не Month

Date dx3(Year(1998),Date::mar,30); // OK

Следующая фатальная и неожиданная ошибка выявится только на этапе выполнения программы.

Date dx2(Year(4),Date::mar,1998); // ошибка на этапе выполнения:

                                  // Year::Invalid

Стоило ли выполнять дополнительную работу и вводить обозначения для лет? Естественно, это зависит от того, какие задачи вы собираетесь решать с помощью типа Date, но в данном случае мы сомневаемся в этом и не хотели бы создавать отдельный класс

Year
.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Когда мы программируем, то всегда устанавливаем критерии качества для данного приложения. Как правило, мы не можем позволить себе роскошь очень долго искать идеальное решение, если уже нашли достаточно хорошее. Втягиваясь в поиски наилучшего решения, мы настолько запутаем программу, что она станет хуже, чем первоначальный вариант. Как сказал Вольтер: “Лучшее — враг хорошего”.

Обратите внимание на слова

static const
в определениях переменных
min
и
max
. Они позволяют нам определить символические константы для целых типов в классах. Использование модификатора
static
по отношению к члену класса гарантирует, что в программе существует только одна копия его значения, а не по одной копии на каждый объект данного класса.

9.7.2. Копирование

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

Затем необходимо решить, можно ли копировать объекты и как это делать? Для класса

Date
или перечисления
Month
ответ очевиден: копирование необходимо, и его смысл тривиален: просто копируются все члены класса. Фактически это предусмотрено по умолчанию. Если не указано ничего другого, компьютер сделает именно это. Например, если перечисление
Date
используется для инициализации или стоит в правой части оператора присваивания, то все его члены будут скопированы.

Date holiday(1978, Date::jul, 4);    // инициализация

Date d2 = holiday;

Date d3 = Date(1978, Date::jul, 4);

holiday = Date(1978, Date::dec, 24); // присваивание

d3 = holiday;

Обозначение

Date(1978, Date::dec, 24)
означает создание соответствующего неименованного объекта класса 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
. Многие классы имеют вполне разумные значения по умолчанию; иначе говоря, для них существует очевидный ответ на вопрос: какое значение следует использовать, если инициализация не выполнена? Рассмотрим пример.

132
{"b":"847443","o":1}