- •2. Механизм передачи аргументов командной строки в программу.
- •3. Доступ к аргументам командной строки
- •4. Параметры операционной системы.
- •6. Расшифровка командной строки.
- •7. Запуск и Окончание Процесса программы.
- •8. Синтаксические Соглашения Аргументов Программы
- •9. Опции Программ Синтаксического анализа
- •10. Синтаксический анализ Длинных Опций
- •11. Переменные среды
- •12. Доступ к Среде
- •13. Стандартные Переменные среды
- •14. Завершение Программы
- •15. Прерывание выполнения Программы
- •16. Внутренняя организация Окончания
- •17. Ввод/вывод файлов с помощью командной строки.
- •18. Массивы указателей
- •19. Указатели на указатели
- •20. Открытие файла
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().