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

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

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

110

Часть I • Введение в програ1\<1^ирование но 0+4»

Заказчик становится привилегированным, если сумма предыдущей сделки пре­ вышает $1500, а текущая цена покупки достигла $200.

i f (amount > 200 && previous_total > 1500) processPreferredOrderO;

else processNormalOrder();

Для проверки данного кода потребуются три тестовых варианта. Один должен предусматривать проход ветви true, когда amount > 200 и previous_total > 1500 равны true (например, amount = 200.01 и previous_total = 1500.01). Два тестовых варианта необходимы для прохода ветви false. В одном тесте должно устанав­ ливаться в true условие amount > 200, в false :— условие previous_total > 1500 (например, amount > 200.00 и previous_total > 1500.01). Другой тест должен уста­ навливать в false условие amount > 200, в true — условие previous_total > 1500 (например, amount > 200.01 и previous_total > 1500.00). Согласно таблице4.1, каждый тест должен предусматривать проверку граничных условий. Так как они независимы, можно установить в false и amount > 200, и previous_total < 1500 (amount = 200.00, previous_total > 1500.00). Однако такая проверка не позволит выявить все ошибки и не даст уверенности в корректности программы.

Аналогично операции И, операцию ИЛИ с независимыми условиями нужно проверять на трех тестовых случаях: когда первое условие равно true, а второе — false, когда первое равно false, а второе — true и когда оба равны false. Рас­ смотрим пример, где displayRelaxationPackageO и displayActivePackageO— функции, определенные в другом месте программы:

i f (age > 65 | | previous_history ==1) displayRelaxationPackage();

else displayActivePackageO;

Тестовый вариант для данного фрагмента программы должен учитывать три ситуации:

age > 65

есть true, а previous_history == 1 дает

false

age > 65

есть false, а previous_history == 1 дает

true

и age > 65, и previous_history == 1 равны false

 

В первых двух вариантах проверяется ветвь true условного оператора, а послед­ ний тестовый вариант покрывает ветвь false. Поскольку условия в логической операции независимы, их можно одновременно установить в true, однако нет необходимости тестировать случай, когда и age > 65, и previous_history == 1 равны true, поскольку эта проверка не выявит ошибок, которые не могут пока­ зать три предыдущих теста.

С о в е т у е м для операции && нужно тестировать три случая:

первое условие равно false, второе равно false и оба равны true.

Для операции | | надо проверять следующие три случая:

первое условие равно true, второе равно true, оба равны false.

Вложенные условные операторы и их оптимизация

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

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

111

понять намерения разработчика. Если нужно включить в ветвь несколько опера­ торов, то используется составной оператор в фигурных скобках. Единственный "подводный камень" в применении вложенных условных операторов состоит в соответствии if и else. Каждый else должен соответствовать ближайшему if.

i f

(условие)

 

 

 

 

 

i f

(условие1)

 

 

 

 

 

onepaTop_true1;

 

 

 

 

else

/ /

относится

к i f

с условием1

 

onepaTOp_false1;

 

 

 

 

else

/ /

относится

к

i f

с условием

i f

(условие2)

 

 

 

 

 

оператор_

true2;

 

 

 

 

else

/ /

относится

к

i f

с условием2

 

onepaTop_false2;

 

 

 

 

Это несложный пример: здесь каждый условный оператор представляет собой полный оператор с ветвями true и false. Ситуация может осложниться, если один из операторов true или false пропунден, что обычно происходит, когда програм­ мист видит подобие условий в операторах и пытается оптимизировать исходный код, т. е. сделать его более понятным и выразительным.

