НА ГЛАВНУЮ
Меню сайта
Категория
Ghost++ [1]
С++ [55]
Развлечение
ON - LINE
Опрос
Отдаю предпочтение
Всего ответов: 345
Оbserver Ward

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Друзья сайта
Заведи себе Бота
Hаша кнопка
Для обмена банерами , наша кнопка для размещения у вас на сайте

Клансайт USSR


Главная » Статьи » Программирование » С++

5. Инструкции (1)
5. Инструкции

Мельчайшей независимой частью С++ программы является инструкция. Она соответствует предложению естественного языка, но завершается точкой с запятой (;), а не точкой. Выражение С++ (например, ival + 5) становится простой инструкцией, если после него поставить точку с запятой. Составная инструкция – это последовательность простых, заключенная в фигурные скобки. По умолчанию инструкции выполняются в порядке записи. Как правило, последовательного выполнения недостаточно для решения реальных задач. Специальные управляющие конструкции позволяют менять порядок действий в зависимости от некоторых условий и повторять составную инструкцию определенное количество раз. Инструкции if, if-else и switch обеспечивают условное выполнение. Повторение обеспечивается инструкциями цикла while, do-while и for.
5.1. Простые и составные инструкции

Простейшей формой является пустая инструкция. Вот как она выглядит:

; // пустая инструкция

Пустая инструкция используется там, где синтаксис С++ требует употребления инструкции, а логика программы – нет. Например, в следующем цикле while, копирующем одну строку в другую, все необходимые действия производятся внутри круглых скобок (условной части инструкции). Однако согласно правилам синтаксиса С++ после while должна идти инструкция. Поскольку нам нечего поместить сюда (вся работа уже выполнена), приходится оставить это место пустым:

while ( *string++ = inBuf++ )
; // пустая инструкция

Случайное появление лишней пустой инструкции не вызывает ошибки компиляции. Например, такая строка

ival = dval + sval;; // правильно: лишняя пустая инструкция

состоит из двух инструкций – сложения двух величин с присваиванием результата переменной ival и пустой.
Простая инструкция состоит из выражения, за которым следует точка с запятой. Например:

// простые инструкции
int ival = 1024; // инструкция определения переменной
ival; // выражение
ival + 5; // еще одно выражение
ival = ival +5; // присваивание

Условные инструкции и инструкции цикла синтаксически требуют употребления единственной инструкции, связанной с ними. Однако, как правило, этого недостаточно. В таких случаях употребляются составные инструкции – последовательность простых, заключенная в фигурные скобки:

if ( ival0 > ival1 ) {
   // составная инструкция, состоящая
   // из объявления и двух присваиваний
   int temp = ivalO;
   ivalO = ival1;
   ival1 = temp;
}

Составная инструкция может употребляться там же, где простая, и не нуждается в завершающей точке с запятой.
Пустая составная инструкция эквивалентна пустой простой. Приведенный выше пример с пустой инструкцией можно переписать так:

while ( *string++ = *inBuf++ )
{} // пустая инструкция

Составную инструкцию, содержащую определения переменных, часто называют блоком. Блок задает локальную область видимости в программе – идентификаторы, объявленные внутри блока (как temp в предыдущем примере), видны только в нем. (Блоки, области видимости и время жизни объектов рассматриваются в главе 8.)
5.2. Инструкции объявления

В С++ определение объекта, например

int ival;

рассматривается как инструкция объявления (хотя в данном случае более правильно было бы сказать определения). Ее можно использовать в любом месте программы, где разрешено употреблять инструкции. В следующем примере объявления помечены комментарием //#n, где n – порядковый номер.

#include <fstream>
#include <string>
#include <vector>
int main()
{
   string fileName; // #1
   cout << "Введите имя файла: ";
   cin >> fileName;
   if ( fileName.empty() ) {
      // странный случай
      cerr << "Пустое имя файла. Завершение работы.\n";
      return -1;
   }
   ifstream inFile( fileName.c_str() ); // #2
      if ( ! inFile ) {
      cerr << "Невозможно открыть файл.\n";
      return -2;
   }

   string inBuf; // #3
   vector< string > text; // #4
   while ( inFile >> inBuf ) {
      for ( int ix = 0; ix < inBuf .size(); ++ix ) // #5
         // можно обойтись без ch,
         // но мы использовали его для иллюстрации
         if (( char ch = inBuf[ix] )=='.'){ // #6
            ch = '_';
            inBuf[ix] = ch;
         }
         text.push_back( inBuf );
   }
   if ( text.empty() )
      return 0;
   // одна инструкция объявления,
   // определяющая сразу два объекта
   vector<string>::iterator iter = text.begin(), // #7
         iend = text.end();
   while ( iter != -iend ) {
      cout << *iter << '\n';
      ++iter;
   }
  return 0;
}

