Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
36
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

описуються граматикою Г1. Як розділювач операторів у послідовності приймемо крапку з комою. З огляду на те, що послідовність операторів відповідає списку з розділювачами у вигляді крапки з комою і використовуючи визначені раніше конструкції ідентифікатора та арифметичного виразу без дужок, одержуємо шукану схему граматики у вигляді:

Г3 : R = { <U> <A3>, <A3> <S> <R1>,

<R1> ; <S> <R1>, <R1> $,

<S> <I> <B2>, <B2> = <H>, <R2> $}

6. Перейдемо до написання граматик для умовних операторів, аналогічні тим, що використовуються, наприклад в мові С, з розділювачами 'if', ‘else’. Нехай як умову в таких операторах будемо використовувати відношення, що складається з двох ідентифікаторів, з'єднаних знаками = і <, а в якості оператора, що стоїть після умовного оператора ‘if’ чи 'else', - оператор присвоювання з арифметичним виразом у правій частині. Структура такого оператора визначається двома видами послідовностей фіксованої довжини, для опису яких можна скористатися простим перерахуванням компонент. Перша послідовність визначає повний і скорочений умовні оператори, а друга - конструкцію "відношення". Схема граматики, що задає ці послідовності, може бути зображена так:

Г4 : R = { <V> ’if’ <R4> <C2>,

<C2> <S> <C3>,

<C3> ’else’ <S>,

<C3> $,

<R4> <I> <R3>, <R3> < <I>, <R3> == <I> } .

У цій граматиці <S> визначається схемою граматики Г3.

7. Як останню конструкцію, розглянемо опис операторів циклу, аналогічні тим, що використовуються, наприклад в мові С з розділювачами 'while', 'do'. Кожен оператор може бути описаний у вигляді простої послідовності, у якій використовуються нетермінали, визначені в побудованих раніше граматиках Г3 і Г4. На практиці часто зу-

51

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

Г: R = { <W> ’while’ <R4> <C4>,

<C4> <S> }

Г: R = { <W1> ’do’<S> <C5>, <C5> ’while’<R4> }.

52

4 ТЕХНОЛОГІЯ ЗАПИСУ АЛГОРИТМІВ ЗА ДОПОМОГОЮ МОВИ ПРОГРАМУВАННЯ С.

4.1 ЕЛЕМЕНТИ МОВИ

Під елементами мови будемо розуміти : алфавіт, константи, ключові слова, ідентифікатори та коментарі.

Компілятор аналізує текст С-програми, послідовно зчитуючи рядки та розбиваючи їх на лексеми.

Визначення1. Лексема - це одиниця тексту програми, яка становить для компілятора самостійний зміст і яка не містить в собі інших лексем.

Ніякі лексеми, крім символьних рядків, не можуть продовжуватись на кількох рядках тексту програми. Коли компілятор виявляє лексему, він намагається включити в неї максимальну кількість символів.

Приклад 1.

int i,j,k;

i=j+++k; // це еквівалентно i=(j++)+k;

В останньому рядку здійснюється аналіз компілятором неоднозначної конструкції +++ наступним чином: спочатку розглядається перший +. Якщо б наступний символ був би ідентифікатором, то цей + був би проінтерпретований як знак бінарної операції додавання. Однак наступний символ – ще один + . Тоді конструкція ++ розглядається як допустима постфіксна операція інкремента–++. А далі–третій + вже інтерпретується як знак операції додавання.

Алфавіт мови являє собою сукупність наступних множин:

великі та малі латинські літери;

десяткові цифри ;

спеціальні символи (+, -, *, /, #, %, ^, &, *, (, ), |,?, :, ; );

представлювані символи (спеціальні символи, яким відповідає

клавіша клавіатури). Наприклад: \t-табуляція, \n-новий рядок. Константи бувають числові, символьні та символьні рядки. Чи-

слові константи є цілими та з плаваючою крапкою. Цілі константи поділяються на

-десяткові

<десяткове ціле> ::= <цифра>{<цифра>}

-вісімкові (утворюються за допомогою префікса 0)

53

<вісімкове ціле>::= 0 <вісімкова цифра >{<вісімкова цифра>}

-шістнадцяткові (утворюються за допомогою префікса 0x)

<шістнадцяткове ціле>::=0x<десяткова цифра>| a|b|c|d|e|f|A|B|C|D|E|F {<десяткова цифра>| a|b|c|d|e|f|A|B|C|D|E|F}

Приклад:

int i=12345; /*змінна і ініціалізується звичайною десятковою константою */

printf(“i=%d”,i);//результат-12345

int i=023; /*змінна і ініціалізується вісімковою константою */

printf(“i=%d”,i);/*результат-19-значення вісімкової константи у десятковій системі числення*/

int i=0x13;/*змінна і ініціалізується шістнадцятковою константою */

printf(“i=%d”,i); /*результат-19*.

Константи з плаваючою крапкою синтаксично записуються так: [<цифри>].[<цифри>][e|E [-]<цифри>]

Приклад: double f=123.2343e-12;

Детальніше ці константи будуть розглянуті у 6 розділі.

Символьні константи записуються з використанням одинарних лапок: `<символ>`

Приклад: ‘d’

Символьні рядки записуються за допомогою подвійних лапок: “<символи>”

Приклад: “Hello,world!”

Розглянемо детальніше символьні константи. Символьна константа - це один символ, поміщений в одинарних лапках, як наприклад, 'а'. Значенням символьної константи є чисельне значення цього символу у внутрішньому машинному наборі символів. Наприклад, у наборі символів ascii символьний нуль, чи '0', має значення 48, а в коді ebcdic - 240, і обоє ці значення зовсім відмінні від числа 0. Написання '0' замість чисельного значення, такого як 48 чи 240, робить програму незалежною від конкретного чисельного представлення цього символу в даній машині. Деякі неграфічні символи можуть бути представлені як символьні константи за допомогою умовних послідовностей, як, наприклад, \n (новий рядок), \t (табуляція), \0 (нульовий символ), \\ (зворотна коса риска), ` (одинарні лапки) і т.д. Хоча вони виглядають як два символи, насправді є одним. Крім того, мож-

54

на згенерувати довільну константу у вигляді '\ddd', де ddd - від однієї до трьох восімкових цифр, як у наступному рядку програми:

#define A '\x41' /* це код символа А */

або '\xff', де ff-одна чи дві шістнадцяткові цифри

#include <stdio.h> void main(){

printf("ab\0cd\nxyz");

putchar('\n');

}

Запам’ятайте, що '\0' служить ознакою кінця рядка в пам’яті, а '\n' - в файлі. В рядку "abcd\n" неявно вже знаходиться нульовий сим-

вол: 'a','b','c','d','\n','\0'. Рядок "ab\0cd\nxyz" - це 'a', 'b', '\0', 'c', 'd', '\n', 'x', 'y', z','\0'.

Писати рядок "abcd\0" не варто, оскільки тоді він буде мати в кінці два нульових символи (що не є помилкою, але для чого?). Функція printf (буде розглянута далі) друкує рядок до нульового символу, а не до закриваючих лапок.

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

#define maxline 1000 char line[maxline + 1];

чи

seconds = 60 * 60 * hours;

Ідентифікатор - це послідовність літер, цифр та символів підкреслювання, що починається літерою чи символом підкреслювання.

<ідентифікатор>::=<буква> | <ідентифікатор> <буква> | <ідентифікатор> <цифра>

(буквою у наведеній БНФ будемо вважати і символ підкреслювання). За замовчуванням компілятор розрізняє великі та малі літери.

Кількість значущих символів – 32. Коментарі в мові С задаються так: /*<текст>*/

Приклад: main()

/*функція main повинна бути присутньою обов”язково в С- програмі*/

{

}

55

4.2 СТРУКТУРА С-ПРОГРАМИ:

C–програма може містити:

-директиви препроцесору (див. детальніше пункт 4.6)

Приклад:

#include<stdio.h> #define N 100

- вказівки компілятору

Приклад:

#pragma argsused

-оголошення та визначення (див. детальніше пункт 7.7)

Втексті програми можна використовувати коментарі.

С-програма повинна обов’язково містити функцію main(), з якої починається виконання програми. Наприклад, програма, що друкує текст "hello, world" має вигляд:

#include<stdio.h>//підключаються бібліотеки, що містять //функції вводу-виводу та ін.

main()//заголовок функції main

{

printf("hello, world\n");//функція виводу на екран

}

4.3 ОПИСУВАЧІ

Для того, щоб будь-яка конструкція мови могла бути використана в програмі, вона повинна бути відомою компілятору. Іншими словами, щоб її використати, попередньо необхідно її описати. Фундаментальним поняттям при визначенні конструкцій мови є поняття описувача. Синтаксично описувач задається наступною БНФ:

<описувач>::= <ідентифікатор>| <описувач> [ ]|

<описувач> [<константний вираз>]| <описувач>( )| <описувач> (< список типів параметрів>)| *<описувач>| (<описувач>)

Разом з ключовим словом, що визначає тип даних, описувач дозволяє визначити будь-який об’єкт мови.

Приклад:

int i;//описаний ідентифікатор і типу int

int i[3];//описаний масив і з трьох елементів типу int int *i;//описаний вказівник і на тип int

56

int i( );//описана функція і

int i(int,int);/*описана функція і, що повертає значення типу int і має два парметра типу int */

Найпростiший варiант описувача - iдентифiкатор.

Складеним описувачем є конструкція, що утворюється в результаті використання кількох ознак вказівника, масиву чи функції. Причому в даній інтерпретації не допускається масив функцій. Функція також не може повертати масив, тобто не допускається конструкція виду: typedef int masyv[10];

masyv f();

Функція не може повертати функцію.

Порядок інтерпретації складеного описувача наступний:

1.інтерпретація починається з ідентифікатора;

2.інтерпретуються дужки, що стоять справа від ідентифікатора, тобто - масив чи функція;

3.інтерпретується *, що стоїть зліва;

Приклад:

double * (** i[5])(int)

- ідентифікатор “i” є масив з п’яти елементів вказівників на вказівник на функцію, що має один аргумент типу int та повертає вказівник на тип double.

4.4 ОПЕРАЦІЇ МОВИ С

4.4.1 Арифметичні операції та операції порівняння

Розглянемо основні операції мови С. Звичайно, є ряд тривіальних операцій, що мають аналогічний результат у більшості мов програмування. До таких операцій можемо віднести:

унарний мінус (-) що здійснює арифметичне заперечення операнда. Приклад:

i=12;

i=-i; //i==-12

додавання (+), віднімання (-), множення (*), ділення (/), залишок від ділення цілих значень (%) .

Приклад:

int i=5,j=4,s,p,q; s=i+j; //s==9 p=i*2; //p==10 q=i%j //q==1

бінарні операції порівняння: менше(<), більше(>), менше або дорівнює (<=), більше або дорівнює (>=), не дорівнює (!=), дорівнює (==).

57

Приклад:

if(a<b) a=b;else b=a;

Водночас є ряд операцій, які є специфічними для С-С++. На них ми зупинимося дещо детальніше.

4.4.2 Логічні операції логічне заперечення операнда (!)

Унарная операція заперечення перетворить ненульовий чи “істинний” операнд у 0, а нульовий чи “хибний” операнд у 1. Звичайне використання операції ! полягає в записі

if(!inword)

замість if(inword == 0)

бінарні логічне “і” (&&)

логічне “або” ( || )

Вирази, де використовуються операції && і ||, обчислюються зліва направо, причому їхній розгляд припиняється відразу ж, як тільки стає ясно, чи буде результат істиною чи хибним значенням. Ці властивості дуже суттєві для написання правильно працюючих програм.

Приклад: a=1;

if (a==1 || a==2) printf(“%d”,a);

Тут порівняння а==2 вже не здійснюється.

4.4.3Побітові логічні операції

Умові С передбачено ряд операцій для роботи з бітами; ці операції не можна застосовувати до змінних типу float чи double:

побітове ”і” (&) - результатом операції є кон”юнкція побітового представлення чисел

побітове включаюче “або” (|)-результатом операції є диз”юнкція побітового представлення чисел

побітове виключаюче “або” (^)-результатом операції є додавання по модулю 2 побітового представлення чисел

зсув вліво (<<)-зсуває вліво побітове представлення лівого операнда на число розрядів, вказане як правий операнд (справа дописуються нулі)

зсув вправо (>>) -зсуває вправо побітове представлення лівого операнда на число розрядів, вказане як правий операнд(зліва дописується копрія знакового біта чи 0)

58

доповнення (~)-результатом операції є побітове заперечення операнда

Побітова операція & часто використовується для маскування деякої кількості бітів; наприклад, оператор c = n & 0177; передає в 'с' сім молодших бітів n, встановлюючи інші біти рівними нулю.

Операція | використовується для вмикання бітів: c = x | mask; - встановлює у одиницю ті біти в х, що дорівнюють одиниці в mask.

Варто бути уважним і відрізняти побітові операції & і | від логічних зв'язок && і ||, що передбачають обчислення значення істинності зліва направо. Наприклад, якщо х=1, а y=2, то значення x&y дорівнює нулю, у той час як значення x&&y дорівнює одиниці.

Операції зсуву << і >> здійснюють відповідно зсув вліво і вправо свого лівого операнда на число бітових позицій, що задаються правим операндом. Таким чином, х<<2 зсуває х вліво на дві позиції, заповнюючи вивільнені біти нулями, що еквівалентно множенню на 4. Зсув вправо величини без знака заповнює вивільнювані біти нулями.

Унарна операція ~ дає доповнення до цілого; це означає, що кожен біт зі значенням 1 одержує значення 0 і навпаки. Ця операція звичайно виявляється корисною у виразах типу x&~077, де останні шість бітів х стають нульовими. Підкреслимо, що вираз x&~077 не залежить від довжини слова . Щоб проілюструвати використання деяких операцій з бітами, розглянемо функцію getbits(x,p,n), що повертає /зсунутими до правого краю/ починаючи з позиції р поле змінної х довжиною n бітів. Ми припускаємо, що крайній правий біт має номер 0, і що n і p - розумно задані додатні числа. Наприклад, getbits(x,4,3) повертає зсунутими до правого краю біти, що займають позиції 4,3 і 2:

getbits(x, p, n) /* get n bits from position p */ unsigned x, p, n;

{

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

}

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

59

в інших; доповнення ~ створює маску з одиницями в n крайніх правих бітах.

Приклад:

char i=13, j=7,q,w,e,r,t; q=i&j; //результат: i==5 w=i|j; //результат: 15 e=i^j; //результат: 9 r=j<<1; //результат: 14

// i представляється побітово як 00001101

// j - 00000111 //результати– i&j – 00000101 , i|j –00001111, i^j –

00001010, j<<1 – 00001110

4.4.4 Операції адресації та взяття значення адресація (&)- повертає адресу операнда

взяття значення (*)-повертає значення , адреса якого вказується операндом:

Ці операції мають фундаментальне значення в програмуванні. Детальний розгляд їх буде зроблений пізніше, де будемо вивчати вказівники та масиви. А зараз розглянемо лише маленький приклад:

int *p; int a=2;

p=&a;//р містить адресу змінної а int b;

b=*p;//b містить значення 2

4.4.5 Операція послідовного виконання

<вираз>,<вираз>[…] Використовується у випадку коли необхідно обчислити значен-

ня кількох виразів в ситуації де по синтаксису повинен бути один. Результатом операції послідовного виконання є значення останньої операції.

Приклад:

а) for(i=0;i<30;i++, j++, k++) {}

Тут у виразі – прирості є операція послідовного виконання, яка забезпечує одночасне збільшення значень змінних i, j та k.

б) Нехай оголошена функція int f(int); що має один аргумнент типу int.

Тоді можемо здійснити її виклик так: f(a+=b,6) або f(i++,b=5,c=9,a=b+4,k=7);

60

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