Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

202

#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, но не вне его.

С++ для начинающих

203

Помещать же инструкцию объявления внутри тела блока 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

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

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

(a)

}

(b)

С++ для начинающих

204

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;

(c)

}

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;

(d)

}

enum { illustrator = 1, photoshop, photostyler = 2 }; switch ( ival ) {

case illustrator: --i11us_1icense; break;

case photoshop: --pshop_1icense; break;

case photostyler: --psty1er_license;

(e)

break;

С++ для начинающих

205

}

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; ...

инструкции-инициализации:

С++ для начинающих

206

for ( char *ptr = getStr(); ...

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

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

инструкция не выполняется ни разу. Правильные условия можно записать так:

(... char ch = getNextChar(); ... )

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

( ...

...; ++-index )

( ...

...; ptr = ptr->next )

( ...

...; ++i, --j, ++cnt )

выражение не выполняется ни разу. Правильные выражения выглядят таким образом:

( ... ...; ) // пустое выражение

const int sz = 24;

 

int ia[ sz ];

 

vector<int> ivec( sz );

 

for ( int ix = 0; ix < sz; ++ix )

{

ivec[ ix ] = ix;

 

ia[ ix ]= ix;

 

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

}

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

1.инструкция-инициализации выполняется один раз перед началом цикла. В данном примере объявляется переменная ix, которая инициализируется значением 0.

2.Вычисляется условие. Если оно равно true, выполняется составная инструкция тела цикла. В нашем примере, пока ix меньше sz, значение ix присваивается элементам ivec[ix] и ia[ix]. Когда значением условия станет false, выполнение цикла прекратится. Если самое первое вычисление условия даст false, составная инструкция выполняться не будет.

3.Вычисляется выражение. Как правило, его используют для модификации переменной, фигурирующей в инструкции-инициализации и проверяемой в условии. В нашем примере ix увеличивается на 1.

С++ для начинающих

207

Эти три шага представляют собой полную итерацию цикла 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 )

компиляции8:

8 До принятия стандарта языка С++ видимость объектов, определенных внутри круглых скобок for, простиралась на весь блок или функцию, содержащую данную инструкцию. Например, употребление двух циклов for внутри одного блока

Примечание [O.A.2]: Нумера ция сносок сбита, как и вся остальная. Необходима проверка.

С++ для начинающих

208

// ...

Упражнение 5.8

(a)

for ( int *ptr = &ia, ix = 0;

ix < size && ptr != ia+size; ++ix, ++ptr )

// ...

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

(b)

for ( ; ; ) {

if ( some_condition ) break;

// ...

}

{

//верно для стандарта С++

//в предыдущих версиях C++ - ошибка: ival определена дважды for (int ival = 0; ival < size; ++iva1 ) // ...

for (int ival = size-1; ival > 0; ival ) // ...

}

в ранних версиях языка вызывало ошибку: ival определена дважды. В стандарте С++ данный текст синтаксически правилен, так как каждый экземпляр ival является локальным для своего блока.

С++ для начинающих

209

(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.Выполнить инструкцию, если условие истинно.

С++ для начинающих

210

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() будет возвращать бессмысленную величину. Увы, С++ не гарантирует, что два указателя адресуют один и

С++ для начинающих

211

тот же массив. Как мы увидим в главе 12, стандартные универсальные алгоритмы реализованы подобным же образом, они принимают параметрами указатели на первый и последний элементы массива.

Упражнение 5.11

(a)

string bufString, word;

while ( cin >> bufString >> word )

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

(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. Если повторяющееся слово найдено, напечатайте его. В противном случае напечатайте сообщение о том, что слова не повторялись.