Программа содержит семь инструкций объявления и восемь определений объектов. Объявления действуют локально; переменная объявляется непосредственно перед первым использованием объекта.
В 70-е годы философия программирования уделяла особое внимание тому, чтобы определения всех объектов находились в начале программы или блока, перед исполняемыми инструкциями. (В С, например, определение переменной не является инструкцией и обязано располагаться в начале блока.) В некотором смысле это была реакция на идиому использования переменных без предварительного объявления, чреватую ошибками. Такую идиому поддерживал, например, FORTRAN.
Поскольку в С++ объявление является обычной инструкцией, ему разрешено появляться в любом месте программы, где допустимо употребление инструкции, что дает возможность использовать локальные объявления.
Необходимо ли это? Для встроенных типов данных применение локальных объявлений является скорее вопросом вкуса. Язык их поощряет , разрешая объявлять переменные внутри условных частей инструкций if, if-else, switch, while, for. Те программисты, которые любят этот стиль, верят, что таким образом делают свои программы более понятными.
Локальные объявления становятся необходимостью, когда мы используем объекты классов, имеющие конструкторы и деструкторы. Если мы помещаем все объявления в начало блока или функции, происходят две неприятные вещи:

    * конструкторы всех объектов вызываются перед исполнением первой инструкции блока. Применение локальных объявлений позволяет "размазать” расходы на инициализацию по всему блоку;
    * что более важно, блок или функция могут завершиться до того, как будут действительно использованы все объявленные в начале объекты. Скажем, наша программа из предыдущего примера имеет два аварийных выхода: при вводе пользователем пустого имени файла и при невозможности открыть файл с заданным именем. При этом последующие инструкции функции уже не выполняются. Если бы объекты inBuf и next были объявлены в начале блока, конструкторы и деструкторы этих объектов в случае ненормального завершения функции вызывались бы совершенно напрасно.

Инструкция объявления может состоять из одного или более определений. Например, в нашей программе мы определяем два итератора вектора в одной инструкции:

// одна инструкция объявления,
// определяющая сразу два объекта
vector<string>::iterator iter = text.begin(),
      lend = text.end();

Эквивалентная пара, определяющая по одному объекту, выглядит так:

vector<string>::iterator iter = text.begin();
vector<string>::iterator lend = text.end();

Хотя определение одного или нескольких объектов в одном предложении является скорее вопросом вкуса, в некоторых случаях – например, при одновременном определении объектов, указателей и ссылок – это может спровоцировать появление ошибок. Скажем, в следующей инструкции не совсем ясно, действительно ли программист хотел определить указатель и объект или просто забыл поставить звездочку перед вторым идентификатором (используемые имена переменных наводят на второе предположение):

// то ли хотел определить программист?
string *ptrl, ptr2;

Эквивалентная пара инструкций не позволит допустить такую ошибку:

string *ptr1;
string *ptr2;

В наших примерах мы обычно группируем определения объектов в инструкции по сходству употребления. Например, в следующей паре

int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0;
int charCnt=0, wordCnt=0;

первая инструкция объявляет пять очень похожих по назначению объектов – счетчиков пяти гласных латинского алфавита. Счетчики для подсчета символов и слов определяются во второй инструкции. Хотя такой подход нам кажется естественным и удобным, нет никаких причин считать его хоть чем-то лучше других.
Упражнение 5.1

Представьте себе, что вы являетесь руководителем программного проекта и хотите, чтобы применение инструкций объявления было унифицировано. Сформулируйте правила использования объявлений объектов для вашего проекта.
Упражнение 5.2

Представьте себе, что вы только что присоединились к проекту из предыдущего упражнения. Вы совершенно не согласны не только с конкретными правилами использования инструкций объявления, но и вообще с навязыванием каких-либо правил для этого. Объясните свою позицию.
5.3. Инструкция if

Инструкция if обеспечивает выполнение или пропуск инструкции или блока в зависимости от условия. Ее синтаксис таков:

if ( условие )
инструкция

условие заключается в круглые скобки. Оно может быть выражением, как в этом примере:

if(a+b>c) { ... }

или инструкцией объявления с инициализацией:

if ( int ival = compute_value() ){...}

Область видимости объекта, объявленного в условной части, ограничивается ассоциированной с if инструкцией или блоком. Например, такой код вызывает ошибку компиляции:

if ( int ival = compute_value() ) {
   // область видимости ival
   // ограничена этим блоком
}
// ошибка: ival невидим
if ( ! ival ) ...

Попробуем для иллюстрации применения инструкции if реализовать функцию min(), возвращающую наименьший элемент вектора. Заодно наша функция будет подсчитывать число элементов, равных минимуму. Для каждого элемента вектора мы должны проделать следующее:

   1. Сравнить элемент с текущим значением минимума.
   2. Если элемент меньше, присвоить текущему минимуму значение элемента и сбросить счетчик в 1.
   3. Если элемент равен текущему минимуму, увеличить счетчик на 1.
   4. В противном случае ничего не делать.
   5. После проверки последнего элемента вернуть значение минимума и счетчика.

Необходимо использовать две инструкции if:

if ( minVal > ivec[ i ] )...// новое значение minVal
if ( minVal == ivec[ i ] )...// одинаковые значения

Довольно часто программист забывает использовать фигурные скобки, если нужно выполнить несколько инструкций в зависимости от условия:

if ( minVal > ivec[ i ] )
minVal = ivec[ i ];
occurs = 1; // не относится к if!

Такую ошибку трудно увидеть, поскольку отступы в записи подразумевают, что и minVal=ivec[i], и occurs=1 входят в одну инструкцию if. На самом же деле инструкция

occurs = 1;

не является частью if и выполняется безусловно, всегда сбрасывая occurs в 1. Вот как должна быть составлена правильная if-инструкция (точное положение открывающей фигурной скобки является поводом для бесконечных споров):

if ( minVal > ivec[ i ] )
{
   minVal = ivec[ i ];
   occurs = 1;
}

Вторая инструкция if выглядит так:

if ( minVal == ivec [ i ] )
   ++occurs;

Заметим, что порядок следования инструкций в этом примере крайне важен. Если мы будем сравнивать minVal именно в такой последовательности, наша функция всегда будет ошибаться на 1:

