Глава 8. позвольте компьютеру принимать решения
Начиная с этого момента, ваше знакомство с основами программирования будет продвигаться вперед семимильными шагами. Сказанное совершенно не означает, что вопросы, освещаемые в этой и последующих главах, будут более сложными, чем те, в изучении которых вы уже преуспели. Вовсе нет. Но когда вы соедините то, что вам предстоит изучить теперь, с тем, что вы уже знаете, то подниметесь на новый уровень в постижении премудростей программирования.
Начиная с этой главы, мы будем придавать особое значение изучению логики построения программы. Так как вы уже достаточно хорошо знакомы с синтаксисом и структурой языка Си/Cи++, то отметите эту смену акцентов. Теперь, вместо того чтобы тратить время на каждую точку с запятой или скобку, вам предлагается сконцентрировать внимание на алгоритмах и способах решения проблем. Вы узнаете, что обычно не существует «единственно верного» способа написать программу, решение одной и той же задачи может идти различными путями.
В этой главе рассказывается, как сделать так, чтобы компьютер мог принимать решения. Вместо того чтобы выполнять все инструкции в том порядке, в каком вы их написали, программа будет выбирать, какую из инструкций ей следует выполнить в том или ином случае, основываясь на введенных вами критериях. Фактически, вы сможете использовать приемы, которые будут описаны в этой главе для того, чтобы разрешить большинство логических проблем, с которыми столкнулись при чтении предыдущих глав.
If — маленькое слово с большими возможностями
Все эти чудеса выполняются с помощью ключевого слова if. Это короткое слово имеет, однако, большой вес. Инструкция if используется в тех случаях, когда необходимо решить, должна ли быть выполнена конкретная инструкция программы (if по-английски значит «если»). Структура if выглядит следующим образом:
if (condition)instruction;Этой записью мы говорим: «Если некоторое условие выполняется (является истинным), инструкция должна быть выполнена» (рис.8.1). То есть, компьютер, встретив ключевое слово if, выполняет инструкцию, следующую за ним, если условие в скобках является истинным.
Рис. 8.1. Структура инструкции if
Если условие не выполняется, компьютер пропускает инструкцию, записанную после if, и переходит к следующим строкам программы. Использование if не будет вызывать у вас затруднений, как только вы запомните основные моменты:
- условие заключается в круглые скобки;
- точку с запятой ставят не после условия, а только в конце инструкции;
- Си относится к языкам свободного формата, поэтому условие и инструкцию можно помещать в одной строке. Разделяя их, мы просто делаем программу более удобной для чтения.
Условия
Условием в инструкции if является сравнение значений: значение переменной или константы сравнивается с литералом, или со значением другой переменной или константы. Сравнение выполняется с помощью одного из следующих операторов отношения:
Оператор | Значение | Пример |
== | равно | if (tax == 0.06) |
> | больше | if (hours > 40) |
< | меньше | if (hours < 40) |
>= | больше или равно | if (salary >= 10000) |
<= | меньше или равно | if (cost <= limit) |
!= | не равно | if (count != 1) |
Обратите внимание: когда вы хотите узнать, равны ли два значения друг другу, то должны использовать оператор отношения, состоящий из двух знаков равенства (==) подряд. Если поставить только один знак равенства, компилятор сгенерирует предупреждение, или, реже, ошибку. Единичный знак равенства (=) используется для обозначения присваивания значения переменной.
Простейшая инструкция с использованием if выглядит примерно так:
if (time > 11) puts("Уже поздно, ступайте домой.");Здесь говорится: «Если значение переменной time больше 11, тогда следующее сообщение должно быть выведено на дисплей». Если значение переменной time окажется меньше 11, сообщение не появится.
С помощью условия if можно проверять значения числовых или символьных переменных, но не строк. Например, при компиляции следующего фрагмента программы компилятор не сообщит об ошибке, но и нужный результат тоже не будет получен:
gets(name);if (name == "Адам") puts("Позвоните домой");Строковые переменные являются темой отдельного разговора и подробно обсуждаются в главе 10.
В программе, приведенной в Листинге 8.1, используется инструкция if. Эта программа является вариантом программы, которую мы уже видели в предыдущей главе. В ней рассчитывалась общая стоимость наименования товара с учетом налога на продажи. Здесь добавлен расчет специального налога на предметы роскоши для товаров, цена которых превышает 40000 долларов. Расчет этого налога выполняется в инструкции:
if (cost > 40000.00) luxury = cost * 0.005;
Листинг 8.1. Программа расчета стоимости товаров с учетом налога на предметы роскоши.
/*luxury1.c*/main() { float cost, tax, luxury, total; luxury = 0.0; printf("Введите цену товара: "); scanf("%f", &cost); tax = cost * 0.06; if (cost > 40000.00) luxury = cost * 0.005; total = cost + tax + luxury; printf("Стоимость единицы товара с учетом налогов составляет %.2f", total); }В этих инструкциях говорится: «Если значение переменной cost больше 40000, то переменной luxury присваивается значение, равное значению cost, умноженному на 0.5 процента». Последние две строки инструкций в программе выполняются в любом случае, независимо от того, присутствует налог на предметы роскоши или нет, так как эти строки расположены после точки с запятой, завершающей инструкцию if.
Заметьте, что в начале программы переменной luxury присваивается начальное значение, равное 0, в отличие, например, от переменной tax. Причина в том, что переменной tax в любом случае будет присвоено значение, полученное в результате выполнения математических операций tax = cost * 0.06, а расчет и присваивание значения переменной luxury осуществляется только в том случае, если стоимость превышает 40тысяч долларов. Если условие не соблюдается, расчет не производится. В этом случае, если бы переменная luxury не была инициализирована, в выражение расчета общей стоимости было бы подставлено случайное значение, хранящееся в памяти. Поэтому представляется в высшей степени разумным, если вы всегда будете присваивать начальное значение переменным, которые используются в инструкции if, так же, как это делается при использовании счетчика и аккумулятора.
Используя операторы отношения больше и меньше, удостоверьтесь, что с их помощью вы составили именно те условия, которые необходимы. В приведенном примере налог на предметы роскоши добавляется только в том случае, если цена товара хотя бы на один цент превышает 40 тысяч долларов, то есть если вещь стоит 40 тысяч долларов 1 цент и больше. Если стоимость товара составляет ровно 40 тысяч долларов, налог на предметы роскоши не добавляется. Если бы налогом облагались товары, стоимостью 40 тысяч долларов и выше, следовало бы указать условие:
if (cost >= 40000.00)Разница между операторами больше и больше или равно либо меньше и меньше или равно с виду кажется незначительной, но она может весьма существенно сказаться на результатах, получаемых от программы.
Составные инструкции
В общем случае if выполняет только одну инструкцию. Если возникает необходимость, чтобы при выполнении одного условия выполнялось несколько команд, следует использовать составную инструкцию. Составной инструкцией называется последовательность любых инструкций, заключенных в фигурные скобки. С точки зрения синтаксиса языка такая последовательность будет рассматриваться как единая инструкция.
В качестве примера рассмотрим программу, текст которой приведен в Листинге8.2. Здесь при истинности одного условия выполняются две инструкции. Открывающая фигурная скобка после условия указывает начало составной инструкции, а закрывающая фигурная скобка в конце второй инструкции обозначает ее конец. Все инструкции, помещенные внутри фигурных скобок, будут выполнены только при истинности соответствующего условия. Табуляцию мы использовали только для того, чтобы подчеркнуть, что обе инструкции являются частями инструкции if. Для компилятора же табуляция не содержит никакой информации.
Листинг 8.2. Составные инструкции.
/*luxury2.c*/main() { float cost, tax, luxury, total; luxury = 0.0; printf("Введите цену товара: "); scanf("%f", &cost); tax = cost * 0.06; if (cost > 40000.00) { luxury=cost*0.005; printf("Сумма налога на предметы роскоши составляет %.2f\n", luxury); } total = cost + tax + luxury; printf("Стоимость единицы товара с учетом налогов составляет %.2f", total); }Конструкция if...else
Сама по себе инструкция if используется в тех случаях, когда важно выполнить некие действия при истинности определенного условия. Возможна ситуация, когда при истинности условия должен быть выполнен один набор инструкций, а в противном случае — другой. Допустим, мы хотим, чтобы в тех случаях, когда цена какого-либо товара ниже той, для которой установлен налог на предметы роскоши, программа генерировала сообщение: «Для данного наименования налог на предметы роскоши не установлен». Это можно сделать с помощью двух инструкций if:
if (cost > 40000.00) { luxury = cost * 0.005; printf("Размер налога на предметы роскоши для \ данного наименования составляет %.2f", luxury); }if (cost < 40000.00) puts("Для данного наименования налог \ на предметы роскоши не установлен");Но есть более эффективный способ. Можно объединить обе инструкции в одну, пользуясь тем, что есть только два возможных случая в использовании одной и той же переменной: либо цена товара больше 40 тысяч долларов, либо цена товара меньше или равна указанной сумме. Если одно из условий не выполняется, следовательно, выполняется второе условие, так что можно
Рис. 8.2. Инструкции, модифицированные с использованием ключевого слова else
скомбинировать их, используя ключевое слово else (которое переводится как «иначе»):
if (condition) instruction;else instruction;Здесь сказано: «Если условие истинное, то должна быть выполнена команда, являющаяся частью инструкции if, иначе надо выполнить инструкцию, следующую за else». Инструкция, помещенная после ключевого слова else, выполняется только в том случае, если условие оказалось ложным. Если возникает необходимость выполнить в этом случае несколько инструкций, можно использовать составную инструкцию, заключив ее в фигурные скобки точно так же, как для if. Точка с запятой ставится в конце каждой инструкции и не ставится после ключевого слова else.
Для того чтобы вывести на экран сообщение об отсутствии налога на предметы роскоши, программу можно слегка изменить, как это показано на рис.8.2. Обратите внимание, что в этом случае нет необходимости непременно присваивать переменной luxury начальное значение, так как в инструкции if теперь учитываются все возможные варианты условия.
Дополненный Опросник
В главе 7 мы предложили в качестве примера несколько программ, которые выводили на экран монитора вопросы и ответы. Поскольку вы тогда еще не познакомились c инструкцией if, то не имели возможности вести подсчет очков за правильные ответы. Подсчет очков, как демонстрирует программа из Листинга8.3, представляет собой, по существу, сравнение правильного ответа, заложенного в тексте программы, и ответа, введенного с клавиатуры.
В этой программе используются функции, которые выводят вопрос на экран монитора, вводят с клавиатуры ответ пользователя, определяют правильность ответа и подсчитывают количество верных и ошибочных ответов. И вопрос, и правильный ответ передаются функции — вопрос в виде строки литералов, ответ в виде целого числа. Программа построена таким образом, чтобы в нее можно было при желании добавлять вопросы, дописывая инструкции вызова функции ask():
ask("9+5= ", 14);Листинг 8.3. Опросник с подсчетом очков.
/*score*/int correct, wrong;main() { char question[15]; int answer; correct = 0; wrong = 0; ask("4 + 5 = ", 9); ask("6 + 2 = ", 8); ask("5 + 5 = ", 10); ask("4 + 7 = ", 11); printf ("Количество верных ответов: %d.\n", correct); printf ("Количество неверных ответов: %d.\n", wrong); }ask(quest, ans)char quest[15];int ans; { int guess; printf("%s", quest); scanf("%d", &guess); if (guess == ans) ++correct; else ++wrong; return(0); }Логические операторы
Как вы уже могли заметить в приведенных выше примерах, инструкция if проверяет выполнение условия только для одной переменной и одного значения. Значит, в инструкции можно ввести только одно условие с целью проверки его истинности. На самом деле часто возникает необходимость проверить в условии более одного значения.
Посмотрите на программу, приведенную в Листинге8.4. В ней предполагается, что не каждая единица продаваемого товара облагается налогом на продажи. Поэтому, вместо того чтобы автоматически добавлять сумму налога к стоимости каждого наименования товара, программа спрашивает, должен ли данный товар облагаться налогом, и если да — добавляет сумму в размере 6 процентов от стоимости товара.
Листинг 8.4. Работа программы основывается на указаниях пользователя.
/*iftax.c*/main() { int taxable; float cost, tax; tax = 0.0; printf("Введите цену товара: "); scanf("%f", &cost); printf("Введите Y, если товар облагается налогом, N, если не облагается: "); taxable = getchar(); if (taxable == 'Y') tax = cost * 0.06; printf("\nСтоимость товара с учетом налога составляет %f", cost + tax); }Если на ваш взгляд программа написана вполне корректно, посмотрите ее текст еще раз более внимательно — там есть серьезное упущение. Программа написана так, что пользователь должен в ответ на запрос ввести прописную букву Y, если товар облагается налогом. Если пользователь вводит строчную букву y, программа будет считать, что налог в данном случае не взимается, ведь в инструкции if в качестве правильного условия рассматривается только прописная буква.
|
В подобной ситуации правильнее было бы проверять оба возможных варианта ввода, то есть и строчную и прописную буквы Y. Можно сделать это с помощью двух инструкций if. А можно использовать логический оператор ИЛИ, который выглядит как две вертикальные черты:
if (taxable == 'Y' || taxable == 'y')В данной инструкции сказано: «Если переменная taxable имеет значение Y ИЛИ y, то...» Таким образом, мы добьемся того, что товар будет рассматриваться как облагаемый налогом, если выполняется одно из этих двух условий. Если не выполняется ни одно из них, то есть пользователь ввел любой другой символ, товар будет считаться не облагаемым налогом. Условие должно быть целиком помещено в круглые скобки, причем имя переменной taxable следует повторить дважды. Запись условия как (taxable == 'Y' || 'y') приведет к ошибке компиляции.
Есть три логических оператора: ИЛИ (||), И (&&) и отрицания (!). Оператор ИЛИ означает, что для выполнения инструкции if достаточно истинности одного из двух (или обоих вместе) заданных условий. Оператор И указывает на то, что должны быть истинными оба условия одновременно. Оператор отрицания означает, что инструкция if выполняется, если некое условие оказалось ложным.
Операторы И и ИЛИ можно использовать не только для проверки равенства переменной одному из двух значений (как мы уже делали), но и для тестирования значений различных переменных. Например, вы пишете программу, в которую вводится размер годового дохода пользователя и количество иждивенцев (Листинг8.5).
Листинг 8.5. Проверка значений двух переменных.
/*twovars.c*/main() { int depents; float income; puts("Укажите сумму Вашего годового дохода"); scanf("%f", &income); puts("Пожалуйста, укажите количество иждивенцев"); scanf("%d", &depents); if (income < 20000 && depents > 2) puts("Вы освобождены от уплаты подоходного налога"); }В условии if здесь проверяются значения двух переменных: income и depents. Для того чтобы сообщение, записанное в инструкции if, оказалось выведенным на экран, значение переменной income должно быть меньше 20 тысяч долларов, И одновременно значение переменной depents должно быть больше двух. Если хотя бы одно из перечисленных условий не выполняется, например, количество иждивенцев равно одному или доход составляет 20001 доллар, функция puts() не будет выводить на экран сообщение об освобождении от уплаты налога.
Будьте очень внимательны, когда используете оператор И, чтобы быть уверенным, что ваша программа работает нужным образом. Например, никогда не используйте оператор И для проверки двух альтернативных значений одной переменной. Условие
if (taxable == 'Y' && taxable == "y")никогда не будет выполнено, так как одна переменная не может одновременно иметь два значения. Тем не менее, используя оператор И, можно проверить, находится ли значение одной и той же переменной в определенных границах допустимых значений.
Для примера предположим, что налогом на предметы роскоши облагаются товары, цена которых находится в пределах от 40 тысяч до 60 тысяч долларов. Условие проверяется следующим образом:
if (cost >= 40000.00 && cost <= 60000.00)Для того чтобы выполнялась инструкция if, должны быть истинными оба условия одновременно, так как мы использовали оператор И. При этом цена товара должна быть в одно и то же время больше или равна 40000 и меньше или равна 60000 долларов, то есть находится в пределах определенных допустимых значений (рис.8.3).
Если в данном случае использовать оператор ИЛИ, то это приведет к ошибочному выполнению инструкции, так как любое значение переменной cost будет рассматриваться как удовлетворяющее условию. Напротив, если мы хотим
Рис. 8.3. Так можно узнать, находится ли число в границах определенных значений
определить, находится ли значение вне каких-то установленных границ значений, мы должны использовать именно оператор ИЛИ и поменять местами операторы «больше или равно» и «меньше или равно». При такой записи условие if будет проверять, выходит ли значение числа за указанные рамки, определяя, меньше это число или больше установленных ограничивающих значений:
if (income <= 20000.00 || income >= 60000.00) puts("Вы не относитесь к среднему классу");Оператор отрицания называют унарным оператором, так как он работает только с одним объектом, а именно, с переменной или константой. Условие считается не выполненным (ложным) в том случае, когда значение выражения, стоящего в круглых скобках после if, равно0. При любом другом значении выражения, будь оно положительным или отрицательным, полагается, что условие выполнено. Например, следующие инструкции выводят на экран монитора сообщение: «Ошибка в расчете», так как переменная count имеет значение, равное0:
int count;count = 0;if (! count) puts("Ошибка в расчете");Такая запись условия в точности соответствует строке
if (count == 0)Аналогичным образом, следующая инструкция выводит на экран сообщение: «Правильный результат», так как переменная count имеет значение, отличное от нуля:
int count;count = 1;if (count) puts("Правильный результат");Запись if (count) аналогична записи if (count != 0), которая определяет, отличается ли значение переменной от нуля. В последующих главах вы узнаете, как используются унарные операторы.
Вложенные инструкции if
Конструкция if...else может содержать инструкции любого типа. Они могут включать ввод и вывод значений, выполнение математических операций или вызов собственных функций. Но инструкция в условии может оказаться и другой инструкцией if. В этом случае она будет называться вложенной инструкцией. Ниже приведен пример, где одна инструкция if вложена в другую:
if (income > 100000) if (status == 'S') taxrate = 0.35;Второе условие проверяется только в том случае, если выполнено первое, так что значение переменной taxrate присваивается только при выполнении обоих условий. Ту же самую логическую конструкцию можно было записать следующим образом:
if (income > 100000 && status == 'S') taxrate = 0.35;Обе инструкции выполняют одну и ту же задачу, но второй способ записи, с использованием оператора &&, кажется более ясным, так как нет необходимости расшифровывать смысл второй инструкции if. Достаточно просто прочитать инструкцию, чтобы понять принцип действия: «Если значение переменной income больше чем 100000 И одновременно переменная status имеет значение "S", переменной taxrate присваивается значение 0.35».
Как правило, любые две последовательно вложенные инструкции if можно заменить одной инструкцией, использующей логический оператор И. Опять же, как правило, имеет смысл избегать вложенных инструкций if, так как они могут приводить к появлению запутанных ситуаций, логических ошибок и трудных для чтения фрагментов текста программы. Посмотрите на следующий пример:
main() { float income; scanf("%f", &income); if (income >= 20000.00) if (income <= 100000.00) puts("Размер Вашего подоходного налога составляет 22%"); else puts("Ваш доход меньше 20000$ - подоходный налог равен 15%"); }В этом случае логика рассуждений автора программы была, по-видимому, такова: «Если значение переменной income больше 20000 и меньше 100000, следует вывести одно сообщение, а если значение income меньше 20000, должно быть выведено второе сообщение».
Компиляция программы пройдет без ошибок, но, к несчастью, работать она будет неправильно. Когда вы введете значение меньшее, чем 20000, не будет выполняться ни одна из функций puts(), а когда введете значение больше 100000, подоходный налог окажется определенным в размере 15процентов.
Причина ошибки кроется в том, что ключевое слово else связано с ближайшей к нему инструкцией if независимо от отступов, которые были сделаны в тексте программы. В этой программе использование табуляции создает впечатление, что else связано с первым условием if, но это не так. На самом деле else связано со второй инструкцией if, которая выполняется только в тех случаях, когда значение переменной income больше 20000 и меньше 100000.
Если вы хотите, чтобы программа работала в соответствии с вашей логикой, инструкции следует написать примерно таким образом:
if (income >= 20000.00) { if (income <= 100000.00) puts("Размер Вашего подоходного налога составляет 22%"); }else puts("Ваш доход не превышает 20 тысяч долларов, \поэтому размер подоходного налога составляет 15%");Фигурные скобки изолируют вложенную инструкцию, и теперь ключевое слово else действительно будет связано с первой инструкцией if.
Еще лучше написать эту же программу с использованием только одной инструкции if:
if (income >= 20000.00 && income <= 100000.00) puts("Размер Вашего подоходного налога составляет 22%");else puts("Ваш доход не превышает 20 тысяч долларов, \поэтому размер подоходного налога составляет 15%");В этом варианте мы полностью исключаем путаницу между вложенными инструкциями if и использование дополнительных наборов фигурных скобок.
Даже теперь, после того как мы разобрались с логикой программы, она все еще имеет некоторое упущение. Все сообщения, представляемые программой, относятся к уровню дохода, не превышающему 100 тысяч долларов. В хорошо продуманной программе должны быть приняты во внимание все возможные ситуации. Если в программу, в том виде, в каком она существует сейчас, ввести значение переменной income, равное 150000, программа не выдаст никакого сообщения. Значит ли это, что и подоходный налог платить не обязательно?
Один из способов учета всех возможных вариантов приведен в Листинге8.6. В программе используются вложенные инструкции if...else. Постарайтесь убедиться, что вы действительно понимаете, как они сгруппированы. Если первое условие является истинным (income < 20000.00), выполняется первая функция puts(), а все остальные инструкции пропускаются. Первое ключевое слово else связано с первым if, так что проверка второго условия (income < 100000.00) осуществляется только в том случае, если первое условие оказалось ложным.
Листинг 8.6. Использование вложенных инструкций для учета всех возможных условий.
/*brackets*/main() { float income; puts("Укажите размер Вашего годового дохода: "); scanf("%f", &income); if (income < 2000.00) puts("Размер Вашего подоходного налога составляет 15%"); else if (income < 2000.00) puts("Размер Вашего подоходного налога составляет 22%"); else puts("Размер Вашего подоходного налога составляет 35%"); }Обратите внимание на то, что хотя возможны три различных варианта значений переменной, используются только два условия if. При использовании последовательной комбинации if...else требуется написать на одно условие меньше, чем количество возможных вариантов. Действительно, если существует три различных условия, то при невыполнении первого и второго условия обязательно должно выполняться третье, так что нет необходимости вводить третью инструкцию if для проверки его истинности. Но если бы переменная имела четыре возможных варианта значений, следовало бы ввести уже три комбинации if...else.