Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
knrc (1).pdf
Скачиваний:
16
Добавлен:
12.02.2016
Размер:
926 Кб
Скачать
☆

54

РОЗДIЛ 2. ТИПИ, ОПЕРАТОРИ ТА ВИРАЗИ

Вправа 2-7. Напишiть функцiю invert(x,p,n), яка би повертала x, в якому порядок n бiтiв, починаючи з положення p, було би обернено на протилежний (тобто 1 замiнено на 0 i навпаки), залишаючи решту бiтiв незмiнними.

Вправа 2-8. Напишiть функцiю rightrot(x,n), яка повертає значення цiлого x, оберненого в правий бiк на n позицiй.

2.10Оператори та вирази присвоєння

Вираз на кшталт

i = i + 2

в якому змiнна з лiвого боку одразу повторюється на правому, можна записати в стислiшiй формi

i += 2

Оператор += також називається оператором присвоєння. Бiльшiсть бiнарних операторiв (такi як +, що має лiвий i правий операнд) мають вiдповiдний оператор присвоєння op =, де op може бути одним з наступних

+

-

*

/

%

<<

>> &

^

|

Якщо expr1 i expr2 — це два вирази, тодi

expr1 op= expr2

еквiвалентно

expr1 = (expr1) op (expr2)

крiм випадку, коли expr1 обчислюється тiльки один раз. Звернiть увагу на дужки навколо expr2:

x *= y + 1

означає

x = x * (y + 1)

2.10. ОПЕРАТОРИ ТА ВИРАЗИ ПРИСВОЄННЯ

55

а не

x = x * y + 1

В якостi прикладу наведемо функцiю bitcount, яка обчислює кiлькiсть 1-бiтiв в своєму аргументi-цiлому.

/* bitcount: рахує 1-бiти в x */ int bitcount(unsigned x)

{

int b;

for (b = 0; x != 0; x >>= 1) if (x & 01)

b++; return b;

}

Оголошення аргументу x як unsigned (беззнакового) гарантує, що коли його буде змiщено вправо, звiльненi бiти заповняться нулями, а не знаковими бiтами, незалежно вiд машини на якiй було запущено програму.

Крiм стислостi, оператори присвоєння мають переважають тим, що вони краще вiдповiдають способу мислення людей. Ми кажемо додати 2 до i або збiльшити i на 2 , а не взяти i, додати 2, а потiм помiстити результат назад у i . Тому виразу i += 2 надається перевага над i = i+2. На додаток, у складних виразах на зразок

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

оператор присвоєння полегшує розумiння коду, оскiльки читач не змушений ретельно перевiряти, що два довгих вирази дiйсно вiдповiдають один одному, або дивуватися, чому нi. Оператор присвоєння, може навiть допомогти компiлятору виробити ефективнiший код.

Ми вже бачили, що твердження присвоєння повертає певне значення i може вживатися у бiльших виразах; найтиповiшим прикладом може бути

while ((c = getchar()) != EOF)

...

Iншi оператори присвоєння (+=, -= тощо) також можуть зустрiчатися всерединi виразiв, хоч i не так часто.

Вусiх таких виразах, тип виразу присвоєння збiгається з типом лiвого операнду,

азначення вiдповiдає значенню пiсля присвоєння.

56

РОЗДIЛ 2. ТИПИ, ОПЕРАТОРИ ТА ВИРАЗИ

Вправа 2-9. У двiйковiй числовiй системi (two’s complement number system), x &= (x-1) вилучає крайнiй правий бiт з x. Пояснiть чому. Скористайтеся з цього спостереження для написання швидшої версiї bitcount.

2.11Вирази умов

Вирази на зразок

if (a > b) z = a;

else

z = b;

обчислюють z як найбiльше значення з-помiж a та b. Умовний вираз iз використанням трiйчастого оператора ?: дають альтернативний спосiб написання подiбної конструкцiї. У виразi

expr1 ? expr2 : expr3

вираз expr1 розглядається першим. Якщо expr1 ненульовий (iстина), тодi обчислюється expr2, i це й буде кiнцевим значенням цiлого виразу умови. У протилежному випадку, обчислюється expr3, i вiн стане кiнцевим значенням. Оцiнюється тiльки один iз expr2 та expr3. Таким чином, щоб встановити z до максимального значення a або b, ми напишемо

z = (a > b) ? a : b;

/* z = max(a, b) */

Слiд зауважити, що вираз умови — це дiйсно вираз, i може бути використаний там де будь-який iнший. Якщо expr2 i expr3 — рiзних типiв, тип результату визначається правилами перетворення, обговорених ранiше в цьому роздiлi. Наприклад, якщо f має тип float, а n — це int, тодi вираз

(n > 0) ? f : n

буде типу float, незалежно вiд того, чи n додатнiй.

Дужки не обов’язковi навколо першого виразу оскiльки прiоритет ? й : дуже низький, лишень трохи вищий за присвоєння. Проте, їх рекомендовано вживати, оскiльки вони роблять умовну частину виразу очевиднiшою.

Умовнi вирази, як правило, призводять до стислiшого коду. Наприклад, наступний цикл виводить n елементiв масиву, по 10 на кожний рядок, стовпчики будучи роздiленi пробiлом, кожний рядок (включаючи останнiй) завершується символом нового рядка.

2.12. ПРIОРИТЕТ I ПОСЛIДОВНIСТЬ ОБЧИСЛЕННЯ

