Главная страница
Навигация по странице:

  • Упражнение 5.3

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

  • Упражнение 5.7

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница19 из 93
    1   ...   15   16   17   18   19   20   21   22   ...   93
    191
    Здесь инструкция2 сама является if-инструкцией. Если minVal меньше ivec[i], никаких действий не производится.
    В следующем примере выполняется одна из трех инструкций:
    ++occurs;
    Составные инструкции if-else могут служить источником неоднозначного толкования, если частей else больше, чем частей if. К какому из if отнести данную часть else?
    (Эту проблему иногда называют проблемой висячего else). Например:
    }
    Судя по отступам, программист предполагает, что else относится к самому первому, внешнему if. Однако в С++ неоднозначность висячих else разрешается соотнесением их с последним встретившимся if. Таким образом, в действительности предыдущий фрагмент означает следующее:
    }
    Одним из способов разрешения данной проблемы является заключение внутреннего if в фигурные скобки: if ( minVal < ivec[ i ] )
    {} // пустая инструкция else if ( minVal > ivec[ i ] ) { minVal = ivec[ i ]; occurs = 1;
    } else // minVal == ivec[ i ] if ( minVal <= ivec[ i ] ) if ( minVal == ivec[ i ] )
    ++occurs; else { minVal
    = ivec[ i ]; occurs
    = 1; if ( minVal <= ivec[ i ] ) { if ( minVal == ivec[ i ] )
    ++occurs; else { minVal
    = ivec[ i ]; occurs
    = 1;
    }

    С++ для начинающих
    192
    }
    В некоторых стилях программирования рекомендуется всегда употреблять фигурные скобки при использовании инструкций if-else, чтобы не допустить возможности неправильной интерпретации кода.
    Вот первый вариант функции min(). Второй аргумент функции будет возвращать количество вхождений минимального значения в вектор. Для перебора элементов массива используется цикл for. Но мы допустили ошибку в логике программы. Сможете ли вы заметить ее?
    }
    Обычно функция возвращает только одно значение. Однако согласно нашей спецификации в точке вызова должно быть известно не только само минимальное значение, но и количество его вхождений в вектор. Для возврата второго значения мы использовали параметр типа ссылка. (Параметры-ссылки рассматриваются в разделе 7.3.)
    Любое присваивание значения ссылке occurs изменяет значение переменной, на которую она ссылается: if ( minVal <= ivec[ i ] ) { if ( minVal == ivec[ i ] )
    ++occurs;
    } else { minVal
    = ivec[ i ]; occurs
    = 1;
    #include int min( const vector &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;

    С++ для начинающих
    193
    }
    Альтернативой использованию параметра-ссылки является применение объекта класса pair
    , представленного в разделе 3.14. Функция min() могла бы возвращать два значения в одной паре:
    }
    К сожалению, и эта реализация содержит ошибку. Где же она? Правильно: мы инициализировали minVal нулем, поэтому, если минимальный элемент вектора больше нуля, наша реализация вернет нулевое значение минимума и нулевое значение количества вхождений.
    Программу можно изменить, инициализировав minVal первым элементом вектора: int minVal = ivec[0];
    Теперь функция работает правильно. Однако в ней выполняются некоторые лишние действия, снижающие ее эффективность. int main()
    { int occur_cnt = 0; vector< int > ivec;
    // occur_cnt получает значение occurs
    // из функции min() int minval = min( ivec, occur_cnt );
    // ...
    // альтернативная реализация
    // с помощью пары
    #include
    #include typedef pair min_va1_pair; min_va1_pair min( const vector &ivec )
    { int minVal = 0; int occurs = 0;
    // то же самое ... return make_pair( minVal, occurs );

    С++ для начинающих
    194
    // ...
    Поскольку ix инициализируется нулем, на первой итерации цикла значение первого элемента сравнивается с самим собой. Можно инициализировать ix единицей и избежать ненужного выполнения первой итерации. Однако при оптимизации кода мы допустили другую ошибку (наверное, стоило все оставить как было!). Сможете ли вы ее обнаружить?
    // ...
    Если ivec[0] окажется минимальным элементом, переменная occurs не получит значения 1. Конечно, исправить это очень просто, но сначала надо найти ошибку: occurs = 1;
    К сожалению, подобного рода недосмотры встречаются не так уж редко: программисты тоже люди и могут ошибаться. Важно понимать, что это неизбежно, и быть готовым тщательно тестировать и анализировать свои программы.
    Вот окончательная версия функции min() и программа main(), проверяющая ее работу:
    // исправленная версия min()
    // оставляющая возможность для оптимизации ... int minVal = ivec[0]; occurs = 0; int size = ivec.size(); for
    ( int ix = 0; ix < size; ++ix )
    { if ( minVal == ivec[ ix ] )
    ++occurs;
    // оптимизированная версия min(),
    // к сожалению, содержащая ошибку... int minVal = ivec[0]; occurs = 0; int size = ivec.size(); for
    ( int ix = 1; ix < size; ++ix )
    { if ( minVal == ivec[ ix ] )
    ++occurs; int minVal = ivec[0];

    С++ для начинающих
    195
    }
    Результат работы программы:
    Минимальное значение: 1 встречается: 5 раз.
    В некоторых случаях вместо инструкции if-else можно использовать более краткое и выразительное условное выражение. Например, следующую реализацию функции min():
    } можно переписать так:
    #include
    #include 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 ivec( ia, ia+14 ); int occurs = 0; int minVal = min( ivec, occurs ); cout << "
    Минимальное значение: " << minVal
    << " встречается: " << occurs << " раз.\n"; return 0; template inline const valueType& min( valueType &vall, valueType &va12 )
    { if ( vall < va12 ) return vall; return va12;

    С++ для начинающих
    196
    }
    Длинные цепочки инструкций if-else, подобные приведенной ниже, трудны для восприятия и, таким образом, являются потенциальным источником ошибок.
    ++uCnt;
    В качестве альтернативы таким цепочкам С++ предоставляет инструкцию switch. Это тема следующего раздела.
    Упражнение 5.3
    Исправьте ошибки в примерах: template inline const valueType& min( valueType &vall, valueType &va12 )
    { return ( vall < va12 ) ? vall : va12; 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' )

    С++ для начинающих
    197
    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
    (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 )

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

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

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

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

    необязательной метки default, которая является аналогом части else инструкции if-else. Инструкции, соответствующие этой метке, выполняются, если условие не отвечает ни одной из меток case. Например, мы можем подсчитать суммарное количество встретившихся символов, не являющихся гласными буквами:
    ++non_vowe1_cnt;
    Константное выражение в метке case должно принадлежать к целому типу, поэтому следующие строки ошибочны: case ival: // не константа
    Кроме того, две разные метки не могут иметь одинаковое значение.
    Выражение условия в инструкции switch может быть сколь угодно сложным, в том числе включать вызовы функций. Результат вычисления условия сравнивается с метками case
    , пока не будет найдено равное значение или не выяснится, что такого значения нет.
    Если метка обнаружена, выполнение будет продолжено с первой инструкции после нее, если же нет, то с первой инструкции после метки default (при ее наличии) или после всей составной инструкции switch. char ch; while ( cm >> ch ) case 'a': case 'e': case 'i': case 'o': default: // любой символ, не являющийся гласной
    // неверные значения меток case 3.14: // не целое

    С++ для начинающих
    199
    В отличие от if-else инструкции, следующие за найденной меткой, выполняются друг за другом, проходя все нижестоящие метки case и метку default. Об этом часто забывают. Например, данная реализация нашей программы выполняется совершенно не так, как хотелось бы:
    }
    Если значение ch равно i, выполнение начинается с инструкции после case 'i' и iCnt возрастет на 1. Однако следующие ниже инструкции, ++oCnt и ++uCnt, также выполняются, увеличивая значения и этих переменных. Если же переменная ch равна a, изменятся все пять счетчиков.
    Программист должен явно дать указание компьютеру прервать последовательное выполнение инструкций в определенном месте switch, вставив break. В абсолютном большинстве случаев за каждой метке case должен следовать соответствующий break. break прерывает выполнение switch и передает управление инструкции, следующей за закрывающей фигурной скобкой, – в данном случае производится вывод. Вот как это должно выглядеть:
    #include 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';

    С++ для начинающих
    200
    }
    Если почему-либо нужно, чтобы одна из секций не заканчивалась инструкцией break, то желательно написать в этом месте разумный комментарий. Программа создается не только для машин, но и для людей, и необходимо сделать ее как можно более понятной для читателя. Программист, изучающий чужой текст, не должен сомневаться, было ли нестандартное использование языка намеренным или ошибочным.
    При каком условии программист может отказаться от инструкции break и позволить программе провалиться сквозь несколько меток case? Одним из таких случаев является необходимость выполнить одни и те же действия для двух или более меток. Это может понадобиться потому, что с case всегда связано только одно значение. Предположим, мы не хотим подсчитывать, сколько раз встретилась каждая гласная в отдельности, нас интересует только суммарное количество всех встретившихся гласных. Это можно сделать так:
    }
    Некоторые программисты подчеркивают осознанность своих действий тем, что предпочитают в таком случае писать метки на одной строке: switch ( ch ) { case 'a':
    ++aCnt; break; case 'e':
    ++eCnt; break; case 'i':
    ++iCnt; break; case 'o':
    ++oCnt; break; case 'u':
    ++uCnt; break; int vowelCnt = 0;
    // ... switch ( ch )
    {
    // любой из символов a,e,1,o,u
    // увеличит значение vowelCnt case 'a': case 'e': case 'i': case 'o': case 'u':
    ++vowe1Cnt; break;

    С++ для начинающих
    201
    }
    В данной реализации все еще осталась одна проблема: как будут восприняты слова типа
    UNIX
    Наша программа не понимает заглавных букв, поэтому заглавные U и I не будут отнесены к гласным. Исправить ситуацию можно следующим образом:
    }
    Метка default является аналогом части else инструкции if-else. Инструкции, соответствующие default, выполняются, если условие не отвечает ни одной из меток case
    . Например, добавим к нашей программе подсчет суммарного количества согласных: switch ( ch )
    {
    // допустимый синтаксис case 'a': case 'e': case 'i': case 'o': case 'u':
    ++vowe1Cnt; break; 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;

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

    С++ для начинающих
    203
    Помещать же инструкцию объявления внутри тела блока switch не разрешается. Данный фрагмент кода не будет пропущен компилятором: break;
    Если бы разрешалось объявлять переменную таким образом, то ее было бы видно во всем блоке switch, однако инициализируется она только в том случае, если выполнение прошло через данную метку case.
    Мы можем употребить в этом месте составную инструкцию, тогда объявление переменной file_name будет синтаксически правильным. Использование блока гарантирует, что объявленная переменная видна только внутри него, а в этом контексте она заведомо инициализирована. Вот как выглядит правильный текст: break;
    }
    Упражнение 5.5
    Модифицируйте программу из данного раздела так, чтобы она подсчитывала не только буквы, но и встретившиеся пробелы, символы табуляции и новой строки.
    Упражнение 5.6
    Модифицируйте программу из данного раздела так, чтобы она подсчитывала также количество встретившихся двухсимвольных последовательностей ff, fl и fi.
    Упражнение 5.7
    Найдите и исправьте ошибки в следующих примерах:
    (a)
    }
    (b) case illegal_definition:
    // ошибка: объявление не может
    // употребляться в этом месте string file_name = get_file_name();
    // ... case ok:
    {
    // ок string file_name = get_file_name();
    // ... switch ( ival ) { case 'a': aCnt++; case 'e': eCnt++; default: iouCnt++;

    С++ для начинающих
    204
    }
    (c)
    }
    (d)
    }
    (e) break; switch ( ival ) { case 1: int ix = get_value(); ivec[ ix ] = ival; break; default: ix = ivec.sizeQ-1; ivec[ ix ] = ival; switch ( ival ) { case 1, 3, 5, 7, 9: oddcnt++; break; case 2, 4, 6, 8, 10: evencnt++; break; 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; enum { illustrator = 1, photoshop, photostyler = 2 }; switch ( ival ) { case illustrator:
    --i11us_1icense; break; case photoshop:
    --pshop_1icense; break; case photostyler:
    --psty1er_license;

    С++ для начинающих
    205
    }
    5.5.
    Инструкция цикла for
    Как мы видели, выполнение программы часто состоит в повторении последовательности инструкций – до тех пор, пока некоторое условие остается истинным. Например, мы читаем и обрабатываем записи файла, пока не дойдем до его конца, перебираем элементы массива, пока индекс не станет равным размерности массива минус 1, и т.д. В С++ предусмотрено три инструкции для организации циклов, в частности for и while, которые начинаются проверкой условия. Такая проверка означает, что цикл может закончиться без выполнения связанной с ним простой или составной инструкции. Третий тип цикла, do while, гарантирует, что тело будет выполнено как минимум один раз: условие цикла проверяется по его завершении. (В этом разделе мы детально рассмотрим цикл for; в разделе 5.6 разберем while, а в разделе 5.7 – do while.)
    Цикл for обычно используется для обработки структур данных, имеющих фиксированную длину, таких, как массив или вектор:
    }
    Синтаксис цикла for следующий: инструкция инструкция-инициализации может быть либо выражением, либо инструкцией объявления. Обычно она используется для инициализации переменной значением, которое увеличивается в ходе выполнения цикла. Если такая инициализация не нужна или выполняется где-то в другом месте, эту инструкцию можно заменить пустой (см. второй из приведенных ниже примеров). Вот примеры правильного использования инструкции-инициализации:
    #include int main() { int ia[ 10 ]; for ( int ix = 0; ix < 10; ++-ix ) ia[ ix ] = ix; vector ivec( ia, ia+10 ); vector::iterator iter = ivec.begin() ; for ( ; iter != ivec.end(); ++iter )
    *iter *= 2; return 0; for (
    инструкция-инициализации; условие; выражение )
    // index и iter определены в другом месте for ( index =0; ... for ( ; /* пустая инструкция */ ... for ( iter = ivec.begin(); ... for ( int 1o = 0,hi = max; ...

    С++ для начинающих
    1   ...   15   16   17   18   19   20   21   22   ...   93


    написать администратору сайта