Страница:
подробно про это - в главе "Взаимодействие с UNIX".
4.45. Эмуляция основ библиотеки STDIO, по мотивам 4.2 BSD.
#include <fcntl.h>
#define BUFSIZ 512 /* стандартный размер буфера */
#define _NFILE 20
#define EOF (-1) /* признак конца файла */
#define NULL ((char *) 0)
#define IOREAD 0x0001 /* для чтения */
#define IOWRT 0x0002 /* для записи */
#define IORW 0x0004 /* для чтения и записи */
#define IONBF 0x0008 /* не буферизован */
#define IOTTY 0x0010 /* вывод на терминал */
#define IOALLOC 0x0020 /* выделен буфер malloc-ом */
#define IOEOF 0x0040 /* достигнут конец файла */
#define IOERR 0x0080 /* ошибка чтения/записи */
____________________
|- Заметим еще, что если дескриптор fd связан с терминалом, то можно узнать полное
имя этого устройства вызовом стандартной функции
extern char *ttyname();
char *tname = ttyname(fd);
Она выдаст строку, подобную "/dev/tty01". Если fd не связан с терминалом - она вернет
А. Богатырев, 1992-95 - 166 - Си в UNIX
extern char *malloc(); extern long lseek();
typedef unsigned char uchar;
uchar sibuf[BUFSIZ], sobuf[BUFSIZ];
typedef struct _iobuf {
int cnt; /* счетчик */
uchar *ptr, *base; /* указатель в буфер и на его начало */
int bufsiz, flag, file; /* размер буфера, флаги, дескриптор */
} FILE;
FILE iob[_NFILE] = {
{ 0, NULL, NULL, 0, IOREAD, 0 },
{ 0, NULL, NULL, 0, IOWRT|IOTTY, 1 },
{ 0, NULL, NULL, 0, IOWRT|IONBF, 2 },
};
#define stdin (&iob[0])
#define stdout (&iob[1])
#define stderr (&iob[2])
#define putchar(c) putc((c), stdout)
#define getchar() getc(stdin)
#define fileno(fp) ((fp)->file)
#define feof(fp) (((fp)->flag & IOEOF) != 0)
#define ferror(fp) (((fp)->flag & IOERR) != 0)
#define clearerr(fp) ((void) ((fp)->flag &= ~(IOERR | IOEOF)))
#define getc(fp) (--(fp)->cnt < 0 ? \
filbuf(fp) : (int) *(fp)->ptr++)
#define putc(x, fp) (--(fp)->cnt < 0 ? \
flsbuf((uchar) (x), (fp)) : \
(int) (*(fp)->ptr++ = (uchar) (x)))
int fputc(int c, FILE *fp){ return putc(c, fp); }
int fgetc( FILE *fp){ return getc(fp); }
____________________
NULL.
____________________
|= Ссылка на управляющий терминал процесса хранится в u-area каждого процесса:
u_ttyp, u_ttyd, поэтому ядро в состоянии определить какой настоящий терминал следует
открыть для вас. Если разные процессы открывают /dev/tty, они могут открыть в итоге
разные терминалы, т.е. одно имя приводит к разным устройствам! Смотри главу про
UNIX.
А. Богатырев, 1992-95 - 167 - Си в UNIX
/* Открытие файла */
FILE *fopen(char *name, char *how){
register FILE *fp; register i, rw;
for(fp = iob, i=0; i < _NFILE; i++, fp++)
if(fp->flag == 0) goto found;
return NULL; /* нет свободного слота */
found:
rw = how[1] == '+';
if(*how == 'r'){
if((fp->file = open(name, rw ? O_RDWR:O_RDONLY)) < 0)
return NULL;
fp->flag = IOREAD;
} else {
if((fp->file = open(name, (rw ? O_RDWR:O_WRONLY)| O_CREAT |
(*how == 'a' ? O_APPEND : O_TRUNC), 0666 )) < 0)
return NULL;
fp->flag = IOWRT;
}
if(rw) fp->flag = IORW;
fp->bufsiz = fp->cnt = 0; fp->base = fp->ptr = NULL;
return fp;
}
/* Принудительный сброс буфера */
void fflush(FILE *fp){
uchar *base; int full= 0;
if((fp->flag & (IONBF|IOWRT)) == IOWRT &&
(base = fp->base) != NULL && (full=fp->ptr - base) > 0){
fp->ptr = base; fp->cnt = fp->bufsiz;
if(write(fileno(fp), base, full) != full)
fp->flag |= IOERR;
}
}
/* Закрытие файла */
void fclose(FILE *fp){
if((fp->flag & (IOREAD|IOWRT|IORW)) == 0 ) return;
fflush(fp);
close(fileno(fp));
if(fp->flag & IOALLOC) free(fp->base);
fp->base = fp->ptr = NULL;
fp->cnt = fp->bufsiz = fp->flag = 0; fp->file = (-1);
}
/* Закрытие файлов при exit()-е */
void _cleanup(){
register i;
for(i=0; i < _NFILE; i++)
fclose(iob + i);
}
/* Завершить текущий процесс */
void exit(uchar code){
_cleanup();
_exit(code); /* Собственно системный вызов */
}
А. Богатырев, 1992-95 - 168 - Си в UNIX
/* Прочесть очередной буфер из файла */
int filbuf(FILE *fp){
static uchar smallbuf[_NFILE];
if(fp->flag & IORW){
if(fp->flag & IOWRT){ fflush(fp); fp->flag &= ~IOWRT; }
fp->flag |= IOREAD; /* операция чтения */
}
if((fp->flag & IOREAD) == 0 || feof(fp)) return EOF;
while( fp->base == NULL ) /* отвести буфер */
if( fp->flag & IONBF ){ /* небуферизованный */
fp->base = &smallbuf[fileno(fp)];
fp->bufsiz = sizeof(uchar);
} else if( fp == stdin ){ /* статический буфер */
fp->base = sibuf;
fp->bufsiz = sizeof(sibuf);
} else if((fp->base = malloc(fp->bufsiz = BUFSIZ)) == NULL)
fp->flag |= IONBF; /* не будем буферизовать */
else fp->flag |= IOALLOC; /* буфер выделен */
if( fp == stdin && (stdout->flag & IOTTY)) fflush(stdout);
fp->ptr = fp->base; /* сбросить на начало буфера */
if((fp->cnt = read(fileno(fp), fp->base, fp->bufsiz)) == 0 ){
fp->flag |= IOEOF; if(fp->flag & IORW) fp->flag &= ~IOREAD;
return EOF;
} else if( fp->cnt < 0 ){
fp->flag |= IOERR; fp->cnt = 0; return EOF;
}
return getc(fp);
}
А. Богатырев, 1992-95 - 169 - Си в UNIX
/* Вытолкнуть очередной буфер в файл */
int flsbuf(int c, FILE *fp){
uchar *base; int full, cret = c;
if( fp->flag & IORW ){
fp->flag &= ~(IOEOF|IOREAD);
fp->flag |= IOWRT; /* операция записи */
}
if((fp->flag & IOWRT) == 0) return EOF;
tryAgain:
if(fp->flag & IONBF){ /* не буферизован */
if(write(fileno(fp), &c, 1) != 1)
{ fp->flag |= IOERR; cret=EOF; }
fp->cnt = 0;
} else { /* канал буферизован */
if((base = fp->base) == NULL){ /* буфера еще нет */
if(fp == stdout){
if(isatty(fileno(stdout))) fp->flag |= IOTTY;
else fp->flag &= ~IOTTY;
fp->base = fp->ptr = sobuf; /* статический буфер */
fp->bufsiz = sizeof(sobuf);
goto tryAgain;
}
if((base = fp->base = malloc(fp->bufsiz = BUFSIZ))== NULL){
fp->bufsiz = 0; fp->flag |= IONBF; goto tryAgain;
} else fp->flag |= IOALLOC;
} else if ((full = fp->ptr - base) > 0)
if(write(fileno(fp), fp->ptr = base, full) != full)
{ fp->flag |= IOERR; cret = EOF; }
fp->cnt = fp->bufsiz - 1;
*base++ = c;
fp->ptr = base;
}
return cret;
}
/* Вернуть символ в буфер */
int ungetc(int c, FILE *fp){
if(c == EOF || fp->flag & IONBF || fp->base == NULL) return EOF;
if((fp->flag & IOREAD)==0 || fp->ptr <= fp->base)
if(fp->ptr == fp->base && fp->cnt == 0) fp->ptr++;
else return EOF;
fp->cnt++;
return(* --fp->ptr = c);
}
/* Изменить размер буфера */
void setbuffer(FILE *fp, uchar *buf, int size){
fflush(fp);
if(fp->base && (fp->flag & IOALLOC)) free(fp->base);
fp->flag &= ~(IOALLOC|IONBF);
if((fp->base = fp->ptr = buf) == NULL){
fp->flag |= IONBF; fp->bufsiz = 0;
} else fp->bufsiz = size;
fp->cnt = 0;
}
А. Богатырев, 1992-95 - 170 - Си в UNIX
/* "Перемотать" файл в начало */
void rewind(FILE *fp){
fflush(fp);
lseek(fileno(fp), 0L, 0);
fp->cnt = 0; fp->ptr = fp->base;
clearerr(fp);
if(fp->flag & IORW) fp->flag &= ~(IOREAD|IOWRT);
}
/* Позиционирование указателя чтения/записи */
#ifdef COMMENT
base ptr случай IOREAD
| |<----cnt---->|
0L |б у |ф е р |
|=======######@@@@@@@@@@@@@@======== файл file
| |<-p->|<-dl-->|
|<----pos---->| | |
|<----offset(new)-->| |
|<----RWptr---------------->|
где pos = RWptr - cnt; // указатель с поправкой
offset = pos + p = RWptr - cnt + p = lseek(file,0L,1) - cnt + p
отсюда: (для SEEK_SET)
p = offset+cnt-lseek(file,0L,1);
или (для SEEK_CUR) dl = RWptr - offset = p - cnt
lseek(file, dl, 1);
Условие, что указатель можно сдвинуть просто в буфере:
if( cnt > 0 && p <= cnt && base <= ptr + p ){
ptr += p; cnt -= p; }
#endif /*COMMENT*/
А. Богатырев, 1992-95 - 171 - Си в UNIX
int fseek(FILE *fp, long offset, int whence){
register resync, c; long p = (-1);
clearerr(fp);
if( fp->flag & (IOWRT|IORW)){
fflush(fp);
if(fp->flag & IORW){
fp->cnt = 0; fp->ptr = fp->base; fp->flag &= ~IOWRT;
}
p = lseek(fileno(fp), offset, whence);
} else if( fp->flag & IOREAD ){
if(whence < 2 && fp->base && !(fp->flag & IONBF)){
c = fp->cnt; p = offset;
if(whence == 0) /* SEEK_SET */
p += c - lseek(fileno(fp), 0L, 1);
else offset -= c;
if(!(fp->flag & IORW) &&
c > 0 && p <= c && p >= fp->base - fp->ptr
){ fp->ptr += (int) p; fp->cnt -= (int) p;
return 0; /* done */
}
resync = offset & 01;
} else resync = 0;
if(fp->flag & IORW){
fp->ptr = fp->base; fp->flag &= ~IOREAD; resync = 0;
}
p = lseek(fileno(fp), offset-resync, whence);
fp->cnt = 0; /* вынудить filbuf(); */
if(resync) getc(fp);
}
return (p== -1 ? -1 : 0);
}
/* Узнать текущую позицию указателя */
long ftell(FILE *fp){
long tres; register adjust;
if(fp->cnt < 0) fp->cnt = 0;
if(fp->flag & IOREAD) adjust = -(fp->cnt);
else if(fp->flag & (IOWRT|IORW)){ adjust = 0;
if(fp->flag & IOWRT &&
fp->base && !(fp->flag & IONBF)) /* буферизован */
adjust = fp->ptr - fp->base;
} else return (-1L);
if((tres = lseek(fileno(fp), 0L, 1)) < 0) return tres;
return (tres + adjust);
}
А. Богатырев, 1992-95 - 172 - Си в UNIX
Структуры ("записи") представляют собой агрегаты разнородных данных (полей раз-
ного типа); в отличие от массивов, где все элементы имеют один и тот же тип.
struct {
int x, y; /* два целых поля */
char s[10]; /* и одно - для строки */
} s1;
Структурный тип может иметь имя:
struct XYS {
int x, y; /* два целых поля */
char str[10]; /* и одно - для строки */
};
Здесь мы объявили тип, но не отвели ни одной переменной этого типа (хотя могли бы).
Теперь опишем переменную этого типа и указатель на нее:
struct XYS s2, *sptr = &s2;
Доступ к полям структуры производится по имени поля (а не по индексу, как у масси-
вов):
имя_структурной_переменной.имя_поля
указатель_на_структуру -> имя_поля
то есть
не а
#define ВЕС 0 struct { int вес, рост; } x;
#define РОСТ 1 x.рост = 175;
int x[2]; x[РОСТ] = 175;
Например
s1.x = 13;
strcpy(s2.str, "Finish");
sptr->y = 27;
Структура может содержать структуры другого типа в качестве полей:
struct XYS_Z {
struct XYS xys;
int z;
} a1;
a1.xys.x = 71; a1.z = 12;
Структура того же самого типа не может содержаться в качестве поля - рекурсивные
определения запрещены. Зато нередко используются поля - ссылки на структуры такого
же типа (или другого). Это позволяет организовывать списки структур:
struct node {
int value;
struct node *next;
};
Очень часто используются массивы структур:
А. Богатырев, 1992-95 - 173 - Си в UNIX
struct XYS array[20]; int i = 5, j;
array[i].x = 12;
j = array[i].x;
Статические структуры можно описывать с инициализацией, перечисляя значения их полей
в {} через запятую:
extern struct node n2;
struct node n1 = { 1, &n2 },
n2 = { 2, &n1 },
n3 = { 3, NULL };
В этом примере n2 описано предварительно для того, чтобы &n2 в строке инициализации
n1 было определено.
Структуры одинакового типа можно присваивать целиком (что соответствует присваи-
ванию каждого из полей):
struct XYS s1, s2; ...
s2 = s1;
в отличие от массивов, которые присваивать целиком нельзя:
int a[5], b[5]; a = b; /* ОШИБОЧНО ! */
Пример обращения к полям структуры:
typedef struct _Point {
short x, y; /* координаты точки */
char *s; /* метка точки */
} Point;
Point p; Point *pptr; short *iptr;
struct _Curve {
Point points[25]; /* вершины ломанной */
int color; /* цвет линии */
} aLine[10], *linePtr = & aLine[0];
...
pptr = &p; /* указатель на структуру p */
p.x = 1; p.y = 2; p.s = "Grue";
linePtr->points[2].x = 54; aLine[5].points[0].y = 17;
В ы р а ж е н и е значение
---------+------------+------------+-----------+-----------
p.x | pptr->x | (*pptr).x | (&p)->x | 1
---------+------------+------------+-----------+-----------
&p->x | ошибка
-----------+----------------+------------------+-----------
iptr= &p.x | iptr= &pptr->x | iptr= &(pptr->x) | адрес поля
-----------+----------------+--------+---------+-----------
*pptr->s | *(pptr->s) | *p.s | p.s[0] | 'G'
-----------+----------------+--------+---------+-----------
pptr->s[1] | (&p)->s[1] | p.s[1] | 'r'
-----------+----------------+------------------+-----------
&p->s[1] | ошибка
-----------+----------------+------------------+-----------
(*pptr).s | pptr->s | p.s | "Grue"
-----------+----------------+------------------+-----------
*pptr.s | ошибка
-----------------------------------------------+-----------
А. Богатырев, 1992-95 - 174 - Си в UNIX
Вообще (&p)->field = p.field
pptr->field = (*pptr).field
Объединения - это агрегаты данных, которые могут хранить в себе значения данных
разных типов на одном и том же месте.
struct a{ int x, y; char *s; } A;
union b{ int i; char *s; struct a aa; } B;
Структура:
________________________
A: | A.x int | Три поля
------------------------ расположены подряд.
| A.y int | Получается как бы
------------------------ "карточка" с графами.
| A.s char * |
------------------------
А у объединений поля расположены "параллельно",
на одном месте в памяти.
_______________________________________________________
B: | B.i int | B.s char * | B.aa : B.aa.x int |
-----------| | struct a : B.aa.y int |
---------------| : B.aa.s char * |
|___________________________|
Это как бы "ящик" в который можно поместить значение любого типа из перечисленных, но
не ВСЕ ВМЕСТЕ ("и то и это", как у структур), а ПО ОЧЕРЕДИ ("или/или"). Размер его
достаточно велик, чтоб вместить самый большой из перечисленных типов данных.
Мы можем занести в union значение и интерпретировать его как другой тип данных -
это иногда используется в машинно-зависимых программах. Вот пример, выясняющий поря-
док байтов в short числах:
union lb {
char s[2]; short i;
} x;
unsigned hi, lo;
x.i = (02 << 8) | 01;
hi = x.s[1]; lo = x.s[0];
printf( "%d %d\n", hi, lo);
или так:
#include <stdio.h>
union {
int i;
unsigned char s[sizeof(int)];
} u;
void main(){
unsigned char *p;
int n;
u.i = 0x12345678;
for(n=0, p=u.s; n < sizeof(int); n++, p++){
printf("%02X ", *p);
}
putchar('\n');
}
А. Богатырев, 1992-95 - 175 - Си в UNIX
или порядок слов в long числах:
union xx {
long l;
struct ab {
short a; /* low word */
short b; /* high word */
} ab;
} c;
main(){ /* На IBM PC 80386 печатает 00020001 */
c.ab.a = 1; c.ab.b = 2; printf("%08lx\n", c.l );
}
5.1. Найдите ошибки в описании структурного шаблона:
structure { int arr[12],
char string,
int *sum
}
5.2. Разработайте структурный шаблон, который содержал бы название месяца, трехбук-
венную аббревиатуру месяца, количество дней в месяце и номер месяца. Инициализируйте
его для невисокосного года.
struct month {
char name[10]; /* или char *name; */
char abbrev[4]; /* или char *abbrev; */
int days;
int num;
};
struct month months[12] = { /* индекс */
{"Январь" , "Янв", 31, 1 }, /* 0 */
{"Февраль", "Фев", 28, 2 }, /* 1 */
...
{"Декабрь", "Дек", 31, 12}, /* 11 */
}, *mptr = & months[0]; /* или *mptr = months */
main(){
struct month *mptr;
printf( "%s\n", mptr[1].name );
printf( "%s %d\n", mptr->name, mptr->num );
}
Напишите функцию, сохраняющую массив months в файл; функцию, считывающую его из
файла. Используйте fprintf и fscanf.
В чем будет разница в функции чтения, когда поле name описано как char name[10]
и как char *name?
Ответ: во втором случае для сохранения прочитанной строки надо заказывать память
динамически при помощи malloc() и сохранять в ней строку при помощи strcpy(), т.к.
память для хранения самой строки в структуре не зарезервирована (а только для указа-
теля на нее).
Найдите ошибку в операторах функции main(). Почему печатается не "Февраль", а
какой-то мусор? Указание: куда указывает указатель mptr, описанный в main()? Ответ: в
"неизвестно куда" - это локальная переменная (причем не получившая начального значе-
ния - в ней содержится мусор), а не то же самое, что указатель mptr, описанный выше!
Уберите описание mptr из main.
А. Богатырев, 1992-95 - 176 - Си в UNIX
Заметим, что для распечатки всех или нескольких полей структуры следует ЯВНО
перечислить в printf() все нужные поля и указать форматы, соответствующие типам этих
полей. Не существует формата или стандартной функции, позволяющей распечатать все
поля сразу (однако такая функция может быть написана вами для конкретного типа струк-
тур). Также не существует формата для scanf(), который вводил бы структуру целиком.
Вводить можно только по частям - каждое поле отдельно.
5.3. Напишите программу, которая по номеру месяца возвращает общее число дней года
вплоть до этого месяца.
5.4. Переделайте предыдущую программу таким образом, чтобы она по написанному бук-
вами названию месяца возвращала общее число дней года вплоть до этого месяца. В прог-
рамме используйте функцию strcmp().
5.5. Переделайте предыдущую программу таким образом, чтобы она запрашивала у пользо-
вателя день, месяц, год и выдавала общее количество дней в году вплоть до данного
дня. Месяц может обозначаться номером, названием месяца или его аббревиатурой.
5.6. Составьте структуру для учетной картотеки служащего, которая содержала бы сле-
дующие сведения: фамилию, имя, отчество; год рождения; домашний адрес; место работы,
должность; зарплату; дату поступления на работу.
5.7. Что печатает программа?
struct man {
char name[20];
int salary;
} workers[] = {
{ "Иванов", 200 },
{ "Петров", 180 },
{ "Сидоров", 150 }
}, *wptr, chief = { "начальник", 550 };
main(){
struct man *ptr, *cptr, save;
ptr = wptr = workers + 1;
cptr = &chief;
save = workers[2]; workers[2] = *wptr; *wptr = save;
wptr++; ptr--; ptr->salary = save.salary;
printf( "%c %s %s %s %s\n%d %d %d %d\n%d %d %c\n",
*workers[1].name, workers[2].name, cptr->name,
ptr[1].name, save.name,
wptr->salary, chief.salary,
(*ptr).salary, workers->salary,
wptr - ptr, wptr - workers, *ptr->name );
}
Ответ:
С Петров начальник Сидоров Сидоров
180 550 150 150
2 2 И
5.8. Разберите следующий пример:
#include <stdio.h>
struct man{
А. Богатырев, 1992-95 - 177 - Си в UNIX
char *name, town[4]; int salary;
int addr[2];
} men[] = {
{ "Вася", "Msc", 100, { 12, 7 } },
{ "Гриша", "Len", 120, { 6, 51 } },
{ "Петя", "Rig", 140, { 23, 84 } },
{ NULL, "" , -1, { -1, -1 } }
};
main(){
struct man *ptr, **ptrptr;
int i;
ptrptr = &ptr;
*ptrptr = &men[1]; /* men+1 */
printf( "%s %d %s %d %c\n",
ptr->name,
ptr->salary,
ptr->town,
ptr->addr[1],
ptr[1].town[2] );
(*ptrptr)++;
/* копируем *ptr в men[0] */
men[0].name = ptr->name; /* (char *) #1 */
strcpy( men[0].town, ptr->town ); /* char [] #2 */
men[0].salary = ptr->salary; /* int #3 */
for( i=0; i < 2; i++ )
men[0].addr[i] = ptr->addr[i]; /* массив #4 */
/* распечатываем массив структур */
for(ptr=men; ptr->name; ptr++ )
printf( "%s %s %d\n",
ptr->name, ptr->town, ptr->addr[0]);
}
Обратите внимание на такие моменты:
1) Как производится работа с указателем на указатель (ptrptr).
2) При копировании структур отдельными полями, поля скалярных типов (int, char,
long, ..., указатели) копируются операцией присваивания (см. строки с пометками
#1 и #3). Поля векторных типов (массивы) копируются при помощи цикла, поэле-
ментно пересылающего массив (строка #4). Строки (массивы букв) пересылаются
стандартной функцией strcpy (строка #2). Все это относится не только к полям
структур, но и к переменным таких типов. Структуры можно также копировать не по
полям, а целиком: men[0]= *ptr;
3) Запись аргументов функции printf() лесенкой позволяет лучше видеть, какому фор-
мату соответствует каждый аргумент.
4) При распечатке массива структур мы печатаем не определенное их количество (рав-
ное размеру массива), а пользуемся указателем NULL в поле name последней струк-
туры как признаком конца массива.
5) В поле town мы храним строки из 3х букв, однако выделяем для хранения массив из
4х байт. Это необходимо потому, что строка "Msc" состоит не из 3х, а из 4х бай-
тов: 'M','s','c','\0'.
При работе со структурами и указателями большую помощь могут оказать рисунки. Вот как
(например) можно нарисовать данные из этого примера (массив men изображен не весь):
А. Богатырев, 1992-95 - 178 - Си в UNIX
--ptr-- --ptrptr--
ptr | * |<------|---* |
---|--- ----------
|
/ =========men[0]==
/ men:|name | *---|-----> "Вася"
| |---------------|
| |town |M|s|c|\0|
| |---------------|
| |salary| 100 |
| |---------------|
| |addr | 12 | 7 |
\ -----------------
\ =========men[1]==
\-->|name | *---|-----> "Гриша"
............
5.9. Составьте программу "справочник по таблице Менделеева", которая по названию
химического элемента выдавала бы его характеристики. Таблицу инициализируйте массивом
структур.
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;
А. Богатырев, 1992-95 - 179 - Си в UNIX
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;
}
А. Богатырев, 1992-95 - 180 - Си в UNIX
/* вставка после 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
А. Богатырев, 1992-95 - 181 - Си в UNIX
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;
Зато доступ к ячейкам массива выглядит теперь менее изящно:
А. Богатырев, 1992-95 - 182 - Си в UNIX
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 часто используется в качестве битового флага: вместо
4.45. Эмуляция основ библиотеки STDIO, по мотивам 4.2 BSD.
#include <fcntl.h>
#define BUFSIZ 512 /* стандартный размер буфера */
#define _NFILE 20
#define EOF (-1) /* признак конца файла */
#define NULL ((char *) 0)
#define IOREAD 0x0001 /* для чтения */
#define IOWRT 0x0002 /* для записи */
#define IORW 0x0004 /* для чтения и записи */
#define IONBF 0x0008 /* не буферизован */
#define IOTTY 0x0010 /* вывод на терминал */
#define IOALLOC 0x0020 /* выделен буфер malloc-ом */
#define IOEOF 0x0040 /* достигнут конец файла */
#define IOERR 0x0080 /* ошибка чтения/записи */
____________________
|- Заметим еще, что если дескриптор fd связан с терминалом, то можно узнать полное
имя этого устройства вызовом стандартной функции
extern char *ttyname();
char *tname = ttyname(fd);
Она выдаст строку, подобную "/dev/tty01". Если fd не связан с терминалом - она вернет
А. Богатырев, 1992-95 - 166 - Си в UNIX
extern char *malloc(); extern long lseek();
typedef unsigned char uchar;
uchar sibuf[BUFSIZ], sobuf[BUFSIZ];
typedef struct _iobuf {
int cnt; /* счетчик */
uchar *ptr, *base; /* указатель в буфер и на его начало */
int bufsiz, flag, file; /* размер буфера, флаги, дескриптор */
} FILE;
FILE iob[_NFILE] = {
{ 0, NULL, NULL, 0, IOREAD, 0 },
{ 0, NULL, NULL, 0, IOWRT|IOTTY, 1 },
{ 0, NULL, NULL, 0, IOWRT|IONBF, 2 },
};
#define stdin (&iob[0])
#define stdout (&iob[1])
#define stderr (&iob[2])
#define putchar(c) putc((c), stdout)
#define getchar() getc(stdin)
#define fileno(fp) ((fp)->file)
#define feof(fp) (((fp)->flag & IOEOF) != 0)
#define ferror(fp) (((fp)->flag & IOERR) != 0)
#define clearerr(fp) ((void) ((fp)->flag &= ~(IOERR | IOEOF)))
#define getc(fp) (--(fp)->cnt < 0 ? \
filbuf(fp) : (int) *(fp)->ptr++)
#define putc(x, fp) (--(fp)->cnt < 0 ? \
flsbuf((uchar) (x), (fp)) : \
(int) (*(fp)->ptr++ = (uchar) (x)))
int fputc(int c, FILE *fp){ return putc(c, fp); }
int fgetc( FILE *fp){ return getc(fp); }
____________________
NULL.
____________________
|= Ссылка на управляющий терминал процесса хранится в u-area каждого процесса:
u_ttyp, u_ttyd, поэтому ядро в состоянии определить какой настоящий терминал следует
открыть для вас. Если разные процессы открывают /dev/tty, они могут открыть в итоге
разные терминалы, т.е. одно имя приводит к разным устройствам! Смотри главу про
UNIX.
А. Богатырев, 1992-95 - 167 - Си в UNIX
/* Открытие файла */
FILE *fopen(char *name, char *how){
register FILE *fp; register i, rw;
for(fp = iob, i=0; i < _NFILE; i++, fp++)
if(fp->flag == 0) goto found;
return NULL; /* нет свободного слота */
found:
rw = how[1] == '+';
if(*how == 'r'){
if((fp->file = open(name, rw ? O_RDWR:O_RDONLY)) < 0)
return NULL;
fp->flag = IOREAD;
} else {
if((fp->file = open(name, (rw ? O_RDWR:O_WRONLY)| O_CREAT |
(*how == 'a' ? O_APPEND : O_TRUNC), 0666 )) < 0)
return NULL;
fp->flag = IOWRT;
}
if(rw) fp->flag = IORW;
fp->bufsiz = fp->cnt = 0; fp->base = fp->ptr = NULL;
return fp;
}
/* Принудительный сброс буфера */
void fflush(FILE *fp){
uchar *base; int full= 0;
if((fp->flag & (IONBF|IOWRT)) == IOWRT &&
(base = fp->base) != NULL && (full=fp->ptr - base) > 0){
fp->ptr = base; fp->cnt = fp->bufsiz;
if(write(fileno(fp), base, full) != full)
fp->flag |= IOERR;
}
}
/* Закрытие файла */
void fclose(FILE *fp){
if((fp->flag & (IOREAD|IOWRT|IORW)) == 0 ) return;
fflush(fp);
close(fileno(fp));
if(fp->flag & IOALLOC) free(fp->base);
fp->base = fp->ptr = NULL;
fp->cnt = fp->bufsiz = fp->flag = 0; fp->file = (-1);
}
/* Закрытие файлов при exit()-е */
void _cleanup(){
register i;
for(i=0; i < _NFILE; i++)
fclose(iob + i);
}
/* Завершить текущий процесс */
void exit(uchar code){
_cleanup();
_exit(code); /* Собственно системный вызов */
}
А. Богатырев, 1992-95 - 168 - Си в UNIX
/* Прочесть очередной буфер из файла */
int filbuf(FILE *fp){
static uchar smallbuf[_NFILE];
if(fp->flag & IORW){
if(fp->flag & IOWRT){ fflush(fp); fp->flag &= ~IOWRT; }
fp->flag |= IOREAD; /* операция чтения */
}
if((fp->flag & IOREAD) == 0 || feof(fp)) return EOF;
while( fp->base == NULL ) /* отвести буфер */
if( fp->flag & IONBF ){ /* небуферизованный */
fp->base = &smallbuf[fileno(fp)];
fp->bufsiz = sizeof(uchar);
} else if( fp == stdin ){ /* статический буфер */
fp->base = sibuf;
fp->bufsiz = sizeof(sibuf);
} else if((fp->base = malloc(fp->bufsiz = BUFSIZ)) == NULL)
fp->flag |= IONBF; /* не будем буферизовать */
else fp->flag |= IOALLOC; /* буфер выделен */
if( fp == stdin && (stdout->flag & IOTTY)) fflush(stdout);
fp->ptr = fp->base; /* сбросить на начало буфера */
if((fp->cnt = read(fileno(fp), fp->base, fp->bufsiz)) == 0 ){
fp->flag |= IOEOF; if(fp->flag & IORW) fp->flag &= ~IOREAD;
return EOF;
} else if( fp->cnt < 0 ){
fp->flag |= IOERR; fp->cnt = 0; return EOF;
}
return getc(fp);
}
А. Богатырев, 1992-95 - 169 - Си в UNIX
/* Вытолкнуть очередной буфер в файл */
int flsbuf(int c, FILE *fp){
uchar *base; int full, cret = c;
if( fp->flag & IORW ){
fp->flag &= ~(IOEOF|IOREAD);
fp->flag |= IOWRT; /* операция записи */
}
if((fp->flag & IOWRT) == 0) return EOF;
tryAgain:
if(fp->flag & IONBF){ /* не буферизован */
if(write(fileno(fp), &c, 1) != 1)
{ fp->flag |= IOERR; cret=EOF; }
fp->cnt = 0;
} else { /* канал буферизован */
if((base = fp->base) == NULL){ /* буфера еще нет */
if(fp == stdout){
if(isatty(fileno(stdout))) fp->flag |= IOTTY;
else fp->flag &= ~IOTTY;
fp->base = fp->ptr = sobuf; /* статический буфер */
fp->bufsiz = sizeof(sobuf);
goto tryAgain;
}
if((base = fp->base = malloc(fp->bufsiz = BUFSIZ))== NULL){
fp->bufsiz = 0; fp->flag |= IONBF; goto tryAgain;
} else fp->flag |= IOALLOC;
} else if ((full = fp->ptr - base) > 0)
if(write(fileno(fp), fp->ptr = base, full) != full)
{ fp->flag |= IOERR; cret = EOF; }
fp->cnt = fp->bufsiz - 1;
*base++ = c;
fp->ptr = base;
}
return cret;
}
/* Вернуть символ в буфер */
int ungetc(int c, FILE *fp){
if(c == EOF || fp->flag & IONBF || fp->base == NULL) return EOF;
if((fp->flag & IOREAD)==0 || fp->ptr <= fp->base)
if(fp->ptr == fp->base && fp->cnt == 0) fp->ptr++;
else return EOF;
fp->cnt++;
return(* --fp->ptr = c);
}
/* Изменить размер буфера */
void setbuffer(FILE *fp, uchar *buf, int size){
fflush(fp);
if(fp->base && (fp->flag & IOALLOC)) free(fp->base);
fp->flag &= ~(IOALLOC|IONBF);
if((fp->base = fp->ptr = buf) == NULL){
fp->flag |= IONBF; fp->bufsiz = 0;
} else fp->bufsiz = size;
fp->cnt = 0;
}
А. Богатырев, 1992-95 - 170 - Си в UNIX
/* "Перемотать" файл в начало */
void rewind(FILE *fp){
fflush(fp);
lseek(fileno(fp), 0L, 0);
fp->cnt = 0; fp->ptr = fp->base;
clearerr(fp);
if(fp->flag & IORW) fp->flag &= ~(IOREAD|IOWRT);
}
/* Позиционирование указателя чтения/записи */
#ifdef COMMENT
base ptr случай IOREAD
| |<----cnt---->|
0L |б у |ф е р |
|=======######@@@@@@@@@@@@@@======== файл file
| |<-p->|<-dl-->|
|<----pos---->| | |
|<----offset(new)-->| |
|<----RWptr---------------->|
где pos = RWptr - cnt; // указатель с поправкой
offset = pos + p = RWptr - cnt + p = lseek(file,0L,1) - cnt + p
отсюда: (для SEEK_SET)
p = offset+cnt-lseek(file,0L,1);
или (для SEEK_CUR) dl = RWptr - offset = p - cnt
lseek(file, dl, 1);
Условие, что указатель можно сдвинуть просто в буфере:
if( cnt > 0 && p <= cnt && base <= ptr + p ){
ptr += p; cnt -= p; }
#endif /*COMMENT*/
А. Богатырев, 1992-95 - 171 - Си в UNIX
int fseek(FILE *fp, long offset, int whence){
register resync, c; long p = (-1);
clearerr(fp);
if( fp->flag & (IOWRT|IORW)){
fflush(fp);
if(fp->flag & IORW){
fp->cnt = 0; fp->ptr = fp->base; fp->flag &= ~IOWRT;
}
p = lseek(fileno(fp), offset, whence);
} else if( fp->flag & IOREAD ){
if(whence < 2 && fp->base && !(fp->flag & IONBF)){
c = fp->cnt; p = offset;
if(whence == 0) /* SEEK_SET */
p += c - lseek(fileno(fp), 0L, 1);
else offset -= c;
if(!(fp->flag & IORW) &&
c > 0 && p <= c && p >= fp->base - fp->ptr
){ fp->ptr += (int) p; fp->cnt -= (int) p;
return 0; /* done */
}
resync = offset & 01;
} else resync = 0;
if(fp->flag & IORW){
fp->ptr = fp->base; fp->flag &= ~IOREAD; resync = 0;
}
p = lseek(fileno(fp), offset-resync, whence);
fp->cnt = 0; /* вынудить filbuf(); */
if(resync) getc(fp);
}
return (p== -1 ? -1 : 0);
}
/* Узнать текущую позицию указателя */
long ftell(FILE *fp){
long tres; register adjust;
if(fp->cnt < 0) fp->cnt = 0;
if(fp->flag & IOREAD) adjust = -(fp->cnt);
else if(fp->flag & (IOWRT|IORW)){ adjust = 0;
if(fp->flag & IOWRT &&
fp->base && !(fp->flag & IONBF)) /* буферизован */
adjust = fp->ptr - fp->base;
} else return (-1L);
if((tres = lseek(fileno(fp), 0L, 1)) < 0) return tres;
return (tres + adjust);
}
А. Богатырев, 1992-95 - 172 - Си в UNIX
Структуры ("записи") представляют собой агрегаты разнородных данных (полей раз-
ного типа); в отличие от массивов, где все элементы имеют один и тот же тип.
struct {
int x, y; /* два целых поля */
char s[10]; /* и одно - для строки */
} s1;
Структурный тип может иметь имя:
struct XYS {
int x, y; /* два целых поля */
char str[10]; /* и одно - для строки */
};
Здесь мы объявили тип, но не отвели ни одной переменной этого типа (хотя могли бы).
Теперь опишем переменную этого типа и указатель на нее:
struct XYS s2, *sptr = &s2;
Доступ к полям структуры производится по имени поля (а не по индексу, как у масси-
вов):
имя_структурной_переменной.имя_поля
указатель_на_структуру -> имя_поля
то есть
не а
#define ВЕС 0 struct { int вес, рост; } x;
#define РОСТ 1 x.рост = 175;
int x[2]; x[РОСТ] = 175;
Например
s1.x = 13;
strcpy(s2.str, "Finish");
sptr->y = 27;
Структура может содержать структуры другого типа в качестве полей:
struct XYS_Z {
struct XYS xys;
int z;
} a1;
a1.xys.x = 71; a1.z = 12;
Структура того же самого типа не может содержаться в качестве поля - рекурсивные
определения запрещены. Зато нередко используются поля - ссылки на структуры такого
же типа (или другого). Это позволяет организовывать списки структур:
struct node {
int value;
struct node *next;
};
Очень часто используются массивы структур:
А. Богатырев, 1992-95 - 173 - Си в UNIX
struct XYS array[20]; int i = 5, j;
array[i].x = 12;
j = array[i].x;
Статические структуры можно описывать с инициализацией, перечисляя значения их полей
в {} через запятую:
extern struct node n2;
struct node n1 = { 1, &n2 },
n2 = { 2, &n1 },
n3 = { 3, NULL };
В этом примере n2 описано предварительно для того, чтобы &n2 в строке инициализации
n1 было определено.
Структуры одинакового типа можно присваивать целиком (что соответствует присваи-
ванию каждого из полей):
struct XYS s1, s2; ...
s2 = s1;
в отличие от массивов, которые присваивать целиком нельзя:
int a[5], b[5]; a = b; /* ОШИБОЧНО ! */
Пример обращения к полям структуры:
typedef struct _Point {
short x, y; /* координаты точки */
char *s; /* метка точки */
} Point;
Point p; Point *pptr; short *iptr;
struct _Curve {
Point points[25]; /* вершины ломанной */
int color; /* цвет линии */
} aLine[10], *linePtr = & aLine[0];
...
pptr = &p; /* указатель на структуру p */
p.x = 1; p.y = 2; p.s = "Grue";
linePtr->points[2].x = 54; aLine[5].points[0].y = 17;
В ы р а ж е н и е значение
---------+------------+------------+-----------+-----------
p.x | pptr->x | (*pptr).x | (&p)->x | 1
---------+------------+------------+-----------+-----------
&p->x | ошибка
-----------+----------------+------------------+-----------
iptr= &p.x | iptr= &pptr->x | iptr= &(pptr->x) | адрес поля
-----------+----------------+--------+---------+-----------
*pptr->s | *(pptr->s) | *p.s | p.s[0] | 'G'
-----------+----------------+--------+---------+-----------
pptr->s[1] | (&p)->s[1] | p.s[1] | 'r'
-----------+----------------+------------------+-----------
&p->s[1] | ошибка
-----------+----------------+------------------+-----------
(*pptr).s | pptr->s | p.s | "Grue"
-----------+----------------+------------------+-----------
*pptr.s | ошибка
-----------------------------------------------+-----------
А. Богатырев, 1992-95 - 174 - Си в UNIX
Вообще (&p)->field = p.field
pptr->field = (*pptr).field
Объединения - это агрегаты данных, которые могут хранить в себе значения данных
разных типов на одном и том же месте.
struct a{ int x, y; char *s; } A;
union b{ int i; char *s; struct a aa; } B;
Структура:
________________________
A: | A.x int | Три поля
------------------------ расположены подряд.
| A.y int | Получается как бы
------------------------ "карточка" с графами.
| A.s char * |
------------------------
А у объединений поля расположены "параллельно",
на одном месте в памяти.
_______________________________________________________
B: | B.i int | B.s char * | B.aa : B.aa.x int |
-----------| | struct a : B.aa.y int |
---------------| : B.aa.s char * |
|___________________________|
Это как бы "ящик" в который можно поместить значение любого типа из перечисленных, но
не ВСЕ ВМЕСТЕ ("и то и это", как у структур), а ПО ОЧЕРЕДИ ("или/или"). Размер его
достаточно велик, чтоб вместить самый большой из перечисленных типов данных.
Мы можем занести в union значение и интерпретировать его как другой тип данных -
это иногда используется в машинно-зависимых программах. Вот пример, выясняющий поря-
док байтов в short числах:
union lb {
char s[2]; short i;
} x;
unsigned hi, lo;
x.i = (02 << 8) | 01;
hi = x.s[1]; lo = x.s[0];
printf( "%d %d\n", hi, lo);
или так:
#include <stdio.h>
union {
int i;
unsigned char s[sizeof(int)];
} u;
void main(){
unsigned char *p;
int n;
u.i = 0x12345678;
for(n=0, p=u.s; n < sizeof(int); n++, p++){
printf("%02X ", *p);
}
putchar('\n');
}
А. Богатырев, 1992-95 - 175 - Си в UNIX
или порядок слов в long числах:
union xx {
long l;
struct ab {
short a; /* low word */
short b; /* high word */
} ab;
} c;
main(){ /* На IBM PC 80386 печатает 00020001 */
c.ab.a = 1; c.ab.b = 2; printf("%08lx\n", c.l );
}
5.1. Найдите ошибки в описании структурного шаблона:
structure { int arr[12],
char string,
int *sum
}
5.2. Разработайте структурный шаблон, который содержал бы название месяца, трехбук-
венную аббревиатуру месяца, количество дней в месяце и номер месяца. Инициализируйте
его для невисокосного года.
struct month {
char name[10]; /* или char *name; */
char abbrev[4]; /* или char *abbrev; */
int days;
int num;
};
struct month months[12] = { /* индекс */
{"Январь" , "Янв", 31, 1 }, /* 0 */
{"Февраль", "Фев", 28, 2 }, /* 1 */
...
{"Декабрь", "Дек", 31, 12}, /* 11 */
}, *mptr = & months[0]; /* или *mptr = months */
main(){
struct month *mptr;
printf( "%s\n", mptr[1].name );
printf( "%s %d\n", mptr->name, mptr->num );
}
Напишите функцию, сохраняющую массив months в файл; функцию, считывающую его из
файла. Используйте fprintf и fscanf.
В чем будет разница в функции чтения, когда поле name описано как char name[10]
и как char *name?
Ответ: во втором случае для сохранения прочитанной строки надо заказывать память
динамически при помощи malloc() и сохранять в ней строку при помощи strcpy(), т.к.
память для хранения самой строки в структуре не зарезервирована (а только для указа-
теля на нее).
Найдите ошибку в операторах функции main(). Почему печатается не "Февраль", а
какой-то мусор? Указание: куда указывает указатель mptr, описанный в main()? Ответ: в
"неизвестно куда" - это локальная переменная (причем не получившая начального значе-
ния - в ней содержится мусор), а не то же самое, что указатель mptr, описанный выше!
Уберите описание mptr из main.
А. Богатырев, 1992-95 - 176 - Си в UNIX
Заметим, что для распечатки всех или нескольких полей структуры следует ЯВНО
перечислить в printf() все нужные поля и указать форматы, соответствующие типам этих
полей. Не существует формата или стандартной функции, позволяющей распечатать все
поля сразу (однако такая функция может быть написана вами для конкретного типа струк-
тур). Также не существует формата для scanf(), который вводил бы структуру целиком.
Вводить можно только по частям - каждое поле отдельно.
5.3. Напишите программу, которая по номеру месяца возвращает общее число дней года
вплоть до этого месяца.
5.4. Переделайте предыдущую программу таким образом, чтобы она по написанному бук-
вами названию месяца возвращала общее число дней года вплоть до этого месяца. В прог-
рамме используйте функцию strcmp().
5.5. Переделайте предыдущую программу таким образом, чтобы она запрашивала у пользо-
вателя день, месяц, год и выдавала общее количество дней в году вплоть до данного
дня. Месяц может обозначаться номером, названием месяца или его аббревиатурой.
5.6. Составьте структуру для учетной картотеки служащего, которая содержала бы сле-
дующие сведения: фамилию, имя, отчество; год рождения; домашний адрес; место работы,
должность; зарплату; дату поступления на работу.
5.7. Что печатает программа?
struct man {
char name[20];
int salary;
} workers[] = {
{ "Иванов", 200 },
{ "Петров", 180 },
{ "Сидоров", 150 }
}, *wptr, chief = { "начальник", 550 };
main(){
struct man *ptr, *cptr, save;
ptr = wptr = workers + 1;
cptr = &chief;
save = workers[2]; workers[2] = *wptr; *wptr = save;
wptr++; ptr--; ptr->salary = save.salary;
printf( "%c %s %s %s %s\n%d %d %d %d\n%d %d %c\n",
*workers[1].name, workers[2].name, cptr->name,
ptr[1].name, save.name,
wptr->salary, chief.salary,
(*ptr).salary, workers->salary,
wptr - ptr, wptr - workers, *ptr->name );
}
Ответ:
С Петров начальник Сидоров Сидоров
180 550 150 150
2 2 И
5.8. Разберите следующий пример:
#include <stdio.h>
struct man{
А. Богатырев, 1992-95 - 177 - Си в UNIX
char *name, town[4]; int salary;
int addr[2];
} men[] = {
{ "Вася", "Msc", 100, { 12, 7 } },
{ "Гриша", "Len", 120, { 6, 51 } },
{ "Петя", "Rig", 140, { 23, 84 } },
{ NULL, "" , -1, { -1, -1 } }
};
main(){
struct man *ptr, **ptrptr;
int i;
ptrptr = &ptr;
*ptrptr = &men[1]; /* men+1 */
printf( "%s %d %s %d %c\n",
ptr->name,
ptr->salary,
ptr->town,
ptr->addr[1],
ptr[1].town[2] );
(*ptrptr)++;
/* копируем *ptr в men[0] */
men[0].name = ptr->name; /* (char *) #1 */
strcpy( men[0].town, ptr->town ); /* char [] #2 */
men[0].salary = ptr->salary; /* int #3 */
for( i=0; i < 2; i++ )
men[0].addr[i] = ptr->addr[i]; /* массив #4 */
/* распечатываем массив структур */
for(ptr=men; ptr->name; ptr++ )
printf( "%s %s %d\n",
ptr->name, ptr->town, ptr->addr[0]);
}
Обратите внимание на такие моменты:
1) Как производится работа с указателем на указатель (ptrptr).
2) При копировании структур отдельными полями, поля скалярных типов (int, char,
long, ..., указатели) копируются операцией присваивания (см. строки с пометками
#1 и #3). Поля векторных типов (массивы) копируются при помощи цикла, поэле-
ментно пересылающего массив (строка #4). Строки (массивы букв) пересылаются
стандартной функцией strcpy (строка #2). Все это относится не только к полям
структур, но и к переменным таких типов. Структуры можно также копировать не по
полям, а целиком: men[0]= *ptr;
3) Запись аргументов функции printf() лесенкой позволяет лучше видеть, какому фор-
мату соответствует каждый аргумент.
4) При распечатке массива структур мы печатаем не определенное их количество (рав-
ное размеру массива), а пользуемся указателем NULL в поле name последней струк-
туры как признаком конца массива.
5) В поле town мы храним строки из 3х букв, однако выделяем для хранения массив из
4х байт. Это необходимо потому, что строка "Msc" состоит не из 3х, а из 4х бай-
тов: 'M','s','c','\0'.
При работе со структурами и указателями большую помощь могут оказать рисунки. Вот как
(например) можно нарисовать данные из этого примера (массив men изображен не весь):
А. Богатырев, 1992-95 - 178 - Си в UNIX
--ptr-- --ptrptr--
ptr | * |<------|---* |
---|--- ----------
|
/ =========men[0]==
/ men:|name | *---|-----> "Вася"
| |---------------|
| |town |M|s|c|\0|
| |---------------|
| |salary| 100 |
| |---------------|
| |addr | 12 | 7 |
\ -----------------
\ =========men[1]==
\-->|name | *---|-----> "Гриша"
............
5.9. Составьте программу "справочник по таблице Менделеева", которая по названию
химического элемента выдавала бы его характеристики. Таблицу инициализируйте массивом
структур.
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;
А. Богатырев, 1992-95 - 179 - Си в UNIX
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;
}
А. Богатырев, 1992-95 - 180 - Си в UNIX
/* вставка после 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
А. Богатырев, 1992-95 - 181 - Си в UNIX
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;
Зато доступ к ячейкам массива выглядит теперь менее изящно:
А. Богатырев, 1992-95 - 182 - Си в UNIX
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 часто используется в качестве битового флага: вместо