- •Действия: что делает программа Глава 2 Выражения
- •Арифметические операторы
- •Замечание
- •Операторы отношений
- •Логические операторы
- •Оператор отрицания
- •Операторы инкремента и декремента
- •Поразрядные операторы
- •Поразрядный оператор исключающего или
- •Сдвиг битов влево
- •Дополнение до единицы
- •Сомр 44 0х002с 0000000000101100
- •Операторы присваивания
- •Выражения значений
- •Приоритеты операторов
Приоритеты операторов
В сложных выражениях операторы с более высоким приоритетом выполняются до операторов с более низким приоритетом. Одни операции выполняются слева направо, а другие - справа налево. Это свойство операторов называется ассоциативностью.
Для получения точных результатов, т.е. для указания нужного порядка выполнения операций, не забывайте о здравом смысле и таких хороших помощниках, как круглые скобки. И тогда вам не придется запоминать множество правил, связанных с ассоциативностью и приоритетом операций, для того чтобы понять, в каком порядке вычисляется такое выражение:
у = (( а * х) + b) / (х + с).
Несмотря на то что назначение этой формулы не очевидно (она описывает гиперболическую кривую), круглые скобки не оставляют сомнений относительно последовательности вычисления подвыражений.
Но, учитывая установленный приоритет операций, представленное выше выражение можно записать несколько иначе и получить при этом правильный результат, а именно:
у = ( а * х + b) / (х + с).
Поскольку умножение и деление выполняются слева направо, то можно убрать одну пару круглых скобок в первом выражении. При этом правильность сохраняется, но ясность понимания слегка уменьшается.
Обязательно изучите приоритеты операторов и правила ассоциативности, приведенные в табл. 2.7. Но чем полагаться исключительно на приоритет операторов, добавьте лучше лишнюю пару круглых скобок - они не повлияют на быстродействие, а программа станет более понятной.
Таблица 2.7. Приоритет операторов и порядок вычисления (ассоциативность)
Уровень приоритета
|
Операторы
|
Порядок вычислений
|
|
||
1
|
( ) . .[ ] ->
|
Слева направо
|
|
||
2
|
* & ! ~++-+- (тип) sizeof
|
Справа налево
|
|
||
3
|
* / %
|
Слева направо
|
|
||
4
|
+ -
|
Слева направо
|
|
||
5
|
<< >>
|
Слева направо
|
|
||
6
|
<<= >>=
|
Слева направо
|
|
||
7
|
== !=
|
Слева направо
|
1
|
||
8
|
&
|
Слева направо
|
|
||
9
|
^
|
Слева направо
|
|
||
10
|
|
|
Слева направо
|
|
||
11
|
&&
|
Слева направо
|
|
||
12
|
||
|
Слева направо
|
|
||
13
|
?:
|
Справа налево
|
|
||
14
|
= *= /= += -= %= <<= >>= &= ^= |=
|
Справа налево
|
|
||
15
|
‘
|
Слева направо
|
|
Замечание
Унарный плюс (+) и унарный минус (-) находятся на уровне 2, и их приоритет выше, чем у арифметических плюса и минуса, находящихся на уровне 4. Символ & на уровне 2 является оператором взятия адреса, а символ & на уровне 8 представляет поразрядный оператор И. Символ * на уровне 2 - это оператор разыменования, а символ • на уровне 3 означает оператор умножения. При отсутствии круглых скобок операторы одного уровня вычисляются в соответствии с их порядком вычислений.
Оператор if
Оператор if (Если) действует точно так, как ожидается. Если выражение истинно, оператор выполнит действие; в противном случае программа продолжит свою работу со следующего оператора.
В этом и следующих разделах использование псевдокода помогает объяснить различные формы операторов. Псевдокод оператора if имеет следующий вид:
if (.выражение)
оператор;
Выражением может служить любое выражение, которое в результате дает значение "истина" или "ложь". Если выражение истинно (ненулевое), то оператор выполняется. Если выражение ложно (нуль), то оператор пропускается. Можно записывать операторы if в одной строке:
if (выражение) оператор;
Но обычно оператор if пишется на отдельных строчках, что позволяет выделить его компоненты. Оператор может быть составным:
if (.выражение) {
оператор 1;
оператор 2;
}
Расположение фигурных скобок в составном операторе if строго не регламентируется. Некоторые программисты предпочитают их выравнивать следующим образом:
if (выражение)
{
оператор 1,
оператор 2;
}
Кроме того, для записи сравниваемых выражений можно использовать оператор отношения:
if (выражение == значение)
оператор;
Оператор будет выполнен только в том случае, если выражение равно значению. Не следует записывать операторы if в таком виде:
if (выражение = значение) /* ??? */
опера тор;
в противном случае вы получите предупреждение компилятора о неверном, возможно, присваивании: вы, вероятно, не хотели присваивать значение выражению, а скорее всего, случайно ввели один знак равенства вместо двух - это одна из самых распространенных ошибок.
Опытные программисты (которые, возможно, не такие большие специалисты по вводу программ) часто сокращают выражения оператора if до минимума. "Ободранный как липка" оператор
if (выражение != 0)
оператор;
функционально идентичен следующему:
if (выражение)
оператор;
А ларчик просто открывался... Все дело в том, что логические выражения (выражение != 0) и (выражение) эквивалентны, так как любое ненулевое значение является истинным. Замените выражение, например, литеральным значением 5, и вы поймете, в чем тут фокус. Очевидно, что 5 не равно 0, т.е. первое выражение истинно. И второе также будет иметь значение "истина", поскольку 5 является ненулевым значением, а значит, истинным.
Такого рода сокращения часто встречаются в операторах if. Между записями if (выражение != 0) и if (выражение) нет никакой функциональной разницы. Но использование более длинной формы придаст вашей программе большую наглядность без ущерба для стиля и быстродействия.
Листинг 2.10 показывает, как использовать оператор if. Скомпилируйте и запустите программу, затем введите число от 0 до 10. Если вы введете число, выходящее за пределы этого диапазона, программа выдаст сообщение об ошибке.
Листинг 2.10. CHOICE.С (использование оператора if)
__________________________________________________
1: #include <stdio.h>
2:
3: rnain()
4: {
5: int number;
6: int okay;
7:
8: printf("Enter a number from 1 to 10: ");
9: scanf("%d", &number);
10: okay = (1 <= number) && (number <= 10);
11: if (!okay)
12: printf("Incorrect answer!\n");
13: return okav;
14: }
Строка 10 присваивает переменной okay типа int истинное или ложное значение результата выражения отношения
(1 <= number) && (number <= 10)
Результат выражения представляет собой целое значение - 0, если вычисление даст "ложь", или ненулевое значение в случае "истины". Это значение присваивается переменной okay, после чего строка 11 проверяет выражение !okay (читается "не о'кей"). Если okay ложно (в случае ошибки), то !okay даст значение "истина", и тогда строка 12 выведет сообщение об ошибке.
Совет
Для запоминания выражений отношений в операторах if выбирайте идентификаторы, подобные okay, вместо сложных и ничего не значащих имен. Тщательно выбранные идентификаторы сослужат хорошую службу для понимания текста программы.
Строка 13 возвращает значение переменной okay ("истину" или "ложь") обратно в DOS, хотя в данном случае это просто демонстрация такой возможности. Но в более сложных программах аналогичное возвращаемое значение может означать обнаружение серьезной ошибки.
Вы можете также записывать сложные логические выражения непосредственно в операторах if. Например, вы можете соединить строки 10-12 в один оператор:
if ((1 <= number) && (number <= 10))
printf("Incorrect answer!\n");
Обратите внимание на дополнительную пару скобок вокруг логического выражения. Выражение, которое вычисляет оператор if, должно быть заключено в круглые скобки.
Оператор else
Оператор else (Иначе) является расширением оператора if. После любого оператора if вы можете вставить оператор else для выполнения альтернативного действия. Используйте else следующим образом:
if (выражение)
оператор 1;
else
oneparop 2;
Если выражение истинно (ненулевое), выполняется onepamop1; в противном случае — onepamop2. Операторы могут быть простыми или составными. Вы можете записать:
if (выражение) {
оператор 1;
оператор 2;
} else
оператор З;
Составными могут быть обе части, заключенные в фигурные скобки:
if (выражение) {
оператор 1;
оператор 2;
} else {
оператор 3;
оператор 4;
}
В этой конструкции выполнятся операторы 1 и 2, если выражение окажется истинным; в противном случае будут выполнены операторы 3 и 4.
Составные операторы могут иметь любую глубину вложенности, поэтому оператору 1 разрешено иметь еще один оператор if, который, в свою очередь, может выполнять другие операторы if. Логика глубоко вложенных операторов if вряд ли будет прозрачной, но компилятор не налагает никаких ограничений на их количество, Лучше всего ограничиться двумя уровнями вложения.
Как и в простых операторах if, расположение фигурных скобок в сложных конструкциях if-else - дело вкуса программиста. Некоторые программисты предпочитают выравнивать фигурные скобки так:
if (выражение)
{
оператор 1;
оператор 2;
}
else
{
оператор З;
оператор 4;
}
Вы можете вкладывать несколько конструкций if-else друг в друга, создавая многовариантный выбор:
if (выражение 1)
оператор 1;
else if (выражение 2)
оператор 2;
else
оператор З;
Давайте разберемся, как работает такая конструкция. Если выражение 1 истинно, то выполняется onepamop 1; если истинно выражение 2, то выполняется onepamop 2. Если выражение 1 и выражение 2 имеют значение "ложь", то выполняется оператор З. Можно развить эту идею дальше:
if (выражение 1)
оператор 1;
else if (выражение 2)
оператор 2;
else if (выражение З)
оператор З;
else if (выражение N)
оператор N;
else /* необязательно */
оператор_умолчания; /* необязательно */
Вложенные операторы if-else полезны для выбора одного из нескольких возможных действий, причем гарантируется выполнение оператора (или операторов), связанного с первым истинным выражением. Если ни одно из выражений не имеет значения "истина", выполняется последний (необязательный) оператор _умолчания.
Листинг 2.11, являясь примером использования вложенных операторов if-else, определяет високосные годы. Скомпилируйте и запустите программу, затем введите год, например 2000. Год, открывающий новое столетие, является високосным, если без остатка делится на 400. (На самом деле, поскольку нет года с номером 0, первым годом XXI столетия будет 2001-й год, но не будем придираться.) Промежуточные годы (такие как 1996) будут високосными, если без остатка делятся на 4.
Листинг 2.11. LEAP.С (использование конструкции if-else для определения високосного года)
_____________________________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int leapYear;
6: int year;
7:
8: printf("Leap Year Calculator\n");
9: printf("Year? ");
10: scanf("%d", &year);
11: if (year > 0) {
12: if ((year % 100) == 0)
13: leapYear = ((year % 400) == 0);
14: else
15: leapYear = ((year % 4) == 0);
16: if (leapYear)
17: printf("%d is a leap year\n", year);
18: else
19: printf("%d is not a leap year\n", year);
20: }
21: return 0;
22: }
_____________________________________________________
Оператор if-else, содержащий ошибку (чаще всего, это неправильное расположение фигурных скобок или их отсутствие), может привести к неправильной работе всей программы. Листинг 2.12 демонстрирует испорченный вариант программы.
Листинг 2.12. BADIF.C (неправильный способ вложенности операторов if-else)
_______________________________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int value;
6:
7: printf("Value (1 ...10)? ");
8: scanf("%d", &value),
9: if (value >= 1)
10: if (value > 10)
11: printf("Error: value > 10\n");
12: else
13: printf("Error: value < 1\n");
14: return 0;
15: }
Запустите BADIF и введите число от 1 до 10. Предполагалось, что числа, не входящие в этот диапазон, будут отброшены, но эта программа бракует числа именно из заданного диапазона и некоторые, лежащие за его пределами. Оператор if сравнивает введенное число с единицей, и если оно больше или равно 1, то внутренний оператор if сравнивает его с 10. Если ваше число больше 10, будет выдано первое сообщение об ошибке. Проблема в том, что код не работает так, как ожидается.
Во всем виноват оператор else на строке 12. Несмотря на то, что он выравнивается с оператором if в строке 9, он логически связан с ближайшим оператором if, находящимся выше, - в данном случае с оператором if на строке 10. Вот в этом и заключен корень зла. Теперь ваша задача - исправить программу так, чтобы оператор else работал в одной связке с нужным оператором if.
Запомните такое правило: чтобы проверить логику программы, никогда не полагайтесь на сделанные вами отступы. Они только для вас, компилятор не обращает на них никакого внимания. А вот фигурные скобки заставят его прислушаться к вашим пожеланиям относительно вложенных операторов if-else. Листинг 2.13 демонстрирует исправленный вариант программы.
Листинг 6.13. GOODIF.C (правильная вложенность операторов if-else)
___________________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int value;
6:
7: printf("Value (1 ... 10)? “);
8: scanf("%d", &value);
9: if (value >= 1) {
10: if (value > 10)
11: printf("Error: value > 10\n");
12: } else
13: printf("Error; value < 1\n");
14: return 0;
15: }
__________________________________________________
Сравните листинги 2.12 и 2.13. Дополнительные фигурные скобки в строках 9 и 12 заставляют оператор else работать вместе с оператором if в строке 9. А внутренний оператор if в строках 10-11 сейчас работает правильно.
Условные выражения
Сокращенный оператор if-else, называемый условным выражением, иногда очень полезен. Псевдокод условного выражения выглядит следующим образом:
Выражение 1 ? выражение 2: выражение З;
Программа вычисляет выражение 1. Если оно истинно, то результат всего выражения равен выражению 2. Если же выражение 1 ложно, результат равен выражению З. Условное выражение в точности эквивалентно оператору if-else:
if (выражение 1)
выражение 2;
else
выражение З;
Назначение оператора if-else по сравнению с эквивалентным условным выражением обычно более понятно. Рассмотрим этот факт в связи с выбором одной из этих двух форм.
Обычно вы присваиваете результат условного выражения какой-нибудь переменной. Предположим, вы пишете программу, управляемую меню. Если переменная типа int menuChoice будет равна 'Y', вы хотите установить переменную testValue, равную 100; в противном случае равную нулю. Прямое решение использует оператор if-else:
if (menuChoice == 'Y')
testValue = 100;
else
testValue =0;
Эта конструкция достаточно ясна, но данный оператор требует двух ссылок на переменную testValue. Чтобы обратиться к testValue только один раз и выполнить ту же самую работу, вы можете записать:
testValue = (menuChoice == 'Y') ? 100 : 0;
Если выражение (menuChoice == 'Y') будет истинным, то оператор присвоит переменной testValue значение 100, если ложным - значение 0.
Не следует использовать условные выражения только потому, что они занимают одну строку вместо четырех. В большинстве случаев компилятор генерирует аналогичный код как для оператора if-else, так и для эквивалентного условного выражения. С другой стороны, чтобы избежать двух ссылок на одно и то же выражение (особенно, если они имеют более сложную форму, чем в данном примере с переменной testValue), предпочтительнее использовать условное выражение.
Оператор switch
Вложенный набор операторов if-else может выглядеть как запутанный водопровод старого дома. Система-то работает, но трудно понять, какая труба куда ведет.
Рассмотрим следующую серию операторов if-else, каждый из которых сравнивает выражение с определенным значением:
if (выражение == значение 1)
оператор 1;
else if (выражение == значение 2)
оператор 2;
else if (выражение == значение З}
onepaтор З;
else /* необязательно */
оператор_умолчания; /* необязательно */
Эту конструкцию можно сократить с помощью более простого оператора switch:
switch.(выражение) {
case значение 1:
оператор 1; /* выполняет if (выражение == значение 1) */
break; /* выход из оператора switch */
case значение2:
оператор 2; /* выполняет if (выражение == значение 2) */
break; /* выход из оператора switch */
case значение З:
оператор З; /* выполняет if (выражение == значение З) */
break; /* выход из оператора switch */
default:
оператор_умолчания; /* выполняется, если не было ни
одного совпадения */
}
На первый взгляд эта запись может показаться длиннее, но на практике оператор switch использовать проще, чем эквивалентную конструкцию if-else. За ключевым словом switch следует выражение, которое будет сравниваться с множеством значений. Внутри блока switch селекторы case сравнивают выражение с заданными значениями. Строка
case значение 1:
сравнивает значение 1 с результатом вычисления выражения оператора switch. Если результат сравнения даст значение "истина", то будет выполнен оператор (или блок операторов), следующий за строкой case. Если же результатом сравнения будет "ложь", аналогичным образом будет обрабатываться следующая строка case. Последняя строка оператора switch (default:) - необязательная строка выбора. Она задает действия, выполняемые в случае, если ни одно из значений строк case не совпало со значением выражения оператора switch.
Оператор break в каждом блоке выбора case немедленно осуществляет выход из оператора switch. Операторы switch разработаны таким образом, что если вы напишите их без оператора break
switch(выражение) {
case значение 1:
оператор 1; /* ??? */
case значение 2:
оператор 2; /*???*/
case значение З:
оператор З;
}
и выражение будет равно значению 1, то после выполнения оператора 1 последует выполнение оператора 2 и оператора 3 - вряд ли именно это входило в ваши планы. В операторе switch первый же случай сравнения, давший в результате "истину", приведет к выполнению всех последующих операторов, вплоть до оператора break или до конца оператора switch.
Замечание
Смышленые (может быть; даже слишком) программисты иногда намеренно опускают операторы break из операторов switch, чтобы позволить одному блоку case выполнять действия, перечисленные и в других блоках case. Это технически разрешено, но затрудняет процесс отслеживания результатов и создает трудности при внесении изменений. Лучше всего завершать каждый блок case своим оператором break.
Листинг 2.14 демонстрирует, как писать меню выбора, используя оператор switch. Скомпилируйте и запустите программу, затем введите буквы A, D, S и Q, чтобы выбрать команду. (Команды не делают ничего полезного, вы можете развить эту программу по своему вкусу.) Введите букву, которой не было в этом маленьком списке, и вы увидите, как оператор switch обнаружит ошибку ввода.
Листинг 2.14. MENU.С (оператор switch)
_______________________________________________
1: #include <stdio.h>
2: #include <ctype.h>
3:
4: main()
5: {
6: int choice;
7:
8: printf("Menu: A(dd D(elete S(ort Q(uit: ");
9: choice = toupper(getchar());
10: switch (choice) {
11: case 'A':
12: printf(“You selected Add\n");
13: break;
14: case 'D':
15: printf(“You selected Delete\n");
16: break;
17: case 'S':
18: printf( “You selected Sort\n");
19: break;
20: case 'Q':
21: printf(“You selected Quit\n");
22: break;
23: default:
24: printf("\nIllegal choice!! !\n");
25: }
26: return choice;
27: }
_________________________________________
Чтобы прочитать символ, введенный с клавиатуры, строка 9 вызывает стандартную библиотечную функцию getchar(). Функция toupper() переводит этот символ в верхний регистр (делает прописным), упрощая таким образом определение нажатой клавиши.
Оператор switch начинается па строке 10, предлагая выбранный символ в качестве анализируемого выражения. Последующие блоки case сравнивают переменную choice с литеральными символами, выдавая подтверждающее сообщение в случае, если сравнение было удачным. Если совпадения с заданными буквами не случилось, блок default (строки 23-24) выведет сообщение об ошибке, i
В строке 26 программа возвращает выбранный символ. Хотя в данной программе это и не важно, но аналогичный оператор может передать результат работы меню из одной функции в другую, что определит выбор соответствующей команды.
Оператор while
Оператор while - один из трех операторов цикла, выполняющих повторяющиеся действия. Программы используют его для повторного выполнения операторов в течение всего времени, пока заданное условие истинно. Псевдокод оператора while выглядит следующим образом:
while (выражение)
оператор;
В переводе с английского этот псевдокод можно "прочитать" так: "Пока выражение истинно, выполнять оператор". Как обычно, оператор может быть простым или составным:
while (выражение) {
оператор 1,
оператор 2;
…………
}
В таких конструкциях все операторы выполняются один за другим до тех пор, пока выражение истинно. Оператор while - также называемый циклом while - обычно выполняет, по крайней мере, один оператор, который влияет на заданное выражение. Иначе и не может быть, ведь в противном случае мы получили бы бесконечный цикл. Листинг 2.15 использует оператор while для счета от 1 до 10.
Листинг 2.15. WCOUNT.C (счет от 1 до 10 с помощью цикла while)
______________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int counter;
6:
7: printf("while count\n");
8: counter =1;
9: while (counter <= 10) {
10: printf("%d\n", counter);
11: counter++;
12: }
13: return 0;
14:
______________________________________________________
Строка 8 инициализирует целую переменную counter, называемую управляющей переменной. Оператор while в строках 9-12 использует оператор отношения <= для сравнения значения counter с числом 10. Пока counter меньше или равно 10, будут выполняться операторы на строках 10-11. Строка 11 увеличивает counter на единицу на каждой итерации цикла, гарантируя таким образом, что цикл в конце концов закончится.
Чему будет равно значение переменной counter после окончания работы оператора while? Проверьте свою догадку, вставив следующий оператор между строками 12 и 13:
printf("counter == %d\n", counter);
Имеет ли смысл конечное значение переменной counter (11)? Чему оно будет равно, если заменить выражение в строке 9 на (counter < 10)? Попробуйте также изменить начальное значение counter в строке 8. Что произойдет, если присвоить начальное значение, равное 11? Будет ли цикл выполняться?
Этот тест демонстрирует важное свойство цикла while: если анализируемое выражение с самого начала ложно, ни один из его операторов не будет выполнен.
Вы можете использовать другие виды управляющих переменных в операторах while. Например, листинг 2.16 выводит алфавит, используя в качестве управляющего значения символ.
______________________________________________________
Листинг 2.16. WALPHA.C (вывод алфавита с помощью цикла while)
1: #include <stdio.h>
2:
3: main()
4: {
5: int c;
6:
7: printf(“.,, while alphabet\n");
8: с = 'А';
9: while (с <= 'Z') {
10: printf("%c”,с);
11: c++;
12: }
13: return 0;
14: }
______________________________________________________
.Выражение в строке 9 (с <= 'Z') истинно, если ASCII-значение символа меньше или равно ASCII-значению буквы z.
Оператор do-while
Оператор do-while является в некотором роде перевернутым циклом while. Выраженный в псевдокоде, do-while языка С выглядит следующим образом;
do {
оператор;
} while (выражение);
Выполняется оператор, затем проверяется выражение. И до тех пор, пока значение этого выражения будет истинным, оператор будет выполняться. Сравните эту схему работы с рассмотренным выше оператором while, который вычисляет свое выражение, прежде чем выполнить какой-нибудь оператор. Однако while вообще может не выполнить ни одного оператора, если анализируемое выражение с самого начала было ложным. Но do-while всегда выполняет свои операторы, по крайней мере, один раз, так как проверка выражения осуществляется по завершении итерации цикла. Из этого следует основное правило, необходимое при выборе между while и do-while.
• Спросите себя: "Есть ли, по крайней мере, одно условие, при котором операторы цикла не должны выполняться хотя бы один раз?" Если ответом будет "Да", то, скорее всего, вам следует выбрать цикл while.
• Если ответ на предыдущий вопрос будет "Нет", то вам, вероятно, подойдет цикл do-while. Используйте этот цикл, если, безотносительно к значению проверяемого выражения, операторы в цикле должны быть выполнены, по крайней мере, один раз.
Оператор do-while может быть простым или составным. Однооператорные циклы do-while можно записывать в одной строке:
do оператор; while (выражение);
Можно также записывать их на отдельных строках, делая отступы для удобства восприятия:
do
оператор;
while (выражение);
Однако часто добавляют фигурные скобки, чтобы было понятно, что вся эта запись представляет собой один оператор, а не несколько
do {
оператор;
} while (.выражение);
Многооператорные циклы do-while записывайте следующим образом:
do {
оператор 1;
оператор 2;
} while (.выражение);
Листинг 2.17 аналогичен приведенной выше программе WCOUNT, но считает от 1 до 10, используя цикл do-while. .
Листинг 2.17. DWCOUNT.C (счет от 1 до 10 с помощью цикла do-while)
________________________________________________________________
1:: #include <stdio.h>
2:
3: main()
4: { .
5; int counter;
6:
7: printf("do-while count\n");
8: counter = 0;
9: do {
10: counter++;
11: printf("%d\n", counter);
12: } while (counter < 10);
13: return 0;
14: }
__________________________________________________
Кроме выполнения программы DWCOUNT на компьютере, попытайтесь выполнить ее вручную, записывая значения переменной counter на бумаге. Уясните себе, почему именно выражение (counter < 10) заканчивает цикл после отображения 10-го значения. Почему бы не работать выражению (counter <= 10), как это было в программе WCOUNT? Проверьте свои предположения, вставив операторы printf() для отображения значения переменной counter в интересующие вас места программы (это неплохое средство отладки).
Для цикла do-while вы можете использовать любое средство проверки логического выражения. Чтобы продемонстрировать такую многогранность, листинг 2.18 использует цикл do-while для вывода на экран алфавита.
Листинг 2.18. DWALPHA.C (вывод алфавита с помощью цикла do-while)
______________________________________________________________________
1: #lnclude <stdio.h>
2:
3: main()
4: {
5: int с;
6:
7: printf(“do-while alphabet\n");
8: с = 'A' - 1;'
9: do {
10: c++;
11: printf("%c", с);
12: } while (с < 'Z');
13: return 0;
14: }
_____________________________________________________
Оператор for
Когда вам точно известно (или ваша программа может заранее вычислить), сколько раз должен выполниться блок операторов, из всех циклов лучше всего выбрать оператор for. В псевдокоде оператор for выглядит следующим образом:
for (выражение 1; выражение 2; выражение З;) {
оператор;
}
На первый взгляд, оператор for может показаться сложным, но он станет понятнее, если его представить в виде эквивалентного оператора while. Следующий псевдокод аналогичен по своему результату предыдущему оператору for:
выражение 1;
while (выражение 2;) {
оператор;
выражение З;
}
Теоретически вы можете использовать любые выражения в цикле for, но для выражения 1, выражения 2 и выражения З некоторые типы выражений оказываются предпочтительнее. Например, для того чтобы с помощью цикла for посчитать от 1 до 10, возьмем переменную i типа int. Тогда мы можем записать:
for (i = 1; i <= 10; i++) /* выражения 1, 2 и З */
printf("i == %d\n", i); /* оператор */
Первое выражение цикла for в этом примере присваивает начальное значение переменной i, равное 1; это действие выполняется только один раз, перед самым началом цикла. Второе выражение обычно является выражением отношения, в данном случае оно будет равно "истине", если значение i меньше или равно 10. Третье, и последнее, выражение увеличивает на единицу значение переменной i, приближая, таким образом, цикл к концу.
Предыдущий оператор for, возможно, легче изобразить в виде эквивалентного цикла while:
i = 1; /* выражения 1 */
while (i <= 10) { /* выражения 2 */
printf(“i == %d\n", i); /* оператор */
i++; . /* выражения 3 */
}
Сравните версии for и while и обратите внимание на расположение каждого выражения и оператора. Обе версии функционально идентичны, и, по крайней мере, в этом простом примере компилятор сгенерирует для каждого варианта идентичный код.
Листинг 2.19 использует цикл for для отображения набора видимых ASCII-символов (это символы с значениями от 32 до 127).
Листинг 2.19. ASCII.С (отображение АЗСП-символов с помощью цикла for)
__________________________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: unsigned char с;
6:
7: for (с = 32; (с < 128); c++) {
8: if ((с % 32) == 0) printf("\n");
9: printf("%c", с);
10: }
11: printf("\n");
12: return 0;
13: }
______________________________________________________
Когда вы запустите программу ASCII, она отобразит на экране следующий 96-символьный отчет:
!"#$%&’()*+,-./0123456789:;<=>? @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ 'abcdefghijklinnopqrstuvwxyz{|}~
Программный цикл for (строки 7-10) первоначально устанавливает управляющую переменную с типа unsigned char равной ASCII-значению символа пробела (32). Управляющее выражение (с < 128) завершает цикл после отображения последнего символа, т.е. когда переменная с станет равной 128. Выражение c++ на каждом проходе цикла увеличивает управляющую переменную на единицу.
Оператор if, находящийся внутри оператора for, сравнивает выражение (с % 32), равное остатку от деления с на 32, с нулем. Если результат сравнения - значение "истина", то оператор printf() начинает новую строку. Замените 32 на 16 и вы получите вывод по 16 символов в строке. Выражения оператора for можно опустить, но в этом случае мы получаем бесконечный цикл:
for ( ; ; ) ; /* бесконечный цикл */
Этот цикл for не инициализирует управляющую переменную, не содержит управляющего выражения и не выполняет никаких действий, чтобы завершиться. Он также не имеет никаких операторов. Во время выполнения программа "зависает", пока не произойдет какое-нибудь внешнее событие: например, пропадет электропитание или сигнал прерывания заставит процессор начать выполнение некоторой другой программы. В отсутствие каких-либо внешних воздействий программа застрянет внутри этого цикла, "пока рак не свиснет".
Между прочим, пробел между закрывающей круглой скобкой и завершающей точкой с запятой поставлен не случайно. Без этого пробела некоторые компиляторы С (но, к сожалению, не Borland C++), выдают предупреждение, которое помогает избежать следующей ошибки:
for (i=1; i < 100; i++); /* ??? */
оператор;
Поскольку тело цикла for может быть пустым, компилятор обрабатывает эту дефектную конструкцию как два синтаксически правильных оператора - цикл for с тремя выражениями и отдельный оператор, который выполняется после завершения цикла for. В этом случае цикл работает корректно, не выполняя никаких полезных действий. Если вы захотите построить цикл for таким способом (что не рекомендуется), вставьте дополнительный пробел перед точкой с запятой.
for (i = 1; i < 100; i++) ; /* точка с запятой поставлена специально */
По соглашению пробел означает, что эта необычная конструкция построена специально и не является ошибкой ввода. И, конечно, комментарий не будет лишним.
Замечание
Поскольку выражения оператора for могут быть любыми, можно создавать циклы; которые ничего не делают, или же результаты их работы полностью определяются содержанием их выражений. Такие программы тяжело писать, еще труднее сопровождать, и они не поддаются серьезной отладке. Лучше всего использовать циклы for в их обычном виде. Хотя язык С и позволяет писать непрозрачные операторы, из этого не следует, что вы должны это делать.
Оператор break
Иногда полезно прервать выполнение цикла while, do-while или for. Листинг 2.20 демонстрирует, как оператор break может прервать программу подобно тому, как срочный выпуск новостей прерывает телевизионную передачу.
Листинг 2.20. BREAKER.С (действие оператора break)
_________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int count;
6:
7: printf("\n\nfor loop:\n");
8: for (count = 1; count <= 10; count++) {
9: if (count > 5) break;
10: printf("%d\n", count);
11: }
12:
13: printf("\n\nwhile loop:\n");
14: count = 1;
15: while (count <= 10) {
16: if (count > 5) break;
17: printf("%d\n", count);
18: count++;
19: }
20:
21: printf("\n\ndo/while loop;\n");
22: count = 1;
23: do {
24: if (count > 5) break;
25: printf("%d\n", count);
26: count++;
27: } while (count <= 10);
28:
29: return 0;
30: }
__________________________________________________
BREAKER выполняет операторы for (строки 8-11), while (строки 15-19) и do-while (строки 23-27). Каждый оператор считает от 1 до 10, используя переменную count. Каждый цикл также выполняет оператор break до того, как переменная count достигнет своего конечного значения (строки 9, 16-24). И потому эти циклы заканчиваются преждевременно. Данный метод очень полезен, особенно, когда нужно в спешке "спасаться бегством" из цикла: например, для того, чтобы отреагировать на ошибочные условия.
Операторы break можно использовать и в других случаях. (Вы уже встречали их раньше внутри операторов switch.) Например, так называемый вечный цикл может воспользоваться оператором break для укорачивания длительности "вечности":
for ( ; ; ) ; { /* Выполнять цикл "вечно" */
... /* различные операторы */
if (выражение) /* Если выражение окажется истинным */
break; /* прервать цикл */
Не имея управляющих выражений, оператор for выполняется "вечно" - до тех пор, пока выражение не станет истинным, заставив внутренний оператор if выполнить break, который ''раньше времени" обрывает бесконечное выполнение.
Оператор continue
Оператор continue похож на break, но он заставляет цикл прервать текущую итерацию и начать следующую. Листинг 2.21 демонстрирует различие между операторами break и continue.
Листинг 2.21. CONTINUE.С (действия оператора continue)
___________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int count;
6:
7: printf("\nStarting for loop with continue.. .\n'');
8: for (count = 1; count <= 10; count++) {
9: if (count > 5) continue;
10: printf("%d\n", count);
11: }
12: printf("After for loop, count = %d\n", count);
13:
14: printf("\n\nStarting for loop with break...\n");
15: for (count = 1; count <= 10; count++) {
16: if (count >5) break;
17: printf("%d\n", count);
18: }
19: printf("After for loop, count = %d\n", count);
20: return 0;
21:}
______________________________________________________
Чтобы посчитать от 1 до 10, программа CONTINUE использует два цикла for (строки 8-11 и 15-18). Строка 9 первого цикла for выполнит оператор continue, если переменная count будет больше 5. Строка 16 выполнит оператор break при том же условии. За исключением этого различия приведенные циклы идентичны.
Программа отображает значение переменной count внутри и снаружи циклов. После выхода из второго цикла оно равно 6. Оператор continue начинает первый цикл с его вершины, пропуская оператор printf() на строке 10, но позволяет переменной достичь своего конечного значения. Оператор break немедленно осуществляет выход из цикла, также пропуская printf() на строке 17 и не позволяя расти переменной count.
Оператор goto
Оператор goto заставляет программу выполнить другой оператор и, начиная с его позиции, продолжить выполнение последующих операторов. Поскольку goto позволяет "прыгать" в любое место программы, его использование напоминает желание оставить проторенную дорогу ради замеченной вами извилистой тропинки в поле.
На первый взгляд, goto кажется "ну очень" полезным оператором. Однако на практике он дает программистам слишком большую свободу "скакать" по программе куда угодно. В лучшем случае, результаты программы, которая имеет несколько операторов goto, просто трудно понять, в худшем - такая программа вообще работает плохо.
Если вы не можете обойтись без goto, вставьте метку (неиспользованный ранее идентификатор и двоеточие) в нужном месте программы (перед оператором, на который вы хотите "прыгнуть"). Выполните goto МЕТКА, и "течение" вашей программы будет направлено "по новому руслу". Листинг 2.22 продемонстрирует, как использовать goto для счета от 1 до 10.
Листинг 2.22. GCOUNT.C (счет от 1 до 10 с помощью оператора goto)
____________________________________________________________
1: #include <stdio.h>
2:
3: main()
4: {
5: int count =1;
6:
7: TOP:
8: printf("%d\n", count);
9: count++;
10: if (count <= 10) goto TOP;
11: return 0;
12: }
_____________________________________________
Метки TOP: в строке 7 помечает "пункт назначения" для оператора goto. Оператор if на строке 10 проверяет значение целой переменной count. Если оно меньше или равно 10, goto передаст управление на метку ТОР:, чтобы снова выполнить операторы printf() и count++, пока переменная count не станет больше 10. Когда все-таки это произойдет, оператор if не выполнит goto и программа перейдет к строке 11, где и наступит ее конец.
Программа, конечно, работает, но ей не хватает наглядности циклов while, do-while и for, описанных в этой главе. Понимание goto-варианта требует отслеживания вручную работы каждого оператора. Отладка сложных программ с операторами goto - это кропотливый, если не сказать каторжный, труд. Мало того, компилятор просто ничего не сможет сделать, чтобы оптимизировать вашу goto-программу. Для компилятора цикл на строках 7-10 (если это можно назвать циклом) - просто набор несвязанных операторов.
Тем не менее, вы должны знать, как использовать операторы goto, чтобы не спотыкаться о них в чужой программе. Но не используйте их в своих собственных. Это тот оператор, без которого можно спокойно обойтись.
Останов программы с помощью exit
"Оставленные на произвол судьбы" программы останавливаются после выполнения последнего оператора функции main(). Чтобы избежать предупреждения компилятора, main() должна заканчиваться оператором
return value;
где value - любое целое выражение. Значение, возвращаемое из функции main(), доступно программе, которая выполняла данную, - обычно это принадлежащий DOS файл COMMAND.СОМ, но возможны и другие оболочки, например какой-нибудь командный файл или специальная программа, разработанная для выполнения других.
Другой способ остановить программу - вызвать функцию exit(), которая определена в заголовочном файле STDLIB.H. Выполнение оператора
exit(0);
немедленно завершает программу, закрывает все открытые файлы и выполняет другие разнообразные завершающие действия. Значение в круглых скобках возвращается DOS (или другой программе, которая выполняла данную).
Листинг 2.23 показывает, как использовать exit() для изменения нормального течения программы. YESNO может служить практической утилитой, которую вы можете добавить в командные файлы DOS для организации ввода "Да-Нет". Скомпилируйте листинг, но пока не запускайте его. Следующий листинг покажет, как использовать программу YESNO.
Замечание
Следующие два листинга вы должны выполнять из командной строки DOS. Поскольку YESNO вызывается командным файлом, вы не можете выполнять эти программы как EasyWin-приложения под управлением IDE. Скомпилируйте YESNO.С, затем введите в DOS следующую команду:
bcc yesno.c
Листинг 2.23. YESNO.С (завершение программы с помощью exit())
___________________________________________________
1: #include <stdio. h>
2: #include <stdlib.h>
3: #include <ctype.h>
4:
5: main()
6: {
7: char answer;
8:
9: printf("\nType Y for yes, N for no: ");
10: answer = toupper(getchar());
11: if (answer == 'Y')
12: exit(1);
13: else
14: exit(0);
15: printf(“Thls statement never executes!\n");
16: return 99; /* This statement doesn't execute either! */
17: }
___________________________________________________
Строки 12 и 14 вызывают функцию exit(), передавая в круглых скобках 1 или 0 в качестве аргументов. Переданное значение 1 означает, что была нажата клавиша <Y>; 0 означает любую другую клавишу. Строки 15-16 никогда не выполняются.
Листинг 2.24 представляет собой командный файл, который выполняет программу YESNO. Из командной строки DOS для запуска командного файла введите
testyn
Затем введите <Y>, чтобы повторить вопрос, требующий вашего ввода, или <N> для выхода из программы.
Листинг 2.24. TESTYN.BAT (командный файл для запуска YESNO)
_____________________________________________________
echo off
rem
rem - Test YESNO. С
rem
goto START
:YES
echo You answered yes!
:START
echo.
echo Continue program?
yesno
if errorlevel == 1 goto YES
echo You answered no!
Командный файл проверяет значение системной переменной с именем errorlevel, использующей команду if, которая работает не так, как оператор if языка С. В командных файлах if выражение == значение будет истинным, когда выражение больше или равно значению. (По неизвестной причине символ == в командных файлах DOS эквивалентен С-операции >=.) При положительном ответе командный файл использует команду goto для "перескока" на метку :YES. Отрицательный ответ выведет завершающее сообщение, подтверждающее ваш ввод.
Вы, возможно, захотите сохранить скомпилированную программу вместе с вашими другими системными утилитами. Используйте ее в любых командных файлах, требующих от пользователей ответа "Да" или "Нет".
Резюме
• Программы выполняют действия, вычисляя выражения и выполняя операторы.
• Выражения обозначают операции, которые выполняют программы.
• Операторы языка С: *, /, +, - и % выполняют арифметические операции над операндами. Операторы отношений: <, <=, >, >=, == и != сравнивают два операнда. Логические операторы: && и || сравнивают выражения, имеющие значения "истина" или "ложь". Оператор отрицания ! инвертирует значение логического выражения.
• Оператор инкремента ++ прибавляет единицу к своему операнду. Оператор декремента -- вычитает единицу из своего операнда. Эти операторы могут стоять как перед, так и после своих операндов. Выражения value++ и ++value одинаково воздействуют на значение переменной value.
• Кроме выполнения операций, все выражения имеют значения. Например, значение выражения (А = В) равно значению, присвоенному переменной А. Значение value++ равно значению value до выполнения операции инкремента, значение ++value равно значению value после выполнения инкремента.
• Поразрядные операторы &, |, ^, <<, >> и ~ выполняют булевы логические операции над битами их операндов.
• Операторы присваивания позволяют сократить операторы типа value = value + 10 до value +=10. Операторы присваивания в языке С: *=, /=, +=, - =, %=, <<=, >>=, &=, ^= и |=.
• Все операторы имеют фиксированные приоритеты. В смешанных выражениях подвыражения, использующие операторы с более высоким приоритетом, вычисляются раньше подвыражений, которые используют операторы с более низким приоритетом.
• Выражения вычисляются в соответствии с порядком, определенным для их операторов. Большинство операторов (но не все) имеют порядок вычисления слева направо.
• Для управления порядком вычисления в выражениях всегда можно использовать круглые скобки, изменяя таким образом действующие по умолчанию приоритеты операторов и их ассоциативность.
• Используйте операторы if и if-else для принятия решений путем выбора того или иного оператора в соответствии со значением выражения.
• Используйте операторы switch для упрощения длинных вложенных конструкций If-else. Убедитесь, что каждый блок case заканчивается оператором break.
• Используйте операторы while для создания циклов, которые выполняются, пока проверяемое условие остается истинным. В цикле while условное выражение проверяется в самом начале и, следовательно, если оно изначально ложно, то операторы цикла никогда не будут выполнены.
• Используйте операторы do-while для создания циклов, выполняющихся до тех пор, пока проверяемое условие не станет ложным. В циклах do-while условное выражение вычисляется в конце, и, следовательно, их операторы выполняются, по крайней мере, один раз
• Оператор for, возможно, самый популярный оператор в языке С. Используйте его, когда вы знаете или можете вычислить заранее, сколько итераций должен иметь этот цикл.
• Оператор break немедленно завершает циклы while, do-while.
• Оператор continue заставляет цикл немедленно начать следующую итерацию.
• Оператор goto передает управление на любой помеченный оператор. Поскольку программы, использующие много операторов goto, как правило, трудны для понимания и отладки, оператор goto лучше не использовать вообще.
• Программы обычно заканчиваются выполнением оператора return функции main(), но завершение программы возможно и с помощью функции exit().
Упражнения
2.1. Напишите программу, которая определяет, четное или нечетное заданное целое число.
2.2. Беззнаковое дополнение десятичного числа 88 равно 65447 (0xffa7 в шестнадцатеричном формате). Какое число, подвергнутое вместе с 88 операции исключающего ИЛИ, даст тот же результат?
2.3. Замените цикл for в листинге 2.19 эквивалентным циклом while.
2.4. Модифицируйте листинг 2.23 так, чтобы отбраковывать все ответы, отличные от Y или N.
2.5. Напишите программу, использующую цикл, который предложит ввести значение от 1 до 100. Если введенное число окажется вне этого диапазона, программа должна вывести сообщение об ошибке и снова предложить ввод.
2.6. Используя поразрядные операторы, напишите программу, содержащую выражение, которое ограничивает диапазон значений переменной типа int в пределах от 0 до 31. [
2.7. Напишите программу, которая определяет, делится ли одно целое значение на другое без остатка.
2.8. Модифицируйте листинг 2.15 в направлении обратного счета от 10 до 1.
2.9. Чтобы преобразовать не одно значение температуры, а несколько, вам приходится перезапускать программу листинга 2.1. Добавьте в программу цикл для повторения преобразований до тех пор, пока вы не. захотите выйти из программы.
2.10. В математике факториал n! неотрицательного целого определяется равенством:
n! = 1 * 2 * . . . * n;
Другими словами, n! равен произведению целых чисел от 1 до п включительно. Напишите программу для вычисления n! для положительного целого. (Замечание: факториал нуля равен 1, а не 0. .Ваша программа должна учитывать это условие.)