Конструктор с одним параметром типа string
использует ее для инициализации переменной-члена bookNo
, но переменные units_sold
и revenue
не инициализируются явно. Когда член класса отсутствует в списке инициализации конструктора, он инициализируется неявно, с использованием того же процесса, что и у синтезируемого стандартного конструктора. В данном случае эти члены инициализируются внутриклассовыми инициализаторами. Таким образом, получающий строку конструктор эквивалентен следующему.
//
Sales_data(const std::string &s):
bookNo(s), units_sold(0), revenue(0) { }
Обычно для конструктора лучше использовать внутриклассовый инициализатор, если он есть и присваивает члену класса правильное значение. С другой стороны, если ваш компилятор еще не поддерживает внутриклассовые инициализаторы, то каждый конструктор должен явно инициализировать каждый член встроенного типа.
Следует заметить, что у обоих этих конструкторов тела пусты. Единственное, что должны сделать эти конструкторы, — присвоить значения переменным-членам. Если ничего другого делать не нужно, то тело функции пусто.
В отличие от наших других конструкторов, конструктору, получающему поток istream
, действительно есть что делать. В своем теле этот конструктор вызывает функцию read()
, чтобы присвоить переменным-членам новые значения:
Sales_data::Sales_data(std::istream &is) {
read(is, *this); //
}
У конструкторов нет типа возвращаемого значения, поэтому определение начинается с имени функции. Подобно любой другой функции-члену, при определении конструктора за пределами тела класса необходимо указать класс, которому принадлежит конструктор. Таким образом, синтаксис Sales data::Sales_data
указывает, что мы определяем член класса Sales_data
по имени Sales_data
. Этот член класса является конструктором, поскольку его имя совпадает с именем класса.
В этом конструкторе нет списка инициализации конструктора, хотя с технической точки зрения было бы правильней сказать, что список инициализации конструктора пуст. Даже при том, что список инициализации конструктора пуст, члены этого объекта инициализируются прежде, чем выполняется тело конструктора.
Члены, отсутствующие в списке инициализации конструктора, инициализируются соответствующим внутриклассовым инициализатором (если он есть) или значением по умолчанию. Для класса Sales_data
это означает, что при запуске тела функции на выполнение переменная bookNo
будет содержать пустую строку, а переменные units_sold
и revenue
— значение 0.
Чтобы стало понятней, напомним, что второй параметр функции read()
является ссылкой на объект класса Sales_data
. В разделе 7.1.2 мы обращали внимание на то, что указатель this
используется для доступа к объекту в целом, а не к его отдельному члену. В данном случае для передачи "этого" объекта в качестве аргумента функции read()
используется синтаксис *this
.
Упражнение 7.11. Добавьте в класс Sales_data
конструкторы и напишите программу, использующую каждый из них.
Упражнение 7.12. Переместите определение конструктора Sales_data()
, получающего объект istream, в тело класса Sales_data
.
Упражнение 7.13. Перепишите программу из раздела 7.1.1 так, чтобы использовать конструктор с параметром istream
.
Упражнение 7.14. Напишите версию стандартного конструктора, явно инициализирующую переменные-члены значениями, предоставленными внутриклассовыми инициализаторами.
Упражнение 7.15. Добавьте соответствующие конструкторы в класс Person
.
Кроме определения способа инициализации своих объектов, классы контролируют также то, что происходит при копировании, присвоении и удалении объектов класса. Объекты копируются во многих случаях: при инициализации переменной, при передаче или возвращении объекта по значению (см. раздел 6.2.1 и раздел 6.3.2). Объекты присваиваются при использовании оператора присвоения (см. раздел 4.4). Объекты удаляются, когда они прекращают существование, например, при выходе локального объекта из блока, в котором он был создан (см. раздел 6.1.1). Объекты, хранимые в векторе (или массиве), удаляются при удалении вектора (или массива).