что как A[5][5], так и B[5][5] являются законными ссылками
на отдельное число типа INT. Но A - настоящий массив: под
него отводится 100 ячеек памяти и для нахождения любого ука-
занного элемента проводятся обычные вычисления с прямоуголь-
ными индексами. Для B, однако, описание выделяет только 10
указателей; каждый указатель должен быть установлен так,
чтобы он указывал на массив целых. если предположить, что
каждый из них указывает на массив из 10 элементов, то тогда
где-то будет отведено 100 ячеек памяти плюс еще десять ячеек
для указателей. Таким образом, массив указателей использует
несколько больший объем памяти и может требовать наличие яв-
ного шага инициализации. Но при этом возникают два преиму-
щества: доступ к элементу осуществляется косвенно через ука-
затель, а не посредством умножения и сложения, и строки мас-
сива могут иметь различные длины. Это означает, что каждый
элемент B не должен обязательно указывать на вектор из 10
элементов; некоторые могут указывать на вектор из двух эле-
ментов, другие - из двадцати, а третьи могут вообще ни на
что не указывать.
Хотя мы вели это обсуждение в терминах целых, несомнен-
но, чаще всего массивы указателей используются так, как мы
продемонстрировали на функции 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');
\)
· 121 -
Так как 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);
\)
Теперь может быть развита основная модель, иллюстрирую-
щая дальнейшее использование указателей. Предположим, что
нам надо предусмотреть два необязательных аргумента. Один
утверждает: “напечатать все строки за исключением тех, кото-
рые содержат данную комбинацию”, второй гласит: “перед каж-
дой выводимой строкой должен печататься ее номер”.
· 122 -
Общепринятым соглашением в “с”-программах является то,
что аргумент, начинающийся со знака минус, вводит необяза-
тельный признак или параметр. Если мы, для того, чтобы сооб-
щить об инверсии, выберем -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 обращается к этим функциям через их указатели.
мы урезали обработку ошибок в аргументах с тем, чтобы сосре-
доточиться на главных вопросах.