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

Вакал - Мова Сі (Типи данних та основні структури керування)

.pdf
Скачиваний:
15
Добавлен:
07.03.2016
Размер:
460.57 Кб
Скачать

вираз sizeof(int) надає кількість байт, необхідну для розміщення змінної цілого типу int, тобто 2 байти;

функція malloc(num) резервує num байтів вільної області пам’яті і повертає початкову адресу розміщення у пам’яті цієї послідовності байтів;

ця адреса запам’ятовується в ipoint. Таким чином, динамічно створюється

ціла змінна, до якої можна звертатися *ipoint (аналогічно ^ipoint у Паскалі). Зауваження. Перш ніж присвоїти ціле значення змінній *ipoint, необхідно присвоїти адресу вказівнику ipoint. До отримання адреси вказівник не можна

використовувати у програмі.

3.15. Масиви

Масив – це набір даних (група елементів) однакового типу. З оголошення масиву компілятор повинен одержати інформацію про тип елементів масиву і кількість елементів. У загальному випадку опис будь якого масиву має вигляд

<тип> <ім’я>[<розмір>]; або type name [size]; Приклад: int A[10]; <тип> або type – це тип елементів масиву;

<ім’я> або name – ідентифікатор (им’я) масиву; <розмір>] або size – кількість елементів у масиві.

Перший елемент масиву має індекс 0, тобто name [0], останній – name [size- 1]. Загальний об’єм пам’яті у байтах визначається як size*(sizeof(type)), тобто пам’ять, що розподіляється під масив, дорівнює пам’яті, необхідної для розміщення усіх його елементів. Елементи масиву розташовуються у пам’яті послідовно у зростаючих адресах пам’яті. Розриви між елементами відсутні. Зауважимо, що в деяких випадках <розмір> або size може бути опущено. Наприклад, це може бути у випадку, коли при оголошенні масив ініціалізується або масив є формальним параметром функції.

3.15.1. Багатовимірні масиви

У Турбо Ci можна використовувати багатовимірні масиви, їх оголошення здійснюється так:

<тип> <ім’я>[<розмір1>][<розмір2>][<розмір3>]...

На відміну від мови Паскаль квадратні дужки використовуються для запису кожного індексу. Приклад: inr A[3][4],C[4][2].

Двовимірні масиви розташовуються у пам’яті по рядкам.

У Турбо Ci при оголошення масиву можлива його ініціалізація.

Приклад: int board[2][3]={1,2,3,4,5,6};

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

int board[2][3]={

{1,2,3},

{4,5,6};

41

}

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

int board[ ][ ]={

{1,2,3},

{4,5,6};

}

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

int board[2][3]={

{1,2},

{4};

}

то елементи першого рядка отримують початкові значення 1,2,0 відповідно, а елементи другого рядка - 4, 0, 0.

3.15.2. Масиви і вказівники

Між вказівниками і масивами існує прямий зв’язок. Коли оголошується масив, наприклад, int arr[10], то цим не тільки виділяється пам’ять для 10 елементів масиву цілого типу, але й імя масиву визначається як константний вказівник на перший (з нульовим індексом) елемент масиву, тобто адреса першого елементу масиву

arr=&arr[0];

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

Для доступу до елементів масиву існують два способи:

Перший спосіб пов’язаний з використанням звичайних індексних виразів виду arr[i]; що є посиланням на i- й елемент масиву.

Другий спосіб пов’язаний з використанням імені масиву як вказівника на початок масиву

*(arr+i) = = arr[i];

Розглянемо як обчислюється вираз arr+i. У відповідності з правилами виконання операції додавання вказівника з цілим числом ціла величина перетворюється до адресного представлення шляхом множення її на розмір типу, якому належать елементи масиву (або на який вказує вказівник). Таким чином, вираз (arr+i) представляє собою адресу пам’яті arr+(i*sizeof(type)), яка відноситься до i- го елементу масива. Застосовуючи до отриманої адреси операцію розадресації *(arr+i), одержимо значення i- го елементу масиву. Зауважимо, що також має місце тотожність

&(arr[i]) = = (arr+i) - адреса i- го елементу масива.

42

Таким чином, можна використовувати ці вирази не зважаючи на те, описаний arr як вказівник чи як масив.

Єдина відмінність між описами arr як вказівника чи як масиву полягає у розміщенні самого масиву. Якщо arr описаний як масив, то пам’ять автоматично виділяється для його розміщення. Якщо arr вказівник, то ми повинні самі виділити пам’ять під масив, використовуючи спеціальну функцію, наприклад,

arr=(int*) calloc (10,sizeof(int));

Перший параметр функції 10 вказує для скількох елементів буде резервуватися пам’ять, другий – скільки байт виділяється для кожного елементу. (int*) означає, що ця початкова адреса є вказівником на виділений у пам’яті обсяг в 10х2 = 20 байтів, призначений для зберігання даних цілого типу.

3.16. Рядки

Мова Сі не підтримує окремий тип даних рядок, але вона передбачає два відмінних підходи до визначення рядків. Один полягає у використанні символьного масиву, другий – у використанні вказівника на символ.

Використання символьного масиву:

#include <stdio.h> #include <string.h> main()

{

char name[30]; scanf("%s",name); printf("%s,%s\n",p,name);

}

Використання вказівника на символ:

#include <stdio.h> #include <string.h> main()

{

char *p; p="Привіт"; puts(p);

}

Коли компілятор знаходить оператор p="Привіт" , він робить дві речі:

1)створює рядок "Привіт", обмежений нуль-символом, у деякому місці файлу об’єктного коду;

2)присвоює початкову адресу цього рядка – адресу букви “ П” змінній p. Команда puts(p) друкує символи, доки не зустрінеться нуль-символ. Різниця між двома способами визначення рядків полягає в тому, що

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

якщо використовується вказівник на дані типу Char, то пам’ять для рядка

43

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

Різниця між рядками у Турбо Паскалі та Турбо Ci пов’язана з різницею між символьними масивами у двох мовах. У Турбо Паскалі опис S:String[N] еквівалентний S:array [0..N] of char (виділяється N+1 байт). Рядок має максимальну довжину N символів, перший символ рядка S[1], а S[0] використовується для зберігання поточної довжини рядка. У Турбо Ci опис char S[N] виділяє N байт для рядка і записує адресу в S. При цьому довжина рядка не зберігається, замість цього використовується розділювач – ‘0- символ’, що визначає кінець рядка. Рядок починається з S[0]. Тому рядок S буде містити реальних N-1 символ, тобто S[0] - S[N-2], S[N-1] є ‘0-символом’, тобто 1 байт відводиться для цього розділювача. Отже визначення одного й того ж рядка у Ci у порівнянні з мовою Паскаль містить його довжину на 1 більше. Тобто

var S:String[80]; еквівалентно char S[81];

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

1)використати підпрограму strcpy(S,²Hello²) для побайтової передачі одного рядка у інший;

2)вводити S рядок, використовуючи puts(S) або Scanf.

Другий метод визначення рядків – char *Str. У цьому випадку Str лише вказує на char: пам’ять для рядка не резервується, виділяється лише кілька байт для вказівника. У цьому випадку можна прямо присвоїти Str значення рядкової константи, її адреса просто присвоється Str. Str також можна присвоїти значення іншого вказівника на рядок. За допомогою функції malloc(N) можна виділити N байт пам’яті для розміщення рядка і присвоїти Str адресу цієї області. Потім, використовуючи функцію strcpy, можна переписувати рядки у ці виділені байти.

3.17. Типи переліку

Якщо змінна може приймати лише одне значення з певної множини значень, то рекомендується використовувати тип переліку. Опис типу переліку складається з ключового слова enum, за яким йде ім’я типу преліку і у фігурних дужках через кому вказуються елементи переліку, тобто усі можливі значення змінних цього типу. Ці об’єкти – константні ідентифікатори являються константами типу переліку, так само як 4 є константою типу int, а ‘g’ – константою типу char.

Формат типу переліку:

enum <ім’я_типу переліку> {<ел_т1>, <ел_т2>, ...}

enum <ім’я_ типу_переліку> {<ел_т1>, <ел_т2>, ...} <ім’я_змінної >

Приклади:

enum spectrum{red,orange,yellow,green,blue,violet}; (*)

enum spectrum color;

Перший оператор оголошує новий тип spectrum. Він перераховує всі можливі значення, які можуть приймати змінні цього типу. Другий оператор

44

оголошує color змінною типу spectrum. Можна присвоїти їй довільну константу цього типу, наприклад, color = green;

