Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс ПЯВУ 2 сем / Лекции 2 сем / Л№35.Аргументы кс / Лекция № 30.Аргументы командной строки..odt
Скачиваний:
12
Добавлен:
17.04.2015
Размер:
58.31 Кб
Скачать

19. Указатели на указатели

Программа, выводящая на экран аргументы командной строки (см. листинг 6.2) может быть написана немного по-другому:

Листинг 6.5.

#include <stdio.h>

int main(int argc, char *argv[]){

char *p;

while((p=*argv++) != NULL))

printf("%s\n",p);

return 0;

}

Хотя, на первый взгляд, она стала просто неузнаваемой и напоминает смесь букв, костей, звездочек и спичек.

Но давайте разберемся. Если объявление char *argv[] задает массив указателей на char, то argv - указатель на нулевой элемент этого массива. Получается, что argv - указатель на указатель. Применяя к argv раскрытие ссылки, получим *argv, что равно нулевому элементу массива, то есть argv[0]. Первым элементом массива, записанным с помощью оператора раскрытия ссылки, будет *(argv+1). И так далее вплоть до argv[argc], который, согласно стандарту языка, равен нулевому указателю, т.е. NULL.

В цикле while((p=*argv++)...)(см. листинг 6.5) указателю p присваивается значение *argv, а затем argv увеличивается на единицу1. Значит, в цикле while() перебираются все указатели массива, вплоть до argv[argc]. В этом случае p станет равным NULL, и цикл завершится. Заметим, что проверка (p=*argv++) != NULL) на самом деле не нужна, потому что действительное значение NULL - обычный ноль, то есть число, все биты которого нулевые. Обозначение NULL вводится для того, чтобы явно показать, что в работе участвуют указатели, а не числа. Поэтому условие в цикле может быть записано просто как while(p=*argv++).

Раз уж в этом разделе зашла речь о массивах указателей и указателе на указатель, вспомним, что до сих пор параметры функции main() записывались как main(int argc, char *argv[]). Но теперь мы уже знаем, что вместо массива функции передается указатель на его нулевой элемент. Значит, в списке параметров вместо char a[] (так обозначается массив) логично записать char *a (указатель). Первый вариант более понятен тем, кто программировал на Бейсике или Фортране, а второй (char *) лучше соответствует духу языка Си, потому что функция действительно получает указатель, а не сам массив.

Если вместо массива передается указатель, то что передается вместо массива указателей? Очевидно, указатель на указатель (argv, как выяснилось, и есть указатель на указатель). Значит, список параметров функции main() можно записать и так:

main(argc, char **argv).

То есть, указатель на указатель объявляется в языке Си с помощью двух звездочек. Объявление int **a; говорит компилятору, что **a - это что-то типа int.

Чтобы лучше понять двойное раскрытие ссылки (**), вернемся еще раз к массиву указателей *argv[]. Имя argv указывает, как и в любом массиве, на его нулевой элемент. Значит, *argv - и есть нулевой элемент массива, то есть argv[0]. Но argv[0] - это указатель на первый параметр командной строки, и к нему тоже можно применить оператор раскрытия ссылки *(argv[0]). В результате получится, что *(argv[0]) равно *(*argv) или **argv.

Задача 6.2.

Если параметры командной строки выглядят так же, как на рис. 6.1, то чему равен **argv, **(argv+1), **(argv+2)?

Как обычно, указатель нельзя путать с массивом. Простое объявление int **a выделяет память только для одного указателя, а массив *argv[], с которым мы недавно познакомились, содержит несколько указателей на строки, да и сами строки (аргументы командной строки), тоже можно считать частью этой структуры данных.

Файлы - не массивы!

Программа, созданная в разделе "Массивы указателей" прочитала имена двух файлов из командной строки, сдвинула исходный файл вправо и записала результат в выходной файл. Получилоcь это не очень красиво: пришлось заботиться о том, чтобы не уничтожить нужный файл, и если имя выходного файла было выбрано по ошибке, программа прекращала работу.

Возникает мысль: а нельзя ли записать результат работы в тот же файл, не прибегая к созданию дополнительного выходного файла? Но тогда последовательность действий меняется: нужно создать временный файл, записать туда сдвинутый текст и затем скопировать временный файл в исходный.

