Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Изучаем Cи (2001) [rus]

.pdf
Скачиваний:
170
Добавлен:
16.08.2013
Размер:
3.13 Mб
Скачать

fclose(), которой надо передать указательна закрываемый файл.

Попробуйте взять файл l21.c с исходным текстом, запустить программу, текст которой показан в листинге 6.1, и посмотрите, что окажется в файле z.c. Помните, что программа и файл l21.c должны находиться в одной папке.

Массивы указателей

Программа, с которой мы познакомились в предыдущем разделе, хотя и выполняет стоящую перед ней задачу, но очень неудобна в работе, потому что имя каждого нового файла приходится включать в исходный текст и снова его компилировать. А хочется, чтобы программа, увидев в командной строке что-то вроде

Программа.exe имя_файла.с имя_результата.с

сама открыла файл имя_файла.с, читала бы каждую строчку, предваряла символом табуляции и записывала результат в файл имя_результата.с.

Такая возможность конечно же есть, но чтобы ей воспользоваться, придется познакомиться поближе с функцией main(), которая умеет читать командную строку, то есть параметры, введенные при вызове программы.

Пусть наша программа сдвига текста вправо называется rshift.exe и вызывается как

rshift.exe имя_файла.с имя_результата.с .

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

Вот для чтения этих аргументов как раз и создана функция main(). Ее прототип выглядит так:

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

114

Параметр argc на единицу больше числа аргументов, переданных программе. Это «на единицу больше» возникает из-за того, что один аргумент (имя программы) передается ей всегда. Значит, argc не может быть меньше единицы.

Второй параметр функции main char *argv[] — требует более подробных объяснений, потому что мы с ним до сих пор не встречались. Нам уже хорошо знакомы массивы символов, которые объявляются как char argv[], нам также понятны указатели на char, которые объявляются как char *argv. Но что такое char *argv[]? Легко догадаться, что это массив, элементами которого служат указатели, то есть массив указателей. Запуская программу с двумя аргументами в командной строке

rshift.exe имя_файла.с имя_результата.с,

мы получаем значение argc, равное трем и массив трех указателей argv[0], argv[1], argv[2], первый из которых направлен на имя программы, а два других — на параметры командной строки (см. рис. 6.1)

Рис. 6.1 Так хранятся аргументы командной строки

Чтобы лучше представлять себе, как обрабатываются аргументы командной строки, напишем простенькую программу (см. листинг 6.2), которая выводит на экран все аргументы командной строки, число которых равно argc.

Листинг 6.2

#include <stdio.h>

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

115

int i;

for(i=0;i<argc;i++)

printf("%s\n",argv[i]);

return 0;

}

Если, например, программа запускалась с двумя параметрами

имя_программы.exe file1 file2 ,

то argc равен 3 и функции main() станут доступны три указателя на строки: argv[0], argv[1] и argv[2]. Первый argv[0] укажет на полный путь к программе1, второй argv[1] — на первый аргумент, то есть file1, третий argv[2] — на file2.

Если программа находится в директории G:\CBOOK\SOURCE\, то после запуска с параметрами file1 и

file2

имя_программы.exe file1 file2

она выведет на экран три строки — полный путь к программе и два параметра:

G:\CBOOK\SOURCE\имя_программы.EXE

file1

file2

Теперь можно вернуться к задаче, с которой начиналась эта глава, — сдвигу исходного текста программы вправо. Ясно, что имена файлов (исходного и получившегося в результате сдвига) можно передать в командной строке программы, а их чтение может выглядеть так, как показано в листинге 6.3.

1 Некоторые компиляторы покажут только имя программы, а некоторые — не покажут ничего

116

Листинг 6.3.

#include <stdio.h> #define BSIZE 200

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

FILE *in, *out; if (argc <3) {

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

}

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

if(in==NULL){

printf("Не открывается файл %s\n", argv[1]);

return 1;

}

out=fopen(argv[2],"wb");

buf[0]='\t'; while(fgets(buf+1,BSIZE-1,in) != NULL)

fputs(buf,out);

fclose(in);

fclose(out); return 0;

}

В этой программе приняты определенные меры предосторожности. Во-первых, проверяется число параметров. Если argc меньше трех, программа не сможет работать, ведь ей нужны имена двух файлов. Поэтому при argc < 3

117

выводится сообщение «Слишком мало параметров», и работа завершается.

Во-вторых, проверяется, удачно ли отрыт файл, на имя которого указывает argv[1]. Если написать в командной строке имя несуществующего файла, то функция fopen(argv[1],"rb") возвратит NULL, потому что нельзя прочитать файл, которого нет. В этом случае программа также не будет работать.

Что касается второго файла, то программа будет работать в любом случае: если файла с указанным именем не существует, она создаст его. Если же файл уже есть, программа его уничтожит и на его месте создаст новый.

Чтобы не потерять нужный файл, необходимо каждый раз проверять, существует ли файл с таким именем, и если да — спрашивать, можно ли его уничтожить. С такой задачей справится следующий фрагмент программы:

int notexist, ans;

notexist=0;

if((out=fopen(argv[2],"rb")) == NULL){

notexist=1;

fclose(out);

}

if(notexist){

out=fopen(argv[2],"wb");

}

else{

printf("Удалить %s Y/N?\n",argv[2]);

ans=getchar();

if(ans == 'Y' || ans == 'y')

out=fopen(argv[2],"wb");

else return 1;

118

}

Чтобы выяснить, существует ли файл, мы сначала пытаемся открыть его для чтения. Если это удается, то

(fopen(argv[2], "rb") не возвращает NULL. Значит,

файл, на имя которого указывает argv[2], существует.

Состояние файла запоминается в переменной notexist и файл закрывается (fclose(out)). Если файла с таким именем нет, notexist принимает значение 1, выполняется условие if(notexist), и просто создается файл с указанным именем (параметр "wb").

Если файл существует, notexist=0, и выполняется ветка else{} условной инструкции, в которой спрашивается, можно ли удалить файл. В ответ нужно ввести символ, который (после нажатия Enter) запишется в переменную ans. Если ans равна 'Y' или 'y' (от английского yes — да), файл будет уничтожен. Если же файл изменить нельзя, то программа завершит работу (return 1).

В рассмотренном отрывке программы нам встретилась новая функция getchar(), возвращающая значение символа, введенного с клавиатуры (или, как говорят, стандартного устройства ввода). У функции getchar() нет параметров, она лишь возвращает значение int. Казалось бы, функция, читающая с клавиатуры символ, должна возвращать значение char, но тогда непонятно, как закончить ввод. Либо он будет бесконечным, либо один из символов придется считать признаком конца ввода. Но в значении типа int, возвращаемом getchar(), уместится число, отличное от любого символа. В языке Си признак конца ввода называется EOF, и с помощью getchar() можно вводить с клавиатуры длинные последовательности символов и показывать их на экране, как в программе, показанной в листинге 6.4

Листинг 6.4

#include <stdio.h>

int main() {

119

int c;

while ((c=getchar()) != EOF) { putchar(c);

}

return 0;

}

Символ вводится с клавиатуры, и после нажатия клавиши Enter его значение присваивается переменной c. Далее переменная сравнивается с EOF (символ EOF получается, если нажать клавишу Ctrl и не отпуская ее, нажать Z), и если равенства нет, символ выводится на экран функцией putchar(c). Если же значение, возвращенное getchar(), равно EOF, то программа заканчивает работу.

Присваивание с=getchar() помещено в скобки, потому что оператор != имеет больший приоритет, чем присваивание =. В отсутствии скобок сначала вычислялось бы выражение getchar() != EOF, которое ложно (равно нулю) лишь когда getchar() возвращает EOF. Во всех остальных случаях это выражение истинно, следовательно c будет равно единице, и программа в ответ на любой символ будет печатать две смешные рожицы (убедитесь в этом сами).

Задача 6.1 Напишите программу, которая сдвигала бы текст влево, то есть удаляла бы все символы табуляции, вставленные программой, которую мы разобрали в этом разделе

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

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

Листинг 6.5

#include <stdio.h>

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

char *p;

120

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

1 Совмещение раскрытия ссылки и увеличения указателя уже встречалось нам в разделе «Указатели и массивы» главы 5

121

*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[], с которым мы недавно познакомились, содержит несколько указателей на строки, да и сами строки (аргументы командной строки), тоже можно считать частью этой структуры данных.

122

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

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

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

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

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

Листинг 6.6

#include <stdio.h> #define BSIZE 200

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

int сh;

FILE *in, *tmp; if (argc < 2) {

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

}

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

123

Соседние файлы в предмете Программирование на C++