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

  • Примечание [O.A.2]

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

  • Упражнение 5.11 Какие ошибки допущены в следующих циклах while: // ... cout << "Слов не найдено\n"; Упражнение 5.12

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

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


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница20 из 93
    1   ...   16   17   18   19   20   21   22   23   ...   93
    206
    for ( char *ptr = getStr(); ... условие служит для управления циклом. Пока условие при вычислении дает true, инструкция продолжает выполняться. Выполняемая в цикле инструкция может быть как простой, так и составной. Если же самое первое вычисление условия дает false, инструкция не выполняется ни разу. Правильные условия можно записать так:
    (... char ch = getNextChar(); ... )
    Выражение вычисляется после выполнения инструкции на каждой итерации цикла.
    Обычно его используют для модификации переменной, инициализированной в инструкции-инициализации. Если самое первое вычисление условия дает false, выражение не выполняется ни разу. Правильные выражения выглядят таким образом:
    ( ... ...; ) // пустое выражение
    Для приведенного ниже цикла for
    } порядок вычислений будет следующим:
    1. инструкция-инициализации выполняется один раз перед началом цикла. В данном примере объявляется переменная ix, которая инициализируется значением 0.
    2. Вычисляется условие. Если оно равно true, выполняется составная инструкция тела цикла. В нашем примере, пока ix меньше sz, значение ix присваивается элементам ivec[ix]
    и ia[ix]. Когда значением условия станет false, выполнение цикла прекратится. Если самое первое вычисление условия даст false, составная инструкция выполняться не будет.
    3. Вычисляется выражение. Как правило, его используют для модификации переменной, фигурирующей в инструкции-инициализации и проверяемой в условии. В нашем примере ix увеличивается на 1.
    (... index < arraySize; ... )
    (... iter != ivec.end(); ... )
    (... *stl++ = *st2++; ... )
    ( ... ...; ++-index )
    ( ... ...; ptr = ptr->next )
    ( ... ...; ++i, --j, ++cnt ) const int sz = 24; int ia[ sz ]; vector ivec( sz ); for ( int ix = 0; ix < sz; ++ix )
    { ivec[ ix ] = ix; ia[ ix ]= ix;

    С++ для начинающих
    207
    Эти три шага представляют собой полную итерацию цикла for. Теперь шаги 2 и 3 будут повторяться до тех пор, пока условие не станет равным false, т.е. ix окажется равным или большим sz.
    В инструкции-инициализации можно определить несколько объектов, однако все они должны быть одного типа, так как инструкция объявления допускается только одна:
    // ...
    Объявление объекта в условии гораздо труднее правильно использовать: такое объявление должно хотя бы раз дать значение false, иначе выполнение цикла никогда не прекратится. Вот пример, хотя и несколько надуманный:
    }
    Видимость всех объектов, определенных внутри круглых скобок инструкции for, ограничена телом цикла. Например, проверка iter после цикла вызовет ошибку компиляции
    8
    :
    8 До принятия стандарта языка С++ видимость объектов, определенных внутри круглых скобок for, простиралась на весь блок или функцию, содержащую данную инструкцию.
    Например, употребление двух циклов for внутри одного блока for ( int ival = 0, *pi = &ia, &ri = val; ival < size;
    ++iva1, ++pi, ++ri )
    #include int main()
    { for ( int ix = 0; bool done = ix == 10;
    ++ix ) cout << "ix: " << ix << endl; 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 )
    Примечание [O.A.2]: Нумера ция сносок сбита, как и вся остальная. Необходима проверка.

    С++ для начинающих
    208
    // ...
    Упражнение 5.8
    Допущены ли ошибки в нижеследующих циклах for? Если да, то какие?
    }
    {
    // верно для стандарта С++
    // в предыдущих версиях C++ - ошибка: ival определена дважды for (int ival = 0; ival < size; ++iva1 ) // ... for (int ival = size-1; ival > 0; ival ) // ...
    } в ранних версиях языка вызывало ошибку: ival определена дважды. В стандарте С++ данный текст синтаксически правилен, так как каждый экземпляр ival является локальным для своего блока.
    (a) for ( int *ptr = &ia, ix = 0; ix < size && ptr != ia+size;
    ++ix, ++ptr )
    // ...
    (b) for ( ; ; ) { if ( some_condition ) break;
    // ...

    С++ для начинающих
    209
    // ...
    Упражнение 5.9
    Представьте, что вам поручено придумать общий стиль использования цикла for в вашем проекте. Объясните и проиллюстрируйте примерами правила использования каждой из трех частей цикла.
    Упражнение 5.10
    Дано объявление функции: const vector &v2 );
    Напишите тело функции, определяющей равенство двух векторов. Для векторов разной длины сравнивайте только то количество элементов, которое соответствует меньшему из двух. Например, векторы (0,1,1,2) и (0,1,1,2,3,5,8) считаются равными. Длину векторов можно узнать с помощью функций v1.size() и v2.size().
    5.6.
    Инструкция while
    Синтаксис инструкции while следующий: инструкция
    Пока значением условия является true, инструкция выполняется в такой последовательности:
    1. Вычислить условие.
    2. Выполнить инструкцию, если условие истинно.
    (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 ) bool is_equa1( const vector &vl, while ( условие )

    С++ для начинающих
    210
    3. Если самое первое вычисление условия дает false, инструкция не выполняется.
    Условием может быть любое выражение: while ( cin >> word ){ ... } или объявление с инициализацией:
    }
    В последнем случае ptr видим только в блоке, соответствующем инструкции while, как это было и для инструкций for и switch.
    Вот пример цикла while, обходящего множество элементов, адресуемых двумя указателями:
    }
    Для того чтобы функция sumit() выполнялась правильно, оба указателя должны адресовать элементы одного и того же массива (parray_end может указывать на элемент, следующий за последним). В противном случае sumit() будет возвращать бессмысленную величину. Увы, С++ не гарантирует, что два указателя адресуют один и bool quit = false;
    // ... while ( ! quit ) {
    // ... quit = do_something();
    } string word; while ( symbol *ptr = search( name )) {
    // что-то сделать 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 ] );
    // ...

    С++ для начинающих
    211
    тот же массив. Как мы увидим в главе 12, стандартные универсальные алгоритмы реализованы подобным же образом, они принимают параметрами указатели на первый и последний элементы массива.
    Упражнение 5.11
    Какие ошибки допущены в следующих циклах while:
    // ... cout << "
    Слов не найдено\n";
    Упражнение 5.12
    while обычно применяется для циклов, выполняющихся, пока некоторое условие истинно, например, читать следующее значение, пока не будет достигнут конец файла. for обычно рассматривается как пошаговый цикл: индекс пробегает по определенному диапазону значений. Напишите по одному типичному примеру for и while, а затем измените их, используя цикл другого типа. Если бы вам нужно было выбрать для постоянной работы только один из этих типов, какой бы вы выбрали? Почему?
    Упражнение 5.13
    Напишите функцию, читающую последовательность строк из стандартного ввода до тех пор, пока одно и то же слово не встретится два раза подряд либо все слова не будут обработаны. Для чтения слов используйте while; при обнаружении повтора слова завершите цикл с помощью инструкции break. Если повторяющееся слово найдено, напечатайте его. В противном случае напечатайте сообщение о том, что слова не повторялись.
    (a) string bufString, word; while ( cin >> bufString >> word )
    (b) while ( vector::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 )

    С++ для начинающих
    212
    5.8.
    Инструкция do while
    Представим, что нам надо написать программу, переводящую мили в километры.
    Структура программы выглядит так:
    }
    Проблема заключается в том, что условие вычисляется в теле цикла. for и while требуют, чтобы значение условия равнялось true до первого вхождения в цикл, иначе тело не выполнится ни разу. Это означает, что мы должны обеспечить такое условие до начала работы цикла. Альтернативой может служить использование do while, гарантирующего выполнение тела цикла хотя бы один раз. Синтаксис цикла do while таков: while ( условие ); инструкция выполняется до первой проверки условия. Если вычисление условия дает false
    , цикл останавливается. Вот как выглядит предыдущий пример с использованием цикла do while:
    } while doMore();
    В отличие от остальных инструкций циклов, do while не разрешает объявлять объекты в своей части условия. Мы не можем написать:
    } while ( int foo = get_foo() ) // ошибка int val; bool more = true; // фиктивное значение, нужное для
    // начала цикла while ( more ) { val = getValue(); val = convertValue(val); printValue(val); more = doMore(); do инструкция do { val = getValue(); val = convertValue(val); printValue(val);
    // ошибка: объявление переменной
    // в условии не разрешается do {
    // ... mumble( foo );

    С++ для начинающих
    213
    потому что до условной части инструкции do while мы дойдем только после первого выполнения тела цикла.
    Упражнение 5.14
    Какие ошибки допущены в следующих циклах do while:
    (a)
    // ...
    Упражнение 5.15
    Напишите небольшую программу, которая запрашивает у пользователя две строки и печатает результат лексикографического сравнения этих строк (строка считается меньшей, если идет раньше при сортировке по алфавиту). Пусть она повторяет эти действия, пока пользователь не даст команду закончить. Используйте тип string, сравнение строк и цикл do while.
    5.8.
    Инструкция break
    Инструкция break останавливает циклы for, while, do while и блока switch.
    Выполнение программы продолжается с инструкции, следующей за закрывающей фигурной скобкой цикла или блока. Например, данная функция ищет в массиве целых чисел определенное значение. Если это значение найдено, функция сообщает его индекс, в противном случае она возвращает -1. Вот как выглядит реализация функции: 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 )

    С++ для начинающих
    214
    }
    В этом примере break прекращает выполнение цикла for и передает управление инструкции, следующей за этим циклом, – в нашем случае return. Заметим, что break выводит из блока, относящегося к инструкции for, а не if, хотя является частью составной инструкции, соответствующей if. Использование break внутри блока if, не входящего в цикл или в switch, является синтаксической ошибкой:
    }
    Если эта инструкция используется внутри вложенных циклов или инструкций switch, она завершает выполнение того внутреннего блока, в котором находится. Цикл или switch
    , включающий тот цикл или switch, из которого мы вышли с помощью break, продолжает выполняться. Например:
    }
    // возвращается индекс элемента или -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 ] ) {
    // нашли!
    // запомним индекс и выйдем из цикла
    1oc = ix; break;
    }
    } // конец цикла
    // сюда попадаем по break ... return 1oc;
    // ошибка: неверное использование break if ( ptr ) { if ( *ptr == "quit" ) break;
    // ... white ( cin >> inBuf )
    { switch( inBuf[ 0 ] ) { case '-': for ( int ix = 1; ix < inBuf.size(); ++ix ) { if ( inBuf[ ix ] == ' ' ) break; // #1
    // ...
    // ...
    } break; // #2 case '+':
    // ...
    }

    С++ для начинающих
    215
    Инструкция break, помеченная // #1, завершает выполнение цикла for внутри ветви case '-'
    блока switch, но не сам switch. Аналогично break // #2 завершает выполнение блока switch, но не цикла while, в который тот входит.
    5.9.
    Инструкция continue
    Инструкция continue завершает текущую итерацию цикла и передает управление на вычисление условия, после чего цикл может продолжиться. В отличие от инструкции break
    , завершающей выполнение всего цикла, инструкция continue завершает выполнение только текущей итерации. Например, следующий фрагмент программы читает из входного потока по одному слову. Если слово начинается с символа подчеркивания, оно обрабатывается, в противном случае программа переходит к новому слову.
    }
    Инструкция continue может быть использована только внутри цикла.
    5.10.
    Инструкция goto
    Инструкция goto обеспечивает безусловный переход к другой инструкции внутри той же функции, поэтому современная практика программирования выступает против ее применения.
    Синтаксис goto следующий: goto метка; где метка – определенный пользователем идентификатор. Метка ставится перед инструкцией, на которую можно перейти с помощью goto, и должна заканчиваться двоеточием. Нельзя ставить метку непосредственно перед закрывающей фигурной скобкой. Если же это необходимо, их следует разделить пустой инструкцией:
    }
    Переход через инструкцию объявления в том же блоке с помощью goto невозможен.
    Например, данная функция вызывает ошибку компиляции: while ( cin >> inBuf ) { if ( inBuf[0] '= '_' ) continue; // завершение итерации
    // обработка слова ... end: ; // пустая инструкция

    С++ для начинающих
    216
    }
    Правильная реализация функции помещает объявление ix и использующие его инструкции во вложенный блок:
    }
    Причина такого ограничения та же, что и для объявлений внутри блока switch: компилятор должен гарантировать, что для объявленного объекта конструктор и деструктор либо выполняются вместе, либо ни один из них не выполняется. Это и достигается заключением объявления во вложенный блок.
    Переход назад через объявление, однако, не считается ошибкой. Почему? Перепрыгнуть через инициализацию объекта нельзя, но проинициализировать один и тот же объект несколько раз вполне допустимо, хотя это может привести к снижению эффективности.
    Например: int oops_in_error() {
    // mumble ... goto end;
    // ошибка: переход через объявление int ix = 10;
    // ... код, использующий ix end: ; int oops_in_error() {
    // mumble ... goto end;
    {
    // правильно: объявление во вложенном блоке int ix = 10;
    // ... код, использующий ix
    } end: ;

    С++ для начинающих
    217
    }
    Использование инструкции goto резко критикуется во всех современных языках программирования. Ее применение приводит к тому, что ход выполнения программы становится трудно понять и, следовательно, такую программу трудно модифицировать.
    В большинстве случаев goto можно заменить на инструкции if или циклы. Если вы все- таки решили использовать goto, не перескакивайте через большой фрагмент кода, чтобы можно было легко найти начало и конец вашего перехода.
    5.11.
    Пример связанного списка
    Мы завершали главы 3 и 4 примерами для введения читателя в механизм классов С++. В конце этого раздела мы покажем, как разработать класс, представляющий собой односвязный список. (В главе 6 мы рассмотрим двусвязный список, являющийся частью стандартной библиотеки.) Если вы в первый раз читаете эту книгу, то можете пропустить данный раздел и вернуться к нему после чтения главы 13. (Для усвоения этого материала нужно представлять себе механизм классов С++, конструкторы, деструкторы и т.д. Если вы плохо знаете классы, но все же хотите продолжить чтение данного раздела, мы рекомендуем прочесть пункты 2.3 и 3.15.
    Список представляет собой последовательность элементов, каждый из которых содержит значение некоторого типа и адрес следующего элемента (причем для последнего из них адрес может быть нулевым). К любой такой последовательности всегда можно добавить еще один элемент (хотя реальная попытка подобного добавления может закончиться неудачно, если отведенная программе свободная память исчерпана). Список, в котором нет ни одного элемента, называется пустым.
    Какие операции должен поддерживать список? Добавление (insert), удаление (remove) и поиск (find) определенных элементов. Кроме того, можно запрашивать размер списка
    (size), распечатывать его содержимое (display), проверять равенство двух списков. Мы покажем также, как инвертировать (reverse) и сцеплять (concatenate) списки.
    Простейшая реализация операции size() перебирает все элементы, подсчитывая их количество. Более сложная реализация сохраняет размер как член данных; она намного
    // переход назад через объявление не считается ошибкой. void mumble ( int max_size )
    { begin: int sz = get_size(); if ( sz <= 0 ) {
    // выдать предупреждение ... goto end;
    } else if ( sz > max_size )
    // получить новое значение sz goto begin;
    { // правильно: переход через целый блок int ia = new int[ sz ]; doit( ia, sz ) ; delete [] ia;
    } end:
    ;

    С++ для начинающих
    218
    эффективнее, однако требует некоторого усложнения операций insert() и remove() для поддержки размера в актуальном состоянии.
    Мы выбрали второй вариант реализации функции size() и храним размер списка в члене данных. Мы предполагаем, что пользователи будут достаточно часто применять эту операцию, поэтому ее необходимо реализовать как можно более эффективно.
    (Одним из преимуществ отделения открытого интерфейса от скрытой реализации является то, что если наше предположение окажется неверным, мы сможем переписать реализацию, сохранив открытый интерфейс – в данном случае тип возвращаемого значения и набор параметров функции size() – и программы, использующие эту функцию, не нужно будет модифицировать.)
    Операция insert() в общем случае принимает два параметра: указатель на один из элементов списка и новое значение, которое вставляется после указанного элемента.
    Например, для списка
    1 1 2 3 8 вызов mylist.insert (pointer_to_3, 5); изменит наш список так:
    1 1 2 3 5 8
    Чтобы обеспечить подобную возможность, нам необходимо дать пользователю способ получения адреса определенного элемента. Одним из способов может быть использование функции find() – нахождение элемента с определенным значением: pointer_to_3 = mylist.find( 3 ); find()
    принимает в качестве параметра значение из списка. Если элемент с таким значением найден, то возвращается его адрес, иначе find() возвращает 0.
    Может быть два специальных случая вставки элемента: в начало и в конец списка. Для этого требуется только задание значения:
    1nsert_end( value );
    Предусмотрим следующие операции удаления элемента с заданным значением, первого элемента и всех элементов списка: remove_all(); insert_front( value ); remove( value ); remove_front();

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


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