Книга по работе с WinAVR и AVR Studio / AVR_02-2010
.pdf
СПРАВОЧНЫЙ МАТЕРИАЛ
Книга по работе |
|
Роман Абраш |
|||||||||
|
|
|
|
|
|
|
|
||||
с WinAVR и AVR Studio |
|
г. Новочеркасск |
|||||||||
|
E-mail: arv@radioliga.com |
||||||||||
|
|
|
|
|
|
|
% |
Остаток от целочисленного деления операндов |
|||
|
|
Продолжение. Начало в №1/2010 |
|
|
|||||||
|
|
|
|
| |
Побитовая операция ИЛИ над операндами |
||||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|||||
Указатели |
|
& |
Побитовая операция И над операндами |
||||||||
Указатель – это переменная особого рода: она содержит не |
|
^ |
Побитовая операция ИСКЛЮЧАЮЩЕЕ ИЛИ над |
||||||||
само значение переменной, а адрес той области памяти, где иско- |
|
|
операндами |
||||||||
мое значение хранится. |
|
>> |
Побитовый сдвиг влево |
||||||||
Указатель – это одна из наиболее мощных возможностей язы- |
|
<< |
Побитовый сдвиг вправо |
||||||||
ка Си и, как любое мощное средство, указатель может быть как |
|
Об операциях побитового сдвига следует упомянуть, что эти опе- |
|||||||||
чрезвычайно удобным инструментом, так и источником больших |
|
рации сохраняют знак операнда, если операнд имеет знаковый тип. |
|||||||||
проблем, если используется неумело. |
|
Унарная логическая операция всего одна: |
|||||||||
Для объявления указателя достаточно добавить к типу пере- |
|
Знак |
Операция |
||||||||
менной символ «*»: |
|
! |
Логическое отрицание (НЕ) |
||||||||
|
|
|
|
|
|
|
Логических операций над парой операндов больше: |
||||
unsigned int * iptr; |
|
|
|||||||||
|
|
Знак |
Операция |
||||||||
char * arr[]; |
|
|
|| |
Логическое ИЛИ |
|||||||
long * lptr; |
|
|
&& |
Логическое И |
|||||||
Объявленные таким образом переменные iptr, arr[] и lptr будут |
|
> |
Больше |
||||||||
указателями: iptr – указатель на число типа int, arr – массив указа- |
|
< |
Меньше |
||||||||
телей на символ, lptr – указатель на число типа long. |
|
== |
Эквивалентно |
||||||||
Чтобы обратиться к тем данным, на которые указывает пере- |
|
!= |
Не эквивалентно |
||||||||
менная-указатель, следует предварить ее имя символом «*» (такая |
|
<= |
Меньше или равно |
||||||||
операция получила название «разыменование указателя»): |
|
>= |
Больше или равно |
||||||||
iptr – адрес области памяти, хранящей число типа int |
|
Важно понимать, что для логических выражений определено |
|||||||||
*iptr – значение этого числа |
|
лишь два значения результата: ИСТИНА и ЛОЖЬ, причем истин- |
|||||||||
Очевидно, что использование указателей позволит получить |
|
ным в Си считается любое не равное нулю значение, а ложным – |
|||||||||
доступ к любой произвольной области памяти. Именно в этом и |
|
значение ноль. |
|||||||||
кроется источник возможных проблем: стоит ошибиться в значе- |
|
Вот несколько примеров логических и арифметических выра- |
|||||||||
нии переменной-указателя, как последствия обращения к данным, |
|
жений: |
|
|
|
||||||
на которые она указывает, становятся непредсказуемыми. |
|
|
|
|
|
||||||
5 + 2 * 3 – арифметическое выражение, эквивалентное 11 |
|
||||||||||
|
|
|
|
|
|
|
|
||||
Выражения |
|
(5 + 2) * 3 – арифметическое выражение, эквивалентное 21 |
|
||||||||
|
5 + var * (3 + b) – арифметическое выражение с использованием переменных |
|
|||||||||
Выражение – это запись алгоритма вычислений над операн- |
|
|
|||||||||
дами при помощи знаков математических, логических и прочих дей- |
|
(5 > 2) – логическое выражение со значением ЛОЖЬ |
|
||||||||
ствий. Другими словами: выражение – есть формула вычислений, |
|
(2-7) >= -11 – логическое выражение со значением ИСТИНА |
|
||||||||
записанная средствами языка программирования. |
|
(2-7) >= 0 – логическое выражение со значением ЛОЖЬ |
|
||||||||
|
|
|
|
||||||||
В определении выражения использован новый термин: операнд. |
|
Знаки арифметических и логических операций называются |
|||||||||
Операнд – это выражение, над которым выполняется какое-либо |
|
по-другому операторами, однако понятие оператор более ши- |
|||||||||
действие. Как видите, операнд и выражение оказываются опреде- |
|
рокое, и включает в себя не только какое либо одно действие, |
|||||||||
ленными друг через друга. Надеюсь, для образованного человека |
|
но и целую группу действий, выполняемых по определенным |
|||||||||
не составит труда представить себе, что же такое выражение на |
|
правилам. |
|
|
|
||||||
самом деле – для этого достаточно вспомнить, что такое математи- |
|
Особняком стоит операция, обозначаемая символом «?» – ус- |
|||||||||
ческое выражение. |
|
ловная операция. Эта операция имеет достаточно сложную форму |
|||||||||
В языке Си определены два типа операндов (выражений): ма- |
|
записи: |
|
|
|
||||||
тематические и логические. Математическое выражение состоит |
|
<выражение1> ? <значение1> : <значение2> |
|||||||||
из операндов, эквивалентных числам, и знаков математических |
|
Операция позволяет выбрать в качестве своего результата одно |
|||||||||
операций. Логическое выражение – это выражение отношений, т.е. |
|
из значений – значение1 или значение2 в зависимости от того, |
|||||||||
ответов на вопросы «больше?», «меньше?», «равно?» и т.д. Ана- |
|
истинно или ложно выражение1. В качестве обоих значений могут |
|||||||||
логично математическим, существуют логические действия и соот- |
|
использоваться так же выражения. |
|||||||||
ветствующие им знаки логических операций. |
|
Пример: |
|||||||||
Действия могут выполняться над двумя операндами или над |
|
x > 5 ? 4 : 8 – если x будет меньше или равно 5, то результат |
|||||||||
одним операндом – унарные операции. |
|
операции будет 8, а если x будет больше 5 – результат будет 4. |
|||||||||
В Си определены следующие унарные математические опе- |
|
Смысл в выражениях был бы невелик, если бы результат их |
|||||||||
рации: |
|
|
|
|
|
вычислений было бы невозможно присваивать значениям перемен- |
|||||
Знак |
Операция |
|
ных. Для присвоения значения переменной используется оператор |
||||||||
– |
Унарный минус, признак изменения знака числа на |
|
присваивания, обозначаемый символом «=»: |
||||||||
|
|
противоположный |
|
|
|
|
|
||||
|
|
var = 5 + 2; // присвоить переменной var значение выражения 5 + 2, т.е. 7 |
|
||||||||
+ |
Унарный плюс, означает сохранение знака числа без |
|
|
||||||||
|
|
изменения (принципиального смысла не имеет) |
|
Возвращаясь к описанию переменных, следует заметить, что |
|||||||
++ |
Знак инкремента, увеличение операнда на 1 |
|
можно присваивать значение переменной сразу в момент ее опи- |
||||||||
-- |
Знак декремента, уменьшение операнда на 1 |
|
сания: |
|
|
|
|||||
~ |
Побитовая инверсия числа |
|
|
|
|
|
|||||
int var = 5; // описанная переменная var имеет тип int и значение 5 |
|
||||||||||
Математических операций над двумя операндами больше: |
|
|
|||||||||
|
|||||||||||
Знак |
Операция |
|
В операторе присваивания левее знака «=» должно находиться |
||||||||
+ |
Сумма операндов |
|
так называемое леводопустимое выражение, т.е. либо переменная, |
||||||||
– |
Разность операндов |
|
либо такое выражение, результатом которого будет значение ука- |
||||||||
* |
Произведение операндов |
|
зателя, который покажет место в памяти для хранения результата. |
||||||||
/ |
Частное операндов |
|
Правее знака равенства может находиться любое праводопустимое |
||||||||
|
|
|
|
|
|
|
|
|
5 7 |
||
Радиолюбитель – 02/2010 |
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
||||
СПРАВОЧНЫЙ МАТЕРИАЛ
выражение, т.е. в сущности, любое выражение. Интересный момент |
* / % |
|
12 |
|||||
возникает, если и слева и справа от знака равенства используется |
+ - ( |
|
11 |
|||||
одна и та же переменная: |
|
|
>> и << |
|
10 |
|||
|
|
|
|
< |
<= > >= |
|
9 |
|
S = S + 5; |
|
|
|
|
||||
|
|
|
== |
!= |
|
8 |
||
Такое выражение следует понимать так: «присвоить перемен- |
|
|||||||
& |
|
|
7 |
|||||
ной S значение, равное сумме ее текущего значения и числа 5». |
^ |
|
|
6 |
||||
Можно записать то же самое более коротким способом: |
| |
|
|
5 |
||||
|
|
|
|
&& |
|
|
4 |
|
S += 5; |
|
|
|
|
|
|||
|
|
|
|| |
|
|
3 |
||
|
|
|
|
|
|
|||
В данном примере использован двойной оператор «+=», назы- |
? |
|
|
2 |
||||
ваемый присваивание с суммированием. Значение этого операто- |
= *= /= += -= |
&= ^= |= <<= >>= |
1 |
|||||
ра то же самое: увеличение значения переменной на число правее |
Так как запомнить все приоритеты довольно непросто, жела- |
|||||||
оператора. Кроме присваивания с суммированием допустимы ана- |
тельно на первых порах (и не возбраняется вообще всегда) указы- |
|||||||
логичные комбинации для присваивания с разностью, с умножени- |
вать порядок вычислений при помощи скобок. |
|
||||||
ем, делением или остатком от деления, записываемым соответ- |
Использование операторов ++ и -- совместно с указателями |
|||||||
ственно так: «–=», «*=», «/=» или «%=». Любой такой оператор «раз- |
приводит к необходимости помнить о порядке выполнения опе- |
|||||||
ворачивается» аналогично рассмотренному: |
раций: |
|
|
|
||||
|
|
|
|
|
|
|||
a *= 2 |
равносильно |
a = a * 2 |
|
// вариант 1 |
|
|
||
a /= 2 |
равносильно |
a = a / 2 |
|
ptr = &temp; |
|
|
||
a -= 2 |
равносильно |
a = a – 2 |
|
var = *ptr++; |
|
|
||
a %= 2 |
равносильно |
a = a % 2 |
|
// вариант 2 |
|
|
||
Несколько слов о присваивании значений переменным типа |
ptr = &temp; |
|
|
|||||
указатель. Как было сказано, указатель есть ни что иное, как адрес |
var = (*ptr)++; |
|
|
|||||
|
|
|
||||||
объекта в памяти, соответственно в качестве значения можно ему |
Оба варианта дадут одно и то же значение var, но совсем раз- |
|||||||
присваивать только адрес. Делается это при помощи унарного опе- |
ные значений для temp и ptr: в первом случае переменная temp не |
|||||||
ратора взятия адреса & (не путать с побитовой операцией И !!!): |
изменится, но увеличится на 1 значение указателя ptr; во втором |
|||||||
|
|
|
|
случае неизменным останется значение указателя, но изменится |
||||
int *ptr; |
|
|
|
|||||
|
|
|
содержимое переменной, на которую он указывает, т.е. temp уве- |
|||||
int var; |
|
|
|
|||||
|
|
|
личится на 1. |
|
|
|||
ptr = &var; |
|
|
|
Операторы ++ и -- с указателями работают особым образом: |
||||
|
|
|
они изменяют значение указателя на величину, равную размеру |
|||||
|
|
|
|
|||||
В этом примере объявлен указатель ptr на переменную типа int |
типа элемента, на который указатель указывает. То есть если ука- |
|||||||
и переменная var этого типа; далее происходит присваивание ука- |
затель объявлен как int *ptr, то ptr++ изменит значение указателя |
|||||||
зателю значения адреса переменной var. |
на 2, если long *ptr, то ptr++ увеличит указатель на 4 и т.д. Увеличе- |
|||||||
Теперь операторы |
|
|
ние указателя на 1 происходит только для типа указателя (void *) |
|||||
|
|
|
|
(«абстрактный указатель», просто адрес байта) или (char *). |
||||
*ptr = 12; |
|
|
|
|||||
|
|
|
В любом выражении допустимо комбинировать логические и |
|||||
|
|
|
|
|||||
и |
|
|
|
арифметические выражения, в этом случае следует считать, что |
||||
|
|
|
|
логическому значению ЛОЖЬ соответствует арифметическое зна- |
||||
var = 12; |
|
|
|
|||||
|
|
|
чение ноль, а значению ИСТИНА – арифметическое значение 1: |
|||||
приведут к одинаковому результату. |
|
|
|
|
||||
var = (5 < 2) + 2; |
// переменной var будет присвоено значение 2 |
|||||||
Иногда требуется обратиться к какому-либо участку памяти, |
||||||||
который явно не является конкретной переменной, т.е. адрес учас- |
var = (5 >= 2) + 2; |
// переменной var будет присвоено значение 3 |
||||||
тка известен, но соответствующего ему описанного в программе |
Особое внимание следует обращать на приоритет операций: |
|||||||
объекта нет. В этом случае следует использовать прием, назван- |
|
|
|
|
||||
var = (5 > 2) + 2; |
// var = 3 |
|
||||||
ный явным приведением типа: |
|
|
|
|||||
|
|
|
|
var = 5 > 2 + 2; |
// var = 0 |
|
||
ptr = (int *)0x1000; |
|
|
|
|||||
|
|
Во втором выражении более высокий приоритет имеет ариф- |
||||||
|
|
|
|
|||||
Пример показывает, как указателю ptr присваивается значе- |
метическая операция сложения, поэтому переменной var будет |
|||||||
ние адреса 0x1000, т.е. происходит «обман» бдительного компиля- |
присвоено значение выражения 5 > 4, что ЛОЖНО, т.е. var будет |
|||||||
тора: ему подсовывают указание воспринимать число, как адрес |
равна 0. |
|
|
|||||
переменной типа int, хотя не известно, что именно находится по |
Наиболее простым и универсальным советом будет использо- |
|||||||
этому адресу фактически. Приведение типов приходится делать |
вание скобок везде, где необходимо точно гарантировать поря- |
|||||||
всегда, когда типы операндов не соответствуют желаемым. |
док вычислений – в этом случае можно считать (конечно, лишь |
|||||||
|
|
|
|
условно!) все операторы равноприоритетными, а порядок вычис- |
||||
Приоритеты операций |
|
|
лений определять лишь с помощью скобок. Возможно, это приве- |
|||||
Каждая операция имеет свой приоритет, который определяет |
дет к некоторой излишней «скобочности» программы, зато гаран- |
|||||||
порядок вычисления операции в выражении: если слева направо |
тирует, что планы программиста не разойдутся с мнением компи- |
|||||||
следуют подряд несколько операций одинакового приоритета, |
лятора. |
|
|
|||||
порядок их вычисления не определен, но результат вычисления |
Упомянутые ранее унарные операторы инкремента и декремен- |
|||||||
гарантированно будет верным, в противном случае вычисляются |
та – одна из интересных особенностей языка Си. Эти операторы |
|||||||
прежде операции с наибольшим приоритетом, а затем – с мень- |
могут применяться только к операнду-переменной, и ни в коем слу- |
|||||||
шим. Порядок вычислений может быть изменен при помощи круг- |
чае не к операнду-константе. Но главное, они могут быть префик- |
|||||||
лых скобок: выражение в скобках вычисляется всегда прежде ос- |
сными и постфиксными, т.е. записываться либо слева от пере- |
|||||||
тальных, т.е. имеет наивысший приоритет. |
менной, либо справа: |
|
|
|||||
В таблице перечислены все операции с указанием их приори- |
|
|
|
|
||||
int var = 1; |
|
|
||||||
тетов: |
|
|
|
|
|
|||
Операция |
|
Приоритет |
var++; |
// теперь var = 2 |
|
|||
! ~ «унарные плюс и минус» ++ -- ? операция |
--var; |
// теперь var = 1 |
|
|||||
разименования указателя * |
13 |
|
var--; |
// var = 0 |
|
|||
5 8 |
Радиолюбитель – 02/2010 |
|
СПРАВОЧНЫЙ МАТЕРИАЛ
Казалось бы, к чему два способа выполнить одно и то же? Все |
Операторы |
|||
дело в том, как будет использован результат оператора при вычис- |
Оператор if |
|||
лении выражений. Вот пример: |
Условный оператор if позволяет выполнить группу операторов |
|||
|
|
|
лишь в случае, если какое-то логическое выражение имеет значе- |
|
int var = 5; |
|
|
||
|
|
ние ИСТИНА. Дополнительно может быть определена группа опе- |
||
int b = var++ - 5; |
// var становится равным 6 |
|
раторов, выполняемых в случае, если заданное выражение имеет |
|
int ñ = ++var – 5; |
// var становится равным 7 |
|
значение ЛОЖЬ. |
|
|
|
|||
Переменная b окажется равной 0, а переменная с будет рав- |
Оператор if может быть описан по одному из следующих шаб- |
|||
на 2. Все дело в том, что при постфиксном операторе ++ снача- |
лонов: |
|||
ла используется значение переменной в выражении, а потом вы- |
1. if (<проверка>) <оператор1>; |
|||
полняется оператор инкремента, а при префиксной записи – сна- |
2. if (<проверка>) <оператор1>; else <оператор2>; |
|||
чала выполняется оператор инкремента, а потом уже обновлен- |
3. if (<проверка>) { <список операторов1 ;>} |
|||
ное значение переменной используется при вычислении выра- |
4. if (<проверка>) { <список операторов1 ;>} else {список |
|||
жения. |
|
|
операторов2 ;} |
|
Операторы побитового сдвига << и >> выполняют перемеще- |
Здесь проверка – это логическое выражение, проверяемое на |
|||
ние битов операнда слева на количество разрядов, заданное выра- |
истинность, оператор1 – оператор, выполняемый только в случае |
|||
жением справа. Как уже было сказано, эти операторы сохраняют |
истинности проверки, оператор2 выполняется только в случае лож- |
|||
знак левого операнда, если операнд – число со знаком. Заполне- |
ности проверки. Под списком операторов подразумевается любая |
|||
ние «освобождаемых» разрядов для беззнаковых чисел происхо- |
последовательность любых операторов, причем, как и одиночные, |
|||
дит нулями. |
|
|
эти списки выполняются лишь в соответствующем случае в зависи- |
|
Унарные логические операции, и операция побитовой инвер- |
мости от результата проверки. |
|||
сии имеют лишь один вариант вычисления. Но, к сожалению, на |
Обратите внимание, что для 3 и 4 форм оператора if наличие |
|||
этом простые операторы заканчиваются, все прочие операторы |
завершающей оператор точки с запятой не является необходи- |
|||
имеют более сложную форму записи и, соответственно, выпол- |
мым. В данном случае закрывающая фигурная скобка однознач- |
|||
няют более сложные действия, заслуживая отдельного рассмот- |
но свидетельствует о завершении оператора, в то время как в |
|||
рения. |
|
|
списках каждый из операторов должен завершаться точкой с за- |
|
|
|
|
пятой (если, конечно, оператор в списке не ограничивается фи- |
|
Тип результата выражения |
гурными скобками). |
|||
Выражения могут состоять из операндов различных типов, на- |
Шаблоны оператора if не накладывают никаких ограничений |
|||
пример, не возбраняется осуществлять суммирование переменных |
на то, какие операторы могут быть на месте оператра1 или опера- |
|||
типа char и long, double и unsigned int и т.д. И результат этих вы- |
тора2 или же в соответствующих списках. Это означает, что среди |
|||
числений так же может быть присвоен переменной любого типа. |
них так же может использоваться оператор if. В этом случае может |
|||
Как же ведет себя компилятор в этом случае? |
возникнуть проблема с определением принадлежности части опе- |
|||
Рассмотрим следующий пример: |
ратора else: |
|||
|
|
|
|
|
|
|
|
if (a < 2) if (c > 5) result = 5; else result = 0; |
|
int x1 = 20000, x2 = 15; |
|
|
||
long res; |
|
|
В этом примере стоит задуматься, к какому из операторов if |
|
res = x1 * x2; |
|
|
относится часть else? Язык Си однозначно вводит следующее пра- |
|
|
|
вило: всякий else относится к ближайшему предыдущему if, не име- |
||
Переменной res (длинное целое) присваивается результат про- |
ющему своего else. Таким образом, в вышеприведенном примере |
|||
изведения двух чисел (int), значения переменных укладываются в |
else относится к оператору if(c > 5). |
|||
допустимые диапазоны, ожидается, что значение res будет 300000. |
Очевидно, что запись оператора в одну строку не способ- |
|||
Однако, это ошибочное предположение. |
ствует улучшению восприятия программы. Благодаря тому, что |
|||
Стандартом Си определено, что тип результата выражения оп- |
язык Си допускает использование любых «пробельных» раздели- |
|||
ределяется по набольшему типу входящего в него операнда, т.е. в |
телей, гораздо более удачной следует считать следующую форму |
|||
данном случае результат x1 * x2 будет иметь тип int. Разумеется, |
записи: |
|||
произведение чисел вызовет переполнение результата, и все, что |
|
|||
if (a < 2) |
||||
«не влезло» в размер, будет отброшено, т.е. в результате произве- |
||||
дение будет равно -27680. |
if (c > 5) |
|||
result = 5; |
||||
Такой странный результат получился из-за несоответствия типа |
||||
результата выражения и типа операндов. Чтобы получить верный |
else |
|||
результат, нужно использовать один из операндов типа long: |
result = 0; |
|||
|
|
|
Благодаря использованию табуляций вышеприведенный при- |
|
res = (long)x1 * x2; |
|
|
||
|
|
мер даже визуально показывает принадлежность else соответству- |
||
// èëè òàê |
|
|
||
|
|
ющему оператору if. |
||
res = x1 * (long)x2; |
|
|
||
|
|
Следует отметить, что пример показывает одну из проблем, ха- |
||
Такая запись показывает, что компилятор должен воспринимать |
рактерную для оператора if – так называемую неполную проверку |
|||
операнд x1, как число типа long. Принудительное указание в круг- |
вариантов. Она заключается в следующем: если, например, пере- |
|||
лых скобках нового типа для выражения называется явным преоб- |
менная а равна 5, то переменная result оказывается с неопреде- |
|||
разованием или приведением типа. Ошибкой будет попытка за- |
ленным значением. Возможно, она получит (или получила) нужное |
|||
дать тип для результата всего выражения: |
значение в другом операторе, возможно переменная a никогда не |
|||
|
|
|
получит значение больше 1 (но тогда, к чему вообще проверка?!), |
|
res = (long) (x1 * x2); |
|
|
||
|
|
однако если это не так – проблем не миновать. |
||
Такая запись ничем не отличается от первоначальной и даст |
|
Итак, при использовании оператора if желательно не допускать |
||
неверный результат (т.к. в этой записи явно указано то преобразо- |
двусмысленностей его поведения при различных результатах про- |
|||
вание, которое компилятор и так делает автоматически). |
веряемых выражений. Так же не лишним будет использование фи- |
|||
Компилятор всегда приводит автоматически типы всех операн- |
гурных скобок для определения границ списка операторов, даже |
|||
дов в выражении к наибольшему типу, т.е. от меньшего типа к боль- |
если фактически используется единственный оператор. |
|||
шему. Важно помнить, что если в выражении все операнды типа |
|
|||
char, то они будут приведены к типу int, если нет явного преобразо- |
Оператор switch |
|||
вания типов. При помощи приведения типа программист может из- |
Анализ разных значений переменной при помощи оператора if |
|||
менить его поведение, что чревато большими ошибками. |
может вылиться в «многоэтажную» конструкцию типа такой: |
|||
|
|
|
5 9 |
Радиолюбитель – 02/2010 |
|
|
|
|
|
|
|
|
|
|
СПРАВОЧНЫЙ МАТЕРИАЛ |
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
||||||||||
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
for ([список операторов1]; [условие продолжения]; [список |
|||||
|
|
if (x == 0) x = 5; |
|
|
|
||||||||
|
|
|
|
операторов 2]) [тело цикла]; |
|||||||||
|
|
else |
|
|
|
Здесь список операторов1 – перечень операторов, разделен- |
|||||||
|
|
if (x == 1) x = 12; |
|
|
|
||||||||
|
|
|
|
ных запятыми, которые должны быть выполнены перед началом |
|||||||||
|
|
else |
|
|
цикла; условие продолжения – логическое выражение, истинное |
||||||||
|
|
if (x == 3) x = 36; |
|
|
значение которого есть непременное условие очередной итерации |
||||||||
|
|
// и т.д. до перебора всех вариантов значения х |
|
|
цикла; список операторов2 – перечень операторов, разделенных |
||||||||
|
|
|
|||||||||||
|
|
Это очень неудобная конструкция. Заменить ее призван опера- |
|
запятыми, выполняемых после каждой итерации цикла; тело цик- |
|||||||||
|
|
тор switch, который записывается по следующему шаблону: |
|
ла – это один оператор или ограниченная фигурными скобками пос- |
|||||||||
|
|
switch (<выражение>){ |
|
ледовательность операторов, выполняемых на каждой итерации. |
|||||||||
|
|
case <значение1>: [операторы1]; |
|
|
Самое важное, что любая из этих частей оператора for может |
||||||||
|
|
case <значение2> : [операторы2]; |
|
отсутствовать, о чем свидетельствуют квадратные скобки в шаб- |
|||||||||
|
|
// и т.д. до конца вариантов значений |
|
лоне. |
|||||||||
|
|
[default : [операторы по умолчанию]]; |
|
|
Пример некоторых вариантов оператора цикла for: |
||||||||
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0, s = 0; i < 10; s += i++); |
|
|||||||
|
|
Здесь выражение – это выражение, значения которого анали- |
|
|
|||||||||
|
|
зируются, значение1 – это один из вариантов анализируемого ре- |
|
for (;;); |
|
||||||||
|
|
зультата выражения, операторы1 - соответствующая этому вари- |
|
for (;i < 10;); |
|
||||||||
|
|
|
for (i = 0, j = 9, s = 0; i < 10; i++, j—) s += arr[i][j]; |
|
|||||||||
|
|
анту обработка, значение2 и операторы2 – это соответственно |
|
|
|||||||||
|
|
следующее анализируемое значение и соответствующая ему об- |
|
|
Первый цикл примера вычисляет сумму всех чисел от 0 до 9 и |
||||||||
|
|
работка и т.д. Операторы по умолчанию выполняются в том слу- |
|
помещает результат в переменной s. Рассмотрим более подробно, |
|||||||||
|
|
чае, если выражение имеет значение, не соответствующее ни од- |
|
как это происходит. |
|||||||||
|
|
ному из определенных в строке с ключевым словом case. Операто- |
|
|
Сначала выполняются операторы i=0 и s = 0, которые инициа- |
||||||||
|
|
ры по умолчанию и ключевое слово default могут отсутствовать. |
|
лизируют (ранее объявленные) переменные i и s. Если в момент |
|||||||||
|
|
Результат анализируемого выражения обязательно должен быть |
|
объявления эти переменные уже проинициализированы, эта часть |
|||||||||
|
|
одного из целочисленных типов, а в качестве значений case обяза- |
|
оператора for может отсутствовать. Затем происходит проверка ус- |
|||||||||
|
|
тельно должны использоваться константы или выражения, вычис- |
|
ловия выполнения итерации цикла, т.е. проверка i < 10. Разумеет- |
|||||||||
|
|
ляемые на основе констант. Допускается использовать символы (в |
|
ся, это истинное выражение, и, значит, происходит выполнение тела |
|||||||||
|
|
апострофах). |
|
цикла – очередная (первая) итерация. В рассматриваемом приме- |
|||||||||
|
|
Оператор действует следующим образом: вычисляется значе- |
|
ре тело цикла отсутствует, поэтому просто выполняется заверша- |
|||||||||
|
|
ние выражения, результат проверяется на совпадение с одним из |
|
ющая часть, которая состоит из оператора s += i++, который одно- |
|||||||||
|
|
указанных в строчках case. Если найдено совпадение, то, начиная с |
|
временно выполняет 2 действия: накапливает в переменной s сум- |
|||||||||
|
|
этого места и вплоть до конца оператора (т.е. до закрывающей |
|
му значений переменной i, а затем увеличивает значение i на 1. |
|||||||||
|
|
фигурной скобки), выполняются все обозначенные операторы, в том |
|
После этих действий снова осуществляется проверка условия, за- |
|||||||||
|
|
числе и те, которые относятся к другим, «нижеследующим» за най- |
|
тем очередная итерация и т.д. до тех пор, пока значение i не станет |
|||||||||
|
|
денным значениям. Если совпадений не найдено, то выполнятся |
|
равным 10. В этот момент условие i < 10 станет ложным, и выполне- |
|||||||||
|
|
операторы по умолчанию, если они определены, в противном слу- |
|
ние оператора for закончится. |
|||||||||
|
|
чае не будет выполнено никаких действий. Пример: |
|
|
Продолжим рассмотрение примеров операторов for. Второй |
||||||||
|
|
|
|
|
|
|
цикл, единожды начавшись, никогда не закончится, т.е. это беско- |
||||||
|
|
int res; |
|
|
|||||||||
|
|
|
|
нечный оператор, или бесконечный цикл. В этом случае отсутствие |
|||||||||
|
|
switch (x){ |
|
|
выражения проверки условия трактуется как истинное выраже- |
||||||||
|
|
case 0 : res = 1; |
|
|
ние, т.е. «нет требований к условию – значит, любые варианты |
||||||||
|
|
case 1 : res = 2; |
|
|
подходят». |
||||||||
|
|
default : res = 3; |
|
|
|
Третий пример показывает оператор цикла, который может быть |
|||||||
|
|
} |
|
|
|
|
бесконечным, если переменная i < 10. Т.е. для того, чтобы этот цикл |
||||||
|
|
|
|
|
|
|
|||||||
|
|
Если x в этом примере равен 5, то значение переменной res |
|
не смог «завесить» программу, необходимо, чтобы либо к началу |
|||||||||
|
|
станет равно 3. Однако, если x будет равен 0, то res все равно ока- |
|
цикла переменная i уже содержала значение 10 или более, либо |
|||||||||
|
|
жется равным 3, т.к. поочередно выполнятся все операторы, начи- |
|
каким-то образом во время выполнения цикла значение i должно |
|||||||||
|
|
ная с того, что указан в строке case 0. Чтобы этого бессмысленного |
|
быть изменено. |
|||||||||
|
|
поведения не происходило, введен особый оператор break, кото- |
|
|
Наконец, четвертый пример показывает, как вычислить сумму |
||||||||
|
|
рый вызывает прекращение работы оператора switch досрочно, т.е. |
|
«правой» диагонали матрицы arr размером 10x10. |
|||||||||
|
|
этот оператор является последним исполняемым оператором внут- |
|
|
Следует отметить, что допускается11 внутри списка операто- |
||||||||
|
|
ри switch. Таким образом, правильная запись желаемого алгорит- |
|
ров1 использовать определение переменных, однако, определен- |
|||||||||
|
|
ма должна быть такой: |
|
ные таким образом переменные, считаются существующими толь- |
|||||||||
|
|
|
|
|
|
|
ко пока цикл выполняется, и «исчезают» после его завершения: |
||||||
|
|
int res; |
|
|
|||||||||
|
|
|
|
|
for (int i=0; i < 10; i++) a += i; |
||||||||
|
|
switch (x){ |
|
|
|
Оператор for имеет довольно много вариантов и возможнос- |
|||||||
|
|
case 0 : res = 1; break; |
|
|
тей, однако не стоит увлекаться предоставляемой гибкостью. Чем |
||||||||
|
|
case 1 : res = 2; break; |
|
|
более простая форма оператора будет избрана, тем меньше веро- |
||||||||
|
|
default : res = 3; |
|
|
ятность ошибки программирования: |
||||||||
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// первый способ |
|
|
|
|
|
|
|
|
В этом случае для x равного 0, 1 и 5 будут получены соответ- |
|
|
|
|
|
|
|||||
|
|
ственно значения res 1, 2 и 3. Для последнего варианта наличие |
|
for (int i, j=9, s; i < 10; s += arr[i++][j—]); |
|
||||||||
|
|
«прекращающего» оператора не требуется, это очевидно. |
|
// второй способ |
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
Оператор for |
|
int i=0, s=0, î = 9; |
|
||||||||
|
|
|
for (; i<10; i++) { |
|
|||||||||
|
|
При реализации многих алгоритмов бывает необходимо повто- |
|
|
|||||||||
|
|
рить некую последовательность действий несколько раз. Такое по- |
|
|
s += arr[i][j]; |
|
|||||||
|
|
вторение получило наименование цикла, а оператор, для соответ- |
|
} |
j—; |
|
|||||||
|
|
ствующего действия – оператором цикла. |
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|||||
|
|
В Си имеется несколько операторов цикла, один из которых – |
|
|
|
|
|||||||
|
|
|
11 Такое допущение для компилятора WinAVR возможно лишь в том случае, если |
||||||||||
|
|
оператор for. Шаблон описания оператора следующий: |
|
включен режим соответствия стандарту С99 с расширениями GNU. |
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Радиолюбитель – 02/2010 |
|||
|
|
|
|
|
|
|
|
|
|
|
|
||
СПРАВОЧНЫЙ МАТЕРИАЛ
|
В этом примере приведены два варианта ранее рассмотренно- |
Оператор continue |
|||||
го подсчета суммы «правой» диагонали матрицы, однако первый |
Оператор continue служит для досрочного начала очередной |
||||||
способ требует гораздо больше умственных усилий для понимания |
итерации цикла, т.е. он обеспечивает немедленный переход к про- |
||||||
написанного, чем второй. |
верке условия для циклов do или while, или к переходу к выполне- |
||||||
|
|
|
нию последней группы операторов, указанных в круглых скобках, |
||||
|
Оператор while |
для цикла for. |
|||||
|
Оператор for удобен, если требуется повторить какие-либо |
Этот оператор служит для пропуска части операторов цик- |
|||||
действия определенное число раз. Но иногда число повторений |
ла, если на определенной итерации их выполнять не следует. |
||||||
неизвестно, хотя известно условие завершения цикла (например, |
Пример: |
||||||
при решении уравнения методом последовательных приближений). |
|
|
|
|
|||
int arr[10]; |
|
||||||
В этом случае можно использовать оператор цикла по условию while. |
|
||||||
|
Шаблон этого оператора следующий: |
int found, sum; |
|
||||
|
while (условие) [тело цикла]; |
for (int i = 0; i < 10; i++){ |
|
||||
|
|
if (arr[i] == 12){ |
|
||||
|
Здесь условие – логическое выражение, истинное значение |
|
|
||||
которого является условием продолжения цикла, тело цикла – это |
|
found++; |
|
||||
либо один оператор, либо ограниченная фигурными скобками пос- |
|
continue; |
|
||||
ледовательность операторов. |
} |
|
|
||||
|
Важная особенность while в том, что если к моменту его начала |
} |
sum += arr[i]; |
|
|||
|
|
|
|
||||
условие ложно – тело цикла не выполнится ни разу. |
|
|
|
||||
В этом примере осуществляется подсчет суммы sum всех зна- |
|||||||
|
Пример операторов while: |
||||||
|
|
|
чений массива arr, не равных 12, и одновременно подсчет в found |
||||
while (1); |
|
||||||
|
количества элементов, равных 12. |
||||||
while (PORT == 2) sum++; |
|
|
|
|
|
||
|
Первый пример демонстрирует бесконечный цикл, т.к. ненуле- |
Оператор goto |
|||||
вое значение равносильно истинному логическому выражению. |
Последний оператор языка Си – оператор безусловного пере- |
||||||
Второй пример опрашивает значение переменной PORT и ведет |
хода goto. Шаблон оператора такой: |
||||||
подсчет в переменной sum числа таких опросов до тех пор, пока |
goto <метка>; |
||||||
значение переменной равно двум12 . |
Метка – это идентификатор, при объявлении завершаемый дво- |
||||||
|
По сравнению с оператором for, цикл while допускает значи- |
еточием. Этот идентификатор служит для обозначения определен- |
|||||
тельно меньше вариантов «оформления». Очевидно, что цикл for |
ного места в программе, где метка объявлена (отождествляется с |
||||||
вполне в состоянии заменить цикл while: |
адресом исполняемого кода в памяти программ). При использова- |
||||||
|
|
|
нии в операторе goto метка указывается уже без двоеточия. |
||||
// эквивалент бесконечного цикла |
|
||||||
|
Оператор goto вызывает безусловное продолжение исполне- |
||||||
for (;;); |
|
||||||
|
ния программы с указанной метки. |
||||||
// эквивалент опроса и подсчета числа этих опросов |
|
Пример использования меток и оператора goto: |
|||||
for (; PORT == 2; sum++); |
|
|
|
|
|
||
|
|
|
|
if (a < 5) goto m1; |
|
||
|
Тем не менее, рекомендуется использовать while там, где это |
||||||
оправдано логикой алгоритма, это позволит улучшить «читабель- |
|
a = 0; |
|
||||
ность» программы. |
|
goto m2; |
|
||||
|
|
|
m1: |
|
|||
|
Оператор do |
|
a = 25; |
|
|||
|
m2: |
|
|||||
|
Как было показано, тело оператора while может быть не вы- |
|
|||||
|
|
|
|||||
полнено ни разу, если условие его продолжения сразу ложно. Но |
Этот пример показывает реализацию следующего алгоритма: |
||||||
иногда требуется, чтобы при прочих равных условиях тело цикла |
если значение переменной a меньше пяти, то присвоить перемен- |
||||||
всегда выполнялось хотя бы один раз. Для этих целей применяется |
ной значение 25, в противном случае обнулить переменную a. |
||||||
оператор do, записываемый по следующему шаблону: |
Использование оператора goto в программах считается дур- |
||||||
|
do [тело цикла] while (условие); |
ным тоном среди программистов. Как правило, программы с эти- |
|||||
|
Здесь, как и ранее, тело цикла – это либо один оператор, либо |
ми операторами более запутаны, труднее отлаживаются, таят боль- |
|||||
заключенная в фигурные скобки последовательность операторов, |
ше потенциальных возможностей для ошибок. Метка может нахо- |
||||||
а условие – это логическое выражение, определяющее условие |
диться почти в любом13 месте программы, и соответственно, опе- |
||||||
продолжения итераций цикла. Как и ранее, тело цикла может отсут- |
ратор goto может заставить программу изменить нормальный ход |
||||||
ствовать. |
непредсказуемым образом, если программист случайно забудет |
||||||
|
В остальном цикл do аналогичен циклу while. |
вовремя убрать или изменить нужную метку. Доказано, что любой |
|||||
|
|
|
алгоритм может быть реализован без использования goto, лишь с |
||||
|
Оператор break |
помощью других операторов языка Си. Например, только что рас- |
|||||
|
Оператор break служит для досрочного завершения операто- |
смотренный алгоритм элементарно и гораздо красивее реализу- |
|||||
ров цикла (любых) и оператора switch. Исполнение этого операто- |
ется так: |
||||||
ра равносильно переходу к оператору, следующему за «телом» упо- |
|
|
|
|
|||
if (a < 5) |
|
||||||
мянутых операторов. |
|
||||||
|
Пример: |
|
a = 25; |
|
|||
|
|
|
else |
|
|||
int arr[10]; |
|
|
a = 0; |
|
|||
int found=-1; |
|
|
|
|
|
||
for (int i = 0; i < 10; i++) if (arr[i] == 12) { found = i; break;} |
|
|
|
|
|
||
|
В этом примере осуществляется поиск в массиве arr элемента, |
|
|
|
|
||
равного числу 12. Как только такой элемент будет найден, цикл |
|
|
|
|
|||
завершится досрочно, и значение i будет сохранено в переменной |
|
|
|
|
|||
found. Если после завершения цикла значение found будет равно |
|
|
|
|
|||
-1, это будет означать, что элементов 12 в массиве нет, в противном |
|
|
Продолжение в №3/2010 |
|
|||
случае found будет равно индексу первого найденного элемента. |
|
|
|
|
|||
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
12 То, каким образом значение переменной PORT может измениться – не рассматри- |
13 Об ограничениях на размещение меток см. главу «Структура программы». |
|||||
вается в данном контексте, предполагается пока, что все-таки такое изменение возможно. |
|
|
|
|
|||
|
|
|
6 1 |
Радиолюбитель – 02/2010 |
|
|
|
|
|
|
