Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Липпман.doc
Скачиваний:
8
Добавлен:
14.08.2019
Размер:
7.54 Mб
Скачать

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(), сделав его не ссылкой, а простым объектом. Запустите программу. Как изменилось ее поведение?