Смекни!
smekni.com

Методические указания к выполнению контрольных работ по дисциплине "Основы программирования" (стр. 18 из 40)

if ( c == '\n' )

{

s[i] = c;

++i;

}

более компактной записью

if ( c == '\n' )

s[i++] = c;

Пример 3-6. Рассмотрим функцию strcat(s,t), которая приписывает строку t в конец строки s, образуя конкатенацию строк s и t. При этом предполагается, что в s достаточно места для хранения полученной комбинации.

strcat(s,t) // Присоединить t к окончанию s

char s[], t[]; // s должна быть достаточно большой

{

int i, j;

i = j = 0;

while(s[i] != '\0') // Поиск «хвоста» s

i++;

while((s[i++]=t[j++]) != '\0') // Копируем t

; // «Пустой» оператор

}

Tак как из t в s копируется каждый символ, то для подготовки к следующему прохождению цикла постфиксная операция ++ применяется к обеим переменным i и j.

Упражнение 3-3. Напишите другой вариант функции:

squeeze(s1,s2), который удаляет из строки s1 каждый символ, совпадающий с каким-либо символом строки s2.

Упражнение 3-4. Напишите программу для функции any(s1,s2), которая находит место первого появления в строке s1 какого-либо символа из строки s2 и, если строка s1 не содержит символов строки s2, возвращает значение –1.

3.9. Побитовые логические операции

В языке предусмотрен ряд операций для работы с битами; эти операции нельзя применять к переменным типа float или double.

& – побитовое И (AND);

| – побитовое ИЛИ (включающее OR);

^ – побитовое исключающее ИЛИ;

<< – сдвиг влево;

>> – сдвиг вправо;

~ – побитовое отрицание (дополнение, унарная операция).

Побитовая операция И часто используется для маскирования некоторого множества битов. Например, оператор:

c = n & 0177

передает в c семь младших битов n, полагая остальные равными нулю.

Операция | (побитовое ИЛИ) используется для включения битов:

c = x | mask

устанавливает на единицу те биты в х , которые равны единице в mask.

Следует быть внимательным и отличать побитовые операции & и | от логических связок && и || , которые подразумевают вычисление значения истинности слева направо. Например, если х=1, а y=2, то значение х&y равно нулю, в то время как значение x&&y равно единице (почему?).

Операции сдвига << и >> осуществляют, соответственно, сдвиг влево и вправо своего левого операнда на число битовых позиций, задаваемых правым операндом. Таким образом, х<<2 сдвигает х влево на две позиции, заполняя освобождающиеся биты нулями, что эквивалентно умножению х на 4. Следует отметить, что сдвиг вправо величины без знака заполняет освобождающиеся биты на некоторых машинах, таких как PDP-11, содержанием знакового бита (арифметический сдвиг), а на других – нулем (логический сдвиг).

Унарная операция ~ дает дополнение к целому; это означает , что каждый бит со значением 1 получает значение 0 и наоборот. Эта операция обычно оказывается полезной в выражениях типа:

x & ~077

где последние шесть битов х маскируются нулем. Подчеркнем, что выражение x&~077 не зависит от длины слова и поэтому предпочтительнее, чем, например, x&0177700, где предполагается, что х занимает 16 битов. такая переносимая форма не требует никаких дополнительных затрат, поскольку ~077 является константным выражением и, следовательно, обрабатывается во время компиляции.

Пример 3-7. Чтобы проиллюстрировать использование некоторых операций с битами, рассмотрим функцию getbits(x,p,n), которая возвращает начинающиеся с позиции р поле переменной х длиной n битов, сдвинутыми к правому краю. Мы предполагаем , что крайний правый бит имеет номер 0, и что n и р – разумно заданные положительные числа. например, getbits(x,4,3) возвращает сдвинутыми к правому краю биты, занимающие позиции 4, 3 и 2.

// Получить n, начиная с p-й позиции

getbits(unsigned x, unsigned p, unsigned n)

{

return((x >> (p+1-n)) & ~(~0 << n));

}

Операция x>>(p+1-n) сдвигает желаемое поле в правый конец слова. Описание аргумента x как unsigned гарантирует, что при сдвиге вправо освобождающиеся биты будут заполняться нулями, а не содержимым знакового бита, независимо от того, на какой машине пропускается программа. Все биты константного выражения ~0 равны 1; сдвиг его на n позиций влево с помощью операции ~0<<n создает маску с нулями в n крайних правых битах и единицами в остальных; дополнение ~ создает маску с единицами в n крайних правых битах.

Упражнение 3-5. Переделайте getbits таким образом, чтобы биты отсчитывались слева направо.

Упражнение 3-6. Напишите программу для функции wordlength(), вычисляющей длину слова используемой машины, т.е. Число битов в переменной типа int. Функция должна быть переносимой, т.е. Одна и та же исходная программа должна правильно работать на любой машине.

