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

 // предполагаемый формат: (год: дети взрослые старики)

{

  char ch1 = 0;

  char ch2 = 0;

  char ch3 = 0;

  Distribution dd;

  if (is >> ch1 >> dd.year

         >> ch2 >> dd.young >> dd.middle >> dd.old

         >> ch3) {

    if (ch1!= '(' || ch2!=':' || ch3!=')') {

      is.clear(ios_base::failbit);

      return is;

    }

  }

  else

    return is;

  d = dd;

  return is;

}

Этот код является результатом непосредственного воплощения идей, изложенных в главе 10. Если какие-то места этого кода вам не ясны, пожалуйста, перечитайте эту главу. Мы не обязаны определять тип

Distribution
и оператор
>>
. Однако он упрощает код по сравнению с методом грубой силы, основанным на принципе “просто прочитать данные и построить график”. Наше использование класса
Distribution
разделяет код на логические части, что облегчает его анализ и отладку. Не бойтесь вводить типы просто для того, чтобы упростить код. Мы определяем классы, чтобы программа точнее соответствовала нашему представлению об основных понятиях предметной области. В этом случае даже “небольшие” понятия, использованные локально, например линия, представляющая распределение возрастов по годам, могут оказаться полезными. Имея тип
Distribution
, можем записать цикл чтения данных следующим образом.

string file_name = "japanese-age-data.txt";

ifstream ifs(file_name.c_str());

if (!ifs) error("Невозможно открыть файл ",file_name);

// ...

Distribution d;

while (ifs>>d) {

  if (d.year<base_year || end_year<d.year)

    error("год не попадает в диапазон");

  if (d.young+d.middle+d.old != 100)

    error("Проценты не согласованы");

 // ...

}

Иначе говоря, мы пытаемся открыть файл

japanese-age-data.txt
и выйти из программы, если его нет. Идея не указывать явно имя файла в программе часто оказывается удачной, но в данном случае мы пишем простой пример и не хотим прилагать лишние усилия. С другой стороны, мы присваиваем имя файла
japanese-age-data.txt
именованной переменной типа
string
, поэтому при необходимости его легко изменить.

Цикл чтения проверяет диапазон чисел и согласованность данных. Это основные правила проверки таких данных. Поскольку оператор

>>
сам проверяет формат каждого элемента данных, в цикле чтения больше нет никаких проверок. 

15.6.2. Общая схема

Что мы хотим увидеть на экране? Этот ответ можно найти в начале раздела 15.6. На первый взгляд, для изображения данных нужны три объекта класса

Open_polyline
— по одному на каждую возрастную группу. Каждый график должен быть помечен. Для этого мы решили в левой части окна записать “название” каждой линии. Этот выбор кажется удачнее, чем обычная альтернатива
clearer
, — поместить метку где-то на самой линии. Кроме того, для того чтобы отличать графики друг от друга, мы используем разные цвета и связываем их с метками.

Мы хотим пометить ось x, указав годы. Вертикальная линия, проходящая через отметку 2008, означает год, после которого данные являются результатом экстраполяции.

В качестве названия изображения мы решили просто использовать метку окна.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Сделать так, чтобы графический код был правильным и красиво выглядел, — довольно сложная задача. Основная причина заключается в том, что нам придется выполнить множество кропотливых вычислений, связанных с определением размеров и смещений. Для их упрощения мы начали с определения символических констант, определяющих способ использования экрана.

const int xmax = 600;   // размер окна

const int ymax = 400;

const int xoffset = 100;// расстояние от левого края окна до оси y

const int yoffset = 60; // расстояние от нижнего края окна до оси х

const int xspace = 40;  // пространство между осями

const int yspace = 40;

const int xlength = xmax–xoffset–xspace; // длина осей

const int ylength = ymax–yoffset–yspace;

В принципе эти инструкции определяют прямоугольную область (окно) и вложенный в него прямоугольник (определенный осями).

Программирование. Принципы и практика использования C++ Исправленное издание - _169.png

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

15.6.3. Масштабирование данных

Теперь мы должны определить, как изобразить данные в описанной области. Для этого масштабируем данные так, чтобы они помещались в прямоугольнике, определенном осями координат. Масштабирование осуществляется с помощью масштабных множителей, представляющих собой отношение диапазона изменения данных и меток на осях.

const int base_year = 1960;

const int end_year = 2040;

const double xscale = double(xlength)/(end_year–base_year);

const double yscale = double(ylength)/100;

Мы объявили наши масштабирующие множители (

xscale
и
yscale
) как числа с плавающей точкой — иначе в наших вычислениях возникли бы серьезные ошибки, связанные с округлением. Для того чтобы избежать целочисленного деления, перед делением преобразовываем наши длины в тип
double
(см. раздел 4.3.3).

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