Вариант А: Задан файл (условно, база данных пользовательских учетных записей UNIX) следующего формата:
# comment8
8
username:password:UID:GID:usercomment:home:shell8
username:password:UID:GID:comment:home:shell8
. . .
username:password:UID:GID:comment:home:shell8
# — символ начала комментария,
8 — символ разделителя записей,
: — символ разделителя полей записи,
comment — незначащий комментарий,
username — имя пользователя (строка),
password — пароль пользователя (строка),
UID — идентификатор пользователя (короткое целое),
GID — идентификатор первичной группы пользователя (короткое целое),
comm — комментарий к учетной записи пользователя (строка),
home — путь к домашнему каталогу пользователя (строка),
shell — путь к начальному интерпретатору пользователя (строка).
Разработать определения типов uid_t, gid_t и следующие функции доступа к записям базы данных:
passwd* getpwnam(char *name);— поиск записи по имени пользователя name;
passwd* getpwuid (uid_t uid);— поиск записи по идентификатору пользователя uid;
passwd* getpwent(); — чтение очередной записи из базы данных;
Функции должны возвращать указатель на структуру следующего вида:
struct passwd
{
char *pw_name;
char *pw_passwd;
uid_t pw_uid;
gid_t pw_gid;
char *pw_gecos;
char *pw_dir;
char *pw_shell;
};
При создании кода надо руководствоваться логикой общения с пользователем. Мысленно проигрывайте желаемый сценарий работы с ним и постепенно добивайтесь его воплощения. Начните разработку с создания удобных структур данных. На этой стадии надо учитывать платформу, предпочтения, оказываемые некоторым библиотечным функциям, желаемый стиль и т.д. Предположим, что нам надо разработать приложение в стиле 70-х годов прошлого века на основе функций библиотеки CRT, прототипы которых описаны в файле заголовков stdio.h. Минимальный набор runtime-библиотек задаем в файле stdafx.h:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
В файле Users.cpp:
¨ Объявляем новые типы данных. В нашем случае они определены и заданы заказчиком,
¨ Объявляем глобальные структуры данных — они вычисляются на основе опыта разработки с учетом частоты использования всеми функциями приложения.
#include "stdafx.h"
typedef unsigned short uid_t; // Глобальные типы данных
typedef unsigned short gid_t;
struct passwd // Тип структур для описания пользователя
{
char *pw_name;
char *pw_passwd;
uid_t pw_uid;
gid_t pw_gid;
char *pw_gecos; // Comment
char *pw_dir; // Home
char *pw_shell;
};
//===== Глобальные переменные
FILE *fp; // Файловый указатель
const int maxLen = 128; // Ограничитель длины строки при вводе данных
char buf[maxLen+maxLen]; // Буфер должен вместить данные и сообщение
long posUsers; // Позиция начала данных в файле (без учета заголовка)
char extension[] = ".users"; // Предполагаемое расширение файла с данными
char defaultFile[] = "Test.users"; // Тестовый файл с данными
const char delim = ':'; // Символ разделителя полей
Теперь можно приступить к разработке главной функции, вставляя в нее вызовы других, несуществующих пока функций. Все вместе они должны реализовывать желаемую функциональность приложения.
void main()
{
puts ("Users Service. Getting info from users files\n");
while (true) // Псевдо-бесконечный цикл диалога с пользователем
{
char *fileName = 0;
try
{
fileName = GetFileName(); // Выясняем имя файла с учетными записями
OpenUserFile (fileName); // Попытка открыть файл
QueryUser(); // Диалог, запрашивающий и выполняющий операцию с данными
}
catch (char* s)
{
if (strcmp(s, "Stop") == 0) // Нормальное завершение приложения
{
printf ("Users service: Bye\n");
if (fp)
fclose(fp);
break;
}
printf ("\nError: %s", s); // Ненормальное завершение приложения
if (No ("\nTry again? (Y/N)"))
break;
delete [] fileName;
fileName = 0;
}
}
puts ("\n\n");
}
Здесь определены правила игры с функциями: GetFileName, OpenUserFile и QueryUser, No. Их предстоит разработать. Вставив вызовы несуществующих функций, мы задали желаемые сигнатуры новых строительных кирпичей программы. В процессе создания кода придется реализовать еще какие-то другие операции с данными. При этом используется тот же самый прием: добавить вызовы несуществующих функций, затем приступить к их разработке. Теперь попытаемся разработать новые функции. Их надо либо целиком вставлять перед функцией main, либо вставить их прототипы, а затем давать тела (уже в любом порядке).
Я предпочитаю первый стиль — работать без прототипов. При организации диалога с пользователем распространен такой прием — использовать для временного хранения данных общий буфер (глобальный массив buf). Удобство в том, что его не надо чистить. Некоторые данные, введенные пользователем, удобно отложить в сторону, то есть скопировать их в динамически созданный массив (который надо чистить). В связи с этим целесообразно создать несколько служебных функций (своих маленьких утилит):
bool No (char* prompt) // Просим выбрать ответ: Yes or No
{
printf ("\n%s: ", prompt);
return toupper (getche()) != 'Y';
}
bool Yes (char* prompt) // Просим выбрать ответ: Yes or No
{
printf ("\n%s: ", prompt);
return toupper (getche()) != 'N';
}
char* CopyString() // Часто используемая функция. Она копирует буфер в новую память
{
return strcpy (new char[strlen(buf)+1], buf);
}
char* GetLine (char *prompt) // Просим ввести в буфер buf строку (не более maxLen символов)
{
printf ("%s: ",prompt); // Подсказка пользователю (Что мы у него запрашиваем)
fflush (stdin); // Чистим буфер клавиатуры
char c; int i;
for (i = 0; i<maxLen && (c = getchar()) != EOF && c != '\n'; i++) // Посимвольный ввод
buf[i] = c; // Помещаем символ в буфер buf
buf[i] = 0; // Завершаем строку нулем
return buf;
}
Посимвольный ввод, производимый с помощью getchar, позволяет корректно обработать ситуацию, когда число введенных символов превышает емкость буфера.
char* GetString (char* prompt) // Просим ввести строку символов и возвращаем ее копию
{
GetLine (prompt);
return CopyString();
}
short SetShort (char *s) // Преобразуем строку символов (из буфера) в короткое целое
{
char *stop;
long id = strtol (s, &stop, 0);
if (*stop != 0 || id > 0xffff)
throw "SetShort: Wrong ID field";
if (s != buf)
delete [] s;
return (short)id;
}
Следующая функция понадобится при реализации диалога с пользователем.
char Menu()
{
puts (
"\n\n\tFind a user:\n"
"\n\tn - By name"
"\n\ti - By ID"
"\n\tq - Quit\n");
return getch();
}
Приступим к разработке функций, запланированных при создании главной функции main.
char* GetFileName()
{
//===== Просим ввести имя файла или завершить диалог (вводом символа q)
GetLine ("\n\n\tEnter a users file name (q - quit)");
int len = int(strlen(buf));
if (len == 1 && toupper(buf[0]) == 'Q') // Нормальное завершение приложения
throw "Stop";
char* fileName = 0;
if (len == 0) // Если пользователь не задал имя файла, откроем defaultFile
fileName = strcpy (new char[strlen(defaultFile) + 1], defaultFile);
else
{
int n = int(strlen (extension));
// Если пользователь не задал расширения, дополним имя файла расширением extension
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.