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

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
238
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

Как и в предыдундем листинге, ключевое слово if выравнивается здесь на тот же уровень, что и предшествуюидий/последуюндий операторы, а код в операTope_true выравнивается вправо для демонстрации управляющей структуры.

Обратите внимание на использование именованной константы для абсолютно­ го нуля вместо литерального значения, как было в листинге 4.1. Это считается хорошей практикой программирования — рекомендуется применять именованные константы для ка>вдого литерального значения и помешкать их определения в одно место программы, что упрош,ает сопровождение. Программист будет знать, где искать значение, а одно изменение будет действовать во всей программе. Такой подход намного лучше, чем выискивание каждого вхождения литерала в исходном коде и ошибки из-за пропуш,енных изменений. В данном маленьком примере -273 — лишь небольшое числовое значение, используемое только один раз. Если потре­ буется изменить данное значение, то это все равно, что изменить его в тексте программы (кроме того, вряд ли часто потребуется менять значение абсолютного нуля при ее сопровождении). Следовательно, в данном случае все равно, констан­ та это или литерал. Тем не менее применение символических констант — хорошая практика.

Внимание Oneparop.true и onepaTop_false в условиях может быть при необходимости составным оператором.

Листинг 4.3 демонстрирует модифицированную

Введите

температуру в градусах Цельсия: 20

Вы

ввели

значение 20

 

программу из листинга 4.1, где в ветви true исполь­

 

зуются два оператора, как и в ветви false. Обратите

20 - допустимая температура

 

внимание на применение ключевого слова const. Как

Вы

можете продолжить вычисления

Спасибо,

что воспользовались

программой

уже упоминалось выше, это более популярная техни­

 

 

 

 

ка программирования на C+ + , чем использование

 

4 . 3 . Вывод программы,

директивы препроцессора #define. Вывод программы

Рис.

показан на оис 4 3

 

 

представленной

в листинге 4.3

Листинг 4.3. Условный оператор с составными операторами в ветвях

#include <iostream> using namespace std;

const int ABSOLUTE_ZERO = -273;

int main ()

{int eels;

cout

«

 

"\пВведите температуру в градусах

Цельсия: ";

 

cin

»

eels;

 

 

 

 

 

 

cout

«

 

"\пВы ввели значение " «

eels «

endl;

 

 

i f (eels

 

< ABSOLUTE_ZERO)

 

 

 

 

 

{

cout

«"\пЗначение " « e e l s «

"

недопустимо\п;"

 

 

cout

 

«

"OHO меньше абсолютного

нуля\п"; }

/ /

блок

else

 

 

 

 

 

 

 

 

 

 

{

cout

«

c e l s « " - допустимая температура\п";

/ /

блок

 

cout

 

«

"Вы можете продолжать вычисления\п";

}

 

cout

«

 

"Спасибо, что воспользовались программой"

« e n d l ;

 

return

0;

 

 

 

 

 

 

 

В составных операторах должны использоваться открываюш,ая и закрывающая фигурные скобки. Операторы в каждом составном операторе выравниваются не­ много вправо. Это указывает, что они выполняются последовательно, и помогает

Глава 4• Управление ходом выполнения программы 0^^+

101

понять реализацию кода при сопровождении программы. Некоторые програм­ мисты помещают открывающую и закрывающую фигурные скобки на отдельные строки, считая, что это помогает выделить структуру кода, однако при таком методе программа становится длиннее, а потому труднее ухватить общий смысл кода (особенно, если приходится просматривать ее на дисплее, а не на распечатке). Вот почему вертикальные пробелы лучше использовать реже.

Распространенные ошибки в условных операторах

Условные операторы увеличивают сложность программного кода. Ошибки в таких операторах часто трудно отыскать. Если повезет, то такие ошибки сделают код синтаксически некорректным, но чаще они приводят к неправильному выпол­ нению. Поскольку не все части условного оператора выполняются при каждом прогоне программы, выявление подобных ошибок требует дополнительного пла­ нирования и тестирования.

Ошибки часто происходят и в том случае, если сопровождающий программу программист неверно понял намерения разработчика. Это часто случается из-за некорректного выравнивания или некорректного использования фигурных скобок, разграничивающих составные операторы.

Пропущенная фигурная скобка — частая ошибка в управляющих структурах. Предположим, что операторы из листинга 4.3 записываются следующим образом:

i f

(eels

< ABSOLUTE.ZERO)

 

 

 

 

{

cout

«"\пЗначение " « e e l s

«

" недопуетимо\п"

/ /

блок

 

eout

«

"Оно меньше абсолютного

нуля\п"; }

else

 

 

 

 

/ /

нет скобки

{

cout

«

c e l s « " - допустимая

температура\п";

 

eout

«

"Вы можете продолжать вычисления\п";

 

 

Данная версия программы выглядит корректно и корректно компилируется. И выполняется. По край­ ней мере, если ввести 20, результат будет точно такой же, как на рис. 4.3. Но если ввести -300, вывод будет выглядеть так, как показано на рис. 4.4.

Наверное, понятно, что этот вывод некорректен. Причина в том, что выравнивание вправо видно лишь человеку, но не компилятору. Несмотря на то что оно показывает принадлежность обоих операторов cout к ветви else, компилятор воспринимает все по-другому. Без фигурных скобок он рассматривает второй оператор cout как следующий_оператор, не часть onepaTOpa_false. По его мнению, запись соответ­ ствует следующей:

Введите температуру в градусах Цельсия: -300

Вы ввели значение -300

Значение

-300 недопустимо

Оно

ниже

абсолютного

нуля

Вы

можете

продолжить

вычисления

Спасибо,

что воспользовались программой

Рис.

4 . 4 . Вывод

модифицированной

 

 

программы,

представленной

 

 

в листинге

4,3

i f

(eels

< ABSOLUTE_ZERO)

 

 

 

{

eout

«"\пЗначение " «eels

« " недопуетимо\п";

// блок

else

eout

«

"Оно меньше абсолютного нуля\п"; }

 

 

 

 

// нет скобки

 

 

eout

«

c e l s « " - допустимая температура\п";

eout

« "Вы можете продолжать вычиеления\п";

// следующий_оператор

К

счастью, аналогичная ошибка в ветви true оператора if дает синтаксическую

ошибку:

 

 

 

 

if (eels < ABSOLUTE_ZERO)

" недопустимо\п";

 

 

cout «"\пЗначение " «eels «

// это нонсенс

 

eout «

"Оно меньше абсолютного нуля\п";

102

Часть I ^ Введение в программирование на С^-н

else

{ cout « c e l s « " - допустимая температура\п"; / / блок cout « "Вы можете продолжать вычисления\п"; }

Здесь компилятор заметит пропуск ключевого слова else, поскольку видит он следующий код:

i f (eels

< ABSOLUTE_ZERO)

/ / оператор i f

без else

- нормально

cout «"\пЗначение " <<cels «

" недопустимо\п";

 

 

cout

« "Оно меньше абсолютного нуля\п";

/ /

это нонсенс

else

 

 

 

 

/ /

этот else

не имеет i f

{

cout

«

c e l s « " ~ допустимая температура\п";

/ /

блок

 

 

cout

«

"Вы можете продолжать вычисления\п";

}

 

 

Компилятор думает, что первый оператор cout относится к оператору if без else, а это вполне допустимо. Он полагает, что второй оператор cout — следующий_ оператор, что также нормально. Затем компилятор находит ключевое слово else

иотказывается это понимать.

Со в е т у е м Не забывайте постоянно контролировать фигурные скобки. Они — весьма распространенный источник ошибок.

Важно также обращать внимание на использование точки с запятой в конце оператора С-Ы-. Как уже говорилось выше, отсутствие точки с запятой после оператора С++ ведет к неприятностям. Начинаюш^их программистов часто пугает данное правило. Некоторых это правило так беспокоит, что они начинают ставить точку с запятой в конце каждой строки, нужна она или нет. Применение лишней точки с запятой в исходном коде ведет к созданию пустого оператора, который, по наблюдениям, в большинстве случаев никакого уш,ерба не приносит.

Однако лишняя точка с запятой не всегда так безобидна. Предположим, что директива #clefine из листинга 4.2 записана таким образом:

#clefine ABSOLUTE_ZERO -273;

/ / некорректное определение

Конечно, это некорректно. Здесь не должно быть точки с запятой (но она должна присутствовать после определения константы в листинге 4.3). Между тем компи­ лятор не сообш,ает, что данная строка содержит ошибку. Вместо этого он говорит о неправильной записи условных операторов. Причина срабатывания такой ди­ рективы #define — в подстановке литерала. Каждый раз, когда препроцессор находит идентификатор ABSOLUTE_ZERO, он подставляет его значение в исходный код. Его значение теперь равно "-273;", а не "-273". Для препроцессора это вполне законно, но компилятор получает от препроцессора следуюш,ий условный оператор:

i f (eels

< -273;)

/ / точка

с запятой в выражении - ошибка

cout «"\пЗначение " « e e l s «

" недопустимо\п"

«

"Оно меньше абсолютного нуля\п";

/ / один оператор

Точка с запятой в конце выражения превраш.ает его в оператор. Компилятор сообш,ает, что выражение cels<ABSOLUTE_ZERO содержит лишнюю точку с запятой. Но в листинге 4.2 можно видеть — данное выражение не содержит точку с запя­ той. Легко подумать, что причина в чем-то другом, и начать менять вокруг данной строки все, что внушает подозрения. И чем дальше, тем хуже. Расстояние между местом ошибки (директива #clefine) и ее проявлением (оператор условия) затруд­ няет анализ. Вот ещ,е одна причина в пользу применения ключевого слова const, а не директив #clefine.

Глава 4 • Управление ходом выполнения программы С^-нн

| 103

Иногда точку с запятой ошибочно размещают в конце строки в условном опе­ раторе. Предположим, что оператор из листинга 4.3 записан так:

i f

(eels

< ABSOLUTE_ZERO);

/ /

ветвь true

{

cout

«"\пЗначение " «eels «

" недопуетимо\п";

 

 

 

/ /

еледующий_оператор

 

cout

« "Оно меньше абсолютного нуля\п";

}

/ / блок

else

 

/ /

else

оказывается не в том месте

{cout « eels<<" - допустимая температура\п"; eout « "Вы можете продолжать вычиеления\п"; }

Это синтаксическая ошибка. Чаш.е всего, компилятор не способен указать невер­ ную строку. Вместо этого, он сообщит вам, что ключевое слово else находится не в том месте. Для компилятора дополнительная точка с запятой в конце условного выражения является допустимым предложением. Это пустой оператор, который не создает проблемы в С+4-. Я хочу, чтобы этот код напомнил о том, как компи­ лятор воспринимает этот оператор, соответствующейм комментарием:

i f

(eels

< ABSOLUTE_ZERO)

 

 

 

;

 

 

/ /

не несет функциональной нагрузки

{

cout

«"\пЗначение " «eels «

" недопуетимо\п";

 

 

 

/ /

еледующий_оператор

 

cout

« "Оно меньше абсолютного

нуля\п";

}

/ / блок

else

 

/ /

else

оказывается не в том месте

{eout « e e l s « " - допустимая температура\п"; eout « "Вы можете продолжать вычиеления\п"; }

Компилятор видит условный оператор без else, далее блок с двумя оператора­ ми и одинокое ключевое слово else. Эта строка с else и помечается как ошибоч­ ная (а вовсе не та, где действительно содержится ошибка). В случае ошибок такого рода не нужно тратить время, чтобы понять сообщения компилятора или пытаться реорганизовать структуру исходного кода.

Предположим теперь, что после логического выражения в программе, пред­ ставленной в листинге 4.2, стоит лишняя точка с запятой. Условный оператор из листинга 4.2 будет выглядеть так:

i f

(eels

< ABSOLUTE_ZERO);

/ /

эта точка е запятой определенно вредна

 

eout

«"\пЗначение

" « e e l s

« " недопуетимо\п"

 

 

eout

«

"Оно меньше абсолютного нуля\п";

 

Введите температуру в градусах Цельсия: 20

 

Такой условный оператор не содержит ключевого

слова else, однако отсутствие else здесь не проблема.

Вы ввели значение 20

 

 

 

И такой вариант будет компилироваться без ошибок.

Значение 20 недопустимо

 

 

 

Компилятор

не даст никакого

предупреждения,

 

 

 

и остается лишь уповать на тестирование и отладку.

Оно ниже абсолютного нуля

 

 

Если выполнить эту версию программы, введя значе­

Спасибо, что воспользовались

программой

 

 

 

 

ние 20, то результаты будут отличаться от рис. 4.2.

Рис. 4.5. Вывод модифицированной

Вывод модифицированной программы показан на

рис. 4.5.

 

 

программы

представленной

 

Ошибки такого рода психологически трудно выя-

в листинге

4.2

 

 

 

 

^JT

\:

 

 

 

 

вить и при отладке. Ьсли программа дает объемный

корректный вывод, то одна лишняя строка может легко ускользнуть от внимания программиста. Таким образом, точки с запятой требуют такой же аккуратности и постоянного контроля, что и фигурные скобки.

Применение конструкций управления ставит другой вопрос — тестирования программы. Проблема не нова, однако управляюидие операторы требуют допол­ нительного планирования, квалификации и, конечно, бдительности. При тести­ ровании последовательных программ обычно достаточно одного прогона. Если

I

104 I

Часть I ^ Введений

/|иие на Со­

программа корректна, то и результаты будут корректными — не надо тратить время, силы и деньги на дополнительное тестирование. Когда программа некор­ ректна, она дает некорректные результаты. Это будет очевидно с самого начала, если, конечно, программист невнимателен или не торопится заняться другими задачами.

Внимание все примеры предыдущей главы показывали последовательные программы с единственным маршрутом выполнения. Вот почему корректность программы демонстрирует только один экран.

#

Даже для последовательных программ однократного выполнения не всегда достаточно. Причина в том, что программа может случайно давать корректные результаты. Для иллюстрации рассмотрим пример преобразования значения температуры из градусов Цельсия в градусы Фаренгейта из листинга 3.7. Там использовался некорректный оператор:

 

 

 

 

fahr

= 9 / 5 * Celsius

+ 32;

/ /

точность?

 

 

 

 

 

 

При продумывании ввода тестовых данных важно подумать о простоте тех

 

 

 

 

вычислений, которые придется делать вручную. И это вполне разумно, так как

 

 

 

 

алгоритмы бывают настолько сложными, что ручные расчеты очень трудно сделать

 

 

 

 

корректно. В таком случае можно ожидать, что программист протестирует про­

Введите температуру

в градусах Цельсия: О

грамму из листинга 3.7, введя 0. Результат показан на

рис. 4.6. Как можно видеть, все корректно, так что один

Значение

по Фаренгейту: 32

 

набор исходных данных нельзя считать достаточным даже

 

 

 

 

 

 

 

для последовательных сегментов кода.

 

D..^

yf iC D

л

 

 

 

Вернемся

К примеру ИЗ листинга 4 . 1 . Достаточно ЛИ

гИС.

4-О. Вывод программы,

^

г

г . /

г.^^ /

 

 

представленной

 

выполнить эту программу, введя значение 20 (как на

 

 

в листинге

3.7

 

рис. 4.1)?. Очевидно, нет, так как в

программе есть

 

 

 

 

 

 

 

операторы, которые при таком прогоне не выполняются.

 

 

 

 

А что если эти операторы переводят деньги на счет программиста? Или запуска­

 

 

 

 

ют межконтинентальную ракету? Или приводят к аварийному завершению про­

 

 

 

 

граммы? Или просто дают некорректный вывод? Первый принцип тестирования

 

 

 

 

состоит в том, что тестовые данные должны приводить к выполнению каждого

 

 

 

 

оператора программы как минимум один раз (или более, если нужно защититься

 

 

 

 

от ошибок, скрытых за случайно корректными результатами). Следовательно, для

 

 

 

 

программы из листинга 4.1 потребуется

второй тестовый прогон (в дополнение

 

 

 

 

к показанному на рис. 4.1).

 

 

 

Введите температуру

в градусах Цельсия: -300

На рис. 4.7 показан второй тестовый прогон про­

Вы ввели значение -300

 

 

граммы из листинга 4.1. Как видно, результаты кор­

 

 

ректны, что подкрепляет уверенность в правильности

Значение

-300 недопустимо

 

программы. Вывод подтверждает, что обе ветви услов­

Оно

ниже

абсолютного

нуля

программой

ного оператора работают и дают верные результаты.

Спасибо,

что воспользовались

Достаточно ли такого тестирования? Вероятно,

 

 

 

 

 

 

 

нет. Если значение абсолютного нуля было набрано

 

4.7.

Второй

прогон

программы

некорректно (например, -263 вместо -273), то ре-

Рис.

зультаты обоих тестов все равно будут корректными.

 

 

из листинга

4.1

Таким образом, мы приходим ко второму принципу

 

 

 

 

 

 

 

тестирования. Набор тестовых данных должен вклю­

чать в себя граничные значения выражений в условиях. Это означает, что в каче­ стве исходных данных должно использоваться значение -273. Если абсолютный О был задан как -263, то программа некорректно покажет, что температура -273 недопустима. Другими словами, ввод значения -273 показывает ошибку, которая оказывается скрытой при вводе значения О (см. рис. 4.2).

 

Глава 4 • Управление ходом выполнения nporpatviivifoi C-f^^

|

105

 

Но и это еще не все. Что если абсолютный О был задан как -283, а не -273?

 

Ввод-273 такой ошибки не выявит. Условие -273 < -283 даст false, и программа

 

покажет (корректно), что температура корректна. Это ведет к третьему принципу

 

тестирования: границы выражений условий должны проверяться на true и false.

 

При целочисленных данных это означает использование в качестве тестового

 

ввода значения -274. В случае данных с плавающей точкой нужно выбрать какое-то

 

небольшое приращение, близкое к граничному значению, например, -273,001

 

(или что-то иное — в зависимости от контекста приложения).

 

 

 

В общем случае, если код содержит условие х < у, то его нужно тестировать

 

дважды: один раз на х = у (результат должен

быть равен false),

а другой на

 

X = у - 1 для целых или у минус небольшое значение для данных с плавающей

 

точкой (результат должен быть равен true).

 

 

 

 

Аналогично, если код содержит условие

х > у, то его нужно

тестировать

 

дважды: один раз на х = у (результат должен

быть равен false), а другой на

 

X = у + 1 для целых или у плюс небольшое значение для данных с плавающей

 

точкой (результат должен быть равен true).

 

 

 

 

К сожалению, и это еще не все. Данные

рекомендации не будут

работать

 

с условиями, включающими равенство. Если программа содержит условие х <= у,

 

то тестовый случай х = у даст true, а не false, как при х < у. Для проверки резуль­

 

тата false

программу нужно протестировать на х = у + 1 (или плюс малое значе­

 

ние). Если

же она содержит условие х >= у,

то тестовый случай

х = у должен

 

возвращать true, а не false; вторым тестовым случаем должна быть проверка

 

на X = у - 1 (или у минус малое значение).

 

 

 

 

Все это весьма осложняет дело. Каждое условие в программе должно тестиро­

 

ваться отдельно (два тестовых случая на каждое условие), а число тестовых случа­

 

ев может быть очень велико. У некоторых программистов просто не хватает

 

терпения на анализ, проработку, выполнение и проверку многочисленных тесто­

 

вых случаев. Они ограничиваются визуальной проверкой кода. Это неправильно.

 

Проверка кода — полезное, но ненадежное средство поиска ошибок.

 

 

Когда числа проверяются на равенство (или неравенство), ситуация становится

 

еще более сложной. В листинге 4.4 продемонстрирована программа, которая про­

 

сит пользователя ввести ненулевое целое и проверяет, равно ли данное число О

 

(для защиты от деления на 0). Если число ненулевое, то программа вычисляет

 

обратное значение и квадрат исходного числа. Если введен О, программа просит

 

пользователя следовать инструкциям. Конечно, пример довольно тривиальный, но

 

без лишних сложностей показывает, какие вопросы возникают в подобных случаях.

Листинг 4.4. Проверка значений на неравенство (некорректная версия)

 

 

#include

<iostream>

 

 

 

 

using namespace std;

 

 

 

 

int main

()

 

 

 

 

{

 

 

 

 

 

int num;

 

 

 

 

cout « "\пВведите ненулевое целое число: ";

 

 

 

cin »

num;

 

// должно быть (num != 0)

if (num > 0)

 

{ cout «"\пВы правильно выполнили инструкции";

 

 

 

cout «"\пОбратное значение равно " « 1.0/num;

 

 

 

cout «"\пКвадрат данного значения равен " « num * num; }

else

cout «"\пВы неследуете инструкциям";

cout «"\пСпасибо за использование данной программы" «endl; return 0;

I

106

I

Часть I # Введение в програмг^ирование на С+'*-

 

Введите ненулевое целое число: 20

Заметим, что если вместо 1.0/num записать 1/num, то

 

результат будет неверным, так как при целочисленном

 

Вы правильно

выполнили

инструкции

делении он усекается. Рис. 4.8 показывает, что программа

 

Обратное

значение равно

0.05

проходит тестирование при вводе значения 20 — она счи­

 

Квадрат данного значения равен 400

тает вывод корректным.

 

Спасибо,

что

воспользовались программой

Р и с . 4 . 8 . Вывод первого

теста

Введите ненулевое целое число: О

для листинга

4 А

Вы не следуете инструкциям

 

 

Спасибо, что воспользовались программой

Поскольку одного тестового варианта недостаточно,

 

 

 

программа проверяется с тестовыми данными, нарушаю­

Р и с . 4 . 9 . Вывод

вт,орого

т,еста

щими инструкции. Это позволяет убедиться в выполнении

ветви else оператора if. Как видно из рис. 4.9, программа

для лист^ипга

4.4

 

 

 

проходит и этот тест — она сообш,ает, что пользователь

 

 

 

не следует инструкциям.

 

Стоп, эта программа неверна! При ее вводе была сде­

Введите ненулевое целое число: -20

лана опечатка: вместо num ! = О

набрано num > 0. Кстати,

Вы

не следуете инструкциям

 

неправильный ввод операции отношения — очень распро­

программой

страненная ошибка. При написании программы с боль­

Спасибо, что

воспользовались

шим количеством числовых расчетов программисты иногда

 

 

 

 

забывают о корректной реализации и тестировании пове­

Р и с .

4 . 1 0 .

Вывод т^ретпьего т е с т а

дения программы с отрицательными числами. Для демон­

 

для лист,ипга

4.4

страции такой ошибки можно протестировать программу

в третий раз, введя отрицательное число (см. рис. 4.10). Как можно видеть, программа не принимает ввод и жалуется пользователю на

ошибку. Корректная версия программы показана в листинге 4.5.

Листинг 4.5. Проверка значений на неравенство (корректная версия)

#inclucle <iostream> using namespace std;

int main ()

int num;

 

 

cout « "\пВведите ненулевое целое число: ";

Gin »

num;

/ / теперь это корректно

if (num != 0)

{ cout «"\пВы правильно выполнили инструкции";

cout

«'ЛпОбратное

значение равно " « 1.0/num;

cout

«"\пКвадрат

данного значения равен " « num * num; }

else

 

 

 

cout «"\пВы не следуете инструкциям";

cout «"\пСпасибо за использование данной программы" « e n d l ;

return

0;

 

 

Это подводит еш.е к одному принципу тестирования. Для выражений условия с операциями равенства нужно использовать три теста: для равенства (должно возвращаться true) и для неравенства с каждой стороны (данные тесты должны давать false). То же самое относится к выражениям условия с операцией нера­ венства: проверка на равенства должна давать false, а проверка на неравенст­ ва — true.

Глава 4 • Управление XOAOIV! выполнения программы C-f-^

107

С о в е т у е м Для операторов i f с операциями отношения используйте близкие к граничным тестовые значения. Применение значений, сильно отличающихся от граничных, может привести к пропуску ошибки.

 

 

 

 

Таблица 4.1

Перечисленные принципы сведены в таблицу 4.1.

Тестовые

варианты

 

Условие здесь представлено как х операция у, где

 

 

 

 

операцией может быть ' <', ' >', ' <=', ' >=', ' ==', '! ='.

для простых выражений

условия

Для каждой операции в этой таблице приведены

Выражение

Тест

 

Результат

 

тестовые варианты и ожидаемое значение выражения

X < у

 

X равно у

 

False

в условии. Предполагается, что х и у — целые. Для

 

 

X равно у -

1

True

чисел с плаваюш.ей точкой вместо 1 потребуется

 

 

небольшое прираш^ение. Для равенства или неравен­

X > =

у

X равно у

 

True

ства с нечисловыми данными достаточно двух прове­

 

 

X равно у -

1

False

рок, а не трех.

 

 

Как видно, тестовые варианты для х < у и для

X > у

 

X равно у

 

False

 

 

X >= у одинаковы, но результаты противоположны.

 

 

 

 

 

 

 

X равно у -Н 1

True

Тестовые варианты для х > у и х <= у тоже одинаковы,

X < =

у

X равно у

 

True

но результаты дают обратные. Аналогично тестовые

 

 

X равно у -f- 1

False

варианты для х == у и х ! = у одни и те же с обратными

 

 

результатами. Это значит, что условия х < у и х >= у

 

 

X равно у

 

True

X = = у

 

являются отрицанием друг друга. Все условия, где х < у,

 

 

X равно у + 1

False

можно переписать как ! (х > у) и наоборот, а условие,

 

 

X равно у -

1

False

в котором используется х >= у, переписывается как

 

 

! (х < у). Там, где одно условие дает true, второе —

 

 

 

 

 

X != у

 

X равно у

 

False

false и наоборот.

 

 

X равно у -Ь 1

True

Условия X > у и X <= у также являются отрицанием

 

 

X равно у -

1

True

друг друга, как и х == у и х ! = у. Там, где одно условие

в каждой паре принимает значение true, второе будет равно false.

Для приобретения навыков программирования важно хорошо ориентироваться

вотрицании логических условий. Для условного оператора это означает правиль­ ное форматирование кода. Например, если ветвь true содержит много сложных операторов, а ветвь false — только два или три, то ветвь false может потеряться

впрограмме. Некоторые программисты предпочитают помепдать более короткую последовательность операторов в ветвь true оператора условия. Например, условный оператор в листинге 4.5 можно записать следуюш,им образом:

i f (num == 0)

/ / отрицание (num != 0)

cout «"\пВы не следуете инструкциям";

 

else

 

{cout «"\пВы правильно выполнили инструкции"; cout «'ЛпОбратное значение равно " « 1,0/num;

cout «"\пКвадрат данного значения равен " « num * num; }

Как уже отмечалось, важно не ошибиться и не записать оператор ' ==' как ' -\ Это распространенный источник трудноуловимых ошибок. Например, данный условный оператор можно легко записать как

i f (num = 0)

/ / допустимо в C++

cout «"\пВы не следуете инструкциям";

 

else

 

{cout «"\пВы правильно выполнили инструкции"; cout «'ЛпОбратное значение равно " « 1.0/num;

cout «"\пКвадрат данного значения равен " « num * num; }

108

Часть I« Введение в програтмтрошаитв на 0+4»

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

i f (О == num) / / константа не может быть 1-значением cout «"\пВы не следуете инструкциям";

else

{cout «"\пВы правильно выполнили инструкции"; cout «"\пОбратное значение равно " « 1.0/num;

cout «"\пКвадрат данного значения равен " « num * num; }

Если ошибочно записать О = num, то компилятор помечает это как ошибку, по­ скольку в C-h-h литеральные значения не имеют адреса, с которым может манипу­ лировать программа (это г-значение), хотя и хранятся в памяти, как все прочие объекты. В языках программирования можно выделить обш,ую тенденцию: выво­ дить как можно больше ошибок из категории ошибок этапа выполнения в катего­ рию ошибок этапа компиляции. Помнится, при работе с языком Фортран на PDP-11 я как-то ошибочно установил значение константы 1 в 2, так что каждый раз, когда в программе встречалась единица, компилятор использовал значение 2. Все циклы просто сходили с ума, а я не понимал, что происходит.

Еиде одной распространенной техникой записи логических условий является использование того факта, что в С+4- любое значение дает true, а нулевое — false. Например, многие программисты могли бы записать условный оператор из листинга 4.5 следуюш,им образом:

i f

(num)

/ /

популярная идиома в C++,

то же, что и i f (num != 0)

{

cout

«"\пВы правильно выполнили инструкции";

 

cout

«"\пОбратное

значение равно " « 1.0/num;

 

cout

«"\пКвадрат

данного значения равен " «

num * num; }

else

cout «"\пВы не следуете инструкциям";

С такими идиомами С4-4- нужно освоиться. Они очень популярны. Если исполь­ зуется обратное логическое условие (т. е. num == 0), то многие программисты запишут данный условный оператор так:

i f (!num) / / популярная идиома в C++, то же, что и i f (num == 0) cout «"\пВы не следуете инструкциям";

else

{cout «"\пВы правильно выполнили инструкции"; cout «'ЛпОбратное значение равно " << 1.0/num;

cout «"\пКвадрат данного значения равен " « num * num; }

Обратите внимание, что запись if ! (num) ... некорректна: логическое условие должно заключаться в круглые скобки. Данным средством легко злоупотребить и написать программу, которую будет очень трудно понять.

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

i f (age > 16 && age < 65) precessOrderO;

Глава 4 « Управление ходом выполнения прогроллг^ы С-^-^-

109

else

cout « "Заказчик не привилегированный\п";

Ветвь true в данном условии можно протестировать только одним путем: установив age > 16 и age < 65 в true. Ветвь false тестируется двумя способами: установкой age < 65 в false (например, когда age = 65) или установкой age > 16 в false (когда age = 15). Что выбрать? Если только первый способ, то нельзя будет выявить ошибку, когда второе условие некорректно устанавливается в true, на­ пример при age > 0. Если же выбрать только второй способ, то не обнаружится ошибка некорректной установки в t rue второго условия, например когда age < 250. Вот почему необходимо использовать оба способа прохода по ветви false условного оператора. Это намного сложнее, чем тестирование простого условного оператора, но метод вполне естественный. Продумывая тестовые варианты для установки отдельных условий в true или false, можно руководствоваться таблицей 4.1.

Заметим, что мы не тестировали третий способ прохода по ветви false, когда и age > 16, и age < 65 имеют значение false. Некоторые программисты оправдыва­ ют пропуск такой комбинации, поскольку условия соотносятся друг с другом: их истинное значение зависит от одной и той же переменной age. Содержимое данной переменной влияет на то, равно ли условие true (середина диапазона значений) или false (нижний и верхний диапазоны age). Значение не может одновременно принадлежать к верхнему и нижнему диапазону. Однако для операции И мы тес­ тируем false для этих условий по отдельности. Тестировать их вместе— только попусту тратить время и деньги.

Аналогичные соображения можно применить и к тестированию составных условий ИЛИ. Рассмотрим следуюидий пример, в котором сравниваются два зна­ чения с плавающей точкой:

 

i f

(amtl

< amt2 - 0.01 11 amtl > amt2 + 0.01)

/ /

разница больше 1 цента?

 

 

cout

«

"Разные суммы\п";

 

 

 

 

 

 

else

 

 

 

 

 

 

 

 

 

cout

«

"Одинаковые суммы\п";

 

 

 

 

 

 

 

Ветвь false

этого оператора можно протестировать только одним способом:

 

установив оба условия в false. Ветвь true тестируется двумя способами: установ­

 

кой первого условия в true или второго условия в true. Что выбрать? Ответ

 

тот же, что и в случае с операцией И: придется тестировать оба. Лишь это даст

 

гарантию адекватной проверки обоих условий в соответствии с рекомендациями

 

таблицы 4.1.

 

 

 

 

 

 

 

 

Хорошая новость в том, что не нужно составлять тестовые варианты для

 

третьего способа прохода ветви true, когда значение true имеют оба условия.

 

Эти условия связаны (оба они зависят от значений переменных amtl и amt2) и не

 

 

 

 

 

 

могут

одновременно принимать

значение

Тестовые варианты

 

 

 

Таблица 4.2

true. Такое тестирование будет избыточ-

для

составных условий

ным, даже если условия не соотносятся.

 

Первое

 

Второе

 

В таблице 4.2 показано, какие тестовые

Операция

 

Результат

варианты следует включать в проверку со-

условие

 

условие

ставных условии.

 

 

!___

1

 

1

 

1

 

 

И

True

 

True

Truee

Как уже отмечалось, ес^ш условия в со­

 

True

 

False

False

ставных операторах соотносятся друг с дру­

 

 

гом, это не особенно влияет на стратегию

 

False

 

True

False

 

 

тестирования. Рассмотрим, например, следу-

,,^,,

^

 

 

.

True

юищ1л оператор с зависимыми условиями.

ИЛИ

True

 

False

о

i

 

n ^

^л ^ / \

True

 

False

True

 

 

False

 

True

True

Здесь

функции

processPreferreclOrcler()

 

False

 

False

False

и processNormalOrderO определяются где-то

 

 

 

 

 

 

в другом месте программы и вызываются

 

 

 

 

 

 

в различных

ветвях условного оператора.

Соседние файлы в предмете Программирование на C++