Керниган и Ритчи - Язык программирования Си (793773), страница 30
Текст из файла (страница 30)
Так, если -x служит признаком слова "кроме", которое изменяетзадание на противоположное, а -n указывает на потребность в нумерации строк, то командаfind -x -n образецнапечатает все строки, в которых не найден указанный образец, и, кроме того, перед каждой строкой укажетее номер.Необязательные аргументы разрешается располагать в любом порядке, при этом лучше, чтобы остальнаячасть программы не зависела от числа представленных аргументов.
Кроме того, пользователю было быудобно, если бы он мог комбинировать необязательные аргументы, например, так:find -nx образецА теперь запишем нашу программу.#include <stdio.h>#include <string.h>#define MAXLINE 1000int getline(char *line, int max);/* find: печать строк образцами из 1-го аргумента */main(int angc, char *argv[]){char line[MAXLINE];long lineno = 0;int c, except = 0, number = 0, found = 0;while (—argc > 0 && (*++argv)[0] == '-')while (c = *++argv[0])switch (c) {case 'x':except = 1;break;case 'n' :number = 1;break;default:printf( "find: неверный параметр %с\п", с);argc = 0;found = -1;break;}if (argc != 1)printf ("Используйте: find -x -n образец\п");elsewhile (getline(line, MAXLINE) > 0) {lineno++;if ((strstr(line, *argv) != NULL) != except) {if (number)printf("%ld:", lineno);printf("%s", line);found++;}}return found;}Перед получением очередного аргумента argc уменьшается на 1, a argv "перемещается" на следующийаргумент.
После завершения цикла при отсутствии ошибок argc содержит количество еще не обработанныхаргументов, a argv указывает на первый из них. Таким образом, argc должен быть равен 1, а *аrgvуказывать на образец. Заметим, что *++аrgv является указателем на аргумент-строку, a (*++argv)[0] —его первым символом, на который можно сослаться и другим способом:**++argvПоскольку оператор индексирования [] имеет более высокий приоритет, чем * и ++, круглые скобки здесьобязательны, без них выражение трактовалось бы так же, как *++(argv[0]).
Именно такое выражение мыприменим во внутреннем цикле, где просматриваются символы конкретного аргумента. Во внутреннем циклевыражение *++argv[0] приращивает указатель argv[0].Потребность в более сложных выражениях для указателей возникает не так уж часто. Но если такое случится,то разбивая процесс вычисления указателя на два или три шага, вы облегчите восприятие этого выражения.Упражнение 5.10. Напишите программу expr, интерпретирующую обратную польскую запись выражения,задаваемого командной строкой, в которой каждый оператор и операнд представлены отдельнымаргументом.
Например,expr 2 3 4 + *вычисляется так же, как выражение 2 х (3 + 4).Упражнение 5.11. Усовершенствуйте программы entab и detab (см. упражнения 1.20 и 1.21) такимобразом, чтобы через аргументы можно было задавать список "стопов" табуляции.Упражнение 5.12. Расширьте возможности entab и detab таким образом, чтобы при обращении видаentab -m +n"стопы" табуляции начинались с m-й позиции и выполнялись через каждые n позиций. Разработайте удобныйдля пользователя вариант поведения программы по умолчанию (когда нет никаких аргументов).Упражнение 5.13.
Напишите программу tail, печатающую n последних введенных строк. По умолчаниюзначение n равно 10, но при желании n можно задать с помощью аргумента. Обращение видаtail -nпечатает n последних строк. Программа должна вести себя осмысленно при любых входных данных и любомзначении n. Напишите программу так, чтобы наилучшим образом использовать память; запоминание строкорганизуйте, как в программе сортировки, описанной в параграфе 5.6, а не на основе двумерного массива сфиксированным размером строки.5.11. Указатели на функцииВ Си сама функция не является переменной, но можно определить указатель на функцию и работать с ним,как с обычной переменной: присваивать, размещать в массиве, передавать в качестве параметра функции,возвращать как результат из функции и т.д.
Для иллюстрации этих возможностей воспользуемся программойсортировки, которая уже встречалась в настоящей главе. Изменим ее так, чтобы при задании необязательногоаргумента -n вводимые строки упорядочивались по их числовому значению, а не в лексикографическомпорядке.Сортировка, как правило, распадается на три части: на сравнение, определяющее упорядоченность парыобъектов; перестановку, меняющую местами пару объектов, и сортирующий алгоритм, который осуществляетсравнения и перестановки до тех пор, пока все объекты не будут упорядочены. Алгоритм сортировки независит от операций сравнения и перестановки, так что передавая ему в качестве параметров различныефункции сравнения и перестановки, его можно настроить на различные критерии сортировки.Лексикографическое сравнение двух строк выполняется функцией strcmp(мы уже использовали этуфункцию в ранее рассмотренной программе сортировки); нам также потребуется программа numcmp,сравнивающая две строки как числовые значения и возвращающая результат сравнения в том же виде, вкаком его выдает strcmp.
Эти функции объявляются перед main, а указатель на одну из них передаетсяфункции qsort. Чтобы сосредоточиться на главном, мы упростили себе задачу, отказавшись от анализавозможных ошибок при задании аргументов.#include <stdio. h>#include <string. h>#define MAXLINES 5000 /* максимальное число строк */char *lineptr[MAXLINES]; /* указатели на строки текста */int readlines(char *lineptr[], int nlines);void writelines(char *lineptr[], int nlines);void qsort(void *lineptr[], int left, int right,int (*comp)(void *, void *));int numcmp(char *, char *);/* сортировка строк */main(int argc, char *argv[]){int nlines; /* количество прочитанных строк */int numeric = 0; /* 1, если сорт, по числ. знач.
*/if (argc > 1 && strcmp(argv[1], "-n") == 0)numeric = 1;if ((nlines = readlinesdineptr, MAXLINES)) >= 0) {qsort((void **) lineptr, 0, nlines-1,(int (*)(void*, void*))(numeric ? numcmp : strcmp));writelines(lineptr, nlines);return 0;} else {printf("Введено слишком много строк\п");return 1;}}В обращениях к функциям qsort, strcmp и numcmp их имена трактуются как адреса этих функций, поэтомуоператор & перед ними не нужен, как он не был нужен и перед именем массива.Мы написали qsort так, чтобы она могла обрабатывать данные любого типа, а не только строки символов.Как видно из прототипа, функция qsort в качестве своих аргументов ожидает массив указателей, два целыхзначения и функцию с двумя аргументами-указателями. В качестве аргументов-указателей заданы указателиобобщенного типа void*.
Любой указатель можно привести к типу void* и обратно без потериинформации, поэтому мы можем обратиться к qsort, предварительно преобразовав аргументы в void*.Внутри функции сравнения ее аргументы будут приведены к нужному ей типу. На самом деле этипреобразования никакого влияния на представления аргументов не оказывают, они лишь обеспечиваютсогласованность типов для компилятора./* qsort: сортирует v[left]...v[ right] по возрастанию */void qsort(void *v[], int left, int right,int (*comp)(void *, void *)){int i, last;void swap(void *v[], int, int);if (left >= right) /* ничего не делается, если */return; /* в массиве менее двух элементов */swap(v, left, (left + right )/2);last = left;for (i = left+1; i <= right; i++)if ((*comp)(v[i], v[left]) < 0)swap(v, ++last, i);swap(v, left, last);qsort(v, left, last-1, comp);qsort(v, last+1, right, comp);}Повнимательней приглядимся к объявлениям.
Четвертый параметр функции qsort:int (*comp)(void *, void *)сообщает, что comp — это указатель на функцию, которая имеет два аргумента-указателя и выдает результаттипа int.Использование comp в строкеif ((*comp)(v[i], v[left]) < 0)согласуется с объявлением "comp — это указатель на функцию", и, следовательно, *comp — это функция, а(*comp)(v[i], v[left])— обращение к ней. Скобки здесь нужны, чтобы обеспечить правильную трактовку объявления; без нихобъявлениеint *comp(void *, void *) /* НЕВЕРНО */'говорило бы, что comp — это функция, возвращающая указатель на int, а это совсем не то, что требуется.Мы уже рассматривали функцию strcmp, сравнивающую две строки. Ниже приведена функция numcmp,которая сравнивает две строки, рассматривая их как числа; предварительно они переводятся в числовыезначения функцией atof .#include <stdlib.h>/* numcmp: сравнивает s1 и s2 как числа */int numcmp(char *s1, char *s2){double v1, v2;v1 = atof(s1);v2 = atof(s2);if (v1 < v2)return -1;else if (v1 > v2)return 1;elsereturn 0;}Функция swap, меняющая местами два указателя, идентична той, что мы привели ранее в этой главе заисключением того, что объявления указателей заменены на void*.void swap(void *v[], int i, int j){voidtempv[i]v[j]*temp;= v[i];= v[j];= temp;}Программу сортировки можно дополнить и множеством других возможностей; реализовать некоторые из нихпредлагается в качестве упражнений.Упражнение 5.14.
Модифицируйте программу сортировки, чтобы она реагировала на параметр -r,указывающий, что объекты нужно сортировать в обратном порядке, т. е. в порядке убывания. Обеспечьте,чтобы -r работал и вместе с -n.Упражнение 5.15. Введите в программу необязательный параметр -f, задание которого делало бынеразличимыми символы нижнего и верхнего регистров (например, а и А должны оказаться при сравненииравными).Упражнение 5.16. Предусмотрите в программе необязательный параметр -d, который заставит программупри сравнении учитывать только буквы, цифры и пробелы.
Организуйте программу таким образом, чтобы этотпараметр мог работать вместе с параметром -f.Упражнение 5.17. Реализуйте в программе возможность работы с полями: возможность сортировки по полямвнутри строк. Для каждого поля предусмотрите свой набор параметров. Предметный указатель этой книги 8упорядочивался с параметрами: -df для терминов и -n для номеров страниц.5.12. Сложные объявленияИногда Си ругают за синтаксис объявлений, особенно тех, которые содержат в себе указатели на функции.Таким синтаксис получился в результате нашей попытки сделать похожими объявления объектов и ихиспользование.














