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

42

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

 

Перша назва типу enum набере значення 0, наступна — 1, i так далi, хiба що бу-

ло задано явнi значення. Якщо не всi значення вказано явно, тодi тi, що не задано, продовжуватимуть прогресiю вiд останнього заданого, як показано в наступних прикладах:

enum escapes { BELL = ’\a’, BACKSPACE = ’\b’, TAB = ’\t’, NEWLINE = ’\n’, VTAB = ’\v’, RETURN = ’\r’ };

enum months { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; /* FEB = 2, MAR = 3, etc. */

Назви в рiзних перелiках мають вiдрiзнятися. Значення не обов’язково повиннi вiдрiзнятися в тому самому перелiку.

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

2.4Оголошення

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

int lower, upper, step; char c, line[1000];

Змiннi можна розподiлити помiж оголошень у будь-який спосiб; списки вище так само можна було б написати як

int

lower;

int

upper;

int

step;

char

c;

char

line[1000];

2.5. АРИФМЕТИЧНI ОПЕРАЦIЇ

43

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

Змiнну можна також iнiцiювати (надати їй початкового значення) пiд час її оголошення. Якщо за назвою слiдує знак рiвностi та вираз, то цей вираз служитиме iнiцiалiзатором, як, скажiмо:

char

esc = ’\\’;

int

i = 0;

int

limit

= MAXLINE+1;

float

eps =

1.0e-5;

Якщо зм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нiцiалiзатора, отримають невизначене значення (тобто непотрiб).

Для вказiвки того, що значення змiнної не змiнюватиметься, до оголошення можна додати класифiкатор const. У випадку масивiв класифiкатор const вказує на те, що елементи масиву залишатимуться незмiнними.

const double e = 2.71828182845905; const char msg[] = "warning: ";

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

int strlen(const char[]);

Якщо вiдбудеться спроба змiнити значення змiнної типу const, результат такої дiї залежатиме вiд реалiзацiї.

2.5Арифметичнi операцiї

Арифметичними операторами з двома операндами є +, -, *, / i оператор коефiцiєнту %. Подiл цiлих вiдкидає дробову частину. Вираз

x % y

44

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

повертає залишок подiлу x на y, i нуль, якщо дiлення вiдбудеться без залишку. Наприклад, рiк буде високосним, якщо вiн кратний 4, але не 100, за винятком того, що роки кратнi 400 — теж високоснi. Таким чином

if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) printf("%d is a leap year\n", year);

else

printf("%d is not a leap year\n", year);

Оператор % неможливо застосувати iз float та double. Напрямок округлення у випадку / i знак результату % є машинозалежним для вiд’ємних чисел, так само як дiї у випадку втрати значностi або переповнення. Оператори + та - рiвнi за прiоритетом, але мають менший прiоритет нiж *, / i %, якi в свою чергу, поступаються унарним + та -. Арифметичнi оператори дiють злiва направо.

Таблиця 2.1 у кiнцi цього роздiлу пiдводить пiдсумок прiоритетiв i асоцiативностi (спрягання) всiх операторiв.

2.6Реляцiйнi та логiчнi оператори

Реляцiйними операторами є

> >= < <=

Вони всi мають однаковий прiоритет. Трохи нижче за прiоритетом знаходяться оператори рiвностi:

==!=

Реляцiйнi оператори надiленi меншим прiоритетом за арифметичнi, тож вираз на зразок i < lim-1 розглядатиметься як i < (lim-1), як i очiкувалось.

Цiкавiшими є логiчнi оператори && та ||. Вирази, поєднанi && або ||, оцiнюються злiва направо, i їхня оцiнка закiнчується, як тiльки виявлено iстиннiсть чи хибнiсть результату. Бiльшiсть програм C покладаються на цi властивостi. Ось, наприклад, цикл функцiї вводу, яку ми написали у Роздiлi 1:

for (i=0; i < lim-1 && (c=getchar()) != ’\n’ && c != EOF; ++i) s[i] = c;

Перед тим як прочитати новий знак, необхiдно перевiрити, чи є мiсце для його збереження в масивi s, тож треба спочатку дiзнатися, чи i < lim-1. Якщо ця перевiрка зазнає невдачi, нам не слiд продовжувати далi, i читати наступний знак.

2.7. ПЕРЕТВОРЕННЯ ТИПIВ

45

Так само, було би недоцiльно перевiряти c на предмет EOF (кiнця файла), якщо до цього не викликано getchar; саме тому виклик i присвоєння мають вiдбутися до перевiрки символу, збереженого в c.

Прiоритет && є бiльшим за ||, але обидва поступаються релятивним операторам i операторам рiвностi, тож вирази на зразок

i < lim-1 && (c=getchar()) != ’\n’ && c != EOF

не вимагають додаткових дужок. Але оскiльки прiоритет != переважає оператор присвоєння =, дужки обов’язковi у випадку

(c=getchar()) != ’\n’

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

