В этом разделе приводится полная программа на C для демонстрации многих возможностей языка и элементов стиля программирования.
Эта программа реализует простую записную книжку, которая годится для поддержания набора заметок и показа их на экране. Программа позволяет пользователю показывать заметки, относящиеся к сегодняшней дате, просматривать заметки, добавлять новые, заменять или удалять существующие. Программа показывает справку при неправильном вводе команды, или когда единственным параметром программы является знак вопроса.
Программа полностью соответствует стандарту ANSI C. Она должна работать без изменений на любой системе, для которой есть компилятор C, согласованный с ANSI.
memos.h
Исходный файл memos.h
содержит структуры для хранения записей:
/* Структура для отдельной строки записи. */ typedef struct text_line { struct text_line * next; char text[1]; } TEXT_LINE; /* Структура для заголовка отдельной записи. */ typedef struct memo_el { struct memo_el * prev; struct memo_el * next; TEXT_LINE * text; char date[9]; } MEMO_EL;
memos.c
Далее следует исходный текст программы:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <time.h> #include "memos.h" /* Эта программа реализует возможности простой записной книжки. * Записи можно добавлять в файл, показывать на экране, * удалять. * * Изменено кем почему * ======== === ====== * 87/10/02 Steve McDowell Начальная реализация. * 88/09/20 Steve McDowell Исправлены некоторые стилевые моменты, * введено использование TRUE и * FALSE. */ /* Определение некоторых констант, чтобы сделать код * более читабельным. */ #define TRUE 1 #define FALSE 0 #define NULLCHAR '\0' static const char FileName[] = { "memos.db" }; static const char TempName[] = { "tempmemo.db" }; static MEMO_EL * MemoHead = NULL; static int MemosModified = FALSE; static int QuitFlag = TRUE; typedef enum { INVALID, HELP, ADD, DELETE, REPLACE, SHOW, UP, DOWN, TOP, TODAY, SAVE, QUIT } ACTION; /* Эта таблица ключевые слова действий в "действия" определённые * выше. Кроме того, таблица определяет сокращённые формы команд. */ typedef struct { ACTION act; char * keyword; } ACTION_MAP; static ACTION_MAP KeywordMap[] = { HELP, "help", HELP, "h", ADD, "add", ADD, "a", DELETE, "delete", DELETE, "del", REPLACE, "replace", REPLACE, "rep", SHOW, "show", SHOW, "sh", UP, "up", UP, "u", DOWN, "down", DOWN, "d", DOWN, "", TOP, "top", TODAY, "today", TODAY, "tod", SAVE, "save", SAVE, "sa", QUIT, "quit", QUIT, "q", INVALID, "" }; /* Максимальная длина буфера (максимальная длина строки в записи. */ #define MAXLEN 80 /* Прототипы функций. */ static TEXT_LINE * AddLine(); static MEMO_EL * AddMemo(); static MEMO_EL * DeleteMemo(); static MEMO_EL * DoActions(); static MEMO_EL * DoDownAction(); static MEMO_EL * DoUpAction(); static MEMO_EL * EnterAMemo(); static ACTION GetAction(); static void * MemoMAlloc(); static ACTION PromptAction(); static ACTION ReadAction(); static MEMO_EL * ReadAMemo(); static MEMO_EL * ShowTodaysMemos(); extern int main( int argc, char * argv[] ) /****************************************/ { int index; MEMO_EL * el; printf( "Memo facility\n" ); /* Сравнить аргумент со знаком вопроса, и, если так, * то вывести справку об использовании. */ if( argc == 2 && strcmp( argv[1], "?" ) == 0 ) { Usage(); exit( 0 ); } ReadMemos(); MemosModified = FALSE; QuitFlag = FALSE; /* Использовать параметры командной строки, если они есть, как * первое действие над записями. */ el = NULL; for( index = 1; index < argc; ++index ) { el = DoActions( el, GetAction( argv[index] ) ); if( QuitFlag ) { return( FALSE ); } } HandleMemoActions( el ); return( FALSE ); } static void ReadMemos( void ) /***************************/ /* Читать файл записей, создавая структуры, которые в нём * содержатся. */ { FILE * fid; MEMO_EL * new_el; MEMO_EL * prev_el; int mcount; fid = fopen( FileName, "r" ); if( fid == NULL ) { printf( "Memos file not found." " Starting with no memos.\n" ); return; } /* Цикл чтения всех записей. */ prev_el = NULL; for( mcount = 0;; mcount++ ) { new_el = ReadAMemo( fid ); if( new_el == NULL ) { printf( "%d memo(s) found.\n", mcount ); fclose( fid ); return; } if( prev_el == NULL ) { MemoHead = new_el; new_el->prev = NULL; } else { prev_el->next = new_el; new_el->prev = prev_el; } new_el->next = NULL; prev_el = new_el; } } static int ReadLine( char buffer[], int len, FILE * fid ) /*******************************************************/ /* Прочитать строку из файла записей. Обработать все ошибки * ввода/вывода и EOF. Вернуть длину прочитанного без новой * строки в конце. */ { if( fgets( buffer, len, fid ) == NULL ) { if( feof( fid ) ) { return( EOF ); } perror( "Error reading memos file" ); abort(); } return( strlen( buffer ) - 1 ); } static MEMO_EL * ReadAMemo( FILE * fid ) /**************************************/ /* Прочитать одну запись, создать структуру записи и * заполнить её. Вернуть указатель на запись (NULL, если * ничего не прочитано). */ { MEMO_EL * el; int len; TEXT_LINE * line; char buffer[MAXLEN]; len = ReadLine( buffer, MAXLEN, fid ); if( len == EOF ) { return( NULL ); } /* Первая строка должна быть в виде "Date:" или "Date:YY/MM/DD": */ if( (len != 5 && len != 13) || strncmp( buffer, "Date:", 5 ) != 0 ) { BadFormat(); } buffer[len] = NULLCHAR; el = MemoMAlloc( sizeof( MEMO_EL ) ); el->text = NULL; strcpy( el->date, buffer + 5 ); line = NULL; for( ;; ) { len = ReadLine( buffer, MAXLEN, fid ); if( len == EOF ) { BadFormat(); } buffer[len] = NULLCHAR; if( strcmp( buffer, "====" ) == 0 ) { return( el ); } line = AddLine( buffer, el, line ); } } static TEXT_LINE * AddLine( char buffer[], MEMO_EL * el, TEXT_LINE * prevline ) /************************************************/ /* Добавить строку текста в запись, заботясь обо всех деталях * изменения структуры. */ { TEXT_LINE * line; line = MemoMAlloc( sizeof( TEXT_LINE ) + strlen( buffer ) ); strcpy( line->text, buffer ); line->next = NULL; if( prevline == NULL ) { el->text = line; } else { prevline->next = line; } return( line ); } static ACTION PromptAction( void ) /********************************/ /* Пользователь не указал действия в командной строке, * поэтому предложить ему сделать это. */ { ACTION act; for( ;; ) { printf( "\nEnter an action:\n" ); act = ReadAction(); if( act != INVALID ) { return( act ); } printf( "\nThat selection was not valid.\n" ); Help(); } } static ACTION ReadAction( void ) /******************************/ /* Прочитать команду с терминала. * Вернуть тип команды. */ { char buffer[80]; if( gets( buffer ) == NULL ) { perror( "Error reading action" ); abort(); } return( GetAction( buffer ) ); } static ACTION GetAction( char buffer[] ) /**************************************/ /* Получив строку в buffer, вернуть код соответствующего * действия. * Строка в buffer сначала переводится в нижний регистр, * чтобы распознать ввод в смешанном регистре. */ { ACTION_MAP * actmap; char * bufptr; for( bufptr = buffer; *bufptr != NULLCHAR; ++bufptr ) { *bufptr = tolower( *bufptr ); } for( actmap = KeywordMap; actmap->act != INVALID; ++actmap ) { if( strcmp( buffer, actmap->keyword ) == 0 ) break; } return( actmap->act ); } static void HandleMemoActions( MEMO_EL * el ) /*******************************************/ /* Обработать все команды введённые с клавиатуры. */ { for( ;; ) { el = DoActions( el, PromptAction() ); if( QuitFlag ) break; } } static MEMO_EL * DoActions( MEMO_EL * el, ACTION act ) /****************************************************/ /* Произвести одно действие над записью. */ { MEMO_EL * new_el; MEMO_EL * prev_el; switch( act ) { case HELP: Help(); break; case ADD: new_el = AddMemo( el ); if( new_el != NULL ) { el = new_el; MemosModified = TRUE; } break; case DELETE: el = DeleteMemo( el ); MemosModified = TRUE; break; case REPLACE: prev_el = el; new_el = AddMemo( el ); if( new_el != NULL ) { DeleteMemo( prev_el ); MemosModified = TRUE; } break; case SHOW: DisplayMemo( el ); break; case UP: el = DoUpAction( el ); break; case DOWN: el = DoDownAction( el ); break; case TOP: el = NULL; break; case TODAY: el = ShowTodaysMemos(); break; case SAVE: if( SaveMemos() ) { MemosModified = FALSE; } break; case QUIT: if( WantToQuit() ) { QuitFlag = TRUE; el = NULL; } } return( el ); } static MEMO_EL * AddMemo( MEMO_EL * el ) /**************************************/ /* Добавить запись следом за текущей. */ { MEMO_EL * new_el; MEMO_EL * next; new_el = EnterAMemo(); if( new_el == NULL ) { return( NULL ); } if( el == NULL ) { next = MemoHead; MemoHead = new_el; } else { next = el->next; el->next = new_el; } new_el->prev = el; new_el->next = next; if( next != NULL ) { next->prev = new_el; } return( new_el ); } static MEMO_EL * EnterAMemo( void ) /*********************************/ /* Прочитать запись с клавиатуры, создать структуру записи * и заполнить её. Вернут указатель на запись (NULL, если * ничего не прочитано). */ { MEMO_EL * el; int len; TEXT_LINE * line; char buffer[MAXLEN]; printf( "What date do you want the memo displayed" " (YY/MM/DD)?\n" ); if( gets( buffer ) == NULL ) { printf( "Error reading from terminal.\n" ); return( NULL ); } len = strlen( buffer ); if( len != 0 && (len != 8 || buffer[2] != '/' || buffer[5] != '/') ) { printf( "Date is not valid.\n" ); return( NULL ); } el = MemoMAlloc( sizeof( MEMO_EL ) ); el->text = NULL; strcpy( el->date, buffer ); line = NULL; printf( "\nEnter the text of the memo.\n" ); printf( "To terminate the memo," " enter a line starting with =\n" ); for( ;; ) { if( gets( buffer ) == NULL ) { printf( "Error reading from terminal.\n" ); return( NULL ); } if( buffer[0] == '=' ) { return( el ); } line = AddLine( buffer, el, line ); } } static MEMO_EL * DeleteMemo( MEMO_EL * el ) /*****************************************/ /* Удалить текущую запись. * Вернуть указатель на другую, обычно следующую. */ { MEMO_EL * prev; MEMO_EL * next; MEMO_EL * ret_el; if( el == NULL ) { return( MemoHead ); } prev = el->prev; next = el->next; ret_el = next; if( ret_el == NULL ) { ret_el = prev; } /* Если это первая запись, то установить значение MemoHead. */ if( prev == NULL ) { MemoHead = next; if( next != NULL ) { next->prev = NULL; } } else { prev->next = next; if( next != NULL ) { next->prev = prev; } } DisposeMemo( el ); return( ret_el ); } static MEMO_EL * DoUpAction( MEMO_EL * el ) /*****************************************/ /* Произвести действие UP, включая показ записи. */ { if( el == NULL ) { DisplayTop(); } else { el = el->prev; DisplayMemo( el ); } return( el ); } static MEMO_EL * DoDownAction( MEMO_EL * el ) /*******************************************/ /* Произвести действие DOWN, включая показ записи. */ { MEMO_EL * next_el; next_el = (el == NULL) ? MemoHead : el->next; if( next_el == NULL ) { printf( "No more memos.\n" ); } else { el = next_el; DisplayMemo( el ); } return( el ); } static MEMO_EL * ShowTodaysMemos( void ) /**************************************/ /* Показать все записи, которые: * (1) совпадают с сегодняшней датой * (2) не имеют даты. * Вернуть указатель на последнюю показанную запись. */ { MEMO_EL * el; MEMO_EL * last_el; time_t timer; struct tm ltime; char date[9]; /* Получить сегодняшнюю дату в формате ГГ/ММ/ДД */ time( &timer ); ltime = *localtime( &timer ); strftime( date, 9, "%y/%m/%d", <ime ); last_el = NULL; for( el = MemoHead; el != NULL; el = el->next ) { if( el->date[0] == NULLCHAR || strcmp( date, el->date ) == 0 ) { DisplayMemo( el ); last_el = el; } } return( last_el ); } static void DisplayMemo( MEMO_EL * el ) /*************************************/ /* Показать запись на экране. */ { TEXT_LINE * tline; if( el == NULL ) { DisplayTop(); return; } if( el->date[0] == NULLCHAR ) { printf( "\nUndated memo\n" ); } else { printf( "\nDated: %s\n", el->date ); } for( tline = el->text; tline != NULL; tline = tline->next ) { printf( " %s\n", tline->text ); } } static int SaveMemos( void ) /**************************/ /* Сохранить записи в файле. */ { FILE * fid; MEMO_EL * el; TEXT_LINE * tline; char buffer[20]; if( MemoHead == NULL ) { printf( "No memos to save.\n" ); return( FALSE ); } /* Открыть временный файл на случай непредвиденных * обстоятельств. */ fid = fopen( TempName, "w" ); if( fid == NULL ) { printf( "Unable to open \"%s\" for writing.\n", TempName ); printf( "Save not performed.\n" ); return( FALSE ); } for( el = MemoHead; el != NULL; el = el->next ) { sprintf( buffer, "Date:%s", el->date ); if( !WriteLine( buffer, fid ) ) { return( FALSE ); } tline = el->text; for( ; tline != NULL; tline = tline->next ) { if( !WriteLine( tline->text, fid ) ) { return( FALSE ); } } if( !WriteLine( "====", fid ) ) { return( FALSE ); } } /* Теперь избавиться от старого файла, если он есть, * затем переименовать новый. */ fclose( fid ); fid = fopen( FileName, "r" ); if( fid != NULL ) { fclose( fid ); if( remove( FileName ) != 0 ) { perror( "Can't remove old memos file" ); return( FALSE ); } } if( rename( TempName, FileName ) != 0 ) { perror( "Can't rename new memos file" ); return( FALSE ); } return( TRUE ); } static int WriteLine( char * text, FILE * fid ) /*********************************************/ { if( fprintf( fid, "%s\n", text ) < 0 ) { perror( "Error writing memos file" ); return( FALSE ); } return( TRUE ); } /* Подпрограммы для показа помощи и другого простого текста. */ static void Usage( void ) /***********************/ { printf( "Usage:\n" ); printf( " memos ?\n" ); printf( " displays this text\n" ); printf( " or\n" ); printf( " memos\n" ); printf( " prompts for all actions.\n" ); printf( " or\n" ); printf( " memos action\n" ); printf( " performs the action.\n" ); printf( " More than one action may be specified.\n" ); printf( " action is one of:\n" ); ShowActions(); } static void ShowActions( void ) /*****************************/ { printf( " Help (display this text)\n" ); printf( " Add (add new memo here)\n" ); printf( " DELete (delete current memo)\n" ); printf( " REPlace (replace current memo)\n" ); printf( " SHow (show the current memo again)\n" ); printf( " Up (move up one memo)\n" ); printf( " Down (move down one memo)\n" ); printf( " TOP (move to the top of the list\n" ); printf( " TODay (display today's memos)\n" ); printf( " SAve (write the memos to disk)\n" ); } static void Help( void ) /**********************/ { printf( "Choose one of:\n" ); ShowActions(); printf( " Quit\n" ); } static void DisplayTop( void ) /****************************/ { printf( "Top of memos.\n" ); } static int WantToQuit( void ) /***************************/ /* Проверить, есть ли изменённые, но не сохранённые записи. * Если есть, спросить пользователя, хочет ли он/она выйти * без сохранения записей. */ { char buffer[MAXLEN]; if( !MemosModified || MemoHead == NULL ) { return( TRUE ); } printf( "\nThe memos have been modified but not saved.\n" ); printf( "Do you want to leave without saving them?\n" ); gets( buffer ); return( tolower( buffer[0] ) == 'y' ); } static void BadFormat( void ) /***************************/ { printf( "Invalid format for memos file\n" ); abort(); } static void * MemoMAlloc( int size ) /**********************************/ /* Получить память указанного размера, сообщив и выйдя в случае * неудачи. */ { register char * mem; mem = malloc( size ); if( mem == NULL ) { printf( "Unable to allocate %d characters of memory\n", size ); abort(); } return( mem ); } static void DisposeMemo( MEMO_EL * el ) /*************************************/ /* Освободить память занимаемую записью, включая * её строки. */ { TEXT_LINE * tline; TEXT_LINE * next; tline = el->text; while( tline != NULL ) { next = tline->next; free( tline ); tline = next; } free( el ); }