}
}
printf("Клавиша: %s\n\r", s);
if(c == ESC)
break;
}
tcsetattr(INPUT_CHANNEL, TCSADRAIN, &saved_modes);
}
/* Функция настройки на систему команд дисплея */
void tinit (void) {
/* static */ char Tbuf[2048];
/* Tbuf должен сохраняться все время, пока могут вызываться функции tgetstr().
* Для этого он либо должен быть static, либо вызов функции keyinit()
* должен находиться внутри tinit(), что и сделано.
*/
char *tname;
extern char *getenv();
if((tname = getenv("TERM")) == NULL){
printf("TERM не определено: неизвестный тип терминала.\n");
exit(2);
}
printf("Терминал: %s\n", tname);
/* Прочесть описание терминала в Tbuf */
switch (tgetent(Tbuf, tname)) {
case -1:
printf ("Нет файла TERMCAP (/etc/termcap).\n");
exit (1);
case 0:
printf ("Терминал '%s' не описан.\n", tname);
exit (2);
case 1:
break; /* OK */
}
if(strlen(Tbuf) >= 1024)
printf("Описание терминала слишком длинное - возможны потери в конце описания\n");
keyinit(); /* инициализировать строки, пока Tbuf[] доступен */
}
void main(void){
setlocale(LC_ALL, "");
tinit();
/* keyinit(); */
dotest();
exit(0);
}
По поводу этого алгоритма надо сказать еще пару слов. Его модификация может с успехом
применяться для поиска слов в таблице (команд, ключей в базе данных, итп.): список
слов превращается в дерево. В таком поисковом алгоритме не требуются таймауты, необ-
ходимые при вводе с клавиатуры, поскольку есть явные терминаторы строк - символы
'\0', которых нет при вводе с клавиатуры. В чем эффективность такого алгоритма?
Сравним последовательный перебор при помощи strcmp и поиск в дереве букв:
А. Богатырев, 1992-95 - 386 - Си в UNIX
"zzzzzzzzzza"
"zzzzzzzzzzb"
"zzzzzzzzzzbx"
"zzzzzzzzzzc"
"zzzzzzzzzzcx"
Для линейного перебора (даже в отсортированном массиве) поиск строки zzzzzzzzzzcx
потребует
zzzzzzzzzza | 11 сравнений, отказ
zzzzzzzzzzb | 11 сравнений, отказ
zzzzzzzzzzbx | 12 сравнений, отказ
zzzzzzzzzzc | 11 сравнений, отказ
zzzzzzzzzzcx V 12 сравнений, успех
Всего: 57 шагов. Для поиска в дереве:
__z__z__z__z__z__z__z__z__z__z__a__\0
|_b__\0
| |_x__\0
|
|_c__\0
|_x__\0
потребуется проход вправо (вниз) на 10 шагов, потом выбор среди 'a','b','c', потом -
выбор среди '\0' и 'x'. Всего: 15 шагов. За счет того, что общий "корень" проходится
ровно один раз, а не каждый раз заново. Но это и требует предварительной подготовки
данных: превращения строк в дерево!
8.18. Напишите функцию для "экранного" редактирования вводимой строки в режиме
CBREAK. Напишите аналогичную функцию на curses-е. В curses-ной версии надо уметь
отрабатывать: забой (удаление символа перед курсором), отмену всей строки, смещение
влево/вправо по строке, удаление символа над курсором, вставку пробела над курсором,
замену символа, вставку символа, перерисовку экрана. Учтите, что параллельно с изме-
нением картинки в окне, вы должны вносить изменения в некоторый массив (строку),
которая и будет содержать результат. Эта строка должна быть аргументом функции редак-
тирования.
Забой можно упрощенно эмулировать как
addstr( "\b \b" );
или
addch( '\b' ); delch();
Недостатком этих способов является некорректное поведение в начале строки (при x==0).
Исправьте это!
8.19. На curses-е напишите функцию редактирования текста в окне. Функция должна
возвращать массив строк с обрезанными концевыми пробелами. Вариант: возвращать одну
строку, в которой строки окна разделяются символами '\n'.
8.20. Напишите функцию, рисующую прямую линию из точки (x1,y1) в (x2,y2). Указание:
используйте алгоритм Брезенхема (минимального отклонения). Ответ: пусть функция
putpixel(x,y,color) рисует точку в координатах (x,y) цветом color.
void line(int x1, int y1, int x2, int y2,
int color){
int dx, dy, i1, i2, i, kx, ky;
register int d; /* "отклонение" */
register int x, y;
short /* boolean */ l;
А. Богатырев, 1992-95 - 387 - Си в UNIX
dy = y2 - y1; dx = x2 - x1;
if( !dx && !dy ){
putpixel(x1,y1, color); return;
}
kx = 1; /* шаг по x */
ky = 1; /* шаг по y */
/* Выбор тактовой оси */
if( dx < 0 ){ dx = -dx; kx = -1; } /* Y */
else if( dx == 0 ) kx = 0; /* X */
if( dy < 0 ){ dy = -dy; ky = -1; }
if( dx < dy ){ l = 0; d = dx; dx = dy; dy = d; }
else l = 1;
i1 = dy + dy; d = i1 - dx; i2 = d - dx;
x = x1; y = y1;
for( i=0; i < dx; i++ ){
putpixel( x, y, color );
if( l ) x += kx; /* шаг по такт. оси */
else y += ky;
if( d < 0 ) /* горизонтальный шаг */
d += i1;
else{ /* диагональный шаг */
d += i2;
if( l ) y += ky; /* прирост высоты */
else x += kx;
}
}
putpixel(x, y, color); /* последняя точка */
}
8.21. Составьте программу, которая строит график функции sin(x) на отрезке от 0 до
2*пи. Учтите такие вещи: соседние точки графика следует соединять отрезком прямой,
чтобы график выглядел непрерывным; не забывайте приводить double к int, т.к. коорди-
наты пикселов[*] - целые числа.
8.22. Напишите функцию, которая заполняет в массиве байт count бит подряд, начиная с
x-ого бита от левого края массива:
байт 0 | байт 1
7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 : биты в байте
0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 : x
==========================
x=2, count=11
Такой алгоритм используется в растровой машинной графике для рисования горизонтальных
прямых линий (тогда массив - это видеопамять компьютера, каждый бит соответствует
пикселу на экране).
Ответ (причем мы заполняем биты не просто единицами, а "узором" pattern):
void horizLine(char *addr,int x,int count,char pattern){
static char masks[8] = {
0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
/* индекс в этом массиве равен числу 0-битов слева */
____________________
[*] Пиксел (pixel, pel) - picture element, в машинной графике - точка растра на эк-
ране.
А. Богатырев, 1992-95 - 388 - Си в UNIX
register i;
char mask;
short lbits, rbits; /* число битов слева и справа */
short onebyte; /* единственный байт ? */
addr += x/8; /* в байте 8 бит */
mask = masks[ lbits = x & 7 ]; /* x % 8 */
if( count >= (rbits = 8 - lbits)){
count -= rbits; onebyte = 0;
}else{
mask &= ~masks[ lbits = (x+count) & 7 ];
onebyte = 1;
}
/* Первый байт */
*addr = (*addr & ~mask) | (pattern & mask);
addr++;
/* Для pattern==0xFF можно просто
* *addr++ |= mask;
* поскольку (a &~m)|(0xFF & m) = (a &~m) | m =
* (a|m) & (~m|m) = (a|m) & 0xFF = a | m
* Почему здесь нельзя написать *addr++ = (*addr...) ?
* Потому, что ++ может быть сделан ДО вычисления
* правой части присваивания!
*/
if(onebyte) return;
/* Средние байты */
for(i = count/8; i > 0; --i)
*addr++ = pattern; /* mask==0xFF */
/* Последний байт */
if((lbits = count & 7) == 0) return;
/* последний байт был полным */
mask = ~masks[lbits];
*addr = (*addr & ~mask) | (pattern & mask);
}
Заметим, что для быстродействия подобные алгоритмы обычно пишутся на ассемблере.
8.23. Напишите при помощи curses-а "электронные часы", отображающие текущее время
большими цифрами (например, размером 8x8 обычных символов) каждые 5 секунд. Исполь-
зуйте alarm(), pause().
8.24. Составьте программу, реализующую простой диалоговый интерфейс, основанный на
меню. Меню хранятся в текстовых файлах вида:
А. Богатырев, 1992-95 - 389 - Си в UNIX
файл menu2_12
-----------------------------------------------
ЗАГОЛОВОК_МЕНЮ
+команда_выполняемая_при_входе_в_меню
-команда_выполняемая_при_выходе_из_меню
альтернатива_1
команда1_1
команда1_2
альтернатива_2
команда2_1
команда2_2 #комментарий
команда2_3
альтернатива_3
>menu2_2 #это переход в другое меню
альтернатива_4
>>menu3_7 #хранимое в файле menu3_7
...
...
-----------------------------------------------
Программа должна обеспечивать: возврат к предыдущему меню по клавише Esc (для этого
следует хранить "историю" вызовов меню друг из друга, например в виде "полного имени
меню":
.rootmenu.menu1_2.menu2_4.menu3_1
где menuI_J - имена файлов с меню), обеспечить выход из программы по клавишам 'q' и
ESC, выдачу подсказки по F1, выдачу полного имени меню по F2. Вызов меню при помощи
>> означает замещение текущего меню новым, что соответствует замене последней компо-
ненты в полном имени меню. Вызов >>>> означает вызов меню как функции, т.е. после
выбора в новом меню и выполнения нужных действий автоматически должно быть выдано то
меню, из которого произошел вызов (такой вызов соответствует удлинению полного имени,
а возврат из вызова - отсечению последней компоненты). Этот вызов может быть показан
на экране как появление нового "выскакивающего" окна поверх окна с предыдущим меню
(окно возникает чуть сдвинутым - скажем, на y=1 и x=-2), а возврат - как исчезновение
этого окна. Заголовок меню должен высвечиваться в верхней строке меню:
|-------------------
|--ЗАГОЛОВОК_МЕНЮ---- |
| альтернатива_1 | |
| альтернатива_2 | |
| *альтернатива_3 | |
| альтернатива_4 |--
---------------------
Сначала реализуйте версию, в которой каждой "альтернативе" соответствует единственная
строка "команда". Команды следует запускать при помощи стандартной функции
system(команда).
Усложните функцию выбора в меню так, чтобы альтернативы можно было выбирать по
первой букве при помощи нажатия кнопки с этой буквой (в любом регистре):
Compile
Edit
Run program
8.25. Напишите на curses-е функцию, реализующую выбор в меню - прямоугольной таб-
лице:
А. Богатырев, 1992-95 - 390 - Си в UNIX
слово1 слово4 слово7
слово2 *слово5 слово8
слово3 слово6
Строки - элементы меню - передаются в функцию выбора в виде массива строк. Число
элементов меню заранее неизвестно и должно подсчитываться внутри функции. Учтите,
что все строки могут не поместиться в таблице, поэтому надо предусмотреть "прокручи-
вание" строк через таблицу при достижении края меню (т.е. таблица служит как бы