Керниган и Ритчи - Язык программирования Си (793773), страница 16
Текст из файла (страница 16)
Предположим, что n содержит 5, тогдаx = n++;установит x в значение 5, аx = ++n;установит х в значение 6. И в том и другом случае n станет равным 6. Операторы инкремента и декрементаможно применять только к переменным. Выражения вроде (i+j)++ недопустимы.Если требуется только увеличить или уменьшить значение переменной (но не получить ее значение), какнапримерif (c == '\n')nl++;то безразлично, какой оператор выбрать — префиксный или постфиксный. Но существуют ситуации, когдатребуется оператор вполне определенного типа.
Например, рассмотрим функцию squeeze(s, с), котораяудаляет из строки s все символы, совпадающие с c:/* squeeze: удаляет все с из s */void squeeze(char s[], int с){int i, j;for (i = j = 0; s[i] != '\0'; i++)if (s[i] != c)s[j++] = s[i];s[i] = '\0';}Каждый раз, когда встречается символ, отличный от с, он копируется в текущую j-ю позицию, и только послеэтого переменная j увеличивается на 1, подготавливаясь таким образом к приему следующего символа.
Это вточности совпадает со следующими действиями:if (s[i] != с) {s[j] = s[i];j++;}Другой пример — функция getline, которая нам известна по главе 1. Приведенную там записьif (с == '\n' ) {s[i] = с;++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] != '\0') /* находим конец s */i++;while ((s[i++] = t[j++]) != '\0') /* копируем 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, знаковым и беззнаковым.&— побитовое И.|— побитовое ИЛИ.^— побитовое исключающее ИЛИ.<<— сдвиг влево.>>— сдвиг вправо.~— побитовое отрицание (унарный).1^1, 0^0 => 01^0, 0^1 => 1Оператор & (побитовое И) часто используется для обнуления некоторой группы разрядов. Напримерn = n & 0177;обнуляет в n все разряды, кроме младших семи.Оператор | (побитовое ИЛИ) применяют для установки разрядов; так,х = х | SET_ON;устанавливает единицы в тех разрядах х, которым соответствуют единицы в SET_ON.Оператор ^ (побитовое исключающее ИЛИ) в каждом разряде установит 1, если соответствующие разрядыоперандов имеют различные значения, и 0, когда они совпадают.Поразрядные операторы & и | следует отличать от логических операторов && и ||, которые при вычислениислева направо дают значение истинности.
Например, если x равно 1, а y равно 2, то x & y даст нуль, а x &&у единицу.Операторы << и >> сдвигают влево или вправо свой левый операнд на число битовых позиций, задаваемоеправым операндом, который должен быть неотрицательным. Так, х << 2 сдвигает значение х влево на 2позиции, заполняя освобождающиеся биты нулями, что эквивалентно умножению х на 4. Сдвиг вправобеззнаковой величины всегда сопровождается заполнением освобождающихся разрядов нулями. Сдвигвправо знаковой величины на одних машинах происходит с распространением знака ("арифметическийсдвиг"), на других — с заполнением освобождающихся разрядов нулями ("логический сдвиг").Унарный оператор ~ поразрядно "обращает" целое т.
е. превращает каждый единичный бит в нулевой инаоборот. Напримерх = х & ~077обнуляет в х последние 6 разрядов. Заметим, что запись х & ~077 не зависит от длины слова, и,следовательно, она лучше, чем х & 0177700, поскольку последняя подразумевает, что х занимает 16битов. Не зависимая от машины форма записи ~077 не потребует дополнительных затрат при счете, так как~077 — константное выражение, которое будет вычислено во время компиляции.Для иллюстрации некоторых побитовых операций рассмотрим функцию getbits(x, p, n), котораяформирует поле в n битов, вырезанных из х, начиная с позиции p, прижимая его к правому краю.Предполагается, что 0-й бит — крайний правый бит, а n и p — осмысленные положительные числа.Например, getbits(x, 4, 3) вернет в качестве результата 4, 3 и 2-й биты значения х, прижимая их кправому краю.
Вот эта функция:/* getbits: получает п бит, начиная с р-й позиции */unsigned getbits(unsigned х, int p, int n){return (x >> (p+1-n)) & ~(~0 << n);}Выражение х >> (p+1-n) сдвигает нужное нам поле к правому краю. Константа ~0 состоит из однихединиц, и ее сдвиг влево на n бит (~0 << n)приведет к тому, что правый край этой константы займут nнулевых разрядов. Еще одна операция побитовой инверсии ~ позволяет получить справа n единиц.Упражнение 2.6.
Напишите функцию setbits(x, p, n, y), возвращающую значение x, в котором nбитов, начиная с p-й позиции, заменены на n правых разрядов из y (остальные биты не изменяются).Упражнение 2.7. Напишите функцию invert(х, р, n), возвращающую значение x с инвертированнымиn битами, начиная с позиции p (остальные биты не изменяются).Упражнение 2.8. Напишите функцию rightrot(х, n), которая циклически сдвигает x вправо на nразрядов.2.10. Операторы и выражения присваиванияВыражениеi = i + 2в котором стоящая слева переменная повторяется и справа, можно написать в сжатом виде:i += 2Оператор +=, как и =, называется оператором присваивания.Большинству бинарных операторов (аналогичных + и имеющих левый и правый операнды) соответствуютоператоры присваивания ор=, где ор — один из операторов+*/%<<>>&^|Если выр1 и выр2 — выражения, товыр1 ор= выр2эквивалентновыр1 = (выр1) ор (выр2)с той лишь разницей, что выр1 вычисляется только один раз.
Обратите внимание на скобки вокруг выр2:x *= y + 1эквивалентноx = x * (y + 1)но неx = x * y + 1В качестве примера приведем функцию bitcount, подсчитывающую число единичных битов в своемаргументе целочисленного типа./* bitcount: подсчет единиц в х */int bitcount (unsigned x){int b;for (b = 0; x != 0; x >>= 1)if (x & 01)b++;return b;}Независимо от машины, на которой будет работать эта программа, объявление аргумента x как unsignedгарантирует, что при правом сдвиге освобождающиеся биты будут заполняться нулями, а не знаковым битом.Помимо краткости операторы присваивания обладают тем преимуществом, что они более соответствуюттому, как человек мыслит.
Мы говорим "прибавить 2 к i" или "увеличить i на 2", а не "взять i, добавить 2 изатем вернуть результат в i", так что выражение i += 2 лучше, чем i = i + 2. Кроме того, в сложныхвыражениях вродеyyval[yypv[p3+p4] + yypv[p1+p2]] += 2благодаря оператору присваивания += запись становится более легкой для понимания, так как читателю притакой записи не потребуется старательно сравнивать два длинных выражения, совпадают ли они, иливыяснять, почему они не совпадают. Следует иметь в виду и то, что подобные операторы присваивания могутпомочь компилятору сгенерировать более эффективный код.Мы уже видели, что присваивание вырабатывает значение и может применяться внутри выражения; вотсамый расхожий пример:while ((с = getchar()) != EOF)…В выражениях встречаются и другие операторы присваивания (+=, -= и т.д.), хотя и реже.Типом и значением любого выражения присваивания являются тип и значение его левого операнда послезавершения присваивания.Упражнение 2.9.
Применительно к числам, в представлении которых использован дополнительный код,выражение х &= (х-1) уничтожает самую правую 1 в х. Объясните, почему. Используйте это наблюдениепри написании более быстрого варианта функции bitcount.2.11. Условные выраженияИнструкцииif (а > b)z = a;elsez = b;пересылают в z большее из двух значений a и b. Условное выражение, написанное с помощью тернарного(т.е. имеющего три операнда) оператора ?:, представляет собой другой способ записи этой и подобных ейконструкций. В выражениивыр1 ? выр2 : выр3первым вычисляется выражение выр1. Если его значение не нуль (истина), то вычисляется выражение выр2 изначение этого выражения становится значением всего условного выражения. В противном случаевычисляется выражение выр3 и его значение становится значением условного выражения.
Следует отметить,что из выражений выр2 и выр3 вычисляется только одно из них. Таким образом, чтобы установить в z большееиз a и b, можно написатьz = (a > b) ? а : b; /* z = max(a, b) */Следует заметить, что условное выражение и в самом деле является выражением, и его можно использоватьв любом месте, где допускается выражение. Если выр2 и выр3 принадлежат разным типам, то тип результатаопределяется правилами преобразования, о которых шла речь в этой главе ранее. Например, если f имееттип float, a n — тип int, то типом выражения(n > 0) ? f : nбудет float вне зависимости от того, положительно значение n или нет.Заключать в скобки первое выражение в условном выражении не обязательно, так как приоритет ?: оченьнизкий (более низкий приоритет имеет только присваивание), однако мы рекомендуем всегда это делать,поскольку благодаря обрамляющим скобкам условие в выражении лучше воспринимается.Условное выражение часто позволяет сократить программу.