За визначенням, числове значення релятивного або логiчного виразу дорiвнює 1, якщо спiввiдношення iстинне, i 0 — якщо хибне.

Унарний оператор заперечення ! перетворює ненульовий операнд на 0 (нуль) i, навпаки, нульовий операнд на 1 (одиницю). Поширеним є застосуванням ! в конструкцiях на зразок

if (!valid)

замiсть

if (valid == 0)

Важко узагальнити, яка з цих форм є кращою. Конструкцiї на кшталт !valid легко читаються ( if not valid — якщо не дiйсне ), але складнiшi вирази буває важко зрозумiти.

Вправа 2-2. Напишiть цикл, еквiвалентний наведеному вище, але без використання && або ||.

2.7Перетворення типiв

Коли якийсь оператор має операнди рiзних типiв, останнi перетворюються до спiльного типу, згiдно невеличкого набору правил. Загалом, автоматичними перетвореннями вважаються тi, що обертають вужчий операнд на ширший без втрати iнформацiї, як, наприклад, при перетвореннi цiлого в число з рухомою точкою у виразi на зразок f + i (де i мiстить цiле значення, a f — число з рухомою точкою). Вирази, якi не мають змiсту, як скажiмо використання числа типу float в якостi iндексу —

46

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

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

Тип char — це просто маленьке цiле, тож цей тип може вiльно вживатися в арифметичних операцiях. Це забезпечує суттєвою гнучкiстю в деяких випадках перетворення символiв. Прикладом може служити спрощена цим наївним втiленням, функцiя atoi, яка обертає ланцюжок цифр у їхнiй числовий еквiвалент.

/* atoi: перетворює s на цiле */ int atoi(char s[])

{

int i, n;

n = 0;

for (i = 0; s[i] >= ’0’ && s[i] <= ’9’; ++i) n = 10 * n + (s[i] - ’0’);

return n;

}

Як ми вже згадували це в Роздiлi 1, вираз

s[i] - ’0’

повертає числове значення символу, яке буде збережено в s[i], оскiльки значення ’0’, ’1’ i так далi, утворюють неперервну послiдовнiсть в порядку зростання.

Iншим прикладом перетворення char на int є функцiя lower, яка вiдображає знак у нижньому регiстрi набору знакiв ASCII. Якщо символ не є лiтерою верхнього регiстру, lower повертає її без змiн.

/* lower: переводить c у нижнiй регiстр; тiльки ASCII */ int lower(int c)

{

if (c >= ’A’ && c <= ’Z’) return c + ’a’ - ’A’;

else

return c;

}

Це працює тiльки з набором ASCII, оскiльки вiдповiднi лiтери верхнього i нижнього регiстру знаходяться на сталiй вiдстанi як числовi значення й алфавiт є неперервним — немає нiчого окрiм лiтер мiж A та Z. Це останнє правило не є дiйсним

2.7. ПЕРЕТВОРЕННЯ ТИПIВ

47

у випадку набору символiв EBCDIC, тож цей код перекладав би не тiльки лiтери у випадку EBCDIC.

Стандартний файл заголовка <ctype.h>, описаний у Додатку Б, визначає сiмейство функцiй, що забезпечують можливiстю перевiрок i перетворень, незалежних вiд набору символiв. Так, наприклад, функцiя tolower — це машинонезалежна замiна, наведеної вище, функцiї lower. Так само, перевiрку

c >= ’0’ && c <= ’9’

можна помiняти на

isdigit(c)

З цiєї митi i надалi, ми послуговуватимемося функцiями з <ctype.h>.

Iснує один нюанс, що стосується перетворення знакiв на цiле. Мова C не уточнює, чи змiннi типу char є знаковими чи беззнаковими величинами. Коли char перетворено на int, чи не може це видати вiд’ємне цiле? Вiдповiдь вiдрiзняється на рiзних машинах, вiдображаючи вiдмiнностi в архiтектурах. На деяких машинах, char iз крайнiм лiвим бiтом рiвним 1 буде перетворено на вiд’ємне цiле ( знакове розширення ). На iнших, char зведено до int шляхом додання нулiв iз лiвого боку, а отже — завжди додатнiй.

За визначенням, мова C гарантує, що будь-який знак у машинному стандартному друковному наборi символiв нiколи не буде вiд’ємним, а отже завжди складатиме додатньою величину у виразах. Але довiльнi послiдовностi бiтiв, збереженi в символьних змiнних, можуть виявитися вiд’ємними на деяких машинах i, навпаки — додатнiми на iнших. Заради портабельностi, вказуйте signed (знакове) або unsigned (беззнакове), якщо несимвольнi данi треба зберегти в змiнних типу char.

Реляцiйнi вирази на кшталт i > j i логiчнi вирази, поєднанi && або || мають за визначенням значення 1, якщо iстиннi i 0, якщо хибнi. Таким чином, присвоєння на зразок

d = c >= ’0’ && c <= ’9’

встановлює d до 1, якщо c є цифрою, i 0 — якщо нi. Проте, такi функцiї, як isdigit, можуть повернути будь-яке ненульове значення у випадку iстини. Iстина в тестовiй частинi if, while, for тощо означає просто ненульове значення , тож це не грає особливої ролi.

Неявнi арифметичнi перетворення працюють як i очiкується. Загалом, якщо оператор, такий як + або *, має два операнди (тобто, це бiнарний оператор) вiдмiнних типiв, нижчий тип зводиться до вищого до того як здiйснити операцiю. У Роздiлi 6 Додатка А точно описано правила перетворень. Якщо вiдсутнi беззнаковi операнди, наступного набору правил цiлком вистачить:

48

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

∙Якщо тип якогось з операндiв дорiвнює long double (довгому подвiйному), iнший також буде зведено до long double.

∙Iнакше, якщо ти якогось з операндiв дорiвнює double (подвiйному), iнший також буде перетворено на double.

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

∙Iнакше, перетворити char (знакове) та short (коротке) на int (цiле).

∙Якщо ж ти якогось з операндiв дорiвнює long (довгому), iнший також буде перетворено на long.

Зауважте, що float у виразах не перетворюються автоматично на double; це вiдрiзняється вiд оригiнального визначення. Загалом, математичнi функцiй, як тi з <math.h>, використовуватимуть подвiйну точнiсть. Основною причиною використання float є збереження пам’ятi у випадку великих масивiв або, рiдше, збереження часу на машинах, де арифметика з подвiйною точнiстю — особливо ресурсоємка.

Правила перетворень ускладнюються, коли задiянi беззнаковi операнди. Проблема полягає в тому, що порiвнювання мiж знаковими i беззнаковими значеннями залежать вiд машини, оскiльки вони покладаються на розмiри рiзноманiтних типiв цiлих. Для прикладу, скажiмо, що int дорiвнює 16-и бiтам, а long — 32-ом бiтам. У такому випадку, -1L < 1U, оскiльки 1U, яке є беззнаковим цiлим (unsigned int) зведено до знакового довгого (signed long). Але -1L > 1UL, оскiльки -1L зведено до беззнакового довгого (unsigned long) i, таким чином, здається великим додатнiм числом.

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

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

Довшi цiлi перетворюються на коротшi або char шляхом вiдкидання зайвих бiтiв старшого розряду. Таким чином, при

int i; char c;

i = c; c = i;

значення c залишиться незмiнним. Це залишатиметься iстиною, незалежно вiд того, чи використовується знакове розширення. Однак, при протилежному напрямку присвоєння, можлива втрата iнформацiї.

Якщо x є типу float, а i є int, тодi x = i та i = x, обидва, призводять до перетворення; обернення float на int призводить до вiдкидання дробової частини. Коли

2.7. ПЕРЕТВОРЕННЯ ТИПIВ

49

double (подвiйне) перетворено на float (число з рухомою точкою), округлення чи вiдкидання дробової частини залежить вiд реалiзацiї.

Оскiльки аргумент виклику функцiї також є виразом, перетворення типiв так само може мати мiсце пiд час передачi аргументiв функцiям. За вiдсутностi прототипiв функцiй, char i short стають int, а float стане double. Саме тому ми оголосили аргументи функцiї як int i double, навiть якщо функцiю викликано iз char i float.

I, нарештi, можна змусити явнi перетворення в будь-якому виразi з допомогою унарного оператора зведення. В конструкцiї на зразок

(назва типу) вираз

вираз буде перетворено до вказаного типу, згiдно правил перетворення, наведених вище. Саме поняття зведення можна порiвняти iз тим, нiби вираз було присвоєно змiннiй вказаного типу, яка потiм використовується замiсть цiлої конструкцiї. Так, скажiмо, функцiя бiблiотеки sqrt очiкує аргумент типу double (подвiйного) i видасть нiсенiтницю, якщо ненавмисно передати щось iнше. (sqrt оголошено в <math.h>.) Тож, якщо n є цiлим, ми можемо використати

sqrt((double) n)

для перетворення значення n на тип double перед тим як передати його sqrt. Зауважте, що операцiя зведення просто видає значення вiдповiдного типу, сама n залишиться незмiнною. Оператор зведення має такий самий високий прiоритет як i решта унарних операторiв, як буде вказано в таблицi в кiнцi цього роздiлу. Якщо аргументи оголошено через прототип функцiї, як i повинно вiдбуватися за звичайних обставин, оголошення спричинить до автоматичного зведення будь-яких аргументiв пiд час виклику функцiї. Таким чином, маючи прототип функцiї srqt

double sqrt(double)

виклик

root2 = sqrt(2)

зводить цiле 2 у подвiйне 2.0 без потреби явного зведення. Стандартна бiблiотека включає портабельне втiлення генератора псевдовипадкових чисел i функцiю для iнiцiалiзацiї зерна; перша iлюструє зведення:

unsigned long int next = 1;

/* rand: повертає псевдовипадкове цiле в межах 0..32767 */ int rand(void)

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