Рассмотрим фрагмент системы обработки почтовых заказов, в которой опреде­ ляется сумма заказа и статус покупателя. Если сумма превышает сумму мелкого заказа (пусть это $20), то плата за обслуживание не взимается. Кроме того, при­ вилегированные заказчики получают скидку (10%) и на экран выводится сэконом­ ленная заказчиком сумма. Для мелких заказов скидка не предусматривается. Обычным (непривилегированным) заказчикам обслуживание обходится в $2 за заказ. Как видно, описание процесса довольно длинное. Часто это бывает из-за того, что оно составляется человеком, а человеческий язык не всегда точен и кра­ ток. Если подумать, то некоторая избыточность описания на самом деле полезна, поскольку снижает вероятность непонимания, когда программист пытается интер­ претировать и осмыслить текст.

Листинг 4.6. Вложенные операторы условия

#inclucle <iostream> using namespace std;

int main ()

{

const double DISCOUNT = 0.1, SMALL_ORDER =20; const double SERVICE_CHARGE = 2.0;

double orderAmt, totalAmt; int preferred;

cout «

"\пВведите, пожалуйста, сумму заказа: ";

cin »

orderAmt;

cout »

"Введите 1, если заказчик привилегированный, и О в противном случае: ";

cin = preferred;

if (orderAmt > SMALL_ORDER) if (preferred ==1)

{ cout «"Полагается скидка " «orderAmt*DISCOUNT«endl; totalAmt = orderAmt * (1 - DISCOUNT); }

else

totalAmt = orderAmt;

else

if (preferred == 0)

totalAmt = orderAmt + SERVICE_CHARGE;

i(_JM2_

Часть I ^ Введеи11е в про

else

totalAmt - orderAmt;

cout < "Общая сумма: " « totalAmt « return 0;

Введите, пожалуйста, сумму заказа: 20 Введите 1, если заказчик привилегированный, и О в противном случае: 1 Общая сумма: 20

Рис. 4 . 11 . Вывод для листинга 4.6 (мелкий заказ, привилегированный покупатель)

endl;

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

Представленная в данном листинге реализация весьма хорошо соответствует требованиям, но избыточность многими программистами воспринимается с неудо­ вольствием. Связанное тестирование разных ветвей (preferred == 1 и preferred == 0) наводит на мысль об оптимизации. Обработка в разных ветвях выполняется аналогичная (totalAmt = orderAmt), что еще более убеж­ дает в возможности оптимизировать код. Один из способов оптимизации состоит в том, чтобы начать его с присваивания totalAmt = orderAmt, а затем про-

Введите, пожалуйста, сумму заказа: 20.01 Введите 1, если заказчик привилегированный, и О в противном случае: 1 Полагается скидка 2.001 Общая сумма: 18.009

Рис. 4 . 12 . Вывод для листинга 4.6 (крупный заказ, привилегированный покупатель)

Введите, пожалуйста,

сумму заказа: 20

верить, требуется ли модификация в связи со скидкой

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

Введите 1, если заказчик привилегированный,

ков или платой за обслуживание для мелких заказов

и О в противном случае: О

 

обычных покупателей.

Общая сумма: 22

 

 

Рис. ^4.1 3 . Вывод для

листинга 4.

Введите, пожалуйста, сумму заказа: 20.01

Введите 1, если заказчик привилегированный,

(мелкий

заказ,

и О в противном случае: О

обычный

покупатель)

Общая сумма: 20.01

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

Рис. 4 . 14. Вывод для

лист^инга 4.6

else. Первое решение в листинге 4.6 можно описать

(крупный

заказ,

обычный

покупатель)

следующим псевдокодом:

 

 

if (нeкoтopoe_ycлoвиe_coдepжит_true) обработка_первого_варианта;

else обработка_второго_варианта;

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

обработка_второго_варианта; i f (нeкoтopoe_ycлoвиe_true)

обработка_первого_варианта;

Данная оптимизированная реализация показана в листинге 4.7. Результат пер­ вых двух тестовых вариантов будет в точности таким же, как на рис. 4.11 и 4.12,

 

 

 

Глава 4 * Управление ко^ом выполнения nporpai^i^bi С^-Ф

113

Введите,

пожалуйста,

сумму заказа: 20

Введите,

пожалуйста, сумму заказа: 20.01

Введите

1, если

заказчик

привилегированный,

Введите

1. если

заказчик

привилегированный,

и О в противном

случае: О

и О в противном

случае: О

 

 

Общая сумма: 20

 

 

 

Общая сумма: 22.01

 

 

Рис. 4 - 15 . Вывод

для

листинга 4.7

Рис. 4 . 1 6 . Вывод для

листинга

4.7

 

(мелкий

заказ,

 

(крупный

заказ,

 

 

обычный

покупатпель)

 

обычный

покупатель)

однако тесты на обычных покупателей дают результаты, представленные на рис. 4.15 и 4.16. Они отличаются от результатов, показанных на рис. 4.13 и 4.14. Почему?

Листинг 4.7. Оптимизированный вложенный условный оператор

#inclucle <iostream> using namespace std;

int main ()

{

const double DISCOUNT =0.1. SMALL_ORDER = 20; const double SERVICE_CHARGE = 2.0;

double orderAmt, totalAmt; int preferred;

cout «

"\пВведите, пожалуйста, сумму заказа: ";

cin »

orderAmt;

 

cout »

"Введите 1, если заказчик привилегированный, и О в противном случае: ";

cin = preferred;

// второй вариант

totalAmt = orderAmt;

if (orderAmt > SMALL_ORDER)

// изменить totalAmt, если не мелкий заказ

if (preferred == 1)

 

{

cout «"Полагается скидка " «orderAmt*DISCOUNT«endl;

else

totalAmt = orderAmt * (1 -DISCOUNT); }

 

// это оптический обман

if (preferred == 0)

// для мелкого заказа проверить статус покупателя

totalAmt = orderAmt + SERVICE_CHARGE;

cout < "Общая сумма: " « totalAmt «

endl

return 0;

 

Данная реализация демонстрирует нам "оптический обман". Отступы должны показывать сопровождающему систему программисту (и тестировщику) намере­ ния разработчика, однако это отличается от понимания программного кода компи­ лятором. Согласно правилу соответствия ключевых слов if и else, компилятор интерпретирует условный оператор так:

totalAmt = orderAmt;

// второй

вариант

if (orderAmt > SMALL_ORDER)

// изменить totalAmt, если немелкий заказ

if (preferred == 1)

 

 

{ cout «"Полагается скидка ' «orderAmt*DISCOUNT«endl;

totalAmt = orderAmt * (1

-DISCOUNT); }

else

 

 

if (preferred == 0)

 

// необрабатываются для

totalAmt = orderAmt + SERVICE_CHARGE

 

 

// мелких заказов

1^114

Часть i ^ Вееден1^е в прогрс

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

В данном случае нетрудно добиться обш,ей точки зрения. Достаточно лишь поместить в условном операторе фигурные скобки. Кроме того, составной услов­ ный оператор не должен содержать несколько операторов — допускается лишь один. Составным он называется не потому, что состоит из нескольких операторов, а потому, что содержит блок в фигурных скобках. Условный оператор из листин­ га 4.7 должен выглядеть так:

totalAmt = orderAmt;

/ /

второй вариант

i f

(orderAmt > SMALL.ORDER)

/ /

изменить totalAmt, если не мелкий заказ

{

i f (preferred == 1)

 

 

{cout «"Полагается скидка «orderAmt*DISCOUNT«encll;

totalAmt = orderAmt * (1 DISCOUNT); } }

else

i f (preferred == 0) / / для мелкого заказа проверить статус покупателя { totalAmt = orderAmt + SERVICE_CHARGE; }

Многие программисты находят такой стиль эффективным и каждый раз исполь­ зуют в условном операторе (или любой управляюш^ей конструкции) фигурные скобки. Это помогает избежать другой распространенной проблемы. Часто все начинается с одного оператора в ветви условия, а потому фигурные скобки не используются. Затем при правке и изменении программы добавляется еще один оператор (или несколько). При этом забывают поставить фигурные скобки, осо­ бенно если изменения вносятся сопровождаюш.им ПО программистом. Заключе­ ние каждой ветви условного оператора в фигурные скобки снижает вероятность подобных ошибок. Программисту не нужно будет думать об этом при внесении изменений — очень важное преимуш,ество. Так что канонический вид условного оператора следующий:

i f

(выражение)

 

 

 

{

onepaTop_true;

}

/ /

для будущего расширения

else

 

 

 

{

onepaTop_false;

}

/ /

для будущего расширения

Другой хороший пример вложенных условных операторов и их оптимизации — проблема високосного года. Обычно високосный год без остатка делится на 4. Вот где полезно использовать оператор получения остатка целочисленного деле­ ния. В подобной реализации можно написать:

i f (уеаг%4

!= 0)

/ / если год не делится

на 4, то он не високосный

{ cout «"Год " «year « "

не високосный"

« e n d l ;

}

else

 

 

 

 

{ cout «

"Год " «year «

" високосный"

« e n d l ; }

 

Для такого простого алгоритма все удивительно точно. Алгоритм накапливает ошибку в 1 день примерно каждые 130 лет. Вот почему, когда данный алгоритм был заменен на еще более точный, используемый уже около 1700 лет, коррекция календаря составила всего 14 дней.

Таким образом, более точное правило состоит в том, что если год делится на 4, то он високосный, а если делится на 100, то нет. Учитывая это, наша программа может выглядеть так:

i f (year

% 4 != 0)

/ / если год не делится на 4, то он не високосный

{ cout

«"Год " «year « "

не високосный" «endl; }

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

115

else

 

/ /

 

если он делится на 4 -

он високосный

 

i f (year

% 100 == 0)

/ /

если он не делится на 100

 

{ cout «"Год " «year

« "

не високосный" « e n d l ;

}

 

else

 

 

 

 

 

 

{ cout

« "Год " «year

«

"

високосный" « e n d l ; }

 

 

Все это правда, но не вся правда. При таком правиле теряется один день на сотню лет, что не очень много. Более корректное правило состоит в том, что когда год делится на 100, он не високосный, если только не делится на 400. Тогда он снова високосный. В таких случаях часто используется операция И (&&) или вложенное условие. Решение проблемы показано в листинге 4.8. Если год не делится на 4, он не високосный. И все. Если делится на 4 и делится на 100, то он не високосный, если одновременно не делится на 400 — тогда год високосный. Если год делится на 4 и не делится на 100, то он високосный. Системный ана­ лиз — штука непростая. Представьте, что занимаетесь этим ежедневно.

Листинг 4.8. Решение проблемы високосного года

#include <iostreafTi> using namespace std;

int

main

()

 

 

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

 

 

int

year;

 

 

 

 

 

 

 

 

 

 

cout

«

"Введите

год:

";

 

 

 

 

 

cin

»

year;

 

 

 

 

 

 

 

 

 

i f

(year

% 4

!= 0)

 

 

 

 

/ / н е делится на 4

 

cout

«*Тод

" «year

« "

не високосный" « e n d l ;

}

 

else

 

 

 

 

 

 

 

 

 

 

 

 

 

i f

(year % 100 == 0)

 

 

 

 

 

 

 

 

i f

(year % 400 == 0)

 

 

/ /

делится на 400 (следовательно, на 100)

 

 

 

 

cout

«

"Год " «year

«

" високосный"

« e n d l ;

 

 

 

else

 

 

 

 

 

 

 

/ /

делится на 4 и на 100, но не на 400

 

 

 

 

cout

«"Год

" «year

« "

не високосный"

« e n d l ;

 

else

 

 

 

 

 

 

 

 

 

/ /

делится на 4, но не на 400

 

 

cout

«

"Год

" «year

« "

високосный" « e n d l ;

 

return

0;

 

 

 

 

 

 

 

 

 

 

Здесь три выражения условия, так что сценарий "худшего случая" должен предусматривать шесть проверок. Однако выражения связаны, и выполнять нуж­ но только четыре ветви, поэтому можно обойтись четырьмя следуюш.ими тестовы­ ми вариантами:

год % 4 != О есть true (например, 1999)

год % 4 != О есть false (т. е. год % 4 == О есть true),

год % 100 === О есть true и год % 400 = = 0 тоже дает true (например, 2000)

год % 4 == О и год % 100 == О равны true, но год % 400 == О (например, 1900)

год % 4 === О есть true, но год % 100 == О равно false (например, 2004)

116

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

 

 

 

 

 

Введите

год: 2000

На рис. 4.17 показаны результаты выполнения этого кода для 2000 г.

В данной программе есть ряд проблем, которые относятся не к ее кор­

Год 2000

високосный

ректности, а к эстетике. Здесь три уровня вложенности, и условия явно

 

 

Рис. 4.17

можно объединить. Тестовый случай, когда year

% 4 == О равно true,

содержит две ветви для високосного года, и их также можно скомбини­

Вывод для листинга 4.8

ровать. Как это сделать? Во-первых, поэкспериментируем с отрицанием

(год делится на 4, 100

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

и 400)

 

 

i f (year

% 4 \-

0)

 

 

 

 

/ /

не делится на 4

 

 

cout «"Год "

«year <<" не високосный"

« e n d l ;

 

 

 

 

 

else

 

 

 

 

 

 

 

 

 

 

 

 

 

i f (year % 100 == 0)

 

 

 

 

 

 

 

 

 

i f (year % 400

!= 0)

 

 

 

/ /

делится

на 100,

но не на 400

 

cout

«

"Год " «year

«

" не високосный"

« e n d l ;

 

 

 

 

else

 

 

 

 

 

 

/ /

делится

на 4,

на 100, на 400

 

cout

«"Год

" «year

« "

високосный" <<endl;

 

 

 

 

else

 

 

 

 

 

 

 

/ /

делится

на 4,

но не на 100

 

cout «

"Год "

«year

«

"

високосный" « e n d l ;

 

 

 

 

Теперь можно объединить два следующих друг за другом условия с помощью

 

операции И. Последние две ветви для високосного года тоже скомбинируются.

 

В результате получится более разумное решение:

 

 

 

 

 

i f (year

% 4

!= 0)

 

 

 

 

/ / н е делится на 4,

точка

 

cout «"Год " «year « "

не високосный"

« e n d l ;

 

 

 

 

 

else

 

 

 

 

 

 

 

 

 

 

 

 

 

i f (year % 100 == О && year

% 400 != 0)

/ /

делится на 100,

но не на 400

 

cout

«

"Год "

«year

«

"

не високосный" « e n d l ;

 

 

 

 

else

 

 

 

 

 

 

 

/ /

делится

на 4,

но не на 100

 

cout

«"Год

" «year

« "

високосный"

« e n d l ;

 

 

 

 

Красиво, не правда ли? Здесь только два уровня вложенности, что вполне прием­ лемо. Между тем часть обработки для невисокосного года повторяется, а для программиста, применяющего С4-+, это не очень хорошо. Год не високосный, когда он не делится на 4 или когда условие if (year % 100 == О && year % 400 ! = 0) равно true. Похоже, нужно использовать операцию ИЛИ, не так ли? В листин­ ге 4.9 показан не только корректный и эффективный, но элегантный и лаконич­ ный ответ.

Листинг 4.9. Решение проблемы високосного года

#include

<iostream>

using namespace std;

int main

()

int

year;

cout

«

"Введите год: ";

cin

»

year;

if (year % 4 ! = 0 | | year % 100 == 0 && year % 400 ! = 0) cout «"Год " «year « " не високосный" «endl;

else

cout «"Год " «year « " високосный" «endl; return 0;

Глава 4 • Управление кодом выполнения riporpai^f^foi С^-*-

| 117

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

Несомненно, программа в листинге 4.9 лучше, чем в листинге 4.8, но вот стоит ли тратить время на удаление 6 строк исходного кода (и проверку корректности полученного результата) — вопрос спорный.

Иногда уходит несколько часов на то, чтобы оптимизировать сложные услов­ ные операторы. Нередко результатами можно гордиться, но стоит ли овчинка выделки, оправдано ли это экономически, ясно не всегда, так что смотрите сами.

Итерация

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

Для повторяюш^ихся действий C-f + предлагает три оператора итерации: циклы while, циклы do-while и циклы for. Каждый вид циклов в С4-+ управляет по­ вторным выполнением одного оператора (заканчиваюидегося точкой с запятой) или составного оператора (блока операторов), заключенного в фигурные скобки (после закрываюш,ей фигурной скобки точка с запятой не ставится). Для управ­ ления итерациями во всех типах циклов используются логические выражения, аналогичные выражениям в операторах условия. При вычислении этих логических выражений получается true или false (не ноль или ноль). Они проверяются при каждом повторении цикла. Когда условие цикла становится равным false, итера­ ция завершается. Если условие равно true, то повторяется тело цикла (оператор или блок). Поскольку в разных типах циклов проверка условия выполняется по-разному, нельзя говорить о проверке условия "перед каждой итерацией". Лучше говорить: "для каждого повторения цикла". Независимо от конструкции цикла в теле цикла делается что-то, влияюш,ее на его условие. Если этого не происходит, то условие может всегда принимать значение true — серьезная угро­ за дая любой программы с циклами.

Вцикле while условие проверяется перед каждой итерацией и итерации прекраш.аются, когда оно принимает значение false. Выражение цикла проверяется

иперед первой итерацией, следовательно, возможна ситуация, когда итерации повторятся О раз (условие цикла равно false при первом вхождении).

Вцикле do-while условие проверяется после каждой итерации и цикл прекра­ щается, когда оно становится равным false. Поскольку в первый раз условие про­ веряется только после первой итерации, а не перед ней, цикл всегда выполняется хотя бы один раз.

Цикл for предназначен для выполнения заранее заданного числа итераций. Обычно один и тот же алгоритм можно реализовать с помоидью цикла любого

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

Применение цикла while

Цикл while выполняется как один оператор. Разница между другими операто­ рами и циклом while в том, что тело цикла может выполняться повторно в зависи­ мости от значения логического условия цикла.

Цикл while имеет следующую логическую структуру:

предыдущий_оператор;

// выражение цикла

while (выражение)

118

Часть I * Введение в протрамтирошаиив на 0+*^

 

оператор;

/ / тело цикла

 

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

 

Данная управляющая структура повторяет тело Щ4кла, пока его логическое условие (выражение) принимает значение true. В конечном счете (или даже перед первым проходом) условие становится равным false. Когда это происходит, опе­ ратор цикла пропускается и выполняется следующий оператор. Если цикл управ­ ляет выполнением нескольких операторов, используются ограничители блока (фигурные скобки):

while (выражение)

/ /

обратите внимание на отступ

{ оператор;

оператор; }

/ /

конец тела цикла

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

Обычно при проектировании циклов решается некая задача, которую можно условно назвать обработкой текушлх данных — элементы данных обрабатываются повторно. Это означает, что для каждого элемента данных необходимы инициали­ зация, вычисление, обработка (печать, использование в вычислениях, сохранение или что-то иное, в зависимости от алгоритма). Затем его нужно изменить для следующей итерации, после чего вычислить и обработать снова. И так далее, пока не будет обработан последний элемент. Шаги обработки можно свернуть в формат цикла while, используя следуюш^1Й образец:

инициализация_текущих_данных

 

 

while (вычисление_текущих_данных)

/ /

точка решения

{ обработка_текущих_данных;

/ /

основная цель данного кода

изменение_текущих_данных; }

/ /

не забывайте про этот шаг!

Рассмотрим пример обработки транзакций. Для простоты предположим, что в программе вводятся и складываются 5 величин (ниже будет более реалистичный пример). Поскольку общее чис/ю транзакций известно, можно ввести переменную для подсчета уже обработанных транзакций. Это часть текущих данных. Ее нужно инициализировать значением О и увеличивать на 1 после каждой транзакции. Другим элементом текущих данных будет сумма введенных значений. Ее также потребуется инициализировать О и суммировать на каждой итерации. Но общую сумму нельзя использовать для проверки завершения цикла. А вот счетчик тран­ закций можно. Следовательно, компонентами цикла будут:

Инициализация_текущих_данных: счетчик устанавливается в 1, общая сумма — в О

Вычисление_текущих_данных: проверка, превышает ли значение счетчика транзакций 5

Обработка_текущих_данных: ввод следующего значения транзакции

идобавление его к сумме

Изменение_текущих_данных: увеличение счетчика транзакций на 1

ипереход к его проверке

Реализация представлена в листинге 4.10. Такая конструкция весьма непрактич­ на. Маловероятно, чтобы приложение писалось для конкретного набора данных.

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

119

Листинг 4.10. Цикл while с бесконечным числом итераций

#include

<iostream>

 

 

using namespace std;

 

 

int main

()

 

 

 

{

 

 

 

 

double

t o t a l ,

amount; int

count;

 

total

= 0.0;

count = 1 ;

/ /

инициализация текущих данных

while

(count

<= 5)

/ /

оценка текущих данных

{cout « "Введите количество: ";

 

cin »

amount;

/ /

ввод текущих данных

 

total

+= amount; }

/ /

обработка текущих данных

cout «

"\пСумма по 5 транзакциям равна

"<<total « endl;

return

0;

 

 

 

Это типичный пример ошибки программирования. Цикл выполняется, пока значение count не превысит 5. Когда count станет равно 6, цикл должен завер­ шиться. Проблема в том, что count никогда не станет равно 6 (или другому значению), так как в теле цикла count не изменяется. Для исправления ситуации нужно на каждой итерации в теле цикла увеличивать count на 1. Можно делать это, например, в начале цикла:

while (count

<= 5)

/ /

вычисление текущих данных

{ cout

«

"Введите

количество: ";

cin »

amount;

/ /

ввод текущих данных

t o t a l

+= amount;

/ /

обработка текущих данных

count++;

}

/ /

изменение текущих данных (не забывайте! )

Было бы неплохо иметь возможность писать приложения, где сегмент кода (например, обработка транзакции) применяется к каждому элементу исходных данных нужное число раз, а размер набора данных для каждого выполнения про­ граммы разный. Заранее задавать размер набора данных, как в листинге 4.10,

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

втом, чтобы попросить пользователя указать, сколько элементов нужно обрабо­ тать, и включить данное значение в условие цикла, однако пользователь не всегда доступен. Что если данные поступают по коммуникационному каналу с удаленного компьютера? Тогда первый элемент набора данных часто является счетчиком числа последуюш.их элементов. Но и размер набора данных не всегда заранее известен или может быть очень большим. Одно дело считать пять элементов, другое — сотни и тысячи.

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

Это можно сделать двумя способами. Один состоит в том, чтобы использовать две отдельные переменные для хранения данных и ответа пользователя на вопрос о наличии других данных. После каждой транзакции пользователь отвечает на вопрос, есть ли еш,е данные для обработки. Второй способ — ввод специального значения (контрольного), указываюш^его приложению на завершение набора дан­ ных. Контрольное значение должно отличаться от возможных обычных данных. Для сумм транзакций это может быть отрицательное число или 0. Когда данные поступают по коммуникационной линии, таким значением является последнее значение в наборе данных.

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