Язык С содержит большое количество встроенных операций. Их роль в С значительно больше, чем в других языках программирования. Существует четыре основных класса операций: арифметические, логические, поразрядные и операции сравнения. Кроме них, есть также некоторые специальные операторы, например, оператор присваивания.
Оператор присваивания
Оператор присваивания может присутствовать в любом выражении языка С[1]. Этим С отличается от большинства других языков программирования (Pascal, BASIC и FORTRAN), в которых присваивание возможно только в отдельном операторе. Общая форма оператора присваивания:
имя_переменной=выражение;
Выражение может быть просто константой или сколь угодно сложным выражением. В отличие от Pascal или Modula-2, в которых для присваивания используется знак «:=», в языке С оператором присваивания служит единственный знак присваивания «=». Адресатом (получателем), т.е. левой частью оператора присваивания должен быть объект, способный получить значение, например, переменная.
В книгах по С и в сообщениях компилятора часто встречаются термины lvalue[2] (left side value) и rvalue[3] (right side value). Попросту говоря, lvalue — это объект. Если этот объект может стоять в левой части присваивания, то он называется также модифицируемым (modifiable) lvalue. Подытожим сказанное: lvalue — это объект в левой части оператора присваивания, получающий значение, чаще всего этим объектом является переменная. Термин rvalue означает значение выражения в правой части оператора присваивания.
Преобразование типов при присваиваниях
Если в операции встречаются переменные разных типов, происходит преобразование типов. В операторе присваивания действует простое правило: значение выражения в правой части преобразуется к типу объекта в левой части.
int x;
char ch;
float f;
void func(void)
{
ch = x; /* 1-я строка */
x = f; /* 2-я строка */
f = ch; /* 3-я строка */
f = x; /* 4-я строка */
}
В 1-й строке этого примера старшие двоичные разряды целой переменной х отбрасываются, а в ch заносятся младшие 8 бит. Если значение х лежит в интервале от 0 до 255, то ch и х будут идентичны и потери информации не произойдет. В противном случае в ch будут занесены только младшие разряды переменной х. Во 2-й строке в х будет записана целая часть числа f. В 3-й строке произойдет преобразование целого 8-разрядного числа, хранящегося в ch, в число в плавающем формате. В 4-й строке произойдет то же самое, только с 16-разрядным целым.
Преобразование целых в символы и длинных целых в целые удаляет соответствующее количество старших двоичных разрядов. В 16-разрядной среде теряются 8 битов при преобразовании целого в символ и 16 битов при преобразовании длинного целого в целое. В 32-разрядной среде теряются 24 бита при преобразовании целого в символ и 16 битов при преобразовании целого в короткое целое.
В табл. 2.3. приведены варианты потери информации при некоторых преобразованиях. Необходимо помнить, что преобразование int во float или float в double не повышает точность вычислений. При таком преобразовании только изменяется форма представления числа. Некоторые компиляторы при преобразовании char в int считают переменную char положительной независимо от ее значения. Другие компиляторы считают переменную char отрицательной, если она больше 127. Поэтому для обеспечения переносимости программы необходимо использовать переменные типа char для хранения символов, а переменные типа signed char и int (целый) — для хранения чисел.
Тип адресата | Тип выражения | Потеря информации |
---|---|---|
signed char | char | Если значение > 127, то результат отрицательный |
char | short int | Старшие 6 бит |
char | int (16-разрядный) | Старшие 8 бит |
char | int (32-разрядный) | Старшие 24 бит |
char | long int | Старшие 24 бит |
short int | int (16-разрядный) | Нет |
short int | int (32-разрядный) | Старшие 16 бит |
int (16-разрядный) | long int | Старшие 16 бит |
int (32-разрядный) | long int | Нет |
long int (32-разрядный) | long long int (64-разрядный) | Старшие 32 бита (это относится только к C99) |
int | float | Дробная часть |
float | double | Результат округляется |
double | long double | Результат округляется |
Если какое-либо преобразование не приведено в табл. 2.3, то, чтобы определить, что именно теряется в результате этого преобразования, нужно представить его в виде композиции (суперпозиции, произведения) указанных в таблице преобразований и затем провести последовательные преобразования. Например, преобразование double в int эквивалентно последовательному выполнению двух преобразований: сначала double в float, а затем float в int.
Множественные присваивания
В одном операторе присваивания можно присвоить одно и то же значение многим переменным. Для этого используется оператор множественного присваивания[4], например:
x = y = z = 0;
Следует отметить, что в практике программирования этот прием используется очень часто.
Составное присваивание
Составное присваивание — это разновидность оператора присваивания, в которой запись сокращается и становится более удобной в написании[5]. Например, оператор
x = x+10;
можно записать как
x += 10;
Оператор «+=» сообщает компилятору, что к переменной х нужно прибавить 10.
«Составные» операторы[6] присваивания существуют для всех бинарных операций (то есть операций, имеющих два операнда). Любой оператор вида
переменная = переменная оператор выражение;
можно записать как
переменная оператор = выражение;
Еще один пример:
x = x-100;
означает то же самое, что и
x -= 100;
Составное присваивание значительно компактнее, чем соответствующее простое присваивание, поэтому его иногда называют стенографическим (shorthand) присваиванием. В программах на С этот оператор широко используется, поэтому необходимо хорошо его усвоить.
Арифметические операции
В табл. 2.4 приведены арифметические операции С. Операции +, —, * и / работают так же, как и в большинстве других языков программирования. Их можно применять почти ко всем встроенным типам данных. Если операция / применяется к целому или символьному типам, то остаток от деления отбрасывается. Например, результатом операции 5/2 является 2.
Оператор | Операция |
---|---|
- | Вычитание, так же унарный минус |
+ | Сложение |
* | Умножение |
/ | Деление |
% | Остаток от деления |
— | Декремент[7], или уменьшение |
++ | Инкремент[8], или увеличение |
Оператор деления по модулю % в С работает так же, как и в других языках, его результатом является остаток от целочисленного деления. Этот оператор, однако, нельзя применять к типам данных с плавающей точкой. Применение оператора % иллюстрируется следующим примером:
int x, y;
x = 5;
y = 2;
printf("%d ", x/y); /* напечатает 2 */
printf("%d ", x%y); /* напечатает 1,
остаток от целочисленного деления */
x = 1;
y = 2;
printf("%d %d", x/y, x%y); /* напечатает 0 1 */
Последняя строка программы напечатает 0 1 потому, что при целочисленном делении остаток отбрасывается и здесь результат будет 0, а сам остаток равен 1.
Унарный минус умножает операнд на -1, то есть меняет его знак на противоположный.
Операции увеличения (инкремента) и уменьшения (декремента)
В языке С есть два полезных оператора, значительно упрощающие широко распространенные операции. Это инкремент ++ и декремент —. Оператор ++ увеличивает значение операнда на 1, а — уменьшает на 1. Иными словами,
x = x+1;
можно записать как
++x;
Аналогично оператор
x = x-1;
равносилен оператору
x--;
Как инкремент, так и декремент могут предшествовать операнду (префиксная форма) или следовать за ним (постфиксная форма). Например
x = x+1;
можно записать как в виде
++x;
так и в виде
x++;
Однако префиксная и постфиксная формы отличаются при использовании их в выражениях. Если оператор инкремента или декремента предшествует операнду, то сама операция выполняется до использования результата в выражении. Если же оператор следует за операндом, то в выражении значение операнда используется до выполнения операции инкремента или декремента. То есть для выражения эта операция как бы не существует, она выполняется только для операнда. Например,
x = 10;
y = ++x;
присваивает у значение 11. Однако если написать
x = 10;
y = x++;
то переменной у будет присвоено значение 10. В обоих случаях х присвоено значение 11, разница только в том, когда именно это случилось, до или после присваивания значения переменной у.
Большинство компиляторов С генерируют для инкремента и декремента очень быстрый, эффективный объектный код, значительно лучший, чем для соответствующих операторов присваивания. Поэтому везде, где это возможно, рекомендуется использовать инкремент и декремент.
Приоритет выполнения арифметических операторов следующий:
Наивысший ++ --
- (унарный минус)
* / %
Наинизший + -
Операции с одинаковым приоритетом выполняются слева направо. Используя круглые скобки, можно изменить порядок вычислений. В языке С круглые скобки интерпретируются компилятором так же, как и в любом другом языке программирования: они как бы придают операции (или последовательности операций) наивысший приоритет.
Операции сравнения и логические операции
Операции сравнения — это операции, в которых значения двух переменных сравниваются друг с другом. Логические же операции реализуют средствами языка С операции формальной логики. Между логическими операциями и операциями сравнения существует тесная связь: результаты операций сравнения часто являются операндами логических операций.
В операциях сравнения и логических операциях в качестве операндов и результатов операций используются значения ИСТИНА (true) и ЛОЖЬ (false). В языке С значение ИСТИНА представляется любым числом, отличным от нуля. Значение ЛОЖЬ представляется нулем. Результатом операции сравнения или логической операции являются ИСТИНА (true, 1) или ЛОЖЬ (false, 0).
На заметку | Как в С89, так и в С99 значение ИСТИНА представлено любым отличным от нуля числом, а ЛОЖЬ — нулем. В стандарте С99 дополнительно определен тип данных _Bооl, переменные которого могут принимать значение только или 1. Подробнее см. часть II. |
В табл. 2.5 приведен полный список операций сравнения и логических операций. Таблица истинности логических операций имеет следующий вид:
p | q | p && q | p || q | !p |
---|---|---|---|---|
0 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 0 |
Как операции сравнения, так и логические операции имеют низший приоритет по сравнению с арифметическими. То есть, выражение 10>1+12 интерпретируется как 10>(1+12). Результат, конечно, равен ЛОЖЬ.
В одном выражении можно использовать несколько операций:
10>5 && !(10<9) || 3<4
В этом случае результатом будет ИСТИНА.
В языке С не определена операция «исключающего ИЛИ» (exclusive OR, или XOR). Однако с помощью логических операторов несложно написать функцию, выполняющую эту операцию. Результатом операции «исключающее ИЛИ» является ИСТИНА, если и только если один из операндов (но не оба) имеют значение ИСТИНА. В следующем примере функция xor() возвращает результат операции «исключающее ИЛИ», а операндами служат аргументы функции:
#include <stdio.h>
int xor(int a, int b);
int main(void)
{
printf("%d", xor(1, 0));
printf("%d", xor(1, 1));
printf("%d", xor(0, 1));
printf("%d", xor(0, 0));
return 0;
}
/* Выполнение логической оперции
исключающее ИЛИ над двумя аргументами. */
int xor(int a, int b)
{
return (a || b) && !(a && b);
}
Операторы сравнения | |
---|---|
Оператор | Операция |
> | Больше чем |
>= | Больше или равно |
< | Меньше чем |
<= | Меньше или равно |
== | Равно |
!= | Не равно |
Логические операции | |
Оператор | Операция |
&& | И |
|| | ИЛИ |
! | НЕ, отрицание |
Ниже приведен приоритет логических операций:
Наивысший !
> >= < <=
== !=
&&
Наинизший ||
Как и в арифметических выражениях, для изменения порядка выполнения операций сравнения и логических операций можно использовать круглые скобки. Например, выражение:
!0 && 0 || 0
равно ЛОЖЬ. Однако, если добавить скобки как показано ниже, то результатом будет ИСТИНА:
!(0 && 0) || 0
Необходимо помнить, что результатом любой операции сравнения или логической операции есть 0 или 1. Поэтому следующий фрагмент программы является правильным и в результате его выполнения будет напечатано 1.
int x;
x = 100;
printf("%d", x>10);
Поразрядные операции
В отличие от многих других языков программирования, в С определен полный набор поразрядных операций[9]. Это обусловлено тем, что С был задуман как язык, призванный во многих приложениях заменить ассемблер, который способен оперировать битами данных. Поразрядные операции — это тестирование (проверка), сдвиг или присвоение значений отдельным битам данных. Эти операции осуществляются над ячейками памяти, содержащими данные типа char или int. Данные типа float, double, long double, void или другие более сложные не могут участвовать в поразрядных операциях. В табл. 2.6 приведен полный список знаков поразрядных операций, выполняемых над отдельными разрядами (битами) операндов.
Оператор | Операция |
---|---|
& | И |
| | ИЛИ |
^ | исключающее ИЛИ |
~ | НЕ (отрицание, дополнение к 1) |
>> | Сдвиг вправо |
<< | Сдвиг влево |
Таблицы истинности логических операций и поразрядных операций И, ИЛИ, НЕ совпадают. Отличие лишь в том, что поразрядные операции выполняются над отдельными разрядами (битами) операндов. Операция «исключающее ИЛИ» имеет следующую таблицу истинности:
p | q | p ^ q |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
0 | 1 | 1 |
Как показано в таблице, результат операции «исключающее ИЛИ» равен ИСТИНА если и только если один из операндов равен 1, иначе результат будет равен ЛОЖЬ.
Наиболее часто поразрядные операции применяются при программировании драйверов устройств, таких как модемы, а также процедур, выполняющих операции над файлами, и стандартных программ обслуживания принтера. В них поразрядные операции используются для маскирования определенных битов, например, бита контроля четности[10]. (Этот бит служит для проверки правильности остальных битов в байте. Чаще всего это бит старшего разряда в каждом байте.)
Операция И может быть использована для очищения бита[11]. Иными словами, для гашения бита используется следующее свойство операции И: если бит одного из операндов равен 0, то соответствующий бит результата будет равен 0 независимо от состояния этого бита во втором операнде. Например, следующая функция читает символ из порта модема и обнуляет бит контроля четности:
char get_char_from_modem(void)
{
char ch;
ch = read_modem(); /* чтение символа из
порта модема */
return(ch & 127);
}
Бит контроля четности, находящийся в 8-м разряде байта, обнуляется с помощью операции И. При этом в качестве второго операнда выбирается число, имеющее 1 в разрядах от 1 до 7, и 0 в 8-м разряде. Именно таким числом и является 127, поскольку все биты двоичного представления числа 127, кроме старшего, равны 1. В силу указанного свойства операции И операция ch & 127 оставляет все биты, кроме старшего, без изменения, а старший обнуляет:
Бит контроля четности
|
V
1100 0001 переменная ch содержит символ 'A' с битом четности
0111 1111 двоичное представление числа 127
& --------- поразрядная операция И
0100 0001 символ 'A' с обнуленным битом контроля четности
Поразрядная операция ИЛИ, являющаяся двойственной операции И, применяется для установки необходимых битов в 1. В следующем примере выполняется операция 128 | 3:
|
V
1000 0000 двоичное представление числа 128
0000 0011 двоичное представление числа 3
| --------- поразрядная операция ИЛИ
1000 0011 результат
Операция исключающего ИЛИ (XOR) устанавливает бит результата в 1, если соответствующие биты операндов различны. В следующем примере выполняется операция 127 ^ 120:
|
V
0000 0011 двоичное представление числа 127
0111 1000 двоичное представление числа 120
^ --------- поразрядная операция XOR
0000 0111 результат
Необходимо помнить, что результат логической операции всегда равен 0 или 1. В то же время результатом поразрядной операции может быть любое значение, которое, как видно из предыдущих примеров, не обязательно равно 0 или 1.
Поразрядные операторы сдвига >> и << сдвигают все биты переменной вправо или влево. Общая форма оператора сдвига вправо:
переменная >> количество_разрядов
Общая форма оператора сдвига влево:
переменная << количество_разрядов
Во время сдвига битов в один конец числа, другой конец заполняется нулями. Но если число типа signed int отрицательно, то при сдвиге вправо левый конец заполняется единицами, так что знак числа сохраняется. Необходимо отметить различие между сдвигом и циклическим сдвигом. При циклическом сдвиге биты, сдвигаемые за пределы операнда, появляются на другом конце операнда. А при сдвиге вышедшие за границу биты теряются.
Поразрядные операции сдвига очень полезны при декодировании выходов внешних устройств, например таких, как цифро-аналоговые преобразователи, а также при считывании информации о статусе устройств. Побитовые операторы сдвига могут быстро умножать и делить целые числа. Как показано в табл. 2.7, сдвиг на один бит вправо делит число на 2, а на один бит влево — умножает на 2. Следующая программа иллюстрирует применение операторов сдвига:
/* Пример применения операторов сдвига. */
#include <stdio.h>
int main(void)
{
unsigned int i;
int j;
i = 1;
/* сдвиг влево */
for(j=0; j<4; j++) {
i = i << 1; /* сдвиг i влево на 1 разраяд, что
равносильно умножению на 2 */
printf("Сдвиг влево на %d разр.: %d\n", j, i);
}
/* сдвиг вправо */
for(j=0; j<4; j++) {
i = i >> 1; /* сдвиг i вправо на 1 разраяд, что
равносильно делению на 2 */
printf("Сдвиг вправо на %d разр.: %d\n", j, i);
}
return 0;
}
unsigned char x | x после операции | значение x |
---|---|---|
x = 7 | 0000 0111 | 7 |
x = x << 1 | 0000 1110 | 14 |
x = x << 3 | 0111 0000 | 112 |
x = x << 2 | 1100 0000 | 192 |
x = x >> 1 | 0110 0000 | 96 |
x = x >> 2 | 0001 1000 | 24 |
Каждый сдвиг влево умножает на 2. Потеря информации произошла после операции x << 2 в результате сдвига за левую границу. Каждый сдвиг вправо делит на 2. Сдвиг вправо потерянную информацию не восстановил. |
Поразрядная операция отрицания (дополнения) ~ инвертирует состояние каждого бита операнда. То есть, 0 преобразует в 1, а 1 — в 0.
Поразрядные операции часто используются в процедурах кодирования. Проделав с дисковым файлом некоторые поразрядные операции, его можно сделать нечитаемым. Простейший способ сделать это — применить операцию отрицания к каждому биту:
Исходный байт 0010100
После 1-го отрицания 1101011
После 2-го отрицания 0010100
Обратите внимание, при последовательном применении 2-х отрицаний результатом всегда будет исходное число. Таким образом, 1-е отрицание кодирует состояние байта, а 2-е — декодирует.
В следующем примере оператор отрицания используется в функции шифрования символа:
/* Простейшая процедура шифрования. */
char encode(char ch)
{
return(~ch); /* оперция отрицания */
}
Конечно, взломать такой шифр не представляет труда.
Операция ?
В языке С определен мощный и удобный оператор, который часто можно использовать вместо оператора вида if-then-else. Речь идет о тернарном операторе ?, общий вид которого следующий:
Выражение1 ? Выражение2 : Выражение3;
Обратите внимание на использование двоеточия. Оператор ? работает следующим образом: сначала вычисляется Выражение1, если оно истинно, то вычисляется Выражение2 и его значение присваивается всему выражению; если Выражение1 ложно, то вычисляется Выражение3 и всему выражению присваивается его значение. В примере
x = 10;
y = x>9 ? 100 : 200;
переменной у будет присвоено значение 100. Если бы х было меньше 9, то переменной у было бы присвоено значение 200. Эту же процедуру можно написать, используя оператор if-else:
x = 10;
if(x>9) y = 100;
else y = 200;
Более подробно оператор ? обсуждается в главе 3 в связи с условными операторами.
Операция получения адреса (&) и раскрытия ссылки (*)
Указатель — это адрес объекта в памяти. Переменная типа «указатель» (или просто переменная-указатель) — это специально объявленная переменная, в которой хранится указатель на переменную определенного типа. В языке С указатели служат мощнейшим средством создания программ и широко используются для самых разных целей. Например, с их помощью можно быстро обратиться к элементам массива или дать функции возможность модифицировать свои аргументы. Указатели широко используются для связи элементов в списках, в двоичных деревьях и в других динамических структурах данных. Глава 5 полностью посвящена указателям. В данной главе коротко рассматриваются два оператора, использующиеся для работы с указателями.
Первый из них — оператор &, это унарный оператор, возвращающий адрес операнда в памяти[12]. (Унарной операцией называется операция, имеющая только один операнд.) Например, оператор
m = &count;
записывает в переменную m адрес переменной count. Этот адрес представляет собой адрес ячейки памяти компьютера, в которой размещена переменная. Адрес и значение переменной — совершенно разные понятия. Выражение «&переменная» означает «адрес переменной». Следовательно, инструкция m = &scount; означает: «Переменной m присвоить адрес, по которому расположена переменная count;«.
Допустим, переменная count расположена в памяти в ячейке с адресом 2000, а ее значение равно 100. Тогда в предыдущем примере переменной m будет присвоено значение 2000.
Второй рассматриваемый оператор * является двойственным (дополняющим) по отношению к &[13]. Оператор * является унарным оператором, он возвращает значение объекта, расположенного по указанному адресу. Операндом для * служит адрес объекта (переменной). Например, если переменная m содержит адрес переменной count, то оператор
q = *m;
записывает значение переменной count в переменную q. В нашем примере переменная q получит значение 100, потому что по адресу 2000 записано число 100, причем этот адрес записан в переменной m. Выражение «* адрес» означает «по адресу». Наш фрагмент программы можно прочесть как «q получает значение, расположенное по адресу m«.
К сожалению, символ операции раскрытия ссылки совпадает с символом операции умножения, а символ операции получения адреса — с символом операции поразрядного И. Необходимо помнить, что эти операторы не имеют никакого отношения друг к другу. Операторы * и & имеют более высокий приоритет, чем любая арифметическая операция, кроме унарного минуса, имеющего такой же приоритет.
Если переменная является указателем, то в объявлении перед ее именем нужно поставить символ *, он сообщит компилятору о том, что это указатель на переменную данного типа. Например, объявление указателя на переменную типа char записывается так:
char *ch;
Необходимо понимать, что ch — это не переменная типа char, а указатель на переменную данного типа, это совершенно разные вещи. Тип данных, на который указывает указатель (в данном случае это char), называется базовым типом указателя[14]. Сам указатель является переменной, содержащей адрес объекта базового типа. Компилятор учтет размер указателя в архитектуре компьютера и выделит для него необходимое количество байтов, чтобы в указатель поместился адрес. Базовый тип указателя определяет тип объекта, хранящегося по этому адресу.
В одном операторе объявления можно одновременно объявить и указатель, и переменную, не являющуюся указателем. Например, оператор
int x, *y, count;
объявляет х и count как переменные целого типа, а у — как указатель на переменную целого типа.
В следующей программе операторы * и & используются для записи значения 10 в переменную target. Программа выведет значение 10 на экран.
#include <stdio.h>
int main(void)
{
int target, source;
int *m;
source = 10;
m = &source;
target = *m;
printf("%d", target);
return 0;
}
Операция определения размера sizof
Унарная операция sizeof, выполняемая во время компиляции программы, позволяет определить длину операнда в байтах. Например, если компилятор для чисел типа int отводит 4 байта, а для чисел типа double — 8, то следующая программа напечатает 8 4.
double f;
printf("%d ", sizeof f);
printf("%d", sizeof(int));
Необходимо помнить, что для вычисления размера типа переменной имя типа должно быть заключено в круглые скобки. Имя переменной заключать в скобки не обязательно, но ошибки в этом не будет.
В языке С определяется (с помощью спецификатора класса памяти typedef) специальный тип size_t, приблизительно соответствующий целому числу без знака. Результат операции sizeof имеет тип size_t. Но практически его можно использовать везде, где допустимо использование целого числа без знака.
Оператор sizeof очень полезен для улучшения переносимости программ, так как переносимость существенно зависит от размеров встроенных типов данных. Для примера рассмотрим программу, работающую с базой данных, в которой необходимо хранить шесть целых чисел в одной записи. Если эта программа предназначена для работы на многих компьютерах, ни в коем случае нельзя полагаться на то, что размер целого числа на всех компьютерах будет один и тот же. В программе следует определять размер целого, используя оператор sizeof. Соответствующая программа имеет следующий вид:
/* Запись шести целых чисел в дисковый файл. */
void put_rec(int rec[6], FILE *fp)
{
int len;
len = fwrite(rec, sizeof(int)*6, 1, fp);
if(len != 1) printf("Ошибка при записи");
}
Приведенная функция put_rec() компилируется и выполняется правильно в любой среде, в том числе на 16- и 32-разрядных компьютерах.
И в заключение: оператор sizeof выполняется во время трансляции, его результат в программе рассматривается как константа.
Оператор последовательного вычисления: оператор «запятая»
Оператор «запятая»[15] связывает воедино несколько выражений. При вычислении левой части оператора «запятая» всегда подразумевается, что она имеет тип void. Это значит, что выражение, стоящее справа после оператора «запятая», является значением всего разделенного запятыми выражения. Например, оператор
x = (y=3, y+1);
сначала присваивает у значение 3, а затем присваивает х значение 4. Скобки здесь обязательны, потому что приоритет оператора «запятая» меньший, чем оператора присваивания.
В операторе «запятая» выполняется последовательность операций. Если этот оператор стоит в правой части оператора присваивания, то его результатом всегда является выражение, стоящее последним в списке.
Оператор доступа к члену структуры (оператор . (точка)) и оператор доступа через указатель -> (оператор стрелка)
В языке С операторы . (точка) и -> (стрелка) обеспечивают доступ к элементам структур и объединений. Структуры и объединения — это составные типы данных, в которых под одним именем хранятся многие объекты. (Структуры и объединения подробно рассматриваются в главе 7.)
Оператор точка используется для прямой ссылки на элемент структуры или объединения, т.е. перед точкой стоит имя структуры, а после — имя элемента структуры. Оператор стрелка используется с указателем на структуру или объединение, т.е. перед стрелкой стоит указатель на структуру. Например, во фрагменте программы
struct employee
{
char name[80];
int age;
float wage;
} emp;
struct employee *p = &emp; /* адрес emp заносится в p */
для присвоения члену wage значения 123.33 необходимо записать
emp.wage = 123.23;
То же самое можно сделать, использовав указатель на структуру:
p->wage = 123.23;
Оператор [] и ()
Круглые скобки являются оператором, повышающим приоритет выполнения операций, которые в них заключены. Квадратные скобки служат для индексации массива (массивы подробно рассматриваются в главе 4). Если в программе определен массив, то выражение в квадратных скобках представляет собой индекс массива. Например, в программе
#include <stdio.h>
char s[80];
int main(void)
{
s[3] = 'X';
printf("%c", s[3]);
return 0;
}
значение ‘Х’ сначала присваивается четвертому элементу массива (в С элементы массива нумеруются с нуля), затем этот элемент выводится на экран.
Сводка приоритетов операций
В табл. 2.8 приведены приоритеты всех операций, определенных в С. Необходимо помнить, что все операторы, кроме унарных и «?», связывают (присоединяют, ассоциируют) свои операнды слева направо. Унарные операторы (*, &, -) и «?» связывают (присоединяют, ассоциируют) свои операнды справа налево.
Наивысший | ( ) [ ] -> . |
---|---|
! ~ ++ — — (type) * & sizeof * / % + - << >> < <= > >= == != & ^ | && || ?: = += -= *= /= и т.д. | |
Наинизший | , |
[1]В данном случае под оператором имеется в виду, конечно, знак операции. По этому поводу см. сделанное ранее примечание редактора о переводе термина operator
[2]lvalue — именующее выражение, т.е. выражение, которое может стоять в левой части оператора присваивания. Под lvalue также часто подразумевается адрес переменной. (С идентификатором переменной в программе связано две величины: адрес переменной и ее значение. Адрес используется, когда переменная стоит в левой части присваивания, значение — в правой части присваивания.) Иногда встречается и термин l-значение. Как бы то ни было, этим термином обозначается выражение, которое может находиться в левой части оператора присваивания. Семантически оно представляет собой адрес, по которому размещена переменная, массив, элемент структуры и т.п.
[3]rvalue — значение переменной; иногда переводится, как r-значение, т.е. значение в правой части оператора присваивания.
[4]Множественное присваивание — присваивание одного и того же значения нескольким переменным. Под множественным присваиванием также подразумевается конструкция языка программирования, позволяющая присвоить одно и то же значение нескольким переменным одновременно.
[5]По этой причине варианты оператора присваивания, в которых используется такая запись, называются «сокращенными» или «укороченными». Что касается терминологии, то необходимо отметить также следующее обстоятельство. Хотя термины присваивание и оператор присваивания часто могут рассматриваться как синонимы, составное присваивание не является составным оператором! (Под составным оператором в языке С подразумевают блок.)
[6]Под «составными» операторами в данном случае, конечно, подразумеваются составные знаки операций, т.е. знаки операций, состоящие из нескольких (обычно двух) символов. Составные операторы-блоки не имеют к этому никакого отношения.
[7]На жаргоне программистов: декрементация.
[8]На жаргоне программистов: инкрементация.
[9]Называются также битовыми, побитовыми и логическими операциями.
[10]Бит контроля четности называется также контрольным двоичным разрядом четности, контрольным разрядом четности, проверочным двоичным разрядом четности, проверочным разрядом четности, битом четности, разрядом четности, контрольным битом и битом контроля на четность. Это дополнительный бит, который добавляется к группе (обычно из семи) битов. Передающее устройство устанавливает значение бита четности равным нулю или единице так, чтобы сумма битов в каждом байте всегда была четной или нечетной в зависимости от выбора типа проверки — на четность или нечетность. Невыполнение условия такой проверки на приемном конце линии означает искажение по крайней мере одного бита при передаче. При обнаружении ошибки принимающее устройство делает запрос на повтор данных. Иными словами, это бит, добавляемый к данным для контроля их верности таким образом, чтобы сумма двоичных единиц, составляющих данное, включая единицу контрольного бита, всегда была четной (либо всегда нечетной).
[11]Очищение бита — гашение, т.е. занесение нуля.
[12]Оператор & называется также оператором получения (взятия) адреса.
[13]Оператор * называется также оператором косвенности, оператором раскрытия ссылки и оператором разыменования адреса.
[14]Иногда называется также основным или исходным типом.
[15]Чаще встречается написание без кавычек: оператор запятая. Мы пишем кавычки лишь для того, чтобы новичкам было легче воспринимать несколько непривычное для них название оператора.