Общепринятым соглашением в «С»-программах является то, что аргумент, начинающийся со знака минус, вводит необязательный признак или параметр. Если мы, для того, чтобы сообщить об инверсии, выберем –x (для указания «кроме»), а для указания о нумерации нужных строк выберем –n («номер»), то команда:
find -x -n the
при входных данных
Now is the time
For all good men
To come to the aid
Of their party.
должна выдать
2: For all good men
Пример 6-20. Часто необходимо, чтобы необязательные аргументы могли располагаться в произвольном порядке, и чтобы остальная часть программы не зависела от количества фактически присутствующих аргументов. В частности, вызов функции index не должен содержать ссылку на argv[2], когда присутствует один необязательный аргумент, и на argv[1], когда его нет. Более того, для пользователей удобно, чтобы необязательные аргументы можно было объединить в виде:
find -nx образец
А вот и сама программа:
// Печать строк, содержащих образец, заданный первым
// аргументом
#define maxline 1000
main(int argc, char *argv[])
{
char line[maxline], *s;
long lineno = 0;
int except = 0, number = 0;
while (--argc > 0 && (*++argv)[0] == '-')
for (s = argv[0]+1; *s != '\0'; s++)
switch (*s)
{
case 'x':
except = 1;
break;
case 'n':
number = 1;
break;
default:
printf("find: неверный параметр %c\n",*s);
argc = 0;
break;
}
if (argc != 1)
printf("usage: find -x -n pattern\n");
else
while (getlinе(line, maxline) > 0)
{
lineno++;
if ((index(line, *argv) >= 0) != except)
{
if (number)
printf("%ld: ", lineno);
printf("%s", line);
}
}
}
Аргумент argv увеличивается перед каждым необязательным аргументом, в то время как аргумент argc уменьшается. Если нет ошибок, то в конце цикла величина argc должна равняться 1, а *argv должно указывать на заданную комбинацию. Обратите внимание на то, что *++argv является указателем аргументной строки: (*++argv)[0] – её первый символ. Круглые скобки здесь необходимы, потому что без них выражение бы приняло совершенно отличный и неправильный вид: *++(ARGV[0]). Другой правильной формой была бы **++argv.
Упражнение 6-7. Напишите программу add, вычисляющую обратное польское выражение из командной строки. Например,
add 2 3 4 + *
вычисляет 2´(3+4).
Упражнение 6-8. Модифицируйте программы entab и detab (указанные в качестве упражнений в главе 2) так, чтобы они получали список табуляционных остановок в качестве аргументов. Если аргументы отсутствуют, используйте стандартную установку табуляций.
Упражнение 6-9. Расширьте entab и detab таким образом, чтобы они воспринимали сокращенную нотацию:
entab -m +n
означающую табуляционные остановки через каждые n позиций, начиная со столбца m. Выберите удобное (для пользователя) поведение функции по умолчанию.
Упражнение 6-10. Напишите программу для функции tail, печатающей последние n строк из своего файла ввода. Пусть по умолчанию n равно 10, но это число может быть изменено с помощью необязательного аргумента, так что:
tail -n
печатает последние n строк. Программа должна действовать рационально, какими бы неразумными ни были бы ввод или значение n. Составьте программу так, чтобы она оптимальным образом использовала доступную память: строки должны храниться, как в функции sort, а не в двумерном массиве фиксированного размера.
Пример 6-21. В языке «С» сами функции не являются переменными, но имеется возможность определить указатель на функцию, который можно обрабатывать, передавать другим функциям, помещать в массивы и т.д. Мы проиллюстрируем это, проведя модификацию написанной ранее программы сортировки так, чтобы при задании необязательного аргумента -n она бы сортировала строки ввода численно, а не лексикографически.
Сортировка часто состоит из трех частей:
· сравнения, которое определяет упорядочивание любой пары объектов,
· перестановки, изменяющей их порядок, и
· алгоритма сортировки, осуществляющего сравнения и перестановки до тех пор, пока объекты не расположатся в нужном порядке.
Алгоритм сортировки не зависит от операций сравнения и перестановки, так что, передавая в него различные функции сравнения и перестановки, мы можем организовать сортировку по различным критериям. Именно такой подход используется в нашей новой программе сортировки.
Как и прежде, лексикографическое сравнение двух строк осуществляется функцией strcmp, а перестановка функцией swap; нам нужна еще функция numcmp, сравнивающая две строки на основе численного значения и возвращающая условное указание того же вида, что и strcmp. Эти три функции описываются в main и указатели на них передаются в sort. В свою очередь функция sort обращается к этим функциям через их указатели. Мы урезали обработку ошибок в аргументах с тем, чтобы сосредоточиться на главных вопросах.
// Сортировка вводимых строк
#define lines 100 // Максимальное число строк
main(int argc, char *argv[])
{
char *lineptr[lines]; // Указатели на строки
int nlines; // Число введенных строк
int strcmp(), numcmp(); // Функции сравнения
int swap(); // Функция exchange
int numeric = 0; // 1, если сорт. по числ. значен.
if(argc>1 && argv[1][0] == '-' && argv[1][1]=='n')
numeric = 1;
if(nlines = readlines(lineptr, lines)) >= 0)
{
if (numeric)
sort(lineptr, nlines, numcmp, swap);
else
sort(lineptr, nlines, strcmp, swap);
writelines(lineptr, nlines);
}
else
printf("input too big to sort\n");
}
Здесь strcmp, nimcmp и swap – адреса функций; так как известно, что это функции, операция & здесь не нужна совершенно аналогично тому, как она не нужна и перед именем массива. Передача адресов функций организуется компилятором.
Второй шаг состоит в модификации sort:
// Сортировать строки v[0] ... v[n-1] по возрастанию
sort(char *v[],int n, int (*comp)(), int (*exch)())
{
int gap, i, j;
for(gap = n/2; gap > 0; gap /= 2)
for(i = gap; i < n; i++)
for(j = i-gap; j >= 0; j -= gap)
{
if((*comp)(v[j], v[j+gap]) <= 0)
break;
(*exch)(&v[j], &v[j+gap]);
}
}
Здесь следует обратить определенное внимание на описания. Описание:
int (*comp)()
говорит, что comp является указателем на функцию, которая возвращает значение типа int. Первые круглые скобки здесь необходимы: без них описание:
int *comp()
говорило бы, что comp является функцией, возвращающей указатель на целые, что, конечно, совершенно другая вещь.
Использование comp в строке:
if (*comp)(v[j], v[j+gap]) <= 0)
полностью согласуется с описанием:
comp – указатель на функцию, *comp – сама функция, а
(*comp)(v[j], v[j+gap])
– это обращение к ней. Круглые скобки необходимы для правильного объединения компонентов.
Мы уже приводили функцию strcmp, сравнивающую две строки по первому численному значению:
// Сравнить численно s1 и s2
numcmp(char *s1, char *s2)
{
double atof(), v1, v2;
v1 = atof(s1);
v2 = atof(s2);
if(v1 < v2)
return(-1);
else if(v1 > v2)
return(1);
else
return (0);
}
Заключительный шаг состоит в добавлении функции swap, переставляющей два указателя. Это легко сделать, непосредственно используя то, что мы изложили ранее в этой главе:
// Переставить указатели *px и *py
swap(char *px[],char *py[])
{
char *temp;
temp = *px;
*px = *py;
*py = temp;
}
Имеется множество других необязятельных аргументов, которые могут быть включены в программу сортировки: некоторые из них составляют интересные упражнения.
Упражнение 6-11. Модифицируйте sort таким образом, чтобы она работала с меткой -r, указывающей на сортировку в обратном (убывающем) порядке. Конечно, -r должна работать с -n.
Упражнение 6-12. Добавьте необязательный аргумент -f, объединяющий вместе прописные и строчные буквы, так чтобы различие регистров не учитывалось во время сортировки: данные из верхнего и нижнего регистров сортируются вместе, так что буква 'а' прописное и 'а' строчное оказываются соседними, а не разделенными целым алфавитом.
Упражнение 6-13. Добавьте необязательный аргумент -d (т.н. «словарное упорядочивание»), при наличии которого сравниваются только буквы, числа и пробелы. Позаботьтесь о том, чтобы эта функция работала и вместе с -f.
Упражнение 6-14. Добавьте возможность обработки полей, так чтобы можно было сортировать поля внутри строк. Каждое поле должно сортироваться в соответствии с независимым набором необязательных аргументов. Предметный указатель этой книги сортировался с помощью следующих аргументов:
-df для терминов (категории указателя);
–n для номеров страниц.
Структура – это набор из одной или более переменных, возможно различных типов, сгруппированных под одним именем для удобства обработки (в некоторых языках, самый известный из которых ПАСКАЛЬ, структуры называются «записями»).
Традиционным примером структуры является учетная карточка работающего: «служащий» описывается набором атрибутов таких, как фамилия, имя, отчество (ф.и.о.), адрес, код социального обеспечения, зарплата и т.д. Некоторые из этих атрибутов сами могут оказаться структурами: ф.и.о. Имеет несколько компонент, как и адрес, и даже зарплата.
Структуры оказываются полезными при организации сложных данных особенно в больших программах, поскольку во многих ситуациях они позволяют сгруппировать связанные данные таким образом, что с ними можно обращаться, как с одним целым, а не как с отдельными объектами. В этой главе мы постараемся продемонстрировать то, как используются структуры. Программы, которые мы для этого будем использовать, больше, чем многие другие в этой книге, но все же достаточно умеренных размеров.
Давайте снова обратимся к процедурам преобразования даты из главы 6. Дата состоит из нескольких частей таких, как день, месяц, и год, и, возможно, день года и имя месяца. Эти пять переменных можно объединить в одну структуру вида:
struct date
{
int day;
int month;