Оголошення типу переліку і змінних цього типу може здійснюватися і в іншому форматі. Наприклад:

enum spectrum{red,orange,yellow,green,blue,violet} color;

В будь-якому випадку повинне використовуватися слово enum, яке вказує на те, що тип – тип переліку.

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

Значення іменованих констант у списку переліку можуть бути перевизначені. Наприклад: enum levels{low=100,medium=200,high=2000}

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

enum animal{cat=20, tiger,lion,puma}

присвоює tiger значення 21, lion - 22, puma - 23.

Правила використання елементів переліку:

1.Перелік може містити значення, що повторюються.

2.Константні ідентифікатори у списку переліку повинні відрізнятися від усіх інших імен змінних та імен з інших списків переліку.

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

Опис типу переліку можливий також з використанням ключового слова typedef, наприклад:

typedef enum (sun,mon,tues,wed,thur,fri,sat) days;

У цьому випадку тип days оголошується як тип переліку, а потім за його допомогою оголошуються змінні цього типу, наприклад, days fff;\. Тут fff – змінна типу переліку з можливими значеннями sun,..., sat, і слово enum вже не використовується.

Операції. Змінні типу переліку можуть використовуватися як операнди арифметичних операцій та операцій відношення:

1) можна присвоїти змінній типу переліку константу із списку типу переліку:

color=blue;

2) можна використовувати операції порівняння if (color==green)

.....................

if (color!=blue)

....................

Зауважимо, що можна застосувати арифметичні операції до констант типу переліку, наприклад, color=red+blue color=red*blue.

45

Примітка. Основна причина використання типу переліку полягає у покращанні читабельності програми. Змінні цього типу призначені для використання всередині програми, а не в операціях вводу-виводу. Наприклад, якщо вы бажаєте ввести для змінної color типу spectrum деяке значення, то необхідно вводити, скажімо 1, а не orange.

Приклад:

#include <stdio.h> main()

{int p,n;

enum spectrum{red,orange,yellow,green,blue,violet}; enum spectrum color1,color2;

int m[ ]={5,6,7,8,9},q; scanf(²%d,&color1); q=m[color1]; color2=++color1+(enum spectrum)2;n=color1; p=color1==color2;

printf(²%d %d %d %d %d\n²,color1,color2,p,q,n);

}

Вводимо 1, отримуємо 2 4 0 6 2

3.18. Структури (записи)

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

Формат структури – наводиться список елементів (полів) структури і вказуються їх типи.

struct <ім’я_типу>

{ <тип1> <елемент1>;

………........

<типn> <елементn>;

};

Нехай <ім’я_типу> є rname. Тоді оголошення змінних даного типу має вигляд

struct rname vname1, vname2,...

Другий варіант формату в Ci для прямого визначення змінних типу структури

struct <ім’я_типу>

{ <тип1> <елемент1>;

......………

<типn> <елементn>;

}< ім’я_змінної>;

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

46

typedef struct

{<тип1> <елемент1>;

……..........

<типn> <елементn>; }<ім’я_типу>;

Потім оголошується змінна цього типу <ім’я_типу> < ім’я_змінної >;

Приклад:

struct student {har last[20]; int age;

int tests[5]; float gra;

} current;

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

<ім’я_типу>.<ім’я_элементу>

Приклад: current.age; current.last[5]; і т.д.

Можна описувати вказівники на структури так само, як і вказівники на інші типи даних. Це необхідно для створення зв’язаних структур даних (стеки, списки і т.д.), елементами яких є структури даних.

Приклад структури, яка містить вказівник на структуру того ж самого типу:

struct tree {int number;

struct tree *left; struct tree *right; };

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

#include <stdio.h> #include <alloc.h> main()

{

typedef struct {char name[20]; char class; float decl;

} star;

star mystar1, * mystar2; mystar2=(star*)malloc(sizeof(star)); mystar1.class='N'; strcpy(mystar1.name,"Епсілон Лебедя"); mystar1.decl=3.5167;

47

printf("\n1 поле=%s \n2 поле %c \n3 поле %f\n", mystar1.name,mystar1.class,mystar1.decl); mystar2 ->class ='M'; strcpy(mystar2->name,"Дельта Лебедя"); mystar2->decl =2.4518;

printf("\n1 поле=%s або %s\n2 поле=%c або %c\n3 поле=%f або %f\n", mystar2->name, (*mystar2).name,

mystar2->class, (*mystar2).class, mystar2->decl, (*mystar2).decl);

}

Зауважимо, що, якщо б структура описувалась не через typedef struct {} star; а через struct star{}; тоді змінні б описувались struct star mystar1,

*mystar2.

Опис star *mystar2 оголошує *mystar2 не як змінну типу star, а як вказівник на структуру типу star.

Оператор mystar2=(star*)malloc(sizeof(star)); резервує пам’ять для структури шляхом звернення до функції malloc. Змінна mystar2 отримує значенння адреси, що виробляється цією функцією. Таким чином, динамічно утворюється змінна типу структури star.

При посиланні на поля – елементи структури (а не на всю структуру), яка адресується вказівником mystar2, використовується символ ’->’. Цей символ означає, що ’’ елемент структури направлений у ...’’. Взагалі кажучи, запис pointname -> membname – це принятий у Ci скорочений варіант наступного запису (*pointname).membname. Таким чином, mystar2->class – це елемент структури, на яку робиться посилання, а операція ‘->’ отримання елементу визначає цей елемент структури.

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

3.19. Об’єднанння (суміші)

І Паскаль і Ci містять схожу структуру даних, яка називається в Паскалі записами з варіантами, а у Ci – об’єднаннями. Якщо значенням структури є елемент декартового добутку множин типів компонент, то значенням об’єднання є елемент об’єднання множин типів компонент. Способи опису об’єднання аналогічні способам опису структур, лише з заміною слова struct на слово union.

Формат об’єднання – наводиться список елементів (полів) об’єднання з указанням їх типів

union <ім’я_типу >

{ <тип1> <елемент1>;

………..........

<типn> <елементn>;

};

Нехай <ім’я_типу> є uname. Тоді оголошення змінних даного типу має

48

вигляд

union uname uname1, uname2,...

Існує інший варіант формату в Ci для прямого визначення змінних типу об’єднання

union <ім’я_типу>

{ <тип1> <елемент1>;

……….........

<типn> <елементn>;

}< ім’я_змінної>;

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

typedef union { <тип1> <елемент1>;

.....………...

<типn> <елементn>;

}<ім’я_типу>;

Потім оголошується змінна цього типу

<ім’я_типу> < ім’я_змінної>;

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

Приклад:

typedef union {int w;

struct { char l; char h;

}b;

}trword;

trword xc;

Доступ до кожного поля об’єднання відбувається через вказання його складеного імені, яке складається з імені об’єднання і через точку ’.’ імені відповідного поля. Наприклад, xc.w; xc.b.l; xc.b.h;

У даному прикладі змінна xc може приймати як цілі значення, так і бути структурою. Проте в кожний момент часу запам’ятовується тільки одне значення Змінна – об’єднання приймає значення певного типу, якщо в операторі присвоювання вказуєеться відповідне ім’я компоненти через крапку, тобто, наприклад, xc.w=5 присвлює змінній x значення 5 цілого типу , а xc.b.l=’а’; xc.b.h=’и’ визначає x як структуру з двома елементами типу char.

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

49

У кожний момент часу в змінній типу об’єднання зберігається лише одне значення. Пам’ятати про те, яке, повинен сам користувач. Якщо у змінну було завантажено значення одного типу, а використовується іншого, це приведе до невірного результату. Проте іноді така дія можлива, найчастіше у випадку, коли потрібно виділити частину цілої змінної або завантажити ціле значення в структуру, описану з полями біт.

Приклад: Програма видачі двійкового коду введеного символу. Для завершення вводится символ ’k’.

#include <stdio.h> main()

{

struct byte {unsigned a: 1;

unsigned b: 1; unsigned c: 1; unsigned d: 1; unsigned e: 1; unsigned f: 1; unsigned g: 1; unsigned h: 1;

};

union symb {unsigned char ch;

struct byte bit; } p;

do { p.ch=getche(); printf(":");

printf("%u",p.bit.h); printf("%u",p.bit.g); printf("%u",p.bit.f); printf("%u",p.bit.e); printf("%u",p.bit.d); printf("%u",p.bit.c); printf("%u",p.bit.b); printf("%u",p.bit.a); printf("\n");

} while (p.ch!='k');

}

50