osn_progr_final
.pdfВ цьому випадку в якості параметра функції (а він має бути одним згідно з оголошенням) виступає результат операції послідовного виконання: для випадку (a+=b,6) – це константа 6, для випадку
(i++,b=5,c=9,a=b+4,k=7)– це константа 7.
4.4.6 Операції інкремента(++) та декремента(--)
Існує дві форми - інфіксна та постфіксна. В інфіксній операція стоїть перед операндом, постфікснфй - після. В інфіксній формі спочатку відбувається зміна операнда, а потім він використовується у відповідному виразі. В постфіксній формі спочатку операнд використовується у виразі, а потім його значення змінюється.
Якщо операнд є значенням цілого типу, то ++ - збільшення на одиницю,-- - зменшення на одиницю. Якщо операнд є вказівником, то значення його змінюється на розмір типу, на який він вказує .
Використання операції інкремента дозволяє не тільки скорочувати текст програми шляхом використання цікавих конструкції виду i+++j, але й має особливий зміст в технології стверення програмних компонент. Як приклад розглянемо наступну задачу-жарт. Яка мова програмування краща: С чи С++?
#include <stdio.h> void main()
{
int C = 0;
puts("Що краще: С чи С++?"); if(C > C++)
puts("Очевидно, С краще."); else if (C == C++)
puts("Визначити не вдалося."); else /* C < C++ */
puts("Безсумнівно,краще С++.");
}
Яким буде результат? Давайте детальніше розглянемо цю програму. У рядку
if(C > C++) використана постфіксна форма операції інкремента. Отже, спочатку операнд, в якості якого виступає змінна С, використовується в умовному виразі. Тобто, фактично перевіряється істинність умови C > C, яка є хибною. Але зразу ж після цього спрацьовує операція інкремента, і змінна С стає рівною 1.Тоді виконується оператор else if (C == C++), де маємо аналогічну ситуацію. Тобто, фактично перевіряється істинність умови C == C, яка завжди ви-
61
конується. Тому і друкується повідомлення : Визначити не вда-
лося.
Як бачимо з цього простого прикладу, операції інкремента не просто скорочують запис, але мають особливе змістове навантаження. Щоб зрозуміти його, досить написати аналогічну програму без використання операції інкременту.
4.4.7 Операція присвоювання
Синтаксис: <операнд1>=<операнд2> На відміну від ряду інших мов програмування, в мові Сі присво-
єння виступає не як оператор, а як операція. Як і будь-яка інша операція, операція присвоєння повертає значення. Значенням операції присвоєння є значення правого операнда . Воно повинне мати тип лівого операнда ( якщо типи не співпадають, відбуваються перетворення типів по замовчуванню). Остання обставина дозволяє записувати конструкції виду:
a=(i=5)+4;
a=b=c=d=1;
В першому рядку відбувається одночасно присвоєння відповідного значення (числа 9) змінній а та ініціалізація змінної і. У другому рядку прикладу бачимо ілюстрацію серії ініціалізацій.
Існує складена операція присвоювання. Синтаксично вона складається з двох символів–символу деякої бінарної операції та символу присвоювання:
+=, *=, /=, %=, <<=, >>=, ^=, !=, -=, &=
Специфіка складеної операції присвоювання полягає в тому, що спочатку виконується операція, значок якої стоїть першим, а потім результат присвоюється першому операнду. Приклад:
int i=1,j=2; i+=j; /*i==3*/; i+=1; /*i==2*/;
4.4.8 Умовна операція
<умовний вираз>?<вираз1>:<вираз2> Спочатку обчислюється умовний вираз. Якщо його значення істинне,
то обчислюється вираз1, інакше-вираз2. Типи виразів повинні співпадати.
Конструкція
if (i>5) j=3 ; else j=4;
еквівалентна наступній:
62
(i>5)? j=3 : j=4;
4.4.9 Операція sizeof sizeof <вираз>.
Повертає розмір об`єкта в байтах. int rozmir; rozmir=sizeof (int);
rozmir=sizeof (a+b-4+3.5e-4);
Стосовно імені масиву операція sizeof дає весь розмір масиву в байтах. Наприклад, якщо оголосити масив char s[23]; , то sizeof(s) поверне значення 23.
На практиці може статися ситуація, коли сума розмірів компонент структури не дорівнює її розміру, тоді операція sizeof дасть реальний розмір об`єкта.
4.4.10 Унарний “+”
Існує специфічна унарна операція + , яка забороняє компілятору реорганізовувати вирази. Компілятор з високим рівнем оптимізації намагається реорганізовувати вирази , враховуючи пріоритет операцій. Наявність унарного плюса забезпечить виконання операцій саме в такій послідовності, як вказано в дужках.
Приклад: int x,y=5,z=4;
x=12000*+(y*0.001);
4.4.11 Пріоритет операцій
Пріоритет операцій записаний у наступній табличці. Зростає він зверху вниз. Порядок слід враховувати при обчисленні виразів з рівним пріоритетом.
Операція |
Порядок |
== != |
зліва направо |
|
|
|
|||
() [] -> . |
зліва направо |
& |
зліва направо |
|
^ |
зліва направо |
|||
! ~ |
справа наліво |
|||
| |
зліва направо |
|||
++ -- - |
справа наліво |
|||
&& |
зліва направо |
|||
(type) * & sizeof справа наліво |
||||
|| |
зліва направо |
|||
* / % |
зліва направо |
|||
?: |
справа наліво |
|||
+ - |
зліва направо |
|||
= += -= |
і т.д. справа наліво |
|||
<< >> |
зліва направо |
|||
, |
зліва направо |
|||
< <= > >= |
зліва направо |
|||
|
|
|||
|
|
|
|
63
4.5 ОПЕРАТОРИ МОВИ С
4.5.1 Порожній оператор
Синтаксис: ;
Використовується у випадку по синтаксису, вимагається наявність оператора .
Приклад: for(;;);
Остання крапка з комоюце порожній оператор. Він необхідний синтаксично, оскільки в операторі for після закриваючої круглої дужки обов”язково повинен стояти якийсь оператор (як правило– це складений оператор, що містить в собі ряд інших операторів, що задають тіло циклу). Оскільки в даному випадку відсутні будь-які оператори, то необхідно поставити крапку з комою-порожній оператор.
4.5.2 Складений оператор
Синтаксис: { }
Складений оператор відіграє величезну роль при написанні програм. Він дозволяє синтаксично об”єднувати кілька операторів в одне ціле, що вважатиметься одним оператором. Наприклад, можемо об’єднати в одному складеному операторі декілька простих операторів присвоювання:
{
a=4;
b=5;
c=6;
}
Складений оператор може містити оголошення та визначення. Як буде детальніше розглянуто далі, визначені таким чином змінні будуть мати локальну область дії, тобто доступними будуть лише в межах даного складеного оператора. Складений оператор , очевидно, може містити інші складені оператори:
{ int i=5; j++;
{ float i=4; i+=5;}
}
4.5.3 Умовний оператор
Умовний оператор відіграє ключову роль при описі алгоритмів, що мають структуру типу розгалуджень.
Синтаксис: if (<умовний вираз>) <оператор1>[else<оператор2>]
64
Дія цього оператора є наступною: якщо <умовний вираз> виявиться істинним значенням (а таким значенням в мові С вважається будьяке число, відмінне від нуля), то виконується <оператор1>. В противному випадку, при наявності необов”язкової конструкції [else<оператор2>], виконується <оператор2>.
Приклад:
if (a>5) i++; else j++;
Допускається послідовне використання оператора: if (i>j) i++;
else
if (i==j) j++;
else printf(“the end”);
…
При такий інтерпретаціі знаходиться оператор else і пов’язується з найближчим оператором if у якого відсутня конструкція else. Тобто останній фрагмент програми еквівалентний наступному:
if (i>j) i++; else
{ if (i==j) j++;
else printf(“the end”);}
Використання складеного оператора в такому випадку робить програму більш читабельною і, звичайно, має переваги.
Відмітимо, що на відміну від інших мов програмування, в Сі немає ключового слова then. Ті, хто вивчає Сі , вже знаючи мову Pascal, дуже часто роблять помилки, як у наступному фрагменті програми:
if( x > 2 |
) |
|
then |
x |
= 2; |
Зауважимо також, що умови в операторах if (а також while) повинні братись в круглі дужки. Тому в наступному рядку буде помилка:
if x < 1 x = 1;
4.5.4 Оператор циклу for
Оператор for використовується для запису алгоритмів, що мають циклічну структуру і має в мові програмування С особливе навантаження.
Синтаксис: for([<початковий вираз>];[<умовний вираз>];[<виразприріст>])<оператор>
Дія цього оператора наступна. Спочатку обчислюється <почат-
65
ковий вираз> , тоді перевіряється <умовний вираз>, у випадку його істинності виконується <оператор> і після цього виконується <виразприріст>. Як бачимо, <початковий вираз>, <умовний вираз>,<виразприріст> можуть бути відсутніми. Обов’язковою є лише наявність <оператора> (при цьому зауважимо, що синтаксично повинні бути присутніми відповідні крапки з комою). Якщо умовного виразу немає, то значення його вважається істинним. Тому в певному розумінні мінімальний оператор for, як вже наводилось вище, має вигляд: for(;;);.
Як правило, <початковий вираз> на практиці використовується для початкової ініціалізації змінних, <умовний вираз> визначає умову виконання оператора, що задає тіло циклу, а <вираз-приріст> задає умову зміни змінних, що використовуються в циклі, наприклад, як лічильники. Нехай, наприклад, нам необхідно декілька разів надрукувати повідомлення “Hello, world”. Для цього використаємо оператор for:
for (i=j=0;i<10;i++); printf (“Hello, world \n”);
Відповідне повідомлення надрукується 10 разів. При цьому, як бачимо, у <початковому виразі> ініціалізуються зразу дві змінних-i та j.
Відмітимо, що використовуючи операцію послідовного виконання, можна відповідні вирази робити досить складними. Однак, в таких випадках дуже страждає читабельність програм.
Розглянемо ще один приклад. Нехай необхідно надрукувати рядок довжини WIDTH, в якому спочатку міститься x0 символів '-', потім w символів '*', і до кінця рядка - знову символи '-':
int x; |
|
|
|
for(x=0; x < x0; |
++x) putchar('-'); |
||
for( |
; x < |
x0 + w; x++) putchar('*'); |
|
for( |
; x < |
WIDTH ; ++x) putchar('-'); |
putchar('\n');
або
for(x=0; x < WIDTH; x++)
putchar( x < x0 ? '-' : x < x0 + w ? '*' :
'-' );
putchar('\n');
Допоміжні змінні, які не мають змісту (наприклад, лічильник повторення циклу, який не використовується в самому тілі циклу) прийня-
66
то по традиції позначати однією літерою, наприклад, i, j. Більш того, можна навіть написати:
main(){ int _ ;
for( _ = 0; _ < 10; _++) printf("%d\n", _ );
}
(підкреслювання в ідентифікаторах – рівноправна літера)
4.5.5 Оператори dowhile
Поряд з оператором for, для запису циклічних алгоритмів використовуються також оператори do-while та while.
Синтаксис: while(<вираз>) <оператор>
Дія наступна: поки істинний <вираз> виконується <оператор>.
Синтаксис: do <оператор> while(<вираз>)
Дія наступна: виконується <оператор> поки істинний <вираз>.
Для того, щоб зрозуміти відмінність у цих операторах, розглянемо два простих фрагменти програм:
1)
int i=10; while(i>0) {i--; printf(“%d”,i);
}
2)
int i=10; do
{i--; printf(“%d”,i);
}
while(i>0);
В обох прикладах друкується значення змінної і, починаючи з 10. Однак в першому прикладі останнє надруковане значення “0”, а в другому – “-1”. Дійсно, в другому прикладі спочатку виконується тіло оператора, а вже потім перевіряється умовний вираз. Якщо він виявиться хибним, то оператор більше не виконується.
Відмітимо, що при використанні циклів не потрібно забувати ініціалізувати зміні, що виступають в якості лічильників. Розглянемо фрагмент програми, у якій деяке повідомлення повинно друкуватись 10 разів:
main(){ /* друк фрази 10 раз */ int i;
while(i < 10){
67
printf("%d-ий раз\n", i+1); i++;
}
}
Однак тут автоматична змінна i не була проініціалізована і містить не 0, а якесь довільне значення. Цикл може виконуватись не 10, а довільне число раз (в тому числі і 0 ). Тому у відповідному рядку необхідно написати: int i = 0;
В даному прикладі було б ще краще використовувати цикл for, в якому всі операції над індексом циклу зібрані в одному місці - в заголовку циклу:
for(i=0; i < 10; i++) printf(...);
4.5.6 Оператор continue
Синтаксис: continue;
Передає управління на наступну ітерацію в циклах. Нагадаємо, що в циклах while та do наступна ітерація починається з обчислення умовного виразу, а в операторі for виразу-приросту (а потім умовного виразу) . Розглянемо фрагмент програми:
i=10;
while(i>0)
{
i--;
if (i%2==0) continue; printf(“%d”,i);
}
В цьому прикладі будуть надруковані непарні значення змінної і від 9 до 1. Якщо значення змінної i виявиться парним, то виконується оператор continue, який перериває виконання циклу та виконується наступна ітерація.
4.5.7 Операторперемикач switch
Синтаксис: switch(<вираз>)
{
[оголошення чи визначення ] [case<константний вираз>:<оператор>]
…
[case<константний вираз>:<оператор>]
[default:<оператор>]
}
68
Обчислюється <вираз> і наступним виконується той оператор, який слідує за умовою case, для якої значення константного виразу співпадає з значенням початкового <виразу>.
Приклад: switch(c=getch()) { int i,j,k=0;
case’a’:i++; printf(“%d\n”,i);break; case’b’:j++; break;
default:k++; }
В залежності від значення c відбувається виконання тих операторів , що визначаються умовою case. Тобто,
якщо с==’a’, виконується
i++; printf(“%d\n”,i);break;
якщо с==’b’, виконується j++;break;
інакше виконується
k++;
Відмітимо, що відсутність оператора break в нашому прикладі призведе до того, що після вибору якогось оператора по case-умові будуть виконуватись усі оператори , що стоять після нього, в тому числі і помічені іншими умовами case.
4.5.8 Оператор break
Синтаксис: break;
Перериває виконання операторів циклу та switch. Як вже вказувалось вище, для того, щоб в попередньому прикладі забезпечити виконання лише одного оператора case або default, необхідно в кінці кожної групи операторів case ставити break.
Приклад:
for (i=0;i<10;i++) for (j=0;j<20;j++)
{ if ( j%5==4) break;
…
}
В даному прикладі при виконанні умови j%5==4 відбувається переривання виконання внутрішнього циклу. При цьому зовнішній цикл по i продовжує виконуватись.
4.5.9 Оператор goto
Синтаксис: goto<мітка>
…
69
<мітка>:<оператор> Передає управління на мітку. Міткою може бути будь-який ідентифі-
катор. Наприклад, у випадку коли потрібно вийти з кількох циклів при великому рівні вкладеності (більше ніж 2) єдина можливість - це оператор goto. Можна увійти за допомогою goto в блок, тіло циклу, оператор switch.
Приклад: for(i=0;i<100;i++) for(j=0;j<100;j++) for(k=0;k<100;k++)
if (error(i,j,k)) goto exit; exit: ;
В даному прикладі у випадку, коли значення деякої функції error виявиться ненульовим, переривається виконання усіх циклів та управління передається на мітку exit, за якою стоїть порожній оператор.
4.5.10 Оператор return
Синтаксис: return <константний вираз>
Повертає управління у викликаючу функцію. Розглянемо наступні функції main():
main( ) |
void main( ) |
{ |
{ |
… |
… |
return 0;} |
} |
В першому випадку функція main() за замовчуванням повинна повертати значення типу int. Тому в тілі цієї функції міститься оператор return. В другому випадку тип результату–void, тому оператор return відсутній. Взагалі, будь-яка функція, що має тип результату, відмінний від void, повинна містити оператор return .
Відмітимо, що значення, яке повертається оператором return, на практиці часто використовується для видачі певної інформації . Наприклад, якщо функція відпрацювала нормально, можна повернути нуль.
4.6 ДИРЕКТИВИ ПРЕПРОЦЕСОРУ ТА ВКАЗІВКИ КОМПІЛЯТОРУ
Препроцесор-це програма, яка здійснює обробку тексту на нульовій фазі компіляції. Компілятор мови С сам викликає препроцесор, хоча він може бути викликаний і автономно. Директиви препроцесору широко використовуються для підключення файлів, управління процесом компіляції програми, недопущення недопустимих пере-
70