Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 111 112 113 114 115 116 117 118 119 ... 337
{     // месяц

  int month;       // [0:11] январю соответствует 0

  vector<Day> day; // [1:31] один вектор для всех данных по дням

  Month()          // не больше 31 дня в месяце (day[0]

                   // не используется)

    :month(not_a_month), day(32) { }

};

struct Year {           // год состоит из месяцев

  int year;             // положительный == н.э.

  vector<Month> month;  // [0:11] январю соответствует 0

  Year() :month(12) { } // 12 месяцев в году

};

В принципе каждый класс — это просто вектор, а классы Month и Year содержат идентифицирующие члены month и year соответственно.

 

 В этом примере существует несколько “волшебных констант” (например, 24, 32 и 12). Как правило, мы пытаемся избегать таких литеральных констант в коде. Эти константы носят фундаментальный характер (количество месяцев в году изменяется редко) и в остальной части кода не используются. Однако мы оставили их в коде в основном для того, чтобы напомнить вам о проблеме “волшебных чисел”, хотя намного предпочтительнее использовать символьные константы (см. раздел 7.6.1). Использование числа 32 для обозначения количества дней в месяце определенно требует объяснений; в таком случае число 32 действительно становится “волшебным”. 

10.11.2. Считывание структурированных значений

Класс Reading будет использован только для ввода данных, к тому же он намного проще остальных

struct Reading {

  int day;

  int hour;

  double temperature;

};

istream& operator>>(istream& is, Reading& r)

  // считываем показания температуры из потока is в объект r

  // формат: (3 4 9.7)

  // проверяем формат, но не корректность данных

{

  char ch1;

  if (is>>ch1 && ch1!='('){ // можно это превратить в объект типа

                            // Reading?

    is.unget();

    is.clear(ios_base::failbit);

    return is;

  }

  char ch2;

  int d;

  int h;

  double t;

  is >> d >> h >> t >> ch2;

  if (!is || ch2!=')') error("Плохая запись"); // перепутанные

                                               // показания

  r.day = d;

  r.hour = h;

  r.temperature = t;

  return is;

}

В принципе мы проверяем, правильно ли начинается формат. Если нет, то переводим файл в состояние fail() и выходим. Это позволяет нам попытаться считать информацию как-то иначе. С другой стороны, если ошибка формата обнаруживается после считывания данных и нет реальных шансов на возобновление работы, то вызываем функцию error().

Операции ввода в классе Month почти такие же, за исключением того, что в нем вводится произвольное количество объектов класса Reading, а не фиксированный набор значений (как делает оператор >> в классе Reading).

istream& operator>>(istream& is, Month& m)

  // считываем объект класса Month из потока is в объект m

  // формат: { month feb... }

{

  char ch = 0;

  if (is >> ch && ch!='{') {

    is.unget();

    is.clear(ios_base::failbit); // ошибка при вводе Month

    return is;

  }

  string month_marker;

  string mm;

  is >> month_marker >> mm;

  if (!is || month_marker!="month") error("Неверное начало Month");

  m.month = month_to_int(mm);

  Reading r;

  int duplicates = 0;

  int invalids = 0;

  while (is >> r) {

    if (is_valid(r)) {

      if (m.day[r.day].hour[r.hour] != not_a_reading)

      ++duplicates;

      m.day[r.day].hour[r.hour] = r.temperature;

    }

    else

      ++invalids;

  }

  if (invalids) error("Неверные показания в Month", invalids);

  if (duplicates) error("Повторяющиеся показания в Month",duplicates);

  end_of_loop(is,'}',"Неправильный конец Month");

  return is;

}

Позднее мы еще вернемся к функции month_to_int(); она преобразовывает символические обозначения месяцев, такие как jun, в число из диапазона [0:11]. Обратите внимание на использование функции end_of_loop() из раздела 10.10 для проверки признака завершения ввода. Мы подсчитываем количество неправильных и повторяющихся объектов класса Readings (эта информация может кому-нибудь понадобиться).

Оператор >> в классе Month выполняет грубую проверку корректности объекта класса Reading, прежде чем записать его в память.

const int implausible_min = –200;

const int implausible_max = 200;

bool is_valid(const Reading& r)

// грубая проверка

{

  if (r.day<1 || 31<r.day) return false;

  if (r.hour<0 || 23<r.hour) return false;

  if (r.temperature<implausible_min || implausible_max<r.temperature)

    return false;

  return true;

}

В заключение можем прочитать объекты класса Year. Оператор >> в классе Year аналогичен оператору >> в классе Month.

istream& operator>>(istream& is, Year& y)

  // считывает объект класса Year из потока is в объект y

  // формат: { year 1972... }

{

  char ch;

  is >> ch;

  if (ch!='{') {

    is.unget();

    is.clear(ios::failbit);

    return is;

  }

  string year_marker;

  int yy;

  is >> year_marker >> yy;

  if (!is || year_marker!="year")

    error("Неправильное начало Year");

  y.year = yy;

  while(true) {

    Month m; // каждый раз создаем новый объект m

    if(!(is >> m)) break;

    y.month[m.month] = m;

  }

  end_of_loop(is,'}',"Неправильный конец Year");

  return is;

}

Можно было бы сказать, что этот оператор “удручающе аналогичен”, а не просто аналогичен, но здесь кроется важный нюанс. Посмотрите на цикл чтения. Ожидали ли вы чего-нибудь подобного следующему фрагменту?

Month m;

while (is >> m)

y.month[m.month] = m;

Возможно, да, поскольку именно так мы до сих пор записывали все циклы ввода. Именно этот фрагмент мы написали первым, и он является неправильным. Проблема заключается в том, что функция operator>>(istream& is, Month& m) не присваивает объекту m совершенно новое значение; она просто добавляет в него данные из объектов класса Reading. Таким образом, повторяющаяся инструкция is>>m добавляла бы данные в один и тот же объект m. К

1 ... 111 112 113 114 115 116 117 118 119 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий