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

osn_progr_final

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

сучасних мовах присутні визначені (базові) типи даних (цілі та дійсні арифметичні типи, символьний і, можливо, рядковий тип), є можливість використання об'єктів даних, у тому числі масивів і структур (записів). Для арифметичних даних дозволені звичайні арифметичні операції, для об'єктів даних звичайно передбачена тільки операція присвоювання і можливість звертання до елементів.

У даній главі ми лише коротко розглянемо типи даних, що використовуються при програмуванні задач. Більш детальний виклад представлений у відповідних розділах.

5.1 ПРОСТІ ТИПИ ДАНИХ

Вигляд інформації, представлений кожним словом даних у па- м'яті, може бути різний. Наприклад, деякі слова можуть містити цілі числа, у той час як інші можуть містити символи. Сучасна мова високого рівня дає програмісту абстрактну модель, у якій дані й операції над даними можна специфікувати в об’єктно-орієнтованих поняттях.

При цьому мова високого рівня забезпечує наступні можливості доступу до об'єктів даних у пам'яті:

-на об'єкти даних посилаються за допомогою визначених користувачем імен (ідентифікаторів);

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

У ранніх мовах введення поняття типу даних було чисто прагматичним прийомом, який застосовували з метою полегшити роботу компілятору. І насправді користувач звертав мало уваги на поняття типу. Для сучасних мов розроблені дуже складні механізми типів, що дозволяють програмісту виразити розв’язок задачі з більшою ясністю

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

Сучасні мови забезпечують адекватну множину типів даних і операцій для розв’язку задач в обраній області. У мові є множина визначених типів даних (прості типи) і операцій та визначені механізми для специфікації типів, визначених користувачем. Наступні чотири простих типи даних є загальними для більшості мов:

цілий

дійсний

81

логічний

символьний.

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

Кожен тип визначає множину значень і множину операцій над даними цього типу.

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

Кожна операція, що застосовується до об'єкта даних, повинна належати множині операцій, обумовленій типом об'єкта.

Наприклад, нехай ми маємо наступні описи змінних: float x;

int i; char c;

де float, int, char – ідентифікатори дійсного, цілого та символьного типів відповідно. Ці ідентифікатори використовують для опису змінних простих типів. Тоді присвоювання виду

i=x;

c=x;

некоректні, а x=3.5; i=7;

цілком законні з погляду синтаксису мови.

Природньо, що повинен бути механізм перетворення типів для виконання, наприклад, таких присвоювань:

х=(float) i,

де ім'я типу float застосовується до цілого значення i та дає дійсний результат.

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

5.2 ПОХІДНІ ТИПИ

Наступним кроком у розробці гарної програми є введення поняття типів даних, визначених користувачем. Це дозволить не тільки істотно підвищити надійність програмного забезпечення, але і швидкість його розробки.

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

82

класу об'єктів. Якщо користувач при програмуванні буде використовувати тільки прості типи, то всі об’єкти належать тому самому класу просто тому, що вони одного типу. Це, однак, може не відповідати змісту цих об'єктів у предметній області і програмі.

Розглянемо розв’язок задачі підрахунку площі прямокутного трикутника і кут при одному з катетів. Значення катетів вводяться з клавіатури.

#include <stdio.h>

/* підключення бібіліотек, що містять функції вводувиводу*/

void main(void)

/*заголовок функції main, з якої завжди починається виконання С-програми*/

{

/*початок програми*/

float cat1, cat2, square, kut ;

/* ідентифікатори cat1, cat2 вводяться для використання в програмі значень катетів, square – для одержання значення площі, kut – для збереження величини кута */

scanf(“%f%f”,&cat1,&cat2);

/*за допомогою функції scanf вводимо з клавіатури значення катетів*/

square=cat1*cat2/2;

/*обчислюємо площу трикутника і значення присвоюємо змінній square */

kut=atan(cat1/cat2);

printf(“square=%f ugol=%f \n”, square,kut);

/*за допомогою функції printf друкуємо значення площі трикутника і відповідного кута*/

} /*кінець програми*/

Даний текст для компіляції помилок не містить і програма при виконанні правильно порахує площу і величину кута. Наступний семантично неправильний оператор

square=kut/2;

компілятор пропустить, і результат при виконанні програми буде отриманий неправильний.

Перевагою похідних типів є можливість написання наступної програми:

typedef float znachennia, vel_kut;

/* за допомогою ключового слова typedef вводимо нові типи даних, потім просто посилаємося на введене ім'я типу

*/

znachennia cat1, cat2, square;

83

vel_kut kut;

/* ідентифікатори cat1, cat2 вводяться для використання в програмі значень катетів, square – для одержання значення площі, kyt – для збереження величини кута */

scanf(“%f%f”,&cat1,&cat2);

/*за допомогою функції scanf вводимо з клавіатури значення катетів*/

square=cat1*cat2/2;

/*обчислюємо площу трикутника і значення присвоюємо змінній square */

kut=atan(cat1/cat2);

printf(“square=%f kyt=%f \n”, square,kut);

/*за допомогою функції printf друкуємо значення площі трикутника і відповідного кута*/

} /*кінець програми*/

У даному варіанті програми визначаються два нових типи – znachennia і vel_kut на основі існуючого простого типу float. Описані за допомогою типу znachennia об'єкти містяться явно в класі, що логічно відмінний від класу об'єктів, описаних типом vel_kut. Такі нові типи, називаються похідними типами.

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

square=kut/2;

Чи виявляється така помилка насправді, залежить від методу визначення еквівалентності типів, що використовується в даній мові програмування. У зв'язку з похідними типами виникає ще питання про те, які атрибути успадковує похідний тип від типу, що породжує його.

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

5.3 ЕКВІВАЛЕНТНІСТЬ ТИПІВ

Коли компілятор мови програмування має обробляти операцію ( оператор) присвоювання виду

х= вираз

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

84

значимості тощо. Отже перевірка еквівалентності типів є логічною у багатьох випадках. Можливі два підходи до перевірки еквівалентності: структурна еквівалентність та іменна еквівалентність.

При структурній еквівалентності два об'єкти належать еквівалентним типам, якщо в них однакова структура. Тому при використанні структурної еквівалентності компілятор повинен дозволяти присвоювання в такому операторі, як square=kut/2, тому що структурно ці дві змінні мають тип float.

Фактично при структурній еквівалентності похідні типи – це не більше ніж синоніми для імені типу, що породжує.

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

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

5.4 УСПАДКУВАННЯ АТРИБУТІВ

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

Розглянемо просту програму для обчислення площі прямокут-

ника.

#include <stdio.h> main()

{

typedef float length, square; length x,y;

square a; scanf(“%d%d”,&x,&y); a=x*y;

85

printf(“a=%d”,a);

}

Якщо операція * успадковується від типу float, то розумно припустити, що тип результату операції х*у такий самий, як і тип операндів, тобто в нашому випадку тип length. Отже, операція присвоювання

а=х*у;

незаконна, оскільки змінна a типу square. Операція присвоювання

х=х*у;

є законною, але це нісенітниця з погляду предметної області.

Однак, мова С++ має механізм перевизначення існуючих операцій та механізм визначення нових операцій. Тобто, операція *, визначена для двох операндів типу type1, відрізняється від операції *, визначеної для двох операндів типу type2. У таких випадках говорять, що операція перевантажена. Яка саме операція повинна використовуватись в конкретному контексті – це визначається компілятором при дослідженні типів передбачуваного результату. Тому використання

а=х*у;

цілком коректне.

5.5 ПЕРЕЛІЧУВАНІ ТИПИ

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

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

enum day_of_week {mon, tue, wed, the, fri, sat, son};

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

enum hexx {A=10,B,C,D,E,F};

86

Десяткові цифри 0-9 будуть одночасно і шістнадцятковими, тому немає ніякої необхідності в їх перевизначенні. Тут усі символи від 'А' до ‘F’ є перевантаженими. Оскільки порядкові значення цього нового типу безпосередньо представляють фактичні шістнадцяткові значення, програмування істотно спрощується.

5.6 ЛОГІЧНІ ТИПИ

Логічний тип позначає логічні значення true чи false. Існування таких значень, очевидно, є необхідним у програмуванні. Але конкретна реалізація здійснюється в різних мовах по-різному. Наприклад, у мові Pascal він описується за допомогою ключового слова boolean. В деяких С-подібних мовах істинним вважається будь-яке значення, що відмінне від нульового і хибним відповідно нульове значення (що належить до цілого типу). В новому стандарті С++ є тип bool.

Перелічимо стандартні логічні операції, що звичайно застосовуються до операндів логічного типу, даючи в результаті логічне значення:

логічне доповнення

логічне “і”

логічне “або”

логічне виключаюче “або”.

Операції порівняння - <, >, <>, <=, >=, == завжди повертають логічні значення, наприклад 3 < 5 повертає значення “істина” . Операції відношення є перевантаженими в тому розумінні, що вони можуть застосовуватися до даних різних типів. Зокрема, вони можуть застосовуватися до цілих чи дійсних операндів. Операція порівняння ==, застосована до даних логічного типу, реалізує функцію логічної еквівалентності, а операція <> служить у ролі операції виключаючого

“або”.

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

5.7 СИМВОЛЬНІ ТИПИ

Більшість мов програмування містять визначений символьний тип char. Об'єкти типу char можуть містити одне символьне значення, що представляється на машинному рівні цілим в області від 0 до 255 ([-128,127]). Фактичне представлення кожного символу залежить від набору символів (наприклад, ASCII), що використовується.

87

У таблиці ASCII порядковий номер кожного символу відповідає коду ASCII для цього символу

5.8 ЧИСЛОВІ ТИПИ.

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

Представлення цих двох різних типів чисел на обчислювальній машині ставить ряд проблем, більшість з який пов'язано з вимогою машинної незалежності і мобільності. Цілі числа можуть бути точно представлені на обчислювальній машині, але тільки в скінченій області; дійсні ж числа не завжди допускають точне представлення.

5.9 СТРУКТУРНІ ТИПИ ДАНИХ

Раніше ми розглядали прості типи даних. Об'єкт даних будьякого простого типу має ту властивість, що він містить одне і тільки одне значення. Але для розв’язку більшості задач за допомогою програм потрібно мати можливість опису об'єктів даних, що містять багато значень. Такі об'єкти описуються структурними типами даних, що необхідні по двох причинах. По-перше, для розробки програм методом зверху вниз потрібно вміти описувати дані на різних рівнях. В міру уточнення проекту внутрішня структура даних буде все більше деталізуватися. Таким чином, первісна структура даних описується в термінах складних структур даних, що у свою чергу описуються в термінах більш простих структур даних доти, доки наприкінці не виходить опис у термінах простих об'єктів даних, з яких у кінцевому рахунку складена структура.

Розгляд різних способів, якими нам би хотілися структурувати дані, показує, що вимагаються два різних механізми. По-перше, механізм масивів, що дозволяє групувати набір об'єктів ідентичного типу таким чином, що кожен об'єкт вибирається за допомогою індексу чи номера. По-друге, механізм записів(структур), що дозволяє групувати набір об'єктів різних типів таким чином, що вибір кожного окремого об'єкта здійснюється за допомогою імені компоненти. Надалі ми побачимо, що з другого механізму виросло об”єктноорієнтоване програмування. Ці два механізми можна комбінувати при побудові структур даних довільної складності.

Крім механізмів масивів і записів, є механізм можин, що забезпечує специфічні можливості.

88

Поки ми розглянемо механізми масивів і записів для структурування даних.

5.9.1 Масиви

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

typedef float vec[10];

задає тип vec як об'єднання 10 значень типу float у групу, що індексується цілими числами від 0 до 9. Тепер можна описувати об'єкти типу vec звичайним шляхом, наприклад

vec x,y;

Елементи цих масивів позначаються ім'ям масиву і наступним за ним у квадратних дужках індексом. Наприклад x[2], y[i] позначають другий елемент масиву x та i-й елемент масиву y відповідно.

Вдеяких мовах з масивами можна виконувати деякі операції як

зоб'єктами регулярного типу, розглянутими як єдине ціле, не стосуючись їхньої внутрішньої структури (наприклад, присвоювати масиви).Однак в мовах С-С++ цього робити не можна. Звідси випливає, що наприклад, при описі

float x[15], y[15];

операція

x=y;

не допускається.

Масив можна уявити як реалізацію функції відображення. У наведеному вище прикладі об'єкти х и у визначають відображення з множини цілих чисел від 0 до 14 на множину значень типу float. Таке відображення можна визначити для будь-якого дискретного простого типу даних, а не тільки для цілих. Це можна зробити шляхом перевантаження операції індексування масиву .

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

typedef float matr[10][10];

визначає тип matr як двовимірний масив.

89

5.9.2 Структури

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

enum range {yellow, black, white}; struct car {

float volume; range colour; int year;

}

визначає тип структури, названої car. На поле цієї структури посилаються додаванням до імені структури імені поля, що розділяються крапкою. Наприклад, якщо є

struct car my;

то оператор присвоювання my.year= 1987;

приcвоїть полю year екземпляра структури my значення 1987. Детельніше структури будуть розглянуті у наступному розділі.

5.10 ДЕЯКІ ОСОБЛИВОСТІ ТИПІВ ДАНИХ C

5.10.1 Базові типи даних

1. Як вже відмічалось вище, у мові "C" є кілька базових типів даних: char (символьний), int(цілий), float(з плаваючою крапкою), double(з плаваючою крапкою подвійної точності).

Кількість бітів, що відводяться під ці об'єкти, залежить від конкретної машини. Наприклад, тип int може займати 16, 32 чи навіть 36 біт.

Крім того, є ряд кваліфікаторів, які можна використовувати: short (коротке), long (довге) і unsigned (без знака). Наприклад:

short int

x;

long int

y;

unsigned int

z;

Інформація про базові числові типи даних з кваліфікаторами подається в таблиці:

Повна назва

Скорочена назва

Розмір в

типу

типу

байтах

 

 

 

90

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