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

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

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

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

192

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 изменяет значение переменной, на которую она ссылается:

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

193

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

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

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

194

//исправленная версия 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 не получит

int minVal = ivec[0];

значения 1. Конечно, исправить это очень просто, но сначала надо найти ошибку: occurs = 1;

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

Вот окончательная версия функции min() и программа main(), проверяющая ее работу:

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

195

#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 можно использовать более краткое и

template <class valueType> inline const valueType&

min( valueType &vall, valueType &va12 )

{

if ( vall < va12 ) return vall;

return va12;

выразительное условное выражение. Например, следующую реализацию функции min():

}

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

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

196

 

 

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

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

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

197

(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

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

198

Инструкция 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.

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

199

В отличие от 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 и передает управление инструкции, следующей за закрывающей фигурной скобкой, – в данном случае производится вывод. Вот как это должно выглядеть:

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

200

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;

сделать так:

}

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

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

201

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. Например, добавим к нашей программе подсчет суммарного количества согласных: