Язык программирования Си. Издание 3-е, исправленное - Брайан Керниган
Шрифт:
Интервал:
Закладка:
double sqrt(double);
перед обращением к sqrt в присваивании
root2 = sqrt(2);
целое 2 будет переведено в значение double 2.0 автоматически без явного указания операции приведения.
Операцию приведения проиллюстрируем на переносимой версии генератора псевдослучайных чисел и функции, инициализирующей "семя". И генератор, и функция входят в стандартную библиотеку.
unsigned long int next = 1;
/* rand: возвращает псевдослучайное целое 0…32767 */
int rand(void)
{
next = next * 1103515245 + 12345;
return (unsigned int)(next/65536) % 32768;
}
/* srand: устанавливает "семя" для rand() */
void srand(unsigned int seed)
{
next = seed;
}
Упражнение 2.3. Напишите функцию htol(s), которая преобразует последовательность шестнадцатеричных цифр, начинающуюся с 0x или 0X, в соответствующее целое. Шестнадцатеричными цифрами являются символы 0…9, a…f, А…F.
2.8 Операторы инкремента и декремента
В Си есть два необычных оператора, предназначенных для увеличения и уменьшения переменных. Оператор инкремента ++ добавляет 1 к своему операнду, а оператор декремента -- вычитает 1. Мы уже неоднократно использовали ++ для наращивания значения переменных, как, например, в
if (c == 'n')
++nl;
Необычность операторов ++ и -- в том, что их можно использовать и как префиксные (помещая перед переменной: ++n), и как постфиксные (помещая после переменной: n++) операторы. В обоих случаях значение n увеличивается на 1, но выражение ++n увеличивает n до того, как его значение будет использовано, а n++ - после того. Предположим, что n содержит 5, тогда
x = n++;
установит x в значение 5, а
x = ++n;
установит x в значение 6. И в том и другом случае n станет равным 6. Операторы инкремента и декремента можно применять только к переменным. Выражения вроде (i+j)++ недопустимы.
Если требуется только увеличить или уменьшить значение переменной (но не получить ее значение), как например
if (c=='n')
nl++;
то безразлично, какой оператор выбрать - префиксный или постфиксный. Но существуют ситуации, когда требуется оператор вполне определенного типа. Например, рассмотрим функцию squeeze(s, c), которая удаляет из строки s все символы, совпадающие с c:
/* squeeze: удаляет все c из s*/
void squeeze(char s[], int с)
{
int i, j;
for (i = j =0; s[i] != ' '; i++)
if (s[i] != c)
s[j++] = s[i];
s[i] = ' ';
}
Каждый раз, когда встречается символ, отличный от c, он копируется в текущую j-ю позицию, и только после этого переменная j увеличивается на 1, подготавливаясь таким образом к приему следующего символа. Это в точности совпадает со следующими действиями:
if (s[i] != с)
{
s[j] = s[i];
j++;
}
Другой пример - функция getline, которая нам известна по главе 1. Приведенную там запись
if (c =='n') {
s[i] = c;
++i;
}
можно переписать более компактно:
if (с == 'n')
s[i++] = с;
В качестве третьего примера рассмотрим стандартную функцию strcat(s,t), которая строку t помещает в конец строки s. Предполагается, что в s достаточно места, чтобы разместить там суммарную строку. Мы написали strcat так, что она не возвращает никакого результата. На самом деле библиотечная strcat возвращает указатель на результирующую строку.
/* strcat: помещает t в конец s; s достаточно велика */
void strcat (char s[], char t[])
{
int i, j;
i = j = 0;
while (s[i] != ' ') /* находим конец s */
i++;
while ((s[i++] = t[j++]) != ' ') /* копируем t */
;
}
При копировании очередного символа из t в s постфиксный оператор ++ применяется и к i, и к j, чтобы на каждом шаге цикла переменные i и j правильно отслеживали позиции перемещаемого символа.
Упражнение 2.4. Напишите версию функции squeeze(s1,s2), которая удаляет из s1 все символы, встречающиеся в строке s2.
Упражнение 2.5. Напишите функцию any(s1,s2), которая возвращает либо ту позицию в s1, где стоит первый символ, совпавший с любым из символов в s2, либо -1 (если ни один символ из s1 не совпадает с символами из s2). (Стандартная библиотечная функция strpbrk делает то же самое, но выдает не номер позиции символа, а указатель на символ.)
2.9 Побитовые операторы
В Си имеются шесть операторов для манипулирования с битами. Их можно применять только к целочисленным операндам, т. е. к операндам типов char, short, int и long, знаковым и беззнаковым.
& - побитовое И
| - побитовое ИЛИ
^ - побитовое исключающее ИЛИ.
‹‹ - сдвиг влево.
›› - сдвиг вправо.
~ - побитовое отрицание (унарный).
Оператор & (побитовое И) часто используется для обнуления некоторой группы разрядов. Например
n = n & 0177;
обнуляет в n все разряды, кроме младших семи.
Оператор | (побитовое ИЛИ) применяют для установки разрядов; так,
x = x | SET_ON;
устанавливает единицы в тех разрядах x, которым соответствуют единицы в SET_ON.
Оператор ^ (побитовое исключающее ИЛИ) в каждом разряде установит 1, если соответствующие разряды операндов имеют различные значения, и 0, когда они совпадают.
Поразрядные операторы & и | следует отличать от логических операторов && и ||, которые при вычислении слева направо дают значение истинности. Например, если x равно 1, а y равно 2, то x & y даст нуль, а x && y - единицу.
Операторы ‹‹ и ›› сдвигают влево или вправо свой левый операнд на число битовых позиций, задаваемое правым операндом, который должен быть неотрицательным. Так, x ‹‹ 2 сдвигает значение x влево на 2 позиции, заполняя освобождающиеся биты нулями, что эквивалентно умножению x на 4. Сдвиг вправо беззнаковой величины всегда сопровождается заполнением освобождающихся разрядов нулями. Сдвиг вправо знаковой величины на одних машинах происходит с распространением знака ("арифметический сдвиг"), на других - с заполнением освобождающихся разрядов нулями ("логический сдвиг").
Унарный оператор ~ поразрядно "обращает" целое т. е. превращает каждый единичный бит в нулевой и наоборот. Например
x = x & ~077
обнуляет в x последние 6 разрядов. Заметим, что запись x & ~077 не зависит от длины слова, и, следовательно, она лучше, чем x & 0177700, поскольку последняя подразумевает, что x занимает 16 битов. Не зависимая от машины форма записи ~077 не потребует дополнительных затрат при счете, так как ~077 - константное выражение, которое будет вычислено во время компиляции.
Для иллюстрации некоторых побитовых операций рассмотрим функцию getbits(x, p, n), которая формирует поле в n битов, вырезанных из x, начиная с позиции p, прижимая его к правому краю. Предполагается, что 0-й бит - крайний правый бит, а n и p - осмысленные положительные числа. Например, getbits(x,4,3) вернет в качестве результата 4, 3 и 2-й биты значения x, прижимая их к правому краю. Вот эта функция:
/* getbits: получает n бит, начиная с p-й позиции */
unsigned getbits(unsigned x, int p, int n)
{
return (x ›› (p+1-n)) & ~(~0 ‹‹ n);
}
Выражение x ›› (р+1-n) сдвигает нужное нам поле к правому краю. Константа ~0 состоит из одних единиц, и ее сдвиг влево на n бит (~0 ‹‹ n) приведет к тому, что правый край этой константы займут n нулевых разрядов. Еще одна операция побитовой инверсии ~ позволяет получить справа n единиц.
Упражнение 2.6. Напишите функцию setbits(x, p, n, y), возвращающую значение x, в котором n битов, начиная с p-й позиции, заменены на n правых разрядов из y (остальные биты не изменяются).
Упражнение 2.7. Напишите функцию invert(x, p, n), возвращающую значение x с инвертированными n битами, начиная с позиции p (остальные биты не изменяются).
Упражнение 2.8. Напишите функцию rightrot (x, n), которая циклически сдвигает x вправо на n разрядов.
2.10 Операторы и выражения присваивания
Выражение
i = i + 2;
в котором стоящая слева переменная повторяется и справа, можно написать в сжатом виде:
i += 2;
Оператор +=, как и =, называется оператором присваивания.
Большинству бинарных операторов (аналогичных + и имеющих левый и правый операнды) соответствуют операторы присваивания op=, где op - один из операторов
+
-
*
/
%
‹‹
››
&
^
|
Если выр1 и выр2 - выражения, то