Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
double Sales_data::avg_price() const {
if (units_sold)
return revenue/units_sold;
else
return 0;
}
Имя функции, Sales data::avg_price(), использует оператор области видимости (см. раздел 1.2), чтобы указать, что определяемая функция по имени avg_price объявлена в пределах класса Sales_data. Как только компилятор увидит имя функции, остальная часть кода интерпретируется как относящаяся к области видимости класса. Таким образом, когда функция avg_price() обращается к переменным revenue и units_sold, она неявно имеет в виду члены класса Sales_data.
Определение функции, возвращающей указатель this на объектФункция combine() должна действовать как составной оператор присвоения +=. Объект, для которого вызвана эта функция, представляет собой левый операнд присвоения. Правый операнд передается как аргумент явно:
Sales_data& Sales_data::combine(const Sales_data &rhs) {
units_sold += rhs.units_sold; // добавить члены объекта rhs
revenue += rhs.revenue; // к членам объекта 'this'
return *this; // возвратить объект, для которого вызвана функция
}
Когда наша программа обработки транзакций осуществляет вызов
total.combine(trans); // обновить текущую сумму
адрес объекта total находится в неявном параметре this, а объект trans связан с параметром rhs. Таким образом, при вызове функции combine() выполняется следующий оператор:
units_sold += rhs.units_sold; // добавить члены объекта rhs
В результате произойдет сложение переменных total.units_sold и trans.units_sold, а сумма должна сохраниться снова в переменной total.units_sold.
Самым интересным в этой функции является тип возвращаемого значения и оператор return. Обычно при определении функции, работающей как стандартный оператор, она должна подражать поведению этого оператора. Стандартные операторы присвоения возвращают свой левый операнд как l-значение (см. раздел 144). Чтобы возвратить l-значение, наша функция combine() должна возвратить ссылку (см. раздел 6.3.2). Поскольку левый операнд — объект класса Sales_data, тип возвращаемого значения — Sales_data&.
Как уже упоминалось, для доступа к члену объекта, функция-член которого выполняется, необязательно использовать неявный указатель this. Однако для доступа к объекту в целом указатель this действительно нужен:
return *this; // возвратить объект, для которого вызвана функция
Здесь оператор return обращается к значению указателя this, чтобы получить объект, функция которого выполняется. Таким образом, для этого вызова возвращается ссылка на объект total.
Упражнения раздела 7.1.2Упражнение 7.2. Добавьте функции-члены combine() и isbn() в класс Sales_data, который был написан для упражнений из раздела 2.6.2.
Упражнение 7.3. Пересмотрите свою программу обработки транзакций из раздела 7.1.1 так, чтобы использовать эти функции-члены.
Упражнение 7.4. Напишите класс по имени Person, представляющий имя и адрес человека. Используйте для содержания каждого из этих членов тип string. В последующих упражнениях в этот класс будут добавлены новые средства.
Упражнение 7.5. Снабдите класс Person операциями возвращения имени и адреса. Должны ли эти функции быть константами? Объясните свой выбор.
7.1.3. Определение функций, не являющихся членом класса, но связанных с ним
Авторы классов нередко определяют такие вспомогательные функции, как наши функции add(), read() и print(). Хотя определяемые ими операции концептуально являются частью интерфейса класса, частью самого класса они не являются.
Мы определяем функции, не являющиеся членом класса, как любую другую функцию, т.е. ее объявление обычно отделено от определения (см. раздел 6.1.2). Функции, концептуально являющиеся частью класса, но не определенные в нем, как правило, объявляются (но не определяются) в том же заголовке, что и сам класс. Таким образом, чтобы использовать любую часть интерфейса, пользователю достаточно включить только один файл.
Обычно функция, не являющаяся членом класса, но из состава его интерфейса объявляется в том же заголовке, что и сам класс.
Определение функций read() и print()Функции read() и print() выполняют ту же задачу, что и код в разделе 2.6.2, поэтому и не удивительно, что тела этих функций очень похожи на код, представленный там:
// введенные транзакции содержат ISBN, количество проданных книг и
// цену книги
istream &read(istream &is, Sales_data &item) {
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream &print(ostream &os, const Sales_data &item) {
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
Функция read() читает данные из предоставленного потока в заданный объект. Функция print() выводит содержимое предоставленного объекта в заданный поток.
В этих функциях, однако, следует обратить внимание на два момента. Во- первых, обе функции получают ссылки на соответствующие объекты классов ввода и вывода. Классы ввода и вывода — это типы, не допускающие копирования, поэтому их передача возможна только по ссылке (см. раздел 6.2.2). Кроме того, чтение и запись в поток изменяют его, поэтому обе функции получают обычные ссылки, а не ссылки на константы.
Второй заслуживающий внимания момент: функция print() не выводит новую строку. Обычно функции вывода осуществляют минимальное форматирование. Таким образом, пользовательский код может сам решить, нужна ли новая строка.
Определение функции add()Функция add() получает два объекта класса Sales_data и возвращает новый объект класса Sales_data, представляющий их сумму:
Sales_data add(const Sales_data &lhs, const Sales_data &rhs) {
Sales_data sum = lhs; // копирование переменных-членов из lhs в sum
sum.combine(rhs); // добавить переменные-члены rhs в sum
return sum;
}
В теле функции определяется новый объект класса Sales_data по имени sum, предназначенный для хранения суммы двух транзакций. Инициализируем объект sum копией объекта lhs. По умолчанию копирование объекта класса подразумевает копирование и членов этого объекта. После копирования у членов bookNo, units_sold и revenue объекта sum будут те же значения, что и у таковых объекта lhs. Затем происходит вызов функции combine(), суммирующей значения переменных-членов units_sold и revenue объектов rhs и sum в последний. По завершении возвращается копия объекта sum.
Упражнения раздела 7.1.3Упражнение 7.6. Определите собственные версии функций add(), read() и print().
Упражнение 7.7. Перепишите программу обработки транзакций, написанной для упражнений в разделе 7.1.2, так, чтобы использовать эти новые функции.
Упражнение 7.8. Почему функция read() определяет свой параметр типа Sales_data как простую ссылку, а функция print() — как ссылку на константу?
Упражнение 7.9. Добавьте в код, написанный для упражнений в разделе 7.1.2, операции чтения и вывода объектов класса Person.
Упражнение 7.10. Что делает условие в следующем операторе if?
if (read(read(cin, data1), data2))
7.1.4. Конструкторы
Каждый класс определяет, как могут быть инициализированы объекты его типа. Класс контролирует инициализацию объекта за счет определения одной или нескольких специальных функций-членов, известных как конструкторы (constructor). Задача конструктора — инициализировать переменные-члены объекта класса. Конструктор выполняется каждый раз, когда создается объект класса.
В этом разделе рассматриваются основы определения конструкторов. Конструкторы — удивительно сложная тема. На самом деле мы сможем больше сказать о конструкторах в разделах 7.5, 15.7 и 18.1.3, а также в главе 13.
Имя конструкторов совпадает с именем класса. В отличие от других функций, у конструкторов нет типа возвращаемого значения. Как и другие функции, конструкторы имеют (возможно пустой) список параметров и (возможно пустое) тело. У класса может быть несколько конструкторов. Подобно любой другой перегруженной функции (см. раздел 6.4), конструкторы должны отличаться друг от друга количеством или типами своих параметров.