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

osn_progr_final

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

struct line *pl;

то доступ здійснюється так:

(pl->a).x=4;

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

struct point k[6];

При роботі з структурами можна проводити спеціальний розподіл пам’яті для полів, використовуючи так звані бітові поля. Синтаксично вони задаються так:

<поле>:<константа> Приклад:

struct bit{ int i:1; int p:5; int l:3; };

Тоді поле i буде займати 1 біт, р–5, l–3.

Фундаментальною властивістю структури є можливість описувати поле сруктури як вказівник на цю саму структуру:

struct spysok { int number; char name[10];

struct spysok *next; };

Відповідне поле next може містити адресу екземпляра цієї самої структури, тобто елемента типу struct spysok.. Цей елемент знову може містити вказівник на відповідну структуру і т.д. В результаті ми отримуємо особливу конструкцію, що називається однозв”язним списком. Графічно однозв”язний список можна зобразити наступним чином:

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

на кожному кроці необхідно виділяти пам”ять для наступного елемента

проводити ініціалізацію його полів

111

прив”язувати новоутворений елемент до хвоста вже існуючого списку.

int i;

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

struct spysok * current, *first, *last;

/*виділення пам”яті та ініціалізація полів першого елемента*/

first=(struct spysok*)malloc(sizeof(struct spysok)); first->next=NULL;

first->number=0; gets(first->name);

/*ініціалізація вказівника на кінець списку адресою утвореного елемента*/

last=first;

for(i=1;i<10;i++)

{

/*виділення пам”яті та ініціалізація полів нового елемента*/

current=(struct spysok*)malloc(sizeof(struct spysok)); current->number=i;

gets(current->name); current ->next=NULL;

/*прив”язування новоутвореного елемента до хвоста списку*/

last->next=current;

/*переміщення хвоста на новий елемент*/ last=last->next;

}

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

Нехай, наприклад, необхідно надрукувати значення інформаційного поля name для елемента, у якого поле number містить число 5. Для цього можемо написати:

struct spysok* rob=first;

while (rob->number!=5 && rob!=NULL) rob=rob->next; if(rob!=NULL) puts(rob->name);

else puts(“такого елемента намає”);

112

Для видалення відповідного елемента необхідно корректно провести зв”язок елемента, попереднього до видаленого, з елементом, що стоїть за видаленим щоб не розірвати список. Видалимо, наприклад, елемент, поле number якого рівне 5:

struct spysok * rob=first;

while (rob->next->number!=5 && rob->next!=NULL) rob=rob- >next;

if(rob!=NULL) /*видаляємо елемент, що стоїть за елементом з адресою rob у списку*/

{

struct spysok *current=rob->next; /*зв”язуємо список*/ rob->next=rob->next->next; /*звільняємо пам”ять*/ free(current);

}

else puts(“такого елемента намає”);

Відмітимо, що вказівник last в нашому випадку носить число технічний характер і використаний для подальшої роботи з списком бути не може. Але можна утворити і двозв”язний список, оголосивши в структурі поле, що містить адресу попереднього елемента:

struct spysok { int number; char name[10];

struct spysok *next; struct spysok *prev; };

Тоді в процесі організації такого списку необхідно подбати про двосторонні зв”язки. Якщо написати фрагмент програми, аналогічної для однозв”язного списку, то у відповідному циклі можемо написати: for(i=1;i<10;i++)

{

/*виділення пам”яті та ініціалізація полів нового елемента*/

current=(struct spysok*)malloc(sizeof(struct spysok)); current->number=i;

gets(current->name); current ->next=NULL;

/*прив”язування новоутвореного елемента до хвоста списку*/

current->prev=last;

113

last->next=current;

/*переміщення “хвоста” на новий елемент*/ last=last->next;

}

При цьому видалення аналогічного елемента має вигляд: struct spysok * rob=first;

while (rob->number!=5 && rob->next!=NULL) rob=rob->next; if(rob!=NULL) /*видаляємо елемент з адресою в rob */

{

/*зв”язуємо список*/ rob->prev->next=rob->next; rob->next->prev=rob->prev; /*звільняємо пам”ять*/ free(rob);

}

else puts(“такого елемента намає”);

Аналогічно виглядає і вставка елемента. Нехай, наприклад, нам необхідно вставити новий елемент після заданого (з числовим полем 3):

struct spysok * rob=first;

while (rob->number!=3 && rob->next!=NULL) rob=rob->next; if(rob!=NULL) /*вставляємо елемент після rob */

{

/*виділення пам”яті та ініціалізація полів нового елемента*/

current=(struct spysok*)malloc(sizeof(struct spysok)); current->number=10;

gets(current->name); /*утворення зв”язків*/ current ->next=rob->next; current->prev=rob; rob->next->prev=current; rob->next=current;

}

else puts(“такого елемента намає”);

5.10.5.2 Об’єднання

Синтаксично об’єднання задаються схоже до структур: union [<тег>] {<список оголошення елементів >} <описувач> union <тег> <описувач> […<описувач>]

Приклад:

union u_tag {

114

int ival; float fval; char *pval;

} uval;

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

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

Синтаксично доступ до членів об'єднання здійснюється аналогічно структурам:

Им’яОб’єднання . Им’яЧлена

чи

ВказівникОб’єднання -> Им’яЧлена

Якщо для відстеження типу, збереженого в даний момент у uval, використовується змінна utype, то можна написати програму:

if (utype == int)

printf("%d\n", uval.ival); else if (utype == float)

printf("%f\n", uval.fval); else if (utype == string)

printf("%s\n", uval.pval);

else

printf("bad type %d in utype\n", utype);

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

struct {

char *name; int flags; int utype; union {

115

int ival; float fval; char *pval;

}uval;

}symtab[nsym];

на змінну ival можна послатися як symtab[i].uval.ival

а на перший символ рядка pval як

*symtab[i].uval.pval

5.10.5.3 Приклади програм

Приклад 1. Хеш-таблиці та алгоритми хешування Як приклад використання структур та списків розглянемо про-

граму, що ілюструє роботу з хеш-таблицею.

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

str1

str2

str3 ...

strn

key1

key2

key3 ...

keyn

Логічно для збереження кожного елемента відповідності вибрати структуру, що буде мати принаймні 2 поля: для збереження рядка та його ключа(мабуть, корисним буде і поле–вказівник на саму структуру):

struct cell {

struct cell *next;/*вказівник на черговий елемент

*/

 

 

char* key;

/* ключ

*/

char* val;

/* значення */

};

 

 

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

hashtable[0]-> ...

hashtable[1]-> ...

116

hashtable[2]->..

hashtable[3]->....

hashtable[HASHSIZE]->

Причому до одного списку записуватимуться елементи таблиці, для яких однакове значення має спеціальна хеш-функція. Хеш-функція, аналізуючи рядок, повертає деяке ціле значення, яке і виступає індексом відповідного елемента масиву вказівників (hashtable). В найпростішому варіанті значення функції хешування отримується як остача від ділення суми символьних значень рядка на розмір массиву. При цьому доступ до голови відповідного списку здійснюється моментально: hashtable[hashfunc(...)]. Розмір масиву визначається фіксованим (в нашій програмі–HASHSIZE рівний 21.) В загальному випадку його вибір–це досить складне питання , він залежить від розміру таблиці та інших факторів.

Ось вся програма з коментарями по її окремих блоках:

#include <stdio.h>

#include <string.h> /* прототип для strchr() */ extern void *malloc(unsigned size);

/* типи ключа та значення: в нашому випадку це рядки */ typedef unsigned char uchar;

typedef uchar *VAL; typedef uchar *KEY;

/* Для роботи необхідно реалізувати операції int HASHFUNC(KEY); int EQKEY(KEY, KEY);

void FREEVAL(VAL); void SETVAL(VAL, VAL); void FREEKEY(KEY); void SETKEY(KEY, KEY); */

#define HASHSIZE 21 /* розмір масиву */

uchar *strudup(const uchar *s){ /* створення копії рядка в "кучі" */

uchar *p = (uchar *) malloc(strlen(s)+1); strcpy(p, s); return p;

}

/* одна з можливих хеш-функцій */

unsigned int hash; /* останнє пораховане значення хешфункції */

int HASHFUNC(KEY key){

unsigned int i = 0; uchar *keysrc = key; while(*key){

i = (i << 1)|(i >> 15); /* ROL */ i ^= *key++;

117

}

hash = i % HASHSIZE;

printf( "hash(%s)=%d\n", keysrc, hash); return hash;

}

 

#define EQKEY(s1, s2)

(strcmp(s1, s2) == 0)

#define FREEKEY(s)

free(s)

#define FREEVAL(s)

free(s)

#define SETVAL(at,s)

at = strudup(s)

#define SETKEY(at,s)

at = strudup(s)

#define KEYFMT

"%s"

#define VALFMT

"%s"

/* ===========типо-незалежна частина ============ */ struct cell {

struct cell *next;/* вказівник на черговий элемент

*/

 

 

KEY key;

/* ключ

*/

VAL val;

/* значення */

} *hashtable[ HASHSIZE ]; /* хеш-таблиця */

/* отримання значення по ключу */ struct cell *get(KEY key){

struct cell *p;

for(p = hashtable[HASHFUNC(key)]; p; p = p->next) if(EQKEY(p->key, key))

return p;

return NULL;

/* відсутнє */

}

 

/* занести пару ключ:значення в таблицю */ void set(KEY key, VAL val){

struct cell *p;

/* перевірити чи не було елемента з таким ключем */ if((p = get(key)) == NULL){ /* не було */ if(!(p = (struct cell *) malloc(sizeof(*p))))

return;

SETKEY(p->key, key);

p->next = hashtable[hash]; /* hash обчислене в

get() */

hashtable[hash] = p;

} else /* вже було: змінити значення */

FREEVAL(p->val); SETVAL(p->val, val);

118

}

/* знищення по ключу */ int del(KEY key){

int indx = HASHFUNC(key); struct cell *p, *prev = NULL;

if((p = hashtable[indx]) == NULL) return 0; for( ;p ;prev = p, p=p->next)

if(EQKEY(p->key, key)){ FREEVAL(p->val); FREEKEY(p->key);

if( p == hashtable[indx] ) /* голова спис-

ку */

hashtable[indx] = p->next; else prev->next = p->next;

free((void *) p ); return 1; /* знищений

*/

}

return 0; /* не було такого */

}

/* роздрукувати пару ключ:значення */ void printcell(struct cell *ptr){

putchar('(');

printf( KEYFMT, ptr->key ); putchar(','); printf( VALFMT, ptr->val ); putchar(')');

}

/* роздруковка таблиці (для відладки) */ void printtable(){

register i; struct cell *p; printf("----TABLE CONTENTS----\n"); for(i=0; i < HASHSIZE; i++)

if((p = hashtable[i]) != NULL){ printf( "%d: ", i);

for(; p; p=p->next) printcell(p), putchar(' ');

putchar('\n');

}

}

/* ітератор */ struct celliter {

int index; struct cell *ptr;

};

119

/* видати чергове значення */

struct cell *nextpair(struct celliter *ci){ struct cell *result;

while((result = ci->ptr) == NULL){ if( ++(ci->index) >= HASHSIZE )

return NULL; /* більше немає */ ci->ptr = hashtable[ci->index];

}

ci->ptr = result->next; return result;

}

/* ініціалізація ітератора */

struct cell *resetiter(struct celliter *ci){ ci->index = (-1); ci->ptr = NULL;

return nextpair(ci); /* перше значення */

}

void main(){

/* таблиця з імен та размірів файлів біжучого каталога */ struct celliter ci; struct cell *cl;

char key[40], value[40]; struct cell *val; extern FILE *popen(); FILE *fp; char *s ;

/* popen() читає вивід команди, заданої в 1-ому аргументі */

fp = popen( "ls -s", "r" );

while( fscanf( fp, "%s%s", value, key) == 2 ) set(key, value);

pclose(fp); /* popen() потрібно закривати pclose(); */

for(;;){

 

 

printf( "-> " ); /* запрошення */

 

if( !gets( key )) break;

/* EOF */

 

if( *key == '-' ){ /* -КЛЮЧ

:видалити

*/

printf( del( key+1 ) ? "OK\n" : "немає тако-

го\n");

continue;

}

if( !*key || !strcmp(key, "=")){

/* = :роздрукувати таблицю*/ printtable(); continue;

}

if(s = strchr(key, '=')){

/* КЛЮЧ=ЗНАЧЕННЯ :добавити */

*s++ = '\0';

120

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