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

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

Назад   Вперед
Содержание




Нет комментариев.



Оставить комментарий:
Ваше Имя:
Email:
Антибот: *  
Ваш комментарий: