5.10.

При записи данных в файл (да и вообще) используйте структуры вместо массивов, если элементы массива имеют разное смысловое назначение. Не воспринимайте структуру просто как средство объединения данных разных типов, она может быть и средством объединения данных одного типа, если это добавляет осмысленности нашей программе. Чем плох фрагмент? int data[2]; data[0] = my_key; data[1] = my_value; write(fd, (char *) data, 2 * sizeof(int));

Во-первых, тогда уж лучше указать размер всего массива сразу (хотя бы на тот случай, если мы изменим его размер на 3 и забудем поправить множитель с 2 на 3). write(fd, (char *) data, sizeof data);

Кстати, почему мы пишем data, а не &data? (ответ: потому что имя массива и есть его адрес). Во-вторых, элементы массива имеют разный смысл, так не использовать ли тут структуру? struct _data { int key; int value; } data; data.key = my_key; data.value = my_value; write(fd, &data, sizeof data);

5.11.

Что напечатает следующая программа? Нарисуйте расположение указателей по окончании данной программы. #include <stdio.h> struct lnk{ char c; struct lnk *prev, *next; } chain[20], *head = chain; add(c) char c; { head->c = c; head->next = head+1; head->next->prev = head; head++; } main(){ char *s = "012345"; while( *s ) add( *s++ ); head->c = '-'; head->next = (struct lnk *)NULL; chain->prev = chain->next; while( head->prev ){ putchar( head->prev->c ); head = head->prev; if( head->next ) head->next->prev = head->next->next; } }

5.12.

Напишите программу, составлящую двунаправленный список букв, вводимых с клавиатуры. Конец ввода - буква '\n'. После третьей буквы вставьте букву '+'. Удалите пятую букву. Распечатайте список в обратном порядке. Оформите операции вставки/удаления как функции. Элемент списка должен иметь вид: struct elem{ char letter; /* буква */ char *word; /* слово */ struct elem *prev; /* ссылка назад */ struct elem *next; /* ссылка вперед */ }; struct elem *head, /* первый элемент списка */ *tail, /* последний элемент */ *ptr, /* рабочая переменная */ *prev; /* предыдущий элемент при просмотре */ int c, cmp; ... while((c = getchar()) != '\n' ) Insert(c, tail); for(ptr=head; ptr != NULL; ptr=ptr->next) printf("буква %c\n", ptr->letter);

Память лучше отводить не из массива, а функцией calloc(), которая аналогична функции malloc(), но дополнительно расписывает выделенную память байтом '\0' (0, NULL). Вот функции вставки и удаления: extern char *calloc(); /* создать новое звено списка для буквы c */ struct elem *NewElem(c) char c; { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); /* calloc автоматически обнуляет все поля, * в том числе prev и next */ p->letter = c; return p; } /* вставка после ptr (обычно - после tail) */ Insert(c, ptr) char c; struct elem *ptr; { struct elem *newelem = NewElem(c), *right; if(head == NULL){ /* список был пуст */ head=tail=newelem; return; } right = ptr->next; ptr->next = newelem; newelem->prev = ptr; newelem->next = right; if( right ) right->prev = newelem; else tail = newelem; } /* удалить ptr из списка */ Delete( ptr ) struct elem *ptr; { struct elem *left=ptr->prev, *right=ptr->next; if( right ) right->prev = left; if( left ) left->next = right; if( tail == ptr ) tail = left; if( head == ptr ) head = right; free((char *) ptr); } Напишите аналогичную программу для списка слов. struct elem *NewElem(char *s) { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); p->word = strdup(s); return p; } void DeleteElem(struct elem *ptr){ free(ptr->word); free(ptr); } Усложнение: вставляйте слова в список в алфавитном порядке. Используйте для этого функцию strcmp(), просматривайте список так: struct elem *newelem; if (head == NULL){ /* список пуст */ head = tail = NewElem(новое_слово); return; } /* поиск места в списке */ for(cmp= -1, ptr=head, prev=NULL; ptr; prev=ptr, ptr=ptr->next ) if((cmp = strcmp(новое_слово, ptr->word)) <= 0 ) break;

Если цикл окончился с cmp==0, то такое слово уже есть в списке. Если cmp < 0, то такого слова не было и ptr указывает элемент, перед которым надо вставить слово новое_слово, а prev - после которого (prev==NULL означает, что надо вставить в начало списка); т.е. слово вставляется между prev и ptr. Если cmp > 0, то слово надо добавить в конец списка (при этом ptr==NULL). head ==> "a" ==> "b" ==> "d" ==> NULL | | prev "c" ptr if(cmp == 0) return; /* слово уже есть */ newelem = NewElem( новое_слово ); if(prev == NULL){ /* в начало */ newelem->next = head; newelem->prev = NULL; head->prev = newelem; head = newelem; } else if(ptr == NULL){ /* в конец */ newelem->next = NULL; newelem->prev = tail; tail->next = newelem; tail = newelem; } else { /* между prev и ptr */ newelem->next = ptr; newelem->prev = prev; prev->next = newelem; ptr ->prev = newelem; }

5.13.

Напишите функции для работы с комплексными числами struct complex { double re, im; }; Например, сложение выглядит так: struct complex add( c1, c2 ) struct complex c1, c2; { struct complex sum; sum.re = c1.re + c2.re; sum.im = c1.im + c2.im; return sum; } struct complex a = { 12.0, 14.0 }, b = { 13.0, 2.0 }; main(){ struct complex c; c = add( a, b ); printf( "(%g,%g)\n", c.re, c.im ); }

5.14.

Массивы в Си нельзя присваивать целиком, зато структуры - можно. Иногда используют такой трюк: структуру из единственного поля-массива typedef struct { int ai[5]; } intarray5; intarray5 a, b = { 1, 2, 3, 4, 5 }; и теперь законно a = b; Зато доступ к ячейкам массива выглядит теперь менее изящно: a.ai[2] = 14; for(i=0; i < 5; i++) printf( "%d\n", a.ai[i] ); Также невозможно передать копию массива в качестве фактического параметра функции. Даже если мы напишем: typedef int ARR16[16]; ARR16 d; void f(ARR16 a){ printf( "%d %d\n", a[3], a[15]); a[3] = 2345; } void main(void){ d[3] = 9; d[15] = 98; f(d); printf("Now it is %d\n", d[3]); }

то последний printf напечатает "Now it is 2345", поскольку в f передается адрес массива, но не его копия; поэтому оператор a[3]=2345 изменяет исходный массив. Обойти это можно, использовав тот же трюк, поскольку при передаче структуры в качестве параметра передается уже не ее адрес, а копия всей структуры (как это и принято в Си во всех случаях, кроме массивов).

5.15.

Напоследок упомянем про битовые поля - элементы структуры, занимающие только часть машинного слова - только несколько битов в нем. Размер поля в битах задается конструкцией :число_битов. Битовые поля используются для более компактного хранения информации в структурах (для экономии места). struct XYZ { /* битовые поля должны быть unsigned */ unsigned x:2; /* 0 .. 2**2 - 1 */ unsigned y:5; /* 0 .. 2**5 - 1 */ unsigned z:1; /* YES=1 NO=0 */ } xyz; main(){ printf("%u\n", sizeof(xyz)); /* == sizeof(int) */ xyz.z = 1; xyz.y = 21; xyz.x = 3; printf("%u %u %u\n", xyz.x, ++xyz.y, xyz.z); /* Значение битового поля берется по модулю * максимально допустимого числа 2**число_битов - 1 */ xyz.y = 32 /* максимум */ + 7; xyz.x = 16+2; xyz.z = 11; printf("%u %u %u\n", xyz.x, xyz.y, xyz.z); /* 2 7 1 */ } Поле ширины 1 часто используется в качестве битового флага: вместо #define FLAG1 01 #define FLAG2 02 #define FLAG3 04 int x; /* слово для нескольких флагов */ x |= FLAG1; x &= ~FLAG2; if(x & FLAG3) ...; используется struct flags { unsigned flag1:1, flag2:1, flag3:1; } x; x.flag1 = 1; x.flag2 = 0; if( x.flag3 ) ...;

Следует однако учесть, что машинный код для работы с битовыми полями более сложен и занимает больше команд (т.е. медленнее и длиннее).

К битовым полям нельзя применить операцию взятия адреса "&", у них нет адресов и смещений!

5.16.

Пример на использование структур с полем переменного размера. Часть переменной длины может быть лишь одна и обязана быть последним полем структуры. Внимание: это программистский трюк, использовать осторожно! #include <stdio.h> #define SZ 5 extern char *malloc(); #define VARTYPE char struct obj { struct header { /* постоянная часть */ int cls; int size; /* размер переменной части */ } hdr; VARTYPE body [1]; /* часть переменного размера: в описании ровно ОДИН элемент массива */ } *items [SZ]; /* указатели на структуры */ #define OFFSET(field, ptr) ((char *) &ptr->field - (char *)ptr) int body_offset; /* создание новой структуры */ struct obj *newObj( int cl, char *s ) { char *ptr; struct obj *op; int n = strlen(s); /* длина переменной части (штук VARTYPE) */ int newsize = sizeof(struct header) + n * sizeof(VARTYPE); printf("[n=%d newsize=%d]\n", n, newsize); /* newsize = (sizeof(struct obj) - sizeof(op->body)) + n * sizeof(op->body); При использовании этого размера не учитывается, что struct(obj) выровнена на границу sizeof(int). Но в частности следует учитывать и то, на границу чего выровнено начало поля op->body. То есть самым правильным будет newsize = body_offset + n * sizeof(op->body); */ /* отвести массив байт без внутренней структуры */ ptr = (char *) malloc(newsize); /* наложить поверх него структуру */ op = (struct obj *) ptr; op->hdr.cls = cl; op->hdr.size = n; strncpy(op->body, s, n); return op; } void printobj( struct obj *p ) { register i; printf( "OBJECT(cls=%d,size=%d)\n", p->hdr.cls, p->hdr.size); for(i=0; i < p->hdr.size; i++ ) putchar( p->body[i] ); putchar( '\n' ); } char *strs[] = { "a tree", "a maple", "an oak", "the birch", "the fir" }; int main(int ac, char *av[]){ int i; printf("sizeof(struct header)=%d sizeof(struct obj)=%d\n", sizeof(struct header), sizeof(struct obj)); { struct obj *sample; printf("offset(cls)=%d\n", OFFSET(hdr.cls, sample)); printf("offset(size)=%d\n", OFFSET(hdr.size, sample)); printf("offset(body)=%d\n", body_offset = OFFSET(body, sample)); } for( i=0; i < SZ; i++ ) items[i] = newObj( i, strs[i] ); for( i=0; i < SZ; i++ ){ printobj( items[i] ); free( items[i] ); items[i] = NULL; } return 0; }

5.17.

Напишите программу, реализующую список со "старением". Элемент списка, к которому обращались последним, находится в голове списка. Самый старый элемент вытесняется к хвосту списка и в конечном счете из списка удаляется. Такой алгоритм использует ядро UNIX для кэширования блоков файла в оперативной памяти: блоки, к которым часто бывают обращения оседают в памяти (а не на диске). /* Список строк, упорядоченных по времени их добавления в список, * т.е. самая "свежая" строка - в начале, самая "древняя" - в конце. * Строки при поступлении могут и повторяться! По подобному принципу * можно организовать буферизацию блоков при обмене с диском. */ #include <stdio.h> extern char *malloc(), *gets(); #define MAX 3 /* максимальная длина списка */ int nelems = 0; /* текущая длина списка */ struct elem { /* СТРУКТУРА ЭЛЕМЕНТА СПИСКА */ char *key; /* Для блоков - это целое - номер блока */ struct elem *next; /* следующий элемент списка */ /* ... и может что-то еще ... */ } *head; /* голова списка */ void printList(), addList(char *), forget(); void main(){ /* Введите a b c d b a c */ char buf[128]; while(gets(buf)) addList(buf), printList(); } /* Распечатка списка */ void printList(){ register struct elem *ptr; printf( "В списке %d элементов\n", nelems ); for(ptr = head; ptr != NULL; ptr = ptr->next ) printf( "\t\"%s\"\n", ptr->key ); } /* Добавление в начало списка */ void addList(char *s) { register struct elem *p, *new; /* Анализ - нет ли уже в списке */ for(p = head; p != NULL; p = p->next ) if( !strcmp(s, p->key)){ /* Есть. Перенести в начало списка */ if( head == p ) return; /* Уже в начале */ /* Удаляем из середины списка */ new = p; /* Удаляемый элемент */ for(p = head; p->next != new; p = p->next ); /* p указывает на предшественника new */ p->next = new->next; goto Insert; } /* Нет в списке */ if( nelems >= MAX ) forget(); /* Забыть старейший */ if((new = (struct elem *) malloc(sizeof(struct elem)))==NULL) goto bad; if((new->key = malloc(strlen(s) + 1)) == NULL) goto bad; strcpy(new->key, s); nelems++; Insert: new->next = head; head = new; return; bad: printf( "Нет памяти\n" ); exit(13); } /* Забыть хвост списка */ void forget(){ struct elem *prev = head, *tail; if( head == NULL ) return; /* Список пуст */ /* Единственный элемент ? */ if((tail = head->next) == NULL){ tail=head; head=NULL; goto Del; } for( ; tail->next != NULL; prev = tail, tail = tail->next ); prev->next = NULL; Del: free(tail->key); free(tail); nelems--; }

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед