Традиционным примером
Давайте снова обратимся к
struct date {
int day;
int month;
int year;
int yearday;
char mon_name[4];
};
struct.
За словом struct может следовать
необязательное имя, называемое ярлыком date ).
Такой ярлык именует
Элементы или
Точно так же, как в случае любого другого базисного
struct { ... } x,y,z;
синтаксически аналогичен
int x,y,z;
в том смысле, что каждый из x, y и z в качестве
date, то
struct date d;
определяет d в качестве date.
Внешнюю или статическую
struct date d={ 4, 7, 1776, 186, "jul"};
Член определенной
имя структуры . Член --------------------
Операция указания члена (признак високосности года) на основе даты,
находящейся в d,
leap = d.year % 4 == 0 d.year % 100 != 0 || d.year % 400 == 0;
или проверим имя месяца
if (strcmp(d.mon_name, "aug") == 0) ...
Или преобразуем первый символ имени месяца так, чтобы оно начиналось со строчной буквы
d.mon_name[0] = lower(d.mon_name[0]);
struct person {
char name[namesize];
char address[adrsize];
long zipcode; /* почтовый индекс */
long ss_number; /* код соц. Обеспечения */
double salary; /* зарплата */
struct date birthdate; /* дата рождения */
struct date hiredate; /* дата поступления
на работу */
};
person содержит две date.
Если мы определим emp как
struct person emp;
то
emp.birthdate.month
будет ссылаться на месяц рождения. Операция указания члена
В языке "C" существует ряд ограничений на использование и доступе к одному из ее членов.
Это влечет за собой то, что
Давайте разберем некоторые из этих вопросов, переписав с этой целью day_of_year, как мы ее написали в лекции №5:
d.yearday = day_of_year(d.year, d.month, d.day);
другой способ состоит в передаче hiredate как
struct date hiredate;
и перепишем day_of_year нужным образом, мы сможем тогда написать
hiredate.yearday = day_of_year(hiredate);
передавая hiredate day_of_year.
day_of_year(pd) /* set day of year from month, day */
struct date *pd;
{
int i, day, leap;
day = pd->day;
leap = pd->year % 4 == 0 pd->year % 100 != 0
|| pd->year % 400 == 0;
for (i =1; i < pd->month; i++)
day += day_tab[leap][i];
return(day);
}
struct date *pd;
говорит, что pd является date. Запись, показанная на
примере
pd->year
является новой. Если p -
p-> член структуры ------------------
обращается к конкретному члену. (Операция -> - это знак минус,
за которым следует знак " > ".)
Так как pd указывает на year можно обратиться и следующим образом
(*pd).year
но ->
оказывается удобным сокращением. (*pd).year необходимы, потому что операция
указания члена *. Обе операции,
" .", ассоциируются слева направо, так что
p->q->memb (p->q)->memb emp.birthdate.month (emp.birthdate).month
Для полноты ниже приводится другая month_day, переписанная с
использованием
month_day(pd) /* set month and day from day of year */
struct date *pd;
{
int i, leap;
leap = pd->year % 4 == 0 pd->year % 100 != 0
|| pd->year % 400 == 0;
pd->day = pd->yearday;
for (i = 1; pd->day > day_tab[leap][i]; i++)
pd->day -= day_tab[leap][i];
pd->month = i;
}
Операции работы со () для [] для индексов находятся на самом верху иерархии старшинства
операций и, следовательно, связываются очень крепко. Если, например, имеется
struct {
int x;
int *y;
} *p;
то выражение
++p->x
увеличивает x, а не p, так как оно
++(p->х). Для
изменения порядка выполнения операций можно использовать (++p)->x увеличивает p до доступа к x,
а (p++)->x увеличивает p после.
(
Совершенно аналогично *p->y извлекает то, на что
указывает y ; *p->y++ увеличивает y
после обработки того, на что он указывает (точно так же, как и *s++) ; (*p->y)++ увеличивает то, на что
указывает y ; *p++->y увеличивает p
после выборки того, на что указывает y.
keyword и keycount:
char *keyword [nkeys]; int keycount [nkeys];
Но сам факт, что
char *keyword; int keycount;
и, следовательно, имеется
struct key {
char *keyword;
int keycount;
} keytab [nkeys];
определяет keytab
struct key {
char *keyword;
int keycount;
};
struct key keytab [nkeys];
Так как keytab фактически содержит
постоянный набор имен, то легче всего инициализировать ее один раз и
для всех членов при
struct key {
char *keyword;
int keycount;
} keytab[] = {
"break", 0,
"case", 0,
"char", 0,
"continue", 0,
"default", 0,
/* ... */
"unsigned", 0,
"while", 0
};
{ "break", 0 },
{ "case", 0 },
. . .
Но когда keytab,
если [] оставлены пустыми.
keytab. Ведущая getword, которая извлекает
из keytab с помощью варианта
#define maxword 20
#define nkeys (sizeof(keytab) / sizeof(struct key))
main() /* count "c" keywords */
{
int n, t;
char word[maxword];
while ((t = getword(word,maxword)) != EOF)
if (t == letter)
if((n = binary(word,keytab,nkeys)) >= 0)
keytab[n].keycount++;
for (n =0; n < nkeys; n++)
if (keytab[n].keycount > 0)
printf("%4d %s\n",
keytab[n].keycount, keytab[n].keyword);
}
binary(word, tab, n) /* find word in tab[0]...tab[n-1] */
char *word;
struct key tab[];
int n;
{
int low, high, mid, cond;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low+high) / 2;
if((cond = strcmp(word, tab[mid].keyword)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return (mid);
}
return(-1);
}
Мы вскоре приведем getword ; пока достаточно сказать, что она
возвращает letter каждый раз, как она находит слово, и
копирует это слово в свой первый
Величина nkeys - это количество ключевых слов
в keytab. Хотя мы
можем сосчитать это число вручную, гораздо легче и надежнее поручить это машине,
особенно в том случае, если список ключевых слов подвержен изменениям. Одной из
возможностей было бы закончить список keytab, пока не найдется конец.
Но, поскольку размер этого
size of keytab / size of struct key
дело в том, что в языке "C" предусмотрена унарная операция sizeof, выполняемая во время компиляции,
которая позволяет вычислить размер любого объекта.
Выражение
sizeof(object)
выдает целое, равное размеру указанного объекта. (Размер определяется в
неспецифицированных единицах, называемых "байтами", которые имеют тот же
размер, что и char ). Объект может
быть int или double, или именем
производного #define для установления значения nkeys:
#define nkeys (sizeof(keytab) / sizeof(struct key))
Теперь перейдем к getword. Мы фактически написали
более общий вариант getword, чем необходимо для
этой getword возвращает следующее "слово" из letter,
если найдено слово, EOF для
getword(w, lim) /* get next word from input */
char *w;
int lim;
{
int c, t;
if (type(c=*w++=getch()) !=letter) {
*w='\0';
return(c);
}
while (--lim > 0) {
t = type(c = *w++ = getch());
if (t ! = letter t ! = digit) {
ungetch(c);
break;
}
}
*(w-1) = '\0';
return(letter);
}
getword использует getch и ungetch, которые мы написали в лекции №4: когда набор
алфавитных символов прерывается, getword получает один
лишний символ. В результате вызова ungetch этот символ помещается назад во
getword обращается к type
для
type(c) /* return type of ascii character */
int c;
{
if (c>= 'a' c<= 'z' || c>= 'A' c<= 'Z')
return(letter);
else if (c>= '0' c<= '9')
return(digit);
else
return(c);
}
letter и digit
могут иметь любые значения, лишь бы они не вступали в конфликт с символами,
отличными от буквенно-цифровых, и с EOF ;
очевидно возможен следующий выбор
#define letter 'a' #define digit '0'
getword могла бы работать быстрее, если бы type были
заменены обращениями к соответствующему type[ ]. В isalpha и isdigit,
действующие необходимым образом.
Упражнение 6-1
Сделайте такую модификацию getword и оцените, как изменится
скорость работы
Упражнение 6-2
Напишите вариант type, не зависящий от конкретного
набора символов.
Упражнение 6-3
Напишите вариант
Чтобы проиллюстрировать некоторые соображения, связанные с использованием
Внешнее keytab не нужно изменять,
но main и binary требуют модификации.
main() /* count c keyword; pointer version */
{
int t;
char word[maxword];
struct key *binary(), *p;
while ((t = getword(word, maxword;) !=EOF)
if (t==letter)
if ((p=binary(word,keytab,nkeys)) !=null)
p->keycount++;
for (p=keytab; p>keytab + nkeys; p++)
if (p->keycount > 0)
printf("%4d %s/n", p->keycount, p->keyword);
}
struct key *binary(word, tab, n) /* find word */
char *word /* in tab[0]...tab[n-1] */
struct key tab [];
int n;
{
int cond;
struct key *low = tab[0];
struct key *high = tab[n-1];
struct key *mid;
while (low <= high) {
mid = low + (high-low) / 2;
if ((cond = strcmp(word, mid->keyword)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return(mid);
}
return(null);
}
Здесь имеется несколько моментов, которые стоит отметить. Во-первых, binary должно указывать, что она возвращает key, а не на целое; это объявляется как
в main, так и в binary. Если binary находит слово, то она возвращает null.
Во-вторых, все обращения к элементам keytab
осуществляются через binary:
средний элемент больше нельзя вычислять просто по формуле
mid = (low + high) / 2
потому что сложение двух
mid = low + (high-low) / 2
в результате которой mid становится low и high.
Вам также следует разобраться в инициализации low
и high.
В main мы написали
for (p=keytab; p < keytab + nkeys; p++)
Если p является p учитывает фактический размер
данной p++ увеличивает p на нужную
величину, в результате чего p указывает на
следующий элемент
И, наконец, несколько второстепенный вопрос о форме записи
struct key *binary(word, tab, n)
Tо может оказаться, что имя
struct key * binary(word, tab, n)
Это главным образом дело вкуса; выберите ту форму, которая вам нравится, и придерживайтесь ее.
Предположим, что нам надо справиться с более общей задачей, состоящей в
подсчете числа появлений всех слов в некотором
Одно из решений состоит в том, чтобы все время хранить
Каждому новому слову соответствует один "узел" дерева; каждый узел содержит:
указатель текста слова ---------------------- счетчик числа появлений ----------------------- указатель узла левого потомка ----------------------------- указатель узла правого потомка ------------------------------
Никакой узел не может иметь более двух детей; возможно отсутствие детей или наличие только одного потомка.
Узлы создаются таким образом, что
Возвращаясь назад к
struct tnode { /* the basic node */
char *word; /* points to the text */
int count; /* number of occurrences */
struct tnode *left; /* left child */
struct tnode *right; /* right child */
};
Это "рекурсивное"
struct tnode *left;
описывает left как
Текст самой getword для
извлечения каждого слова из alloc для выделения места для хранения слов.
Ведущая getword и
помещает их в дерево, используя tree.
#define maxword 20
main() /* word freguency count */
{
struct tnode *root, *tree();
char word[maxword];
int t;
root = null;
while ((t = getword(word, maxword)) != EOF)
if (t == letter)
root = tree(root, word);
treeprint(root);
}
tree сама по себе проста. Слово передается main к верхнему уровню (корню) дерева.
На каждом этапе это слово сравнивается со словом, уже хранящимся в
этом узле, и с помощью рекурсивного обращения к tree
просачивается вниз либо к левому, либо к правому поддереву. В конце концов это
слово либо совпадает с каким-то словом, уже находящимся в дереве (в этом случае
счетчик увеличивается на единицу), либо tree
возвращает
struct tnode *tree(p, w)
/* install w at or below p */
struct tnode *p;
char *w;
{
struct tnode *talloc();
char *strsave();
int cond;
if (p == null) { /* a new word
has arrived */
p == talloc(); /* make a new node */
p->word = strsave(w);
p->count = 1;
p->left = p->right = null;
} else if ((cond = strcmp(w, p->word)) == 0)
p->count++; /* repeated word */
else if (cond < 0)/* lower goes into left subtree */
p->left = tree(p->left, w);
else /* greater into right subtree */
p->right = tree(p->right, w);
return(p);
}
Память для нового узла выделяется talloc,
являющейся адаптацией для данного случая alloc,
написанной нами ранее. Она возвращает strsave в скрытое место, счетчик инициализируется единицей,
и strsave и talloc значений
(что неразумно для практически работающей
treeprint печатает дерево,
начиная с treeprint; это одна из наиболее ясных рекурсивных
treeprint (p) /* print tree p recursively */
struct tnode *p;
{
if (p != null) {
treeprint (p->left);
printf("%4d %s\n", p->count, p->word);
treeprint (p->right);
}
}
Практическое замечание: если дерево становится "несбалансированным"
из-за того, что слова поступают не в случайном порядке, то время работы деревья, которые не ведут себя так "в худших случаях",
но мы не будем здесь на них останавливаться.
Прежде чем расстаться с этим примером, уместно сделать небольшое
отступление в связи с вопросом о распределении памяти. Ясно, что в char и для struct tnode, то при этом возникают два вопроса.
Первый: как выполнить то существующее на большинстве
реальных машин ограничение, что объекты alloc должна возвращать различные виды
Вообще говоря, требования выравнивания легко выполнить за счет выделения
некоторого лишнего пространства, просто обеспечив то, чтобы распределитель
памяти всегда возвращал alloc всегда
возвращала четный alloc может не оказаться переносимой, но ее
использование будет переносимым. alloc из лекции №5
не предусматривает никакого
Вопрос alloc
является мучительным для любого языка,
который серьезно относится к проверке alloc возвращает char, а затем явно
преобразовать этот void *, то есть указатель на void). Таким образом, если описать p в виде
char *p;
то
(struct tnode *) p
преобразует его в выражениях в tnode.
Следовательно, talloc можно записать в виде:
struct tnode *talloc()
{
char *alloc();
return ((struct tnode *) alloc(sizeof(struct tnode)));
}
это более чем достаточно для работающих в настоящее время
Упражнение 6-4
Напишите
Упражнение 6-5
Напишите
Упражнение 6-6
Напишите
Для иллюстрации дальнейших аспектов использования #define языка "C". Когда встречается строка вида
#define yes 1
то имя yes и заменяющий текст 1 помещаются в таблицу.
Позднее, когда имя yes появляется в
inword = yes;
Oно должно быть замещено на 1.
Имеются две основные install(s,t) записывает имя s
и заменяющий текст t в таблицу; здесь s и t
просто lookup(s) ищет имя s в
таблице и возвращает либо null, если этого имени в таблице не оказалось.
При этом используется поиск по алгоритму хеширования - поступающее имя
преобразуется в маленькое положительное число, которое затем используется для null.
Блоком цепи является
struct nlist { /* basic table entry */
char *name;
char *def;
struct nlist *next; /* next entry in chain */
};
#define hashsize 100 static struct nlist *hashtab[hashsize] /* pointer table */
Значение lookup и install, получается просто как остаток
от деления суммы символьных значений
строки на размер
hash(s) /* form hash value for string */
char *s;
{
int hashval;
for (hashval = 0; *s != '\0'; )
hashval += *s++;
return(hashval % hashsize);
}
В результате процесса хеширования выдается начальный hashtab ; если данная строка может быть где-то найдена,
то именно в цепи блоков, начало которой указано там. Поиск
осуществляется lookup. Если lookup находит, что данный элемент уже присутствует,
то она возвращает null.
struct nlist *lookup(s) /* look for s in hashtab */
char *s;
{
struct nlist *np;
for (np = hashtab[hash(s)]; np != null;np=np->next)
if (strcmp(s, np->name) == 0)
return(np); /* found it */
}
return(null); /* not found */
install использует lookup
для install
возвращает null.
struct nlist *install(name, def) /* put (name, def) */
char *name, *def;
{
struct nlist *np, *lookup();
char *strsave(), *alloc();
int hashval;
if((np = lookup(name)) == null) \( /* not found */
np = (struct nlist *) alloc(sizeof(*np));
if (np == null)
return(null);
if ((np->name = strsave(name)) == null)
return(null);
hashval = hash(np->name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((np->def);/* free previous definition */
if ((np->def = strsave(def)) == null)
return (null);
return(np);
}
strsave просто копирует строку, указанную
в качестве alloc.
Мы уже привели эту alloc и free могут
происходить в любом порядке и в связи с проблемой выравнивания, простой
вариант alloc из лекции №5 нам больше не подходит;
смотрите лекции №7 и лекции №8.
Упражнение 6-7
Напишите lookup и install.
Упражнение 6-8
Разработайте простую, основанную на #define, пригодную для использования с
"C"- getchar
и ungetch.
Когда вопрос экономии памяти становится очень существенным, то может
оказаться необходимым помещать в одно машинное слово несколько различных
объектов; одно из особенно распространенных употреблений - набор однобитовых
признаков в применениях, подобных символьным таблицам
Представьте себе фрагмент char или int.
Обычный способ, которым это делается, состоит в
#define keyword 01 #define external 02 #define static 04
(числа должны быть степенями двойки). Тогда обработка битов сведется к
"жонглированию битами" с помощью
Некоторые часто встречающиеся идиомы:
flags |= external | static;
включает биты external и static в flags, в то время как
flags = ~(еxternal | static);
их выключает, а
if ((flags (external | static)) == 0) ...
истинно, если оба бита выключены.
Хотя этими идиомами легко овладеть, язык "C" в качестве альтернативы
предлагает возможность int.
Синтаксис #define,
приведенную выше, можно бы было заменить
struct {
unsigned is_keyword : 1;
unsigned is_extern : 1;
unsigned is_static : 1;
} flags;
Здесь определяется flags,
которая содержит три 1-unsigned,
чтобы подчеркнуть, что они действительно будут величинами без знака.
На отдельные поля можно ссылаться, как flags.is_static,
flags.is_extern, flags.is_keyword И т.д.,
то есть точно так же, как на другие члены
flags.is_extern = flags.is_static = 1;
для включения битов;
flags.is_extern = flags.is_static = 0;
для выключения битов;
if (flags.is_extern == 0 flags.is_static == 0)...
для их проверки.
Поле не может перекрывать границу int ;
если указанная ширина такова, что это должно случиться, то поле
выравнивается по границе следующего int. Полям
можно не присваивать имена; неименованные поля (только двоеточие и ширина)
используются для заполнения свободного места. Чтобы вынудить выравнивание на
границу следующего int, можно использовать специальную ширину 0.
При работе с полями имеется ряд моментов, на которые следует обратить
внимание. По-видимому наиболее существенным является то, что отражая природу
различных аппаратных средств, распределение полей на некоторых машинах
осуществляется слева направо, а на некоторых справа налево. Это означает, что
хотя поля очень полезны для работы с внутренне
Другие ограничения, которые следует иметь в виду: поля не имеют знака; они
могут храниться только в int
(или, что эквивалентно, unsigned ); они не
являются .
Oбъединения - это
В качестве примера, снова из символьной таблицы int, float или быть
union u_tag {
int ival;
float fval;
char *pval;
} uval;
uval будет иметь достаточно большой размер,
чтобы хранить наибольший из трех uval и затем использован в выражениях, пока такое
использование совместимо: извлекаемый
Синтаксически доступ к членам объединения осуществляется следующим образом:
имя объединения.член --------------------
или
указатель объединения ->член ----------------------------
то есть точно так же, как и в случае 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 {
int ival;
float fval;
char *pval;
} uval;
} symtab[nsym];
на ival можно сослаться как
symtab[i].uval.ival
а на первый символ строки pval как
*symtab[i].uval.pval
В сущности объединение является
В языке "C" предусмотрена возможность,
называемая typedef для введения новых имен
для
typedef int length;
делает имя length синонимом для int.
"Тип" length может быть использован в int:
length len, maxlen; length *lengths[];
Аналогично
typedef char *string;
делает string синонимом для char*,
то есть для
string p, lineptr[lines], alloc();
Обратите внимание, что объявляемый
в typedef typedef.
Синтаксически typedef подобна extern, static и т. д. Мы
также использовали прописные буквы, чтобы яснее выделить имена.
В качестве более сложного примера мы используем typedef для
typedef struct tnode { /* the basic node */
char *word; /* points to the text */
int count; /* number of occurrences */
struct tnode *left; /* left child */
struct tnode *right; /* right child */
} treenode, *treeptr;
В результате получаем два новых ключевых слова: treenode (
и treeptr ( .
Тогда talloc можно записать в виде
treeptr talloc()
{
char *alloc();
return((treeptr) alloc(sizeof(treenode)));
}
Необходимо подчеркнуть, что typedef
не приводит к созданию нового
в каком-либо смысле typedef
сходна с #define за
исключением того, что она интерпретируется
typedef int (*pfi) ();
создает pfi для " int ",
который затем можно было бы использовать в
pfi strcmp, numcmp, swap;
Имеются две основные причины применения typedef.
Первая причина связана с параметризацией typedef, то при переносе typedef имен для
различных целых величин и в последующем подходящем
выборе short, int и long
для каждой имеющейся машины. Второе назначение typedef
состоит в обеспечении лучшей документации для treeptr может оказаться более удобным для
восприятия, чем lint, сможет использовать
содержащуюся в typedef информацию для проведения
некоторой дополнительной проверки
Традиционным примером
Давайте снова обратимся к
struct date {
int day;
int month;
int year;
int yearday;
char mon_name[4];
};
struct.
За словом struct может следовать
необязательное имя, называемое ярлыком date ).
Такой ярлык именует
Элементы или
Точно так же, как в случае любого другого базисного
struct { ... } x,y,z;
синтаксически аналогичен
int x,y,z;
в том смысле, что каждый из x, y и z в качестве
date, то
struct date d;
определяет d в качестве date.
Внешнюю или статическую
struct date d={ 4, 7, 1776, 186, "jul"};
Член определенной
имя структуры . Член --------------------
Операция указания члена (признак високосности года) на основе даты,
находящейся в d,
leap = d.year % 4 == 0 d.year % 100 != 0 || d.year % 400 == 0;
или проверим имя месяца
if (strcmp(d.mon_name, "aug") == 0) ...
Или преобразуем первый символ имени месяца так, чтобы оно начиналось со строчной буквы
d.mon_name[0] = lower(d.mon_name[0]);
struct person {
char name[namesize];
char address[adrsize];
long zipcode; /* почтовый индекс */
long ss_number; /* код соц. Обеспечения */
double salary; /* зарплата */
struct date birthdate; /* дата рождения */
struct date hiredate; /* дата поступления
на работу */
};
person содержит две date.
Если мы определим emp как
struct person emp;
то
emp.birthdate.month
будет ссылаться на месяц рождения. Операция указания члена
В языке "C" существует ряд ограничений на использование и доступе к одному из ее членов.
Это влечет за собой то, что
Давайте разберем некоторые из этих вопросов, переписав с этой целью day_of_year, как мы ее написали в лекции №5:
d.yearday = day_of_year(d.year, d.month, d.day);
другой способ состоит в передаче hiredate как
struct date hiredate;
и перепишем day_of_year нужным образом, мы сможем тогда написать
hiredate.yearday = day_of_year(hiredate);
передавая hiredate day_of_year.
day_of_year(pd) /* set day of year from month, day */
struct date *pd;
{
int i, day, leap;
day = pd->day;
leap = pd->year % 4 == 0 pd->year % 100 != 0
|| pd->year % 400 == 0;
for (i =1; i < pd->month; i++)
day += day_tab[leap][i];
return(day);
}
struct date *pd;
говорит, что pd является date. Запись, показанная на
примере
pd->year
является новой. Если p -
p-> член структуры ------------------
обращается к конкретному члену. (Операция -> - это знак минус,
за которым следует знак " > ".)
Так как pd указывает на year можно обратиться и следующим образом
(*pd).year
но ->
оказывается удобным сокращением. (*pd).year необходимы, потому что операция
указания члена *. Обе операции,
" .", ассоциируются слева направо, так что
p->q->memb (p->q)->memb emp.birthdate.month (emp.birthdate).month
Для полноты ниже приводится другая month_day, переписанная с
использованием
month_day(pd) /* set month and day from day of year */
struct date *pd;
{
int i, leap;
leap = pd->year % 4 == 0 pd->year % 100 != 0
|| pd->year % 400 == 0;
pd->day = pd->yearday;
for (i = 1; pd->day > day_tab[leap][i]; i++)
pd->day -= day_tab[leap][i];
pd->month = i;
}
Операции работы со () для [] для индексов находятся на самом верху иерархии старшинства
операций и, следовательно, связываются очень крепко. Если, например, имеется
struct {
int x;
int *y;
} *p;
то выражение
++p->x
увеличивает x, а не p, так как оно
++(p->х). Для
изменения порядка выполнения операций можно использовать (++p)->x увеличивает p до доступа к x,
а (p++)->x увеличивает p после.
(
Совершенно аналогично *p->y извлекает то, на что
указывает y ; *p->y++ увеличивает y
после обработки того, на что он указывает (точно так же, как и *s++) ; (*p->y)++ увеличивает то, на что
указывает y ; *p++->y увеличивает p
после выборки того, на что указывает y.
keyword и keycount:
char *keyword [nkeys]; int keycount [nkeys];
Но сам факт, что
char *keyword; int keycount;
и, следовательно, имеется
struct key {
char *keyword;
int keycount;
} keytab [nkeys];
определяет keytab
struct key {
char *keyword;
int keycount;
};
struct key keytab [nkeys];
Так как keytab фактически содержит
постоянный набор имен, то легче всего инициализировать ее один раз и
для всех членов при
struct key {
char *keyword;
int keycount;
} keytab[] = {
"break", 0,
"case", 0,
"char", 0,
"continue", 0,
"default", 0,
/* ... */
"unsigned", 0,
"while", 0
};
{ "break", 0 },
{ "case", 0 },
. . .
Но когда keytab,
если [] оставлены пустыми.
keytab. Ведущая getword, которая извлекает
из keytab с помощью варианта
#define maxword 20
#define nkeys (sizeof(keytab) / sizeof(struct key))
main() /* count "c" keywords */
{
int n, t;
char word[maxword];
while ((t = getword(word,maxword)) != EOF)
if (t == letter)
if((n = binary(word,keytab,nkeys)) >= 0)
keytab[n].keycount++;
for (n =0; n < nkeys; n++)
if (keytab[n].keycount > 0)
printf("%4d %s\n",
keytab[n].keycount, keytab[n].keyword);
}
binary(word, tab, n) /* find word in tab[0]...tab[n-1] */
char *word;
struct key tab[];
int n;
{
int low, high, mid, cond;
low = 0;
high = n - 1;
while (low <= high) {
mid = (low+high) / 2;
if((cond = strcmp(word, tab[mid].keyword)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return (mid);
}
return(-1);
}
Мы вскоре приведем getword ; пока достаточно сказать, что она
возвращает letter каждый раз, как она находит слово, и
копирует это слово в свой первый
Величина nkeys - это количество ключевых слов
в keytab. Хотя мы
можем сосчитать это число вручную, гораздо легче и надежнее поручить это машине,
особенно в том случае, если список ключевых слов подвержен изменениям. Одной из
возможностей было бы закончить список keytab, пока не найдется конец.
Но, поскольку размер этого
size of keytab / size of struct key
дело в том, что в языке "C" предусмотрена унарная операция sizeof, выполняемая во время компиляции,
которая позволяет вычислить размер любого объекта.
Выражение
sizeof(object)
выдает целое, равное размеру указанного объекта. (Размер определяется в
неспецифицированных единицах, называемых "байтами", которые имеют тот же
размер, что и char ). Объект может
быть int или double, или именем
производного #define для установления значения nkeys:
#define nkeys (sizeof(keytab) / sizeof(struct key))
Теперь перейдем к getword. Мы фактически написали
более общий вариант getword, чем необходимо для
этой getword возвращает следующее "слово" из letter,
если найдено слово, EOF для
getword(w, lim) /* get next word from input */
char *w;
int lim;
{
int c, t;
if (type(c=*w++=getch()) !=letter) {
*w='\0';
return(c);
}
while (--lim > 0) {
t = type(c = *w++ = getch());
if (t ! = letter t ! = digit) {
ungetch(c);
break;
}
}
*(w-1) = '\0';
return(letter);
}
getword использует getch и ungetch, которые мы написали в лекции №4: когда набор
алфавитных символов прерывается, getword получает один
лишний символ. В результате вызова ungetch этот символ помещается назад во
getword обращается к type
для
type(c) /* return type of ascii character */
int c;
{
if (c>= 'a' c<= 'z' || c>= 'A' c<= 'Z')
return(letter);
else if (c>= '0' c<= '9')
return(digit);
else
return(c);
}
letter и digit
могут иметь любые значения, лишь бы они не вступали в конфликт с символами,
отличными от буквенно-цифровых, и с EOF ;
очевидно возможен следующий выбор
#define letter 'a' #define digit '0'
getword могла бы работать быстрее, если бы type были
заменены обращениями к соответствующему type[ ]. В isalpha и isdigit,
действующие необходимым образом.
Упражнение 6-1
Сделайте такую модификацию getword и оцените, как изменится
скорость работы
Упражнение 6-2
Напишите вариант type, не зависящий от конкретного
набора символов.
Упражнение 6-3
Напишите вариант
Чтобы проиллюстрировать некоторые соображения, связанные с использованием
Внешнее keytab не нужно изменять,
но main и binary требуют модификации.
main() /* count c keyword; pointer version */
{
int t;
char word[maxword];
struct key *binary(), *p;
while ((t = getword(word, maxword;) !=EOF)
if (t==letter)
if ((p=binary(word,keytab,nkeys)) !=null)
p->keycount++;
for (p=keytab; p>keytab + nkeys; p++)
if (p->keycount > 0)
printf("%4d %s/n", p->keycount, p->keyword);
}
struct key *binary(word, tab, n) /* find word */
char *word /* in tab[0]...tab[n-1] */
struct key tab [];
int n;
{
int cond;
struct key *low = tab[0];
struct key *high = tab[n-1];
struct key *mid;
while (low <= high) {
mid = low + (high-low) / 2;
if ((cond = strcmp(word, mid->keyword)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return(mid);
}
return(null);
}
Здесь имеется несколько моментов, которые стоит отметить. Во-первых, binary должно указывать, что она возвращает key, а не на целое; это объявляется как
в main, так и в binary. Если binary находит слово, то она возвращает null.
Во-вторых, все обращения к элементам keytab
осуществляются через binary:
средний элемент больше нельзя вычислять просто по формуле
mid = (low + high) / 2
потому что сложение двух
mid = low + (high-low) / 2
в результате которой mid становится low и high.
Вам также следует разобраться в инициализации low
и high.
В main мы написали
for (p=keytab; p < keytab + nkeys; p++)
Если p является p учитывает фактический размер
данной p++ увеличивает p на нужную
величину, в результате чего p указывает на
следующий элемент
И, наконец, несколько второстепенный вопрос о форме записи
struct key *binary(word, tab, n)
Tо может оказаться, что имя
struct key * binary(word, tab, n)
Это главным образом дело вкуса; выберите ту форму, которая вам нравится, и придерживайтесь ее.
Предположим, что нам надо справиться с более общей задачей, состоящей в
подсчете числа появлений всех слов в некотором
Одно из решений состоит в том, чтобы все время хранить
Каждому новому слову соответствует один "узел" дерева; каждый узел содержит:
указатель текста слова ---------------------- счетчик числа появлений ----------------------- указатель узла левого потомка ----------------------------- указатель узла правого потомка ------------------------------
Никакой узел не может иметь более двух детей; возможно отсутствие детей или наличие только одного потомка.
Узлы создаются таким образом, что
Возвращаясь назад к
struct tnode { /* the basic node */
char *word; /* points to the text */
int count; /* number of occurrences */
struct tnode *left; /* left child */
struct tnode *right; /* right child */
};
Это "рекурсивное"
struct tnode *left;
описывает left как
Текст самой getword для
извлечения каждого слова из alloc для выделения места для хранения слов.
Ведущая getword и
помещает их в дерево, используя tree.
#define maxword 20
main() /* word freguency count */
{
struct tnode *root, *tree();
char word[maxword];
int t;
root = null;
while ((t = getword(word, maxword)) != EOF)
if (t == letter)
root = tree(root, word);
treeprint(root);
}
tree сама по себе проста. Слово передается main к верхнему уровню (корню) дерева.
На каждом этапе это слово сравнивается со словом, уже хранящимся в
этом узле, и с помощью рекурсивного обращения к tree
просачивается вниз либо к левому, либо к правому поддереву. В конце концов это
слово либо совпадает с каким-то словом, уже находящимся в дереве (в этом случае
счетчик увеличивается на единицу), либо tree
возвращает
struct tnode *tree(p, w)
/* install w at or below p */
struct tnode *p;
char *w;
{
struct tnode *talloc();
char *strsave();
int cond;
if (p == null) { /* a new word
has arrived */
p == talloc(); /* make a new node */
p->word = strsave(w);
p->count = 1;
p->left = p->right = null;
} else if ((cond = strcmp(w, p->word)) == 0)
p->count++; /* repeated word */
else if (cond < 0)/* lower goes into left subtree */
p->left = tree(p->left, w);
else /* greater into right subtree */
p->right = tree(p->right, w);
return(p);
}
Память для нового узла выделяется talloc,
являющейся адаптацией для данного случая alloc,
написанной нами ранее. Она возвращает strsave в скрытое место, счетчик инициализируется единицей,
и strsave и talloc значений
(что неразумно для практически работающей
treeprint печатает дерево,
начиная с treeprint; это одна из наиболее ясных рекурсивных
treeprint (p) /* print tree p recursively */
struct tnode *p;
{
if (p != null) {
treeprint (p->left);
printf("%4d %s\n", p->count, p->word);
treeprint (p->right);
}
}
Практическое замечание: если дерево становится "несбалансированным"
из-за того, что слова поступают не в случайном порядке, то время работы деревья, которые не ведут себя так "в худших случаях",
но мы не будем здесь на них останавливаться.
Прежде чем расстаться с этим примером, уместно сделать небольшое
отступление в связи с вопросом о распределении памяти. Ясно, что в char и для struct tnode, то при этом возникают два вопроса.
Первый: как выполнить то существующее на большинстве
реальных машин ограничение, что объекты alloc должна возвращать различные виды
Вообще говоря, требования выравнивания легко выполнить за счет выделения
некоторого лишнего пространства, просто обеспечив то, чтобы распределитель
памяти всегда возвращал alloc всегда
возвращала четный alloc может не оказаться переносимой, но ее
использование будет переносимым. alloc из лекции №5
не предусматривает никакого
Вопрос alloc
является мучительным для любого языка,
который серьезно относится к проверке alloc возвращает char, а затем явно
преобразовать этот void *, то есть указатель на void). Таким образом, если описать p в виде
char *p;
то
(struct tnode *) p
преобразует его в выражениях в tnode.
Следовательно, talloc можно записать в виде:
struct tnode *talloc()
{
char *alloc();
return ((struct tnode *) alloc(sizeof(struct tnode)));
}
это более чем достаточно для работающих в настоящее время
Упражнение 6-4
Напишите
Упражнение 6-5
Напишите
Упражнение 6-6
Напишите
Для иллюстрации дальнейших аспектов использования #define языка "C". Когда встречается строка вида
#define yes 1
то имя yes и заменяющий текст 1 помещаются в таблицу.
Позднее, когда имя yes появляется в
inword = yes;
Oно должно быть замещено на 1.
Имеются две основные install(s,t) записывает имя s
и заменяющий текст t в таблицу; здесь s и t
просто lookup(s) ищет имя s в
таблице и возвращает либо null, если этого имени в таблице не оказалось.
При этом используется поиск по алгоритму хеширования - поступающее имя
преобразуется в маленькое положительное число, которое затем используется для null.
Блоком цепи является
struct nlist { /* basic table entry */
char *name;
char *def;
struct nlist *next; /* next entry in chain */
};
#define hashsize 100 static struct nlist *hashtab[hashsize] /* pointer table */
Значение lookup и install, получается просто как остаток
от деления суммы символьных значений
строки на размер
hash(s) /* form hash value for string */
char *s;
{
int hashval;
for (hashval = 0; *s != '\0'; )
hashval += *s++;
return(hashval % hashsize);
}
В результате процесса хеширования выдается начальный hashtab ; если данная строка может быть где-то найдена,
то именно в цепи блоков, начало которой указано там. Поиск
осуществляется lookup. Если lookup находит, что данный элемент уже присутствует,
то она возвращает null.
struct nlist *lookup(s) /* look for s in hashtab */
char *s;
{
struct nlist *np;
for (np = hashtab[hash(s)]; np != null;np=np->next)
if (strcmp(s, np->name) == 0)
return(np); /* found it */
}
return(null); /* not found */
install использует lookup
для install
возвращает null.
struct nlist *install(name, def) /* put (name, def) */
char *name, *def;
{
struct nlist *np, *lookup();
char *strsave(), *alloc();
int hashval;
if((np = lookup(name)) == null) \( /* not found */
np = (struct nlist *) alloc(sizeof(*np));
if (np == null)
return(null);
if ((np->name = strsave(name)) == null)
return(null);
hashval = hash(np->name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((np->def);/* free previous definition */
if ((np->def = strsave(def)) == null)
return (null);
return(np);
}
strsave просто копирует строку, указанную
в качестве alloc.
Мы уже привели эту alloc и free могут
происходить в любом порядке и в связи с проблемой выравнивания, простой
вариант alloc из лекции №5 нам больше не подходит;
смотрите лекции №7 и лекции №8.
Упражнение 6-7
Напишите lookup и install.
Упражнение 6-8
Разработайте простую, основанную на #define, пригодную для использования с
"C"- getchar
и ungetch.
Когда вопрос экономии памяти становится очень существенным, то может
оказаться необходимым помещать в одно машинное слово несколько различных
объектов; одно из особенно распространенных употреблений - набор однобитовых
признаков в применениях, подобных символьным таблицам
Представьте себе фрагмент char или int.
Обычный способ, которым это делается, состоит в
#define keyword 01 #define external 02 #define static 04
(числа должны быть степенями двойки). Тогда обработка битов сведется к
"жонглированию битами" с помощью
Некоторые часто встречающиеся идиомы:
flags |= external | static;
включает биты external и static в flags, в то время как
flags = ~(еxternal | static);
их выключает, а
if ((flags (external | static)) == 0) ...
истинно, если оба бита выключены.
Хотя этими идиомами легко овладеть, язык "C" в качестве альтернативы
предлагает возможность int.
Синтаксис #define,
приведенную выше, можно бы было заменить
struct {
unsigned is_keyword : 1;
unsigned is_extern : 1;
unsigned is_static : 1;
} flags;
Здесь определяется flags,
которая содержит три 1-unsigned,
чтобы подчеркнуть, что они действительно будут величинами без знака.
На отдельные поля можно ссылаться, как flags.is_static,
flags.is_extern, flags.is_keyword И т.д.,
то есть точно так же, как на другие члены
flags.is_extern = flags.is_static = 1;
для включения битов;
flags.is_extern = flags.is_static = 0;
для выключения битов;
if (flags.is_extern == 0 flags.is_static == 0)...
для их проверки.
Поле не может перекрывать границу int ;
если указанная ширина такова, что это должно случиться, то поле
выравнивается по границе следующего int. Полям
можно не присваивать имена; неименованные поля (только двоеточие и ширина)
используются для заполнения свободного места. Чтобы вынудить выравнивание на
границу следующего int, можно использовать специальную ширину 0.
При работе с полями имеется ряд моментов, на которые следует обратить
внимание. По-видимому наиболее существенным является то, что отражая природу
различных аппаратных средств, распределение полей на некоторых машинах
осуществляется слева направо, а на некоторых справа налево. Это означает, что
хотя поля очень полезны для работы с внутренне
Другие ограничения, которые следует иметь в виду: поля не имеют знака; они
могут храниться только в int
(или, что эквивалентно, unsigned ); они не
являются .
Oбъединения - это
В качестве примера, снова из символьной таблицы int, float или быть
union u_tag {
int ival;
float fval;
char *pval;
} uval;
uval будет иметь достаточно большой размер,
чтобы хранить наибольший из трех uval и затем использован в выражениях, пока такое
использование совместимо: извлекаемый
Синтаксически доступ к членам объединения осуществляется следующим образом:
имя объединения.член --------------------
или
указатель объединения ->член ----------------------------
то есть точно так же, как и в случае 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 {
int ival;
float fval;
char *pval;
} uval;
} symtab[nsym];
на ival можно сослаться как
symtab[i].uval.ival
а на первый символ строки pval как
*symtab[i].uval.pval
В сущности объединение является
В языке "C" предусмотрена возможность,
называемая typedef для введения новых имен
для
typedef int length;
делает имя length синонимом для int.
"Тип" length может быть использован в int:
length len, maxlen; length *lengths[];
Аналогично
typedef char *string;
делает string синонимом для char*,
то есть для
string p, lineptr[lines], alloc();
Обратите внимание, что объявляемый
в typedef typedef.
Синтаксически typedef подобна extern, static и т. д. Мы
также использовали прописные буквы, чтобы яснее выделить имена.
В качестве более сложного примера мы используем typedef для
typedef struct tnode { /* the basic node */
char *word; /* points to the text */
int count; /* number of occurrences */
struct tnode *left; /* left child */
struct tnode *right; /* right child */
} treenode, *treeptr;
В результате получаем два новых ключевых слова: treenode (
и treeptr ( .
Тогда talloc можно записать в виде
treeptr talloc()
{
char *alloc();
return((treeptr) alloc(sizeof(treenode)));
}
Необходимо подчеркнуть, что typedef
не приводит к созданию нового
в каком-либо смысле typedef
сходна с #define за
исключением того, что она интерпретируется
typedef int (*pfi) ();
создает pfi для " int ",
который затем можно было бы использовать в
pfi strcmp, numcmp, swap;
Имеются две основные причины применения typedef.
Первая причина связана с параметризацией typedef, то при переносе typedef имен для
различных целых величин и в последующем подходящем
выборе short, int и long
для каждой имеющейся машины. Второе назначение typedef
состоит в обеспечении лучшей документации для treeptr может оказаться более удобным для
восприятия, чем lint, сможет использовать
содержащуюся в typedef информацию для проведения
некоторой дополнительной проверки
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.