Хотя мы вели это обсуждение в терминах целых, несомненно, чаще всего массивы указателей используются так, как мы продемонстрировали на функции MONTH_NAME, - для хранения символьных строк различной длины.
Упражнение 5-6.
Перепишите функции DAY_OF_YEAR и MONTH_DAY, используя вместо индексации указатели.
120
5.11. Командная строка аргументов
Системные средства, на которые опирается реализация языка “с”, позволяют передавать командную строку аргументов или параметров начинающей выполняться программе. Когда функция MAIN вызывается к исполнению, она вызывается с двумя аргументами. Первый аргумент (условно называемый ARGC) указывает число аргументов в командной строке, с которыми происходит обращение к программе; второй аргумент (ARGV) является указателем на массив символьных строк, содержащих эти аргументы, по одному в строке. Работа с такими строками - это обычное использование многоуровневых указателей.
Самую простую иллюстрацию этой возможности и необходимых при этом описаний дает программа ECHO, которая просто печатает в одну строку аргументы командной строки, разделяя их пробелами. Таким образом, если дана команда
ECHO HELLO, WORLD то выходом будет HELLO, WORLD по соглашению ARGV[0] является именем, по которому вызывается программа, так что ARGC по меньшей мере равен 1. В приведенном выше примере ARGC равен 3, а ARGV[0], ARGV[1] и ARGV[2] равны соответственно “ECHO”, “HELLO,” и “WORLD”.
Первым фактическим агументом является ARGV[1], а последним ARGV[ARGC-1]. Если ARGC равен 1, то за именем программы не следует никакой командной строки аргументов. Все это показано в ECHO:
MAIN(ARGC, ARGV) /* ECHO ARGUMENTS; 1ST VERSION */ INT ARGC;
CHAR *ARGV[];
\( INT I;
FOR (I = 1; I < ARGC; I++) PRINTF(“%S%C”, ARGV[I], (I<ARGC-1) ? ' ' : '\N');
\)
Поскольку ARGV является указателем на массив указателей, то существует несколько способов написания этой программы, использующих работу с указателем, а не с индексацией массива.
Мы продемонстрируем два варианта.
MAIN(ARGC, ARGV) /* ECHO ARGUMENTS; 2ND VERSION */ INT ARGC;
CHAR *ARGV[];
\( WHILE (--ARGC > 0) PRINTF(“%S%C”,*++ARGV, (ARGC > 1) ? ' ' : '\N');
\)
Так как ARGV является указателем на начало массива строк-аргументов, то, увеличив его на 1 (++ARGV), мы вынуждаем его указывать на подлинный аргумент ARGV[1], а не на ARGV[0].
Каждое последующее увеличение передвигает его на следующий аргумент; при этом *ARGV становится указателем на этот аргумент. одновременно величина ARGC уменьшается; когда она обратится в нуль, все аргументы будут уже напечатаны.
Другой вариант: MAIN(ARGC, ARGV) /* ECHO ARGUMENTS; 3RD VERSION */ INT ARGC;
CHAR *ARGV[];
\( WHILE (--ARGC > 0) PRINTF((ARGC > 1) ? “%S” : “%S\N”, *++ARGV);
\)
Эта версия показывает, что аргумент формата функции PRINTF может быть выражением, точно так же, как и любой другой. Такое использование встречается не очень часто, но его все же стоит запомнить.
Как второй пример, давайте внесем некоторые усовершенствования в программу отыскания заданной комбинации символов из главы 4. Если вы помните, мы поместили искомую комбинацию глубоко внутрь программы, что очевидно является совершенно неудовлетворительным. Следуя утилите GREP системы UNIX, давайте изменим программу так, чтобы эта комбинация указывалась в качестве первого аргумента строки.
#DEFINE MAXLINE 1000 MAIN(ARGC, ARGV) /* FIND PATTERN FROM FIRST ARGUMENT */ INT ARGC;
CHAR *ARGV[];
\( CHAR LINE[MAXLINE];
IF (ARGC != 2) PRINTF (“USAGE: FIND PATTERN\N”);
ELSE WHILE (GETLINE(LINE, MAXLINE) > 0) IF (INDEX(LINE, ARGV[1] >= 0) PRINTF(“%S”, LINE);
\)
Теперь может быть развита основная модель, иллюстрирующая дальнейшее использование указателей. Предположим, что нам надо предусмотреть два необязательных аргумента. Один утверждает: “напечатать все строки за исключением тех, которые содержат данную комбинацию”, второй гласит: “перед каждой выводимой строкой должен печататься ее номер”.
Общепринятым соглашением в “с”-программах является то, что аргумент, начинающийся со знака минус, вводит необязательный признак или параметр. Если мы, для того, чтобы сообщить об инверсии, выберем -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 Нужно, чтобы необязательные аргументы могли располагаться в произвольном порядке, и чтобы остальная часть программы не зависела от количества фактически присутствующих аргументов. в частности, вызов функции INDEX не должен содержать ссылку на ARGV[2], когда присутствует один необязательный аргумент, и на ARGV[1], когда его нет. Более того, для пользователей удобно, чтобы необязательные аргументы можно было объединить в виде:
FIND -NX THE вот сама программа:
#DEFINE MAXLINE 1000 MAIN(ARGC, ARGV) /* FIND PATTERN FROM FIRST ARGUMENT */ 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;
123
CASE 'N': NUMBER = 1;
BREAK;
DEFAULT: PRINTF(“FIND: ILLEGAL OPTION %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.
Упражнение 5-7.
Напишите программу ADD, вычисляющую обратное польское выражение из командной строки. Например, ADD 2 3 4 + * вычисляет 2*(3+4).
Упражнение 5-8.
Модифицируйте программы ENTAB и DETAB (указанные в качестве упражнений в главе 1) так, чтобы они получали список табуляционных остановок в качестве аргументов. Если аргументы отсутствуют, используйте стандартную установку табуляций.
Упражнение 5-9.
Расширьте ENTAB и DETAB таким образом, чтобы они воспринимали сокращенную нотацию ENTAB M +N
124
означающую табуляционные остановки через каждые N столбцов, начиная со столбца M. Выберите удобное (для пользователя) поведение функции по умолчанию.
Упражнение 5-10.
Напишите программу для функции TAIL, печатающей последние N строк из своего файла ввода. Пусть по умолчанию N равно 10, но это число может быть изменено с помощью необязательного аргумента, так что
TAIL -N печатает последние N строк. программа должна действовать рационально, какими бы неразумными ни были бы ввод или значение N. Составьте программу так, чтобы она оптимальным образом использовала доступную память: строки должны храниться, как в функции SORT, а не в двумерном массиве фиксированного размера.
5.12. Указатели на функции В языке “с” сами функции не являются переменными, но имеется возможность определить указатель на функцию, который можно обрабатывать, передавать другим функциям, помещать в массивы и т.д. Мы проиллюстрируем это, проведя модификацию написанной ранее программы сортировки так, чтобы при задании необязательного аргумента -N она бы сортировала строки ввода численно, а не лексикографически.
Сортировка часто состоит из трех частей - сравнения, которое определяет упорядочивание любой пары объектов, перестановки, изменяющей их порядок, и алгоритма сортировки, осуществляющего сравнения и перестановки до тех пор, пока объекты не расположатся в нужном порядке. Алгоритм сортировки не зависит от операций сравнения и перестановки, так что, передавая в него различные функции сравнения и перестановки, мы можем организовать сортировку по различным критериям.
Именно такой подход используется в нашей новой программе сортировки.
Как и прежде, лексикографическое сравнение двух строк осуществляется функцией STRCMP, а перестановка функцией SWAP; нам нужна еще функция NUMCMP, сравнивающая две строки на основе численного значения и возвращающая условное указание того же вида, что и STRCMP. Эти три функции описываются в MAIN и указатели на них передаются в SORT. В свою очередь функция SORT обращается к этим функциям через их указатели.
мы урезали обработку ошибок в аргументах с тем, чтобы сосредоточиться на главных вопросах.
#DEFINE LINES 100 /* MAX NUMBER OF LINES TO BE SORTED */ MAIN(ARGC, ARGV) /* SORT INPUT LINES */ INT ARGC;
CHAR *ARGV[];
\( CHAR LINEPTR[LINES]; / POINTERS TO TEXT LINES */ INT NLINES; /* NUMBER OF INPUT LINES READ */ INT STRCMP(), NUMCMP(); /* COMPARSION FUNCTIONS */ INT SWAP(); /* EXCHANGE FUNCTION */ INT NUMERIC = 0; /* 1 IF NUMERIC SORT */
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: SORT(V, N, COMP, EXCH) /* SORT STRINGS V[0] ... V[N-1] */ CHAR V[]; / INTO INCREASING ORDER */ INT N;
INT (*COMP)(), (*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, сравнивающую две строки по первому численному значению: NUMCMP(S1, S2) /* COMPARE S1 AND S2 NUMERICALLY */ CHAR *S1, *S2;