57

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

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

Символ нового рядка виводиться пiсля кожного десятого елемента i пiсля n-нного. За рештою елементiв слiдує пробiл. Це може здаватися складним, але воно компактнiше нiж еквiвалент iз if-else. Iншим хорошим прикладом є

printf("You have %d items%s.\n", n, n==1 ? "" : "s");

Вправа 2-10. Перепишiть функцiю lower, яка обертає лiтери верхнього регiстру на нижнiй, за використанням виразу умови замiсть if-else.

2.12Прiоритет i послiдовнiсть обчислення

Таблиця 2.1 пiдбиває пiдсумок правил прiоритету й асоцiативностi всiх операторiв, включаючи тi, що ми ще не обговорювали. Оператори, розмiщенi на одному рядку — рiвнi за прiоритетом; рядки знаходяться в порядку зменшення прiоритету, тож, наприклад, *, / та % мають однаковий прiоритет, який є вищим за, скажiмо, бiнарнi + та -.Оператор () стосується виклику функцiї. Оператори -> iз . використовуються для доступу до членiв структур; їх буде розглянуто в Роздiлi 6 разом iз функцiєю sizeof (яка визначає розмiр об’єкту). В Роздiлi 5 обговорюється * (непряме звертання через покажчик) а також & (адреса об’єкту), тодi як в Роздiлi 3 розглянуто оператор-кому.

Звернiть увагу, що прiоритет розрядних операторiв &, ˆ та | нижчий за == та !=. З цього випливає, що вирази перевiрки бiтiв на зразок

if ((x & MASK) == 0) ...

повиннi обов’язково мiстити дужки, щоб добитися бажаного результату.

C, як i бiльшiсть мов, не уточнює послiдовнiсть в якiй обчислюються операнди якогось оператора. (Виняток становлять &&, ||, ?: та ,.) Так, наприклад, у твердженнi

x = f() + g();

f може бути обчислено до g або навпаки; тож, якщо функцiя f або g змiнює якусь змiнну, вiд якої залежить iнша з цих функцiй, тодi x також залежатиме вiд порядку обчислення. Промiжнi результати можуть зберiгатися в тимчасових змiнних, щоб забезпечити певну послiдовнiсть. Аналогiчно, порядок в якому розглядаються аргументи функцiй, також не уточнюється, тож вираз

printf("%d %d\n", ++n, power(2, n));

/* ПОМИЛКА */

58

 

РОЗДIЛ 2. ТИПИ, ОПЕРАТОРИ ТА ВИРАЗИ

 

 

Табл. 2.1: Прiоритет i асоцiативнiсть операторiв

 

 

 

 

 

 

 

 

 

 

 

Оператори

Асоцiативнiсть

 

() [] -> .

злiва направо

 

 

 

! ˜ ++ – + - * (тип ) sizeof

справа налiво

 

* / %

злiва направо

 

+ -

злiва направо

 

<< >>

злiва направо

 

< <= > >=

злiва направо

 

== !=

злiва направо

 

&

злiва направо

 

 

ˆ

злiва направо

 

|

злiва направо

 

&&

злiва направо

 

||

злiва направо

 

?:

справа налiво

 

 

= += -= *= /= %= &= ˆ= |= <<= >>=

справа налiво

 

,

злiва направо

 

Унарнi

&, +, - та * мають вищий прiоритет за

вiдповiднi бiнарнi

форми.

може видати вiдмiнний результат у рiзних компiляторах, залежно вiд того, чи n збiльшено до чи пiсля виклику power. Вирiшенням цього, звичайно, буде написати

++n;

printf("%d %d\n", n, power(2, n));

Виклики функцiй, гнiздованi присвоєння, а також оператори приросту та спаду зумовлюють побiчний ефект — якась змiнна мiняє значення внаслiдок обчислення виразу. В будь-якому виразi з побiчним ефектом можуть бути тонкi вiдмiнностi щодо того в якiй послiдовностi оновлюється значення змiнних, що становлять частину виразу. Одну з нещасливих ситуацiй можна зобразити наступним типовим прикладом:

a[i] = i++;

Питання полягає в тому, чи iндекс вживається зi старим значенням, чи набув нового. Рiзнi компiлятори можуть тлумачити це по-рiзному, i видавати рiзнi значення в залежностi вiд iнтерпретацiї. Стандарт навмисне не уточнює бiльшiсть таких питань. Випадки, коли всерединi виразу має мiсце побiчний ефект (присвоєння нового значення змiннiй), залишено на розсуд компiлятора, оскiльки найкраща послiдовнiсть дiй залежить значною мiрою вiд машинної архiтектури. (Стандарт проте вказує, що всi побiчнi ефекти аргументiв мають вiдбутися до того як викликано функцiю, але це не допоможе при вищенаведеному виклику printf.)

2.12. ПРIОРИТЕТ I ПОСЛIДОВНIСТЬ ОБЧИСЛЕННЯ

59

Мораль зводиться до того, що написання коду, який залежить вiд порядку обчислення, вважається поганою програмiстською практикою для будь-якої мови. Природньо, що треба знати речi, яких треба уникати, але, якщо ви не знатимете як щось працює на рiзних архiтектурах, то i меншою буде ймовiрнiсть, що спокуситесь скористатися з переваг певної реалiзацiї.

60

РОЗДIЛ 2. ТИПИ, ОПЕРАТОРИ ТА ВИРАЗИ

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]