Упражнение 3-7. Напишите программу для функции rightrot(n,b), сдвигающей циклически целое n вправо на b битовых позиций.

Упражнение 3-8. Напишите программу для функции invert(x,p,n), которая инвертирует (т.е. заменяет 1 на 0 и наоборот) n битов x, начинающихся с позиции p, оставляя другие биты неизмененными.

3.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 .

Пример 3-8. В качестве примера приведем функцию bitcount, которая подсчитывает число равных 1 битов у целого аргумента.

// Определить число единичных бит в целом n

bitcount(unsigned n)

(

int b;

for (b = 0; n != 0; n >>= 1)

if (n & 01)

b++;

return(b);

)

Не говоря уже о краткости, такие операторы присваивания имеют то преимущество, что они лучше соответствуют образу человеческого мышления.

Мы говорим: «прибавить 2 к i» или «увеличить i на 2», но не «взять i, прибавить 2 и поместить результат опять в i». Итак, i += 2.

Кроме того, в громоздких выражениях, подобных:

yyval[yypv[p3+p4] + yypv[p1+p2]] += 2 ,

такая операция присваивания облегчает понимание программы, так как читатель не должен скрупулезно проверять, являются ли два длинных выражения действительно одинаковыми, или задумываться, почему они не совпадают. Такая операция присваивания может даже помочь компилятору получить более эффективную программу.

Мы уже использовали тот факт, что операция присваивания «вырабатывает» значение и может входить в выражения. Самый типичный пример:

while ((c = getchar()) != eof) .

Присваивания, использующие другие операции (+=, -= и т.д.), также могут входить в выражения, хотя это случается реже.

Типом выражения присваивания является тип его левого операнда.

Упражнение 3-9. В двоичной системе счисления операция x&(x-1) обнуляет самый правый равный 1 бит переменной x (почему?). Используйте это замечание для написания более быстрой версии функции bitcount.

3.11. Условные выражения

Операторы:

if (a > b)

z = a;

else

z = b;

конечно вычисляют в z максимум из a и b. Условное выражение, записанное с помощью тернарной (т.е. имеющей три операнда) операции «?:», предоставляет другую возможность для записи этой и аналогичных конструкций. В выражении:

е1 ? е 2 : е3

сначала вычисляется выражение е1. Если оно отлично от нуля (истинно), то вычисляется выражение е2, которое и становится значением условного выражения. В противном случае вычисляется е3, и оно становится значением условного выражения. Каждый раз вычисляется только одно из выражения е2 и е3. Таким образом, чтобы положить z равным максимуму из а и в, можно написать:

z = (a > b) ? a : b; // z = max(a,b)

Следует подчеркнуть, что условное выражение действительно является выражением и может использоваться точно так же, как любое другое выражение. Если е2 и е3 имеют разные типы, то тип результата определяется по правилам преобразования, рассмотренным ранее в этой главе. Например, если f имеет тип float, а n – тип int, то выражение:

(n > 0) ? f : n

имеет тип double независимо от того, положительно ли n или нет.

Так как уровень старшинства операции ?: очень низок (ниже может быть только присваивание), то первое выражение в условном выражении можно не заключать в круглые скобки. Однако мы все же рекомендуем это делать, так как скобки делают условную часть выражения более заметной.

Использование условных выражений часто приводит к коротким программам. Например, следующий ниже оператор цикла печатает n элементов массива, по 10 в строке, разделяя каждый столбец одним пробелом и заканчивая каждую строку (включая последнюю) одним символом перевода строки:

for (i = 0; i < n; i++)

printf("%6d%c",a[i],(i%10==9 || i==n-1) ? '&bsol;n' : ' ');

Символ перевода строки записывается после каждого десятого элемента и после n-го элемента. За всеми остальными элементами следует один пробел. Хотя, возможно, это выглядит мудреным, было бы поучительным попытаться записать это, не используя условного выражения.

Упражнение 3-10. Перепишите программу для функции lower, которая переводит прописные буквы в строчные, используя вместо конструкции if-else условное выражение.

3.12. Старшинство и порядок вычисления

В приводимой ниже таблице 3.2. сведены правила старшинства и ассоциативности всех операций, включая и те, которые мы еще не обсуждали. Операции, расположенные в одной строке, имеют один и тот же уровень старшинства; строки расположены в порядке убывания старшинства.

Так, например, операции *, / и % имеют одинаковый уровень старшинства, который выше, чем уровень операций + и -.

Операции -> и . (точка) используются для доступа к элементам структур; они будут описаны в главе 7 вместе с sizeof (размер объекта). В главе 6 обсуждаются операции * (косвенное обращение по указателю) и & (получение адреса объекта). Отметим, что уровень старшинства побитовых логических операций &, ^ и | ниже уровня операций == и != . Это приводит к тому, что осуществляющие побитовую проверку выражения, подобные