if ( minVal > ivec[ i ] ) {
   minVal = ivec[ i ];
   occurs = 1;
}
// если minVal только что получила новое значение,
// то occurs будет на единицу больше, чем нужно
if ( minVal == ivec[ i ] )
   ++occurs;

Выполнение второго сравнения не обязательно: один и тот же элемент не может одновременно быть и меньше и равен minVal. Поэтому появляется необходимость выбора одного из двух блоков в зависимости от условия, что реализуется инструкцией if-else, второй формой if-инструкции. Ее синтаксис выглядит таким образом:

if ( условие )
   инструкция1
else
   инструкция2

инструкция1 выполняется, если условие истинно, иначе переходим к инструкция2. Например:

if ( minVal == ivec[ i ] )
   ++occurs;
else
   if ( minVal > ivec[ i ] ) {
      minVal = ivec[ i ];
      occurs = 1;
   }

Здесь инструкция2 сама является if-инструкцией. Если minVal меньше ivec[i], никаких действий не производится.
В следующем примере выполняется одна из трех инструкций:

if ( minVal < ivec[ i ] )
{} // пустая инструкция
else
if ( minVal > ivec[ i ] ) {
   minVal = ivec[ i ];
   occurs = 1;
}
else // minVal == ivec[ i ]
++occurs;

Составные инструкции if-else могут служить источником неоднозначного толкования, если частей else больше, чем частей if. К какому из if отнести данную часть else? (Эту проблему иногда называют проблемой висячего else). Например:

if ( minVal <= ivec[ i ] )
   if ( minVal == ivec[ i ] )
      ++occurs;
else {
   minVal = ivec[ i ];
   occurs = 1;
}

Судя по отступам, программист предполагает, что else относится к самому первому, внешнему if. Однако в С++ неоднозначность висячих else разрешается соотнесением их с последним встретившимся if. Таким образом, в действительности предыдущий фрагмент означает следующее:

if ( minVal <= ivec[ i ] ) {
   if ( minVal == ivec[ i ] )
      ++occurs;
   else {
      minVal = ivec[ i ];
      occurs = 1;
   }
}

Одним из способов разрешения данной проблемы является заключение внутреннего if в фигурные скобки:

if ( minVal <= ivec[ i ] ) {
   if ( minVal == ivec[ i ] )
      ++occurs;
}
else {
   minVal = ivec[ i ];
   occurs = 1;
}

В некоторых стилях программирования рекомендуется всегда употреблять фигурные скобки при использовании инструкций if-else, чтобы не допустить возможности неправильной интерпретации кода.
Вот первый вариант функции min(). Второй аргумент функции будет возвращать количество вхождений минимального значения в вектор. Для перебора элементов массива используется цикл for. Но мы допустили ошибку в логике программы. Сможете ли вы заметить ее?

#include <vector>
int min( const vector<int> &ivec, int &occurs )
{
   int minVal = 0;
   occurs = 0;
   int size = ivec.size();
   for ( int ix = 0; ix < size; ++ix ) {
      if ( minVal == ivec[ ix ] )
         ++occurs;
      else
      if ( minVal > ivec[ ix ] ) {
         minVal = ivec[ ix ];
         occurs = 1;
      }
   }
  return minVal;
}

Обычно функция возвращает только одно значение. Однако согласно нашей спецификации в точке вызова должно быть известно не только само минимальное значение, но и количество его вхождений в вектор. Для возврата второго значения мы использовали параметр типа ссылка. (Параметры-ссылки рассматриваются в разделе 7.3.) Любое присваивание значения ссылке occurs изменяет значение переменной, на которую она ссылается:

int main()
{
   int occur_cnt = 0;
   vector< int > ivec;
   // occur_cnt получает значение occurs
   // из функции min()
   int minval = min( ivec, occur_cnt );
   // ...
}

Альтернативой использованию параметра-ссылки является применение объекта класса pair, представленного в разделе 3.14. Функция min() могла бы возвращать два значения в одной паре:

// альтернативная реализация
// с помощью пары

#include <uti1ity>
#include <vector>
typedef pair<int,int> min_va1_pair;

min_va1_pair
min( const vector<int> &ivec )
{
   int minVal = 0;
   int occurs = 0;
   // то же самое ...
   return make_pair( minVal, occurs );
}

К сожалению, и эта реализация содержит ошибку. Где же она? Правильно: мы инициализировали minVal нулем, поэтому, если минимальный элемент вектора больше нуля, наша реализация вернет нулевое значение минимума и нулевое значение количества вхождений.
Программу можно изменить, инициализировав minVal первым элементом вектора:

int minVal = ivec[0];

Теперь функция работает правильно. Однако в ней выполняются некоторые лишние действия, снижающие ее эффективность.

// исправленная версия min()
// оставляющая возможность для оптимизации ...
int minVal = ivec[0];
occurs = 0;
int size = ivec.size();
for ( int ix = 0; ix < size; ++ix )
{
   if ( minVal == ivec[ ix ] )
   ++occurs;
   // ...

Поскольку ix инициализируется нулем, на первой итерации цикла значение первого элемента сравнивается с самим собой. Можно инициализировать ix единицей и избежать ненужного выполнения первой итерации. Однако при оптимизации кода мы допустили другую ошибку (наверное, стоило все оставить как было!). Сможете ли вы ее обнаружить?

// оптимизированная версия min(),
// к сожалению, содержащая ошибку...
int minVal = ivec[0];
occurs = 0;
int size = ivec.size();
for ( int ix = 1; ix < size; ++ix )
{
  if ( minVal == ivec[ ix ] )
  ++occurs;
   // ...

Если ivec[0] окажется минимальным элементом, переменная occurs не получит значения 1. Конечно, исправить это очень просто, но сначала надо найти ошибку:

int minVal = ivec[0];
occurs = 1;

К сожалению, подобного рода недосмотры встречаются не так уж редко: программисты тоже люди и могут ошибаться. Важно понимать, что это неизбежно, и быть готовым тщательно тестировать и анализировать свои программы.
Вот окончательная версия функции min() и программа main(), проверяющая ее работу:

#include <iostream>
#include <vector>
int min( const vector< int > &ivec, int &occurs )
{
   int minVal = ivec[ 0 ];
   occurs = 1;
   int size = ivec.size();
   for ( int ix = 1; ix < size; ++ix )
  {
      if ( minVal == ivec[ ix ] )
         ++occurs;
      else
         if ( minVal > ivec[ ix ] ){
            minVal = ivec[ ix ];
            occurs = 1;
         }
   }
   return minVal;
}

int main()
{
   int ia[] = { 9,1,7,1,4,8,1,3,7,2,6,1,5,1 };
   vector<int> ivec( ia, ia+14 );
   int occurs = 0;
   int minVal = min( ivec, occurs );
   cout << "Минимальное значение: " << minVal
         << " встречается: " << occurs << " раз.\n";
   return 0;
}

Результат работы программы:

Минимальное значение: 1 встречается: 5 раз.

В некоторых случаях вместо инструкции if-else можно использовать более краткое и выразительное условное выражение. Например, следующую реализацию функции min():

template <class valueType>
inline const valueType&
min( valueType &vall, valueType &va12 )
{
   if ( vall < va12 )
      return vall;
   return va12;
}

можно переписать так:

template <class valueType>
inline const valueType&
min( valueType &vall, valueType &va12 )
{
   return ( vall < va12 ) ? vall : va12;
}

Длинные цепочки инструкций if-else, подобные приведенной ниже, трудны для восприятия и, таким образом, являются потенциальным источником ошибок.

if ( ch == 'a' ||
   ch == 'A' )
      ++aCnt;
else
if ( ch == 'e' ||
   ch == 'E' )
      ++eCnt;
else
   if ( ch == 'i' ||
   ch == 'I' )
      ++iCnt;
else
   if ( ch == 'o' ||
      ch == '0' )
         ++oCnt;
else
   if ( ch == 'u' ||
      ch == 'U' )
         ++uCnt;

В качестве альтернативы таким цепочкам С++ предоставляет инструкцию switch. Это тема следующего раздела.
Упражнение 5.3

Исправьте ошибки в примерах:

(a) if ( ivall != iva12 )
       ivall = iva12
    else
      ivall = iva12 = 0;

(b) if ( ivat < minval )
         minvat = ival;
      occurs = 1;

(c) if ( int ival = get_value())
         cout << "ival = "
              << ival << endl;
      if ( ! ival )
         cout << "ival = 0\n";

(d) if ( ival = 0 )
      ival = get_value();

(e) if ( iva1 == 0 )
      else ival = 0;

Упражнение 5.4

Преобразуйте тип параметра occurs функции min(), сделав его не ссылкой, а простым объектом. Запустите программу. Как изменилось ее поведение?
5.4. Инструкция switch

Длинные цепочки инструкций if-else, наподобие приведенной в конце предыдущего раздела, трудны для восприятия и потому являются потенциальным источником ошибок. Модифицируя такой код, легко сопоставить, например, разные else и if. Альтернативный метод выбора одного их взаимоисключающих условий предлагает инструкция switch.
Для иллюстрации инструкции switch рассмотрим следующую задачу. Нам надо подсчитать, сколько раз встречается каждая из гласных букв в указанном отрывке текста. (Общеизвестно, что буква e – наиболее часто встречающаяся гласная в английском языке.) Вот алгоритм программы:

   1. Считывать по одному символу из входного потока, пока они не кончатся.
   2. Сравнить каждый символ с набором гласных.
   3. Если символ равен одной из гласных, прибавить 1 к ее счетчику.
   4. Напечатать результат.

Написанная программа была запущена, в качестве контрольного текста использовался раздел из оригинала данной книги. Результаты подтвердили, что буква e действительно самая частая:

aCnt: 394
eCnt: 721
iCnt: 461
oCnt: 349
uCnt: 186

Инструкция switch состоит из следующих частей:

    * ключевого слова switch, за которым в круглых скобках идет выражение, являющееся условием:

      char ch;
      while ( cm >> ch )
        switch( ch )

    * набора меток case, состоящих из ключевого слова case и константного выражения, с которым сравнивается условие. В данном случае каждая метка представляет одну из гласных латинского алфавита:

      case 'a':
      case 'e':
      case 'i':
      case 'o':
      case 'u':

    * последовательности инструкций, соотносимых с метками case. В нашем примере с каждой меткой будет сопоставлена инструкция, увеличивающая значение соответствующего счетчика;
    * необязательной метки default, которая является аналогом части else инструкции if-else. Инструкции, соответствующие этой метке, выполняются, если условие не отвечает ни одной из меток case. Например, мы можем подсчитать суммарное количество встретившихся символов, не являющихся гласными буквами:

      default: // любой символ, не являющийся гласной
      ++non_vowe1_cnt;

Константное выражение в метке case должно принадлежать к целому типу, поэтому следующие строки ошибочны:

// неверные значения меток
case 3.14: // не целое
case ival: // не константа

Кроме того, две разные метки не могут иметь одинаковое значение.
Выражение условия в инструкции switch может быть сколь угодно сложным, в том числе включать вызовы функций. Результат вычисления условия сравнивается с метками case, пока не будет найдено равное значение или не выяснится, что такого значения нет. Если метка обнаружена, выполнение будет продолжено с первой инструкции после нее, если же нет, то с первой инструкции после метки default (при ее наличии) или после всей составной инструкции switch.
В отличие от if-else инструкции, следующие за найденной меткой, выполняются друг за другом, проходя все нижестоящие метки case и метку default. Об этом часто забывают. Например, данная реализация нашей программы выполняется совершенно не так, как хотелось бы:

#include <iostream>
int main()
{
  char ch;
  int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0;
  while ( cin >> ch )
    // Внимание! неверная реализация!
    switch ( ch ) {
      case 'a':
            ++aCnt;
      case 'e':
            ++eCnt;
      case 'i':
            ++iCnt;
      case 'o':
            ++oCnt;
      case 'u':
            ++uCnt;
    }

    cout << "Встретилась a: \t" << aCnt << '\n'
         << "Встретилась e: \t" << eCnt << '\n'
         << "Встретилась i: \t" << iCnt << '\n'
         << "Встретилась o: \t" << oCnt << '\n'
         << "Встретилась u: \t" << uCnt << '\n';
}

Если значение ch равно i, выполнение начинается с инструкции после case 'i' и iCnt возрастет на 1. Однако следующие ниже инструкции, ++oCnt и ++uCnt, также выполняются, увеличивая значения и этих переменных. Если же переменная ch равна a, изменятся все пять счетчиков.
Программист должен явно дать указание компьютеру прервать последовательное выполнение инструкций в определенном месте switch, вставив break. В абсолютном большинстве случаев за каждой метке case должен следовать соответствующий break.
break прерывает выполнение switch и передает управление инструкции, следующей за закрывающей фигурной скобкой, – в данном случае производится вывод. Вот как это должно выглядеть:

switch ( ch ) {
   case 'a':
       ++aCnt;
       break;
   case 'e':
       ++eCnt;
       break;
   case 'i':
       ++iCnt;
       break;
   case 'o':
       ++oCnt;
       break;
   case 'u':
       ++uCnt;
       break;
}

Если почему-либо нужно, чтобы одна из секций не заканчивалась инструкцией break, то желательно написать в этом месте разумный комментарий. Программа создается не только для машин, но и для людей, и необходимо сделать ее как можно более понятной для читателя. Программист, изучающий чужой текст, не должен сомневаться, было ли нестандартное использование языка намеренным или ошибочным.
При каком условии программист может отказаться от инструкции break и позволить программе провалиться сквозь несколько меток case? Одним из таких случаев является необходимость выполнить одни и те же действия для двух или более меток. Это может понадобиться потому, что с case всегда связано только одно значение. Предположим, мы не хотим подсчитывать, сколько раз встретилась каждая гласная в отдельности, нас интересует только суммарное количество всех встретившихся гласных. Это можно сделать так:

int vowelCnt = 0;
// ...
switch ( ch )
{
   // любой из символов a,e,1,o,u
   // увеличит значение vowelCnt
   case 'a':
   case 'e':
   case 'i':
   case 'o':
   case 'u':
   ++vowe1Cnt;
   break;
}

Некоторые программисты подчеркивают осознанность своих действий тем, что предпочитают в таком случае писать метки на одной строке:

switch ( ch )
{
   // допустимый синтаксис
   case 'a': case 'e':
   case 'i': case 'o': case 'u':
   ++vowe1Cnt;
   break;
}

В данной реализации все еще осталась одна проблема: как будут восприняты слова типа

UNIX

Наша программа не понимает заглавных букв, поэтому заглавные U и I не будут отнесены к гласным. Исправить ситуацию можно следующим образом:

switch ( ch ) {
   case 'a': case 'A':
       ++aCnt;
       break;
   case 'e': case 'E':
       ++eCnt;
       break;
   case 'i': case 'I':
       ++iCnt;
       break;
   case 'o': case 'O':
       ++oCnt;
       break;
   case 'u': case 'U':
       ++uCnt;
       break;
}

Метка default является аналогом части else инструкции if-else. Инструкции, соответствующие default, выполняются, если условие не отвечает ни одной из меток case. Например, добавим к нашей программе подсчет суммарного количества согласных:

#include <iostream>
#include <ctype.h>

int main()
{
    char ch;
    int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0,
    consonantCount=0;

    while ( cin >> ch )
        switch ( ch ) {
            case 'a': case 'A':
                ++aCnt;
                break;
            case 'e': case 'E':
                ++eCnt;
                break;
            case 'i': case 'I':
                ++iCnt;
                break;
            case 'o': case 'O':
                ++oCnt;
                break;
            case 'u': case 'U':
                ++uCnt;
                break;
            default:
                if ( isa1pha( ch ) )
                    ++consonantCnt;
                break;
        }

    cout << "Встретилась a: \t" << aCnt << '\n'
        << "Встретилась e: \t" << eCnt << '\n'
        << "Встретилась i: \t" << iCnt << '\n'
        << "Встретилась o: \t" << oCnt << '\n'
        << "Встретилась u: \t" << uCnt << '\n'
        << "Встретилось согласных: \t" << consonantCnt
        << '\n';
}

isalpha() – функция стандартной библиотеки С; она возвращает true, если ее аргумент является буквой. isalpha() объявлена в заголовочном файле ctype.h. (Функции из ctype.h мы будем рассматривать в главе 6.)
Хотя оператор break функционально не нужен после последней метки в инструкции switch, лучше его все-таки ставить. Причина проста: если мы впоследствии захотим добавить еще одну метку после case, то с большой вероятностью забудем вписать недостающий break.
Условная часть инструкции switch может содержать объявление, как в следующем примере:
switch( int ival = get_response() )
ival инициализируется значением, получаемым от get_response(), и это значение сравнивается со значениями меток case. Переменная ival видна внутри блока switch, но не вне его.
Помещать же инструкцию объявления внутри тела блока switch не разрешается. Данный фрагмент кода не будет пропущен компилятором:

case illegal_definition:
// ошибка: объявление не может
// употребляться в этом месте

 string file_name = get_file_name();
// ...
break;

Если бы разрешалось объявлять переменную таким образом, то ее было бы видно во всем блоке switch, однако инициализируется она только в том случае, если выполнение прошло через данную метку case.
Мы можем употребить в этом месте составную инструкцию, тогда объявление переменной file_name будет синтаксически правильным. Использование блока гарантирует, что объявленная переменная видна только внутри него, а в этом контексте она заведомо инициализирована. Вот как выглядит правильный текст:

case ok:
{
    // ок
    string file_name = get_file_name();
    // ...
    break;
}

Упражнение 5.5

Модифицируйте программу из данного раздела так, чтобы она подсчитывала не только буквы, но и встретившиеся пробелы, символы табуляции и новой строки.
Упражнение 5.6

Модифицируйте программу из данного раздела так, чтобы она подсчитывала также количество встретившихся двухсимвольных последовательностей ff, fl и fi.

Упражнение 5.7

Найдите и исправьте ошибки в следующих примерах:

(a)
switch ( ival ) {
   case 'a': aCnt++;
   case 'e': eCnt++;
   default: iouCnt++;
}

(b)
switch ( ival ) {
   case 1:
       int ix = get_value();
       ivec[ ix ] = ival;
       break;
   default:
       ix = ivec.sizeQ-1;
       ivec[ ix ] = ival;
}

(c)
switch ( ival ) {
   case 1, 3, 5, 7, 9:
       oddcnt++;
       break;
   case 2, 4, 6, 8, 10:
       evencnt++;
       break;
 }

(d)
int iva1=512 jva1=1024, kva1=4096;
int bufsize;
// ...
switch( swt ) {
   case ival:
       bufsize = ival * sizeof( int );
       break;
   case jval:
       bufsize = jval * sizeof( int );
       break;
   case kval:
       bufsize = kval * sizeof( int );
       break;
}

(e)
enum { illustrator = 1, photoshop, photostyler = 2 };
switch ( ival ) {
   case illustrator:
       --i11us_1icense;
       break;
   case photoshop:
       --pshop_1icense;
       break;
   case photostyler:
       --psty1er_license;
       break;
}

5.5. Инструкция цикла for

Как мы видели, выполнение программы часто состоит в повторении последовательности инструкций - до тех пор, пока некоторое условие остается истинным. Например, мы читаем и обрабатываем записи файла, пока не дойдем до его конца, перебираем элементы массива, пока индекс не станет равным размерности массива минус 1, и т.д. В С++ предусмотрено три инструкции для организации циклов, в частности for и while, которые начинаются проверкой условия. Такая проверка означает, что цикл может закончиться без выполнения связанной с ним простой или составной инструкции. Третий тип цикла, do while, гарантирует, что тело будет выполнено как минимум один раз: условие цикла проверяется по его завершении. (В этом разделе мы детально рассмотрим цикл for; в разделе 5.6 разберем while, а в разделе 5.7 - do while.)
Цикл for обычно используется для обработки структур данных, имеющих фиксированную длину, таких, как массив или вектор:

#include <vector>
int main() {
int ia[ 10 ];
for ( int ix = 0; ix < 10; ++-ix )
  ia[ ix ] = ix;
vector<int> ivec( ia, ia+10 );
vector<int>::iterator iter = ivec.begin() ;

for ( ; iter != ivec.end(); ++iter )
  *iter *= 2;
return 0;
}

Синтаксис цикла for следующий:

for (инструкция-инициализации; условие; выражение )
   инструкция

инструкция-инициализации может быть либо выражением, либо инструкцией объявления. Обычно она используется для инициализации переменной значением, которое увеличивается в ходе выполнения цикла. Если такая инициализация не нужна или выполняется где-то в другом месте, эту инструкцию можно заменить пустой (см. второй из приведенных ниже примеров). Вот примеры правильного использования инструкции-инициализации:

// index и iter определены в другом месте
for ( index =0; ...
for ( ; /* пустая инструкция */ ...
for ( iter = ivec.begin(); ...
for ( int 1o = 0,hi = max; ...
for ( char *ptr = getStr(); ...

условие служит для управления циклом. Пока условие при вычислении дает true, инструкция продолжает выполняться. Выполняемая в цикле инструкция может быть как простой, так и составной. Если же самое первое вычисление условия дает false, инструкция не выполняется ни разу. Правильные условия можно записать так:

(... index < arraySize; ... )
(... iter != ivec.end(); ... )
(... *stl++ = *st2++; ... )
(... char ch = getNextChar(); ... )

Выражение вычисляется после выполнения инструкции на каждой итерации цикла. Обычно его используют для модификации переменной, инициализированной в инструкции-инициализации. Если самое первое вычисление условия дает false, выражение не выполняется ни разу. Правильные выражения выглядят таким образом:

( ... ...; ++-index )
( ... ...; ptr = ptr->next )
( ... ...; ++i, --j, ++cnt )
( ... ...; ) // пустое выражение

Для приведенного ниже цикла for

const int sz = 24;
int ia[ sz ];
vector<int> ivec( sz );

for ( int ix = 0; ix < sz; ++ix ) {
   ivec[ ix ] = ix;
   ia[ ix ]= ix;
 }

порядок вычислений будет следующим:

   1. инструкция-инициализации выполняется один раз перед началом цикла. В данном примере объявляется переменная ix, которая инициализируется значением 0.
   2. Вычисляется условие. Если оно равно true, выполняется составная инструкция тела цикла. В нашем примере, пока ix меньше sz, значение ix присваивается элементам ivec[ix] и ia[ix]. Когда значением условия станет false, выполнение цикла прекратится. Если самое первое вычисление условия даст false, составная инструкция выполняться не будет.
   3. Вычисляется выражение. Как правило, его используют для модификации переменной, фигурирующей в инструкции-инициализации и проверяемой в условии. В нашем примере ix увеличивается на 1.

Эти три шага представляют собой полную итерацию цикла for. Теперь шаги 2 и 3 будут повторяться до тех пор, пока условие не станет равным false, т.е. ix окажется равным или большим sz.
В инструкции-инициализации можно определить несколько объектов, однако все они должны быть одного типа, так как инструкция объявления допускается только одна:

for ( int ival = 0, *pi = &ia, &ri = val;
   ival < size;
   ++iva1, ++pi, ++ri )
   // ...

Объявление объекта в условии гораздо труднее правильно использовать: такое объявление должно хотя бы раз дать значение false, иначе выполнение цикла никогда не прекратится. Вот пример, хотя и несколько надуманный:

#include <iostream>
int main()
{
   for ( int ix = 0;
      bool done = ix == 10;
      ++ix )
         cout << "ix: " << ix << endl;
}

Видимость всех объектов, определенных внутри круглых скобок инструкции for, ограничена телом цикла. Например, проверка iter после цикла вызовет ошибку компиляции :

int main()
{
   string word;
   vector< string > text;
   // ...
   for ( vector< string >::iterator
      iter = text.begin(),
      iter_end = text.end();
      iter != text.end(); ++iter )
   {
      if ( *iter == word )
      break;
      // ...
}

// ошибка: iter и iter_end невидимы
  if ( iter != iter_end )
  // ...

Упражнение 5.8

Допущены ли ошибки в нижеследующих циклах for? Если да, то какие?

(a)
   for ( int *ptr = &ia, ix = 0;
      ix < size && ptr != ia+size;
         ++ix, ++ptr )
            // ...

(b)
   for ( ; ; ) {
      if ( some_condition )
         break;
      // ...
   }

(c)
   for ( int ix = 0; ix < sz; ++ix )
      // ...

   if ( ix != sz )
      // ...

(d)
   int ix;
   for ( ix < sz; ++ix )
   // ...

(e)
   for ( int ix = 0; ix < sz; ++ix, ++ sz )
   // ...

Упражнение 5.9

Представьте, что вам поручено придумать общий стиль использования цикла for в вашем проекте. Объясните и проиллюстрируйте примерами правила использования каждой из трех частей цикла.
Упражнение 5.10

Дано объявление функции:

bool is_equa1( const vector<int> &vl,
const vector<int> &v2 );

Напишите тело функции, определяющей равенство двух векторов. Для векторов разной длины сравнивайте только то количество элементов, которое соответствует меньшему из двух. Например, векторы (0,1,1,2) и (0,1,1,2,3,5,8) считаются равными. Длину векторов можно узнать с помощью функций v1.size() и v2.size().
5.6. Инструкция while

Синтаксис инструкции while следующий:

while ( условие )
инструкция

Пока значением условия является true, инструкция выполняется в такой последовательности:

   1. Вычислить условие.
   2. Выполнить инструкцию, если условие истинно.
   3. Если самое первое вычисление условия дает false, инструкция не выполняется.

Условием может быть любое выражение:

bool quit = false;
// ...
while ( ! quit ) {
   // ...
   quit = do_something();
}

string word;
while ( cin >> word ){ ... }
или объявление с инициализацией:
while ( symbol *ptr = search( name )) {
   // что-то сделать
}

В последнем случае ptr видим только в блоке, соответствующем инструкции while, как это было и для инструкций for и switch.
Вот пример цикла while, обходящего множество элементов, адресуемых двумя указателями:

int sumit( int *parray_begin, int *parray_end )
{
   int sum = 0;
   if ( ! parray_begin || ! parray_end )
      return sum;
   while ( parray_begin != parray_end )
      // прибавить к sum
      // и увеличить указатель
      sum += *parray_begin++;
   return sum;
}

int ia[6] = { 0, 1, 2, 3, 4, 5 };
int main()
{
   int sum = sumit( &ia[0], &ia[ 6 ] );
   // ...
}

Для того чтобы функция sumit() выполнялась правильно, оба указателя должны адресовать элементы одного и того же массива (parray_end может указывать на элемент, следующий за последним). В противном случае sumit() будет возвращать бессмысленную величину. Увы, С++ не гарантирует, что два указателя адресуют один и тот же массив. Как мы увидим в главе 12, стандартные универсальные алгоритмы реализованы подобным же образом, они принимают параметрами указатели на первый и последний элементы массива.
Упражнение 5.11

Какие ошибки допущены в следующих циклах while:

(a)
   string bufString, word;
   while ( cin >> bufString >> word )
   // ...
(b)
   while ( vector<int>::iterator iter != ivec.end() )
   // ...
(c)
   while ( ptr = 0 )
   ptr = find_a_value();
(d)
   while ( bool status = find( word )) {
   word = get_next_word();
   if ( word.empty() )
   break;
   // ...
   }
   if ( ! status )
   cout << "Слов не найдено\n";

Упражнение 5.12

while обычно применяется для циклов, выполняющихся, пока некоторое условие истинно, например, читать следующее значение, пока не будет достигнут конец файла. for обычно рассматривается как пошаговый цикл: индекс пробегает по определенному диапазону значений. Напишите по одному типичному примеру for и while, а затем измените их, используя цикл другого типа. Если бы вам нужно было выбрать для постоянной работы только один из этих типов, какой бы вы выбрали? Почему?
Упражнение 5.13

Напишите функцию, читающую последовательность строк из стандартного ввода до тех пор, пока одно и то же слово не встретится два раза подряд либо все слова не будут обработаны. Для чтения слов используйте while; при обнаружении повтора слова завершите цикл с помощью инструкции break. Если повторяющееся слово найдено, напечатайте его. В противном случае напечатайте сообщение о том, что слова не повторялись.
5.7. Инструкция do while

Представим, что нам надо написать программу, переводящую мили в километры. Структура программы выглядит так:

int val;
bool more = true; // фиктивное значение, нужное для
// начала цикла

while ( more ) {
   val = getValue();
   val = convertValue(val);
   printValue(val);
   more = doMore();
}

Проблема заключается в том, что условие вычисляется в теле цикла. for и while требуют, чтобы значение условия равнялось true до первого вхождения в цикл, иначе тело не выполнится ни разу. Это означает, что мы должны обеспечить такое условие до начала работы цикла. Альтернативой может служить использование do while, гарантирующего выполнение тела цикла хотя бы один раз. Синтаксис цикла do while таков:

do
   инструкция
while ( условие );

инструкция выполняется до первой проверки условия. Если вычисление условия дает false, цикл останавливается. Вот как выглядит предыдущий пример с использованием цикла do while:

do {
   val = getValue();
   val = convertValue(val);
   printValue(val);
   } while doMore();

В отличие от остальных инструкций циклов, do while не разрешает объявлять объекты в своей части условия. Мы не можем написать:

// ошибка: объявление переменной
// в условии не разрешается
do {
   // ...
   mumble( foo );
} while ( int foo = get_foo() ) // ошибка

потому что до условной части инструкции do while мы дойдем только после первого выполнения тела цикла.
Упражнение 5.14

Какие ошибки допущены в следующих циклах do while:

(a)
do
   string rsp;
   int vail, va12;
   cout << "Введите два числа: ";
   c-in >> vail >> va12;
   cout << "Сумма " << vail
   << " и " << va12
   << " = " << vail + va12 << "\n\n"
   << "Продолжить? [да][нет] ";
   cin >> rsp;
while ( rsp[0] != 'n' );

(b)
do {
   // ...
   } while ( int iva1 = get_response() );
   (c)
   do {
   int ival = get_response();
   if ( iva1 == some_value() )
   break;
} while ( iva1 );
if ( !iva1 )
 // ...

Упражнение 5.15

Напишите небольшую программу, которая запрашивает у пользователя две строки и печатает результат лексикографического сравнения этих строк (строка считается меньшей, если идет раньше при сортировке по алфавиту). Пусть она повторяет эти действия, пока пользователь не даст команду закончить. Используйте тип string, сравнение строк и цикл do while.
5.8. Инструкция break

Инструкция break останавливает циклы for, while, do while и блока switch. Выполнение программы продолжается с инструкции, следующей за закрывающей фигурной скобкой цикла или блока. Например, данная функция ищет в массиве целых чисел определенное значение. Если это значение найдено, функция сообщает его индекс, в противном случае она возвращает -1. Вот как выглядит реализация функции:

// возвращается индекс элемента или -1
int search( int *ia, int size, int value )
{
   // проверка что ia != 0 и size > 0 ...
   int loc = -1;
   for ( int ix = 0; ix < size; ++ix ) {
      if ( value == ia[ ix ] ) {
         // нашли!
         // запомним индекс и выйдем из цикла
         loc = ix;
         break;
      }
   } // конец цикла

   // сюда попадаем по break ...
   return loc;
}

В этом примере break прекращает выполнение цикла for и передает управление инструкции, следующей за этим циклом, – в нашем случае return. Заметим, что break выводит из блока, относящегося к инструкции for, а не if, хотя является частью составной инструкции, соответствующей if. Использование break внутри блока if, не входящего в цикл или в switch, является синтаксической ошибкой:

// ошибка: неверное использование break
if ( ptr ) {
   if ( *ptr == "quit" )
   break;
   // ...
}

Если эта инструкция используется внутри вложенных циклов или инструкций switch, она завершает выполнение того внутреннего блока, в котором находится. Цикл или switch, включающий тот цикл или switch, из которого мы вышли с помощью break, продолжает выполняться. Например:

white ( cin >> inBuf )
{
   switch( inBuf[ 0 ] ) {
      case '-':
         for ( int ix = 1; ix < inBuf.size(); ++ix ) {
            if ( inBuf[ ix ] == ' ' )
               break; // #1
         // ...
         // ...
         }
         break; // #2
      case '+':
         // ...
   }
}

Инструкция break, помеченная // #1, завершает выполнение цикла for внутри ветви case '-' блока switch, но не сам switch. Аналогично break // #2 завершает выполнение блока switch, но не цикла while, в который тот входит.
Категория: С++ | Добавил: r2d2 (29.09.2011)
Просмотров: 1806 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Born in Ussr
Залогиниться
Турниры

/j clan ussr /j clan cccp