сумме всех произведений price[i]*weight[i]. Заметьте, что должно выполняться условие weight.size()<=price.size().
11. Напишите функцию maxv(), возвращающую наибольший элемент вектора.
12. Напишите функцию, которая находит наименьший и наибольший элементы вектора, являющегося ее аргументом, а также вычисляющую их среднее и медиану. Результаты можно вернуть либо в виде структуры struct, либо с помощью механизма передачи аргументов по ссылке. Какой из этих двух способов следует предпочесть и почему?
13. Усовершенствуйте функцию print_until_s() из раздела 8.5.2. Протестируйте ее. Какие наборы данных лучше всего подходят для тестирования? Укажите причины. Затем напишите функцию print_until_ss(), которая выводит на печать сроки, пока не обнаружит строку аргумента quit.
14. Напишите функцию, принимающую аргумент типа vector<string> и возвращающую объект типа vector<int>, содержащий количество символов в каждой строке. Кроме того, найдите самую короткую и самую длинную строки, а также первую и последнюю строки в соответствии с лексикографическим порядком Сколько отдельных функций вы использовали бы для решения этой задачи? Почему?
15. Можно ли объявить константный аргумент функции, который передается не по ссылке (например, void f(const int);)? Что это значит? Зачем это нужно? Почему эта конструкция применяется редко? Испытайте ее; напишите несколько маленьких программ, чтобы увидеть, как она работает.
Послесловие
Большую часть этой (и следующей) главы можно было бы вынести в приложение. Однако в части II нам потребуются многие средства, описанные здесь. Кроме того, очень скоро мы столкнемся с проблемами, для решения которых эти средства были изобретены. При написании простых программ вы неизбежно должны будете решать такие проблемы. Итак, для того чтобы сэкономить время и минимизировать недоразумения, необходим систематический подход, а не серия “случайных” ссылок на справочное руководство и приложения.
Глава 9. Технические детали: классы и прочее
“Помните, все требует времени”.
Пит Хейн (Piet Hein)
В этой главе мы сосредоточим внимание на основном инструменте программирования: языке С++. Мы опишем технические подробности этого языка, связанные в основном с типами, определенными пользователем, иначе говоря, с классами и перечислениями. Описание свойств языка излагается на примере постепенной разработки типа Date. Кроме того, это позволяет продемонстрировать некоторые полезные приемы разработки классов.
9.1. Типы, определенные пользователем
В языке С++ есть встроенные типы, такие как char, int и double (подробнее они описаны в разделе A.8). Тип называется встроенным, если компилятор знает, как представить объекты такого типа и какие операторы к нему можно применять (такие как + и –) без уточнений в виде объявлений, которые создает программист в исходном коде.
Типы, не относящиеся к встроенным, называют типами, определенными пользователем (user-defined types — UDT). Они могут быть частью стандартной библиотеки, доступной в любой реализации языка С++ (например, классы string, vector и ostream, описанные в главе 10), или типами, самостоятельно созданными программистом, как классы Token и Token_stream (см. разделы 6.5 и 6.6). Как только мы освоим необходимые технические детали, мы создадим графические типы, такие как Shape, Line и Text (речь о них пойдет в главе 13). Стандартные библиотечные типы являются такой же частью языка, как и встроенные типы, но мы все же рассматриваем их как определенные пользователем, поскольку они созданы из таких же элементарных конструкций и с помощью тех же приемов, как и типы, разработанные нами; разработчики стандартных библиотек не имеют особых привилегий и средств, которых нет у нас. Как и встроенные типы, большинство типов, определенных пользователем, описывают операции. Например, класс vector содержит операции [] и size() (см. разделы 4.6.1 и В.4.8), класс ostream операцию <<, класс Token_stream операцию get() (см. раздел 6.8), а класс Shape операции add(Point) и set_color() (см. раздел 14.2).
Зачем мы создаем типы? Компилятор не знает всех типов, на основе которых мы хотим создавать свои программы. Это в принципе невозможно, поскольку существует слишком много полезных типов — ни один разработчик языка программирования или компиляторов не может знать обо всех. Каждый день мы разрабатываем новый тип. Почему? Какие типы можно признать хорошими? Типы являются хорошими, если они позволяют прямо отразить идею в коде. Когда мы пишем программу, нам хотелось бы непосредственно воплощать идеи в коде так, чтобы мы сами, наши коллеги и компилятор могли понять, что мы написали. Когда мы хотим выполнять арифметические операции над целыми числами, нам отлично подойдет тип int; когда хотим манипулировать текстом, класс string — хороший выбор; когда хотим манипулировать входной информацией для калькулятора, нам нужны классы Token и Token_stream. Необходимость этих классов имеет два аспекта.
• Представление. Тип “знает”, как представить данные, необходимые в объекте.
• Операции. Тип знает, какие операции можно применить к объектам.
Эту концепцию, лежащую в основе многих идей, можно выразить так: “нечто” имеет данные для представления своего текущего значения, — которое иногда называют текущим состоянием, — и набор операций, которые к ним можно применить. Подумайте о компьютерном файле, веб-странице, CD-плеере, чашке кофе, телефоне, телефонном справочнике; все они характеризуются определенными данными и имеют более или менее фиксированный набор операций, которые можно выполнить. В каждом случае результат операции зависит от данных — текущего состояния объекта.
Итак, мы хотим выразить “идею” или “понятие” в коде в виде структуры данных и набора функций. Возникает вопрос: “Как именно?” Ответ на этот вопрос изложен в данной главе, содержащей технические детали этого процесса в языке С++.
В языке С++ есть два вида типов, определенных пользователем: классы и перечисления. Классы носят намного более общий характер и играют более важную роль в программировании, поэтому мы сосредоточим свое внимание в первую очередь на них. Класс непосредственно выражает некое понятие в программе. Класс (class) — это тип, определенный пользователем. Он определяет, как представляются объекты этого класса, как они создаются, используются и уничтожаются (раздел 17.5). Если вы размышляете о чем-то как об отдельной сущности, то, вполне возможно, должны определить класс, представляющий эту “вещь” в вашей программе. Примерами являются вектор, матрица, поток ввода, строка, быстрое преобразование Фурье, клапанный регулятор, рука робота, драйвер устройства, рисунок на экране, диалоговое окно, график, окно, термометр и часы.
В языке