osn_progr_final
.pdfstruct 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