Но - спросите вы - какая польза от временного файла, ведь его имя тоже может совпасть с именем уже существующего? К счастью, в языке Си есть стандартная функция tmpfile(), которая создает никому не мешающий временный файл с уникальным именем.

Теперь посмотрим на реализацию этой идеи (листинг 6.6)

Листинг 6.6.

#include <stdio.h>

#define BSIZE 200

int main(int argc, char *argv[]){

char buf[BSIZE];

int ch;

FILE *in, *tmp;

if (argc < 2) {

printf("Cлишком мало параметров\n");

return 1;

}

in=fopen(argv[1],"rb");

if(in==NULL){

printf("Не открывается файл %s\n",

argv[1]);

return 1;

}

tmp = tmpfile();

buf[0]='\t';

while(fgets(buf+1,BSIZE-1,in) != NULL)

fputs(buf,tmp);

fclose(in);

in = fopen(argv[1],"wb");

fseek(tmp,0l,SEEK_SET);

while ((ch=fgetc(tmp)) != EOF){

fputc(ch,in);

}

fclose(in);

fclose(tmp);

return 0;

}

Начало программы, показанной в листинге 6.6, хорошо нам знакомо. Единственное отличие от предыдущей версии в том, что теперь проверяется условие argc<2, потому что в командной строке нужно ввести имя только одного, исходного файла.

После его открытия in=fopen(argv[1],"rb") создается временный файл tmp = tmpfile(). Далее идет знакомый фрагмент программы, где читается строчка в исходном файле, и переписывается (уже с символом табуляции) во временный файл.

Далее исходный файл закрывается (fclose(in)) и создается вновь: in = fopen(argv[1],"wb"). Теперь это пустой файл, с тем же, правда, именем, где должны оказаться результаты работы.

Следующая строка

fseek(tmp,0l,SEEK_SET);

до сих пор нам не встречалась, но она крайне важна, потому что проливает свет на природу файлов. Дело в том, что файлы - не массивы. Массив - типичная структура данных с произвольным доступом. Это значит, что доступен каждый его элемент. Мы можем написать a=dig[0] или a=dig[10]. Файлы - совсем другое дело. Чтобы прочитать десятую строку текста, хранимого в файле, необходимо перед этим прочитать предыдущие девять строк. Файл похож на кассету: чтобы услышать десятую песню, необходимо прослушать девять предыдущих или... перемотать ленту.

Вот таким перемотчиком файлов и служит функция fseek(). Можно представить себе, что с каждым открытым файлом связан специальный флажок, которым помечается место, доступное в данный момент для чтения/записи. При открытии файла флажок расположен в самом его начале. Прочитали одну строку - и флажок переместился к началу следующей строки. Когда прочитаны все строки, флажок оказывается в конце файла и при попытке чтения функция fgets() выдает NULL.

То же самое происходит и при записи строк в файл: новая строка пишется туда, где в данный момент находится флажок. Записали строчку, и флажок переместился к ее концу.

Значит, после записи сдвинутого текста флажок переместится в конец файла tmp. Если теперь мы вздумаем читать временный файл, то флажок, установленный в его конце, переместится дальше, где просто ничего нет!

Значит, перед тем как копировать временный файл в исходный, необходимо переместить флажок в начало, именно это и делает функция fseek(tmp,0L, SEEK_SET). Ее первый аргумент tmp - указатель файла, 0L - константа (длинное целое), равная смещению флажка, а SEEK_SET - константа, показывающая, что смещение отсчитывается от начала файла. Аргументы функции fseek() таковы, что временный файл "будет перемотан", и флажок окажется в самом его начале.

Теперь можно читать файл tmp и переписывать его содержимое в заново созданный входной файл. Программа, показанная в листинге 6.5, использует для копирования файлов другие функции - fgetc() и fputc(), очень похожие на getchar() и putchar() (см. листинг 6.4). И та и другая пара функций читает/пишет по символу при каждом обращении к ним. Разница в том, что fgetc()/fputc() читают/пишут из файла в файл, а не с клавиатуры на экран. Можно применить и копирование строк fgets()/fputs().