Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп
Шрифт:
Интервал:
Закладка:
Function(Fct f,double r1,double r2,Point orig);
};
Для определения четырех конструкторов необходимо проделать больше работы, при этом в определениях конструкторов природа значений, заданных по умолчанию, скрыта, а при их явном задании в объявлении функции они выражаются явно. Аргументы по умолчанию часто используются при объявлении конструкторов, но они могут быть полезными для любых функций. Определять аргументы по умолчанию можно лишь для смежных аргументов.
struct Function:Shape {
Function(Fct f,double r1,double r2,Point orig,
int count = 100,double xscale,double yscale); // ошибка
};
Если аргумент имеет значение, заданное по умолчанию, то все последующие аргументы также должны их иметь.
struct Function:Shape {
Function(Fct f,double r1,double r2,Point orig,
int count = 100,double xscale=25,double yscale=25);
};
Иногда угадать удачные значения по умолчанию легко. Например, для строки хорошим выбором значения по умолчанию будет пустой объект класса string, а для вектора — пустой объект класса vector. В других ситуациях, например для класса Function, правильно выбрать значения по умолчанию значительно сложнее: для этого приходится применять метод проб и ошибок. Помните, что вы не обязаны задавать значения по умолчанию и, если вам трудно это сделать, просто предоставьте пользователю самому задать аргумент.
15.3.2. Новые примеры
Мы добавили еще несколько функций — косинус (cos) из стандартной библиотеки и — просто для того, чтобы продемонстрировать, как создать сложную функцию, — косинус с наклоном x/2.
double sloping_cos(double x) { return cos(x)+slope(x); }
Результат приведен ниже.
Соответствующий фрагмент кода выглядит так:
Function s4(cos,r_min,r_max,orig,400,20,20);
s4.set_color(Color::blue);
Function s5(sloping_cos, r_min,r_max,orig,400,20,20);
x.label.move(–160,0);
x.notches.set_color(Color::dark_red);
Кроме сложения этих двух функций, мы сместили метку оси x и (просто для иллюстрации) немного изменили цвет шкалы деления.
В заключение построим графики логарифма, экспоненты, синуса и косинуса.
Function f1(log,0.000001,r_max,orig,200,30,30); // ln()
Function f2(sin,r_min,r_max,orig,200,30,30); // sin()
f2.set_color(Color::blue);
Function f3(cos,r_min,r_max,orig,200,30,30); // cos()
Function f4(exp,r_min,r_max,orig,200,30,30); // exp()
Поскольку значение log(0) не определено (с математической точки зрения оно равно бесконечности), мы начали диапазон изменения функции log с небольшого положительного числа. Результат приведен ниже.
Вместо приписывания меток этим графикам мы изменили их цвет.
Стандартные математические функции, такие как cos(), sin() и sqrt(), объявлены в стандартном библиотечном заголовке <cmath>. Список стандартных математических функций приведен в разделах 24.8 и B.9.2.
15.4. Оси
Для представления данных мы используем класс Axis (например, как в разделе 15.6.4), поскольку график без информации о его масштабе выглядит подозрительно. Класс Axis состоит из линии, определенного количества делений оси и текстовой метки. Конструктор класса Axis вычисляет координаты линии оси и (при необходимости) линий, используемых как деления оси.
struct Axis:Shape {
enum Orientation { x, y, z };
Axis(Orientation d, Point xy, int length,
int number_of_notches=0, string label = "");
void draw_lines() const;
void move(int dx, int dy);
void set_color(Color c);
Text label;
Lines notches;
};
Объекты label и notches остаются открытыми, поэтому пользователи могут ими манипулировать, например приписывать делениям цвет, отличающийся от цвета линии, или перемещать объект label с помощью функции move() в более удобное место. Объект класса Axis — это пример объекта, состоящего из нескольких полунезависимых объектов.
Конструктор класса Axis размещает линии и добавляет на них деления, если значение number_ of_notches больше нуля.
Axis::Axis(Orientation d, Point xy, int length, int n, string lab)
:label(Point(0,0),lab)
{
if (length<0) error("bad axis length");
switch (d){
case Axis::x:
{
Shape::add(xy); // линия оси
Shape::add(Point(xy.x+length,xy.y));
if (0<n) { // добавляет деления
int dist = length/n;
int x = xy.x+dist;
for (int i = 0; i<n; ++i) {
notches.add(Point(x,xy.y),Point(x,xy.y–5));
x += dist;
}
}
label.move(length/3,xy.y+20); // размещает метку под линией
break;
}
case Axis::y:
{ Shape::add(xy); // ось y перемещаем вверх
Shape::add(Point(xy.x,xy.y–length));
if (0<n) { // добавляем деления
int dist = length/n;
int y = xy.y–dist;
for (int i = 0; i<n; ++i) {
notches.add(Point(xy.x,y),Point(xy.x+5,y));
y –= dist;
}
}
label.move(xy.x–10,xy.y–length–10); // размещает метку
// наверху
break;
}
case Axis::z:
error("ось z не реализована");
}
}
По сравнению с большинством реальных программ этот конструктор очень прост, но мы рекомендуем внимательно изучить его, поскольку он не настолько тривиален, как кажется, и иллюстрирует несколько полезных приемов. Обратите внимание на то, как мы храним линию в части класса Shape, унаследованной классом Axis (используя функцию Shape::add()), хотя деления хранятся в виде отдельного объекта (notches). Это позволяет нам манипулировать линией и делениями оси независимо друг от друга; например, мы можем раскрасить их в разные цвета. Аналогично метка была помещена в фиксированное положение, но, поскольку она является независимым объектом, мы всегда можем переместить ее в другое место. Для удобства используем перечисление Orientation.
Поскольку класс Axis состоит из трех частей, мы должны предусмотреть функции для манипулирования объектом класса Axis в целом. Рассмотрим пример.
void Axis::draw_lines() const
{
Shape::draw_lines();
notches.draw(); // цвет делений может отличаться от цвета линии
label.draw(); // цвет метки может отличаться от цвета линии
}
Для рисования объектов notches и label мы используем функцию draw() а не draw_lines(), чтобы иметь возможность использовать информацию о цвете, которая в них хранится. Объект класса Lines хранится в разделе Axis::Shape и использует информацию о цвете, хранящуюся там же.
Мы можем задать цвет линии, деления и метки по отдельности, но с точки