Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
fopen( ). Второй аргумент назван "offset" (вот почему мы выбрали данное имя для переменной). Этот аргумент сообщает, как далеко следует передвинуться от начальной точки (см. ниже); он должен иметь значение типа long, которое может быть положительным (движение вперед) или отрицательным (движение назад). Третий аргумент является кодом, определяющим начальную точку: Код Положение в файле 0 начало файла 1 текущая позиция 2 конец файла Функция fseek( ) возвращает 0, если все хорошо, и -1, если есть ошибка, например попытка 306 перемещаться за границы файла. Теперь мы можем разъяснить наш маленький цикл: while(fseek(fp, offset++, 0)==0) putchar(getc(fp)); Поскольку переменная offset инициализирована нулем, при первом прохождении через цикл мы имеем выражение fseek(fp, OL, 0) означающее, что мы идем в файл, на который ссылается указатель fp, и находим байт, отстоящий на 0 байт от начала, т.е. первый байт. Затем функция putchar( ) печатает содержимое этого байта. При следующем прохождении через цикл переменная offset увеличивается до 1L, и печатается следующий байт. Посуществу, переменная offset действует подобно индексу для элементов файла. Процесс продолжается до тех пор, пока offset нe попытается попасть в fseek( ) после конца файла. В этом случае fseek( ) возвращает значение - 1 и цикл прекращается. Этот последний пример чисто учебный. Нам нe нужно использовать fseek( ), потому что getc( ) так или иначе проходит через файл байт за байтом; fseek( ) приказала getc( ) "посмотреть" туда, куда она сама уже собиралась посмотреть. Вот пример (рис. 15.2), в котором выполняется что-то несколько более необычное (Мы благодарим Вильяма Шекспира за этот пример в пьесе "Двенадцатая ночь"). /* чередование печати в прямом и обратном направлениях */ #include main(number, names) /* вам не нужно применять argc и argv */ int number; char *names[ ]; { FILE *fp; long offset = 0L; if(number < 2) puts(" Мне нужно имя файла в качестве аргумента."); else { if(fp = fopen(names[l], "r")) == 0) printf(" Я не могу открыть %s.\n", names[l]); else { while(fseek(fp, offset++, 0) == 0) { putchar(getc(fp)); if(fseek(fp, -(offset + 3), 2) == 0) putchar(getc(fp)); } fclose(fp); } } } РИС. 15.2. Программа, чередующая печать в прямом и обратном направлениях. Применение этой программы к файлу, содержащему имя "Мальволио", дает такой приятный результат: МоаилльоввоьллиаоМ Наша программа печатает первый символ файла, затем последний, затем второй, затем предшествующий последнему и т.д. Мы только добавили вот эти строки в последнюю программу: if(fseek(fp, -(offset + 3), 2) == 0) putchar(getc(fp)); Код 2 в операторе предполагает, что мы будем считать позиции от конца файла. Знак минус означает счет в обратном направлении. +3 стоит здесь потому, что мы начинаем с последнего регулярного символа файла и пропускаем несколько символов "новая строка" и EOF в самом 307 конце файла. (Точное значение этой корректировки зависит от типа системы. Наши файлы имеют в конце по два символа новой строки, за которыми следуют два EOF, поэтому мы как раз их и обходим.) Таким образом, эта часть программы чередует печать в обратном направлении и печать в прямом направлении. Следует заметить, что в некоторых системах может не предусматриваться код 2 для fseek( ). Теперь оставим на некоторое время файлы и перейдем к другому разделу библиотеки. ПРОВЕРКА И ПРЕОБРАЗОВАНИЕ СИМВОЛОВ Далее Содержание Заголовочный файл ctype.h содержит несколько функций макроопределений, которые проверяют, к какому классу принадлежат символы. Функция isalpha(c), например, возвращает ненулевое значение (истина), если с является символом буквы, и нуль (ложь), если символ не является буквой. Таким образом, isalpha('S') != 0, но isalpha('#') ==0 Ниже перечислены функции, чаще всего находящиеся в этом файле. В каждом случае функция возвращает ненулевое значение, если с принадлежит к опрашиваемому классу, и нуль в противном случае. ФУНКЦИЯ ПРОВЕРЯЕТ, ЯВЛЯЕТСЯ ЛИ С isalpha(c) буквой isdigit(c) цифрой islower(c) строчной буквой isspace(c) пустым символом (пробел, табуляция или новая строка) isupper(c) прописной буквой Ваша система может иметь дополнительные функции, такие как ФУНКЦИЯ ПРОВEРЯEТ, ЯВЛЯЕТСЯ ЛИ С isalnum(c) алфавитноцифровым (буква или цифра) isascii(c) кодом ASCII (0-127) iscntrl(c) управляющим символом ispunct(c) знаком пунктуации Еще две функции выполняют преобразования: toupper(c) преобразует с в прописную букву tolower(c) преобразует с в строчную букву В некоторых системах преобразование выполняется только в случае, если символ находится в регистре (прописных или строчных букв), противоположном тому, с которого следует начинать. Однако надежнее предварительно проверить регистр. Ниже (рис. 15.2.) дана программа, использующая некоторые из этих функций для преобразования всего файла в прописные или строчные буквы, по вашему желанию. Для получения небольшого разнообразия используем диалоговый подход, вместо того чтобы применять аргументы командной строки для снабжения программы информацией. 308 /* преобразование строчных букв в прописные и обратно */ #include #include #define UPPER 1 #define LOWER 0 main( ) { int crit; /* для установки регистра прописных или строчных букв */ char file1[14], file2[14]; /* имена входного и выходного файлов */ crit = choose( ); /* выбирает прописные или строчные буквы */ getfiles(file1, file2); /* получаст имена файлов */ conv(file1, file2, crit); /* выполняет преобразование */ } choose( ) { int ch; printf("Программа преобразует весь файл в прописные буквы или \n"); printf(" в строчные буквы. Вводит U, если нужны прописные буквы\n"); printf(" или вводит L, если нужны строчные буквы. \n"); while((ch=getchar( ))!='U' && ch!='u' && ch!='L' && ch!='l') printf(" Введите, пожайлуста, U или L.\n"); while(getchar( )!='\n') ; /* сбрасывает последний символ новой строки */ if(ch =='U' || ch =='u') { printf(" Все в порядке, есть регистр прописных букв."); return(UPPER); else { printf(" Все в порядке, есть регистр строчных букв."); return(LOWER); } } getfiles(namel, name2); char *namel, name2; { printf(" Какой файл вы хотите преобразовать?\n"); gets(name1); printf(" Это\" %s\" .\n", name1); printf("Какое имя вы хотите выбрать для преобразуемого файла?\n"); while(strcmp(gets(name2), name1) == NULL) printf(" Выберите другое имя.\n" ); printf(" Ваш выходной файл\" %s \".\n", name2); } conv(name1, name2, crit); char *name1, name2; int crit; { int ch; FILE *f1, *f2; if((f1 = fopen(name1, "r" )) == NULL) printf(Извините, я не могу открыть % s. До свидания.\n", name1); else { puts(" Итак, начнем!"); f2 = fopen(name2, "w"); while((ch = getc(f1)) != EOF) if(crit == UPPER) ch = islower(ch) ? toupper(ch) : ch; else ch = isupper(ch) ? tolower(ch) : ch; putc(ch, f2); } fclosc(f2); fclosc(f1); puts("Сделано!"); } } РИС. 15.3. Программа преобразования строчных букв в прописные и обратно. Мы разделили программу на три части: получение от пользователя указания о виде преобразования, получение имени входного и выходного файлов и выполнение преобразования. Чтобы осуществить все это, мы создали разные функции для каждой части. Функция choose( ) довольно проста за исключением, может быть, цикла 309 while(getchar( ) != '\n'); Этот цикл включен для решения проблемы, с которой мы столкнулись в гл. 14. Когда пользователь отвечает на вопрос о виде преобразования, скажем, буквой U, он нажимает клавишу U, а затем клавишу [ввод], которая передает '\n'. Первоначальная функция getchar( ) извлекает U, но оставляет '\n' для следующего чтения строки. Функция gets(), входящая в getnames(), интерпретировала бы '\n' как пустую строку, поэтому мы использовали малый цикл while, чтобы избавиться от символа "новая строка". Действительно, простая getchar( ), сделала бы это, если бы пользователь непосредственно за U нажимал бы [ввод]. Но наша версия, кроме того, предусматривает возможность нажать на клавишу пробела несколько раз перед [ввод]. В функции getnames( ) для вас не должно быть сюрпризов. Учтите, что мы запрещаем пользователю применять одинаковые имена для выходного и входного файлов. Стандартная версия функции fopen( ) не позволяет вам и читать и записывать один и тот же файл, если вы открыли его один раз. Функция conv( ) является функцией копирования с выполнением преобразования. Значение crit используется для определения требуемого преобразования. Работа выполняется простым условным оператором, таким как ch = islower(ch) ? toupper(ch) : ch; Он проверяет, является ли ch строчной буквой. Если да, то символ преобразуется в прописную букву. Если нет, остается как есть. Макрофункции файла ctype.h предоставляют удобные и полезные средства для программирования. Теперь давайте займемся некоторыми более сложными функциями преобразования. ПРЕОБРАЗОВАНИЯ СИМВОЛЬНЫХ СТРОК: atoi( ), atof( ) Далее Содержание Использование scanf( ) для считывания цифровых значений не является самым надежным способом, поскольку scanf( ) легко внести в заблуждение ошибками пользователей при вводе чисел с клавиатуры. Некоторые программисты предпочитают считывать даже числовые данные как символьные строки и преобразовывать строку в соответствующее числовое значение. Для этого используются функции atoi( ) и atof( ). Первая преобразует строку в целое, вторая - в число с плавающей точкой. Вот (рис. 15.4) образец их использования: /* включение atoi( ) */ #include #define issign(c) (((с) == '-' || (с) == '+') ? (1) : (0)) #define SIZE 10 #define YES 1 #define NO 0 main( ) { char ch; static char number[SIZE]; int value; int digit = YES; int count = 0; puts(" Введите, пожалуйста, целое."); gets(number); 310 if(number[SIZE - 1] != '\0') { puts("Слишком много цифр; вы уничтожили меня."); exit(1); } while((ch = number[count]) !='0' && digit == YES) if(!issign(ch) && iisdigit(ch) && !isspace(ch)) digit = NO; if(digit == YES) { value = atoi(number); printf(" Число было %d.\n" , value); } else printf(" Это не похоже на целое."); } РИС. 15.4. Программа использования atoi( ). Мы предусмотрели проверку некоторых ошибок. Во-первых, следует посмотреть, умещается ли входная строка в предназначенном для нее массиве. Поскольку number является статическим символьным массивом, он инициализируется нулями. Если последний элемент массива не является нулем, значит что-то неверно, и программа прекращает работу. Здесь мы использовали библиотечную функцию exit( ), которая выводит нас из программы. Немного позже мы расскажем кратко об этой функции. Затем посмотрим, не содержит ли строка что-нибудь кроме пробелов, цифр и алгебраических знаков. Функция отвергает такие строки, как "дерево" или "1.2Е2". Ее устраивает смесь, подобная "3 - 4 + 2", но atoi( ) будет выполнять дальнейший отбор. Вспомним, что ! является операцией отрицания, поэтому !isdigit(c) означает: "с не является цифрой". Строка value = atoi(nuinbcr); показывает, как используется функция atoi( ). Ее аргумент является указателем символьной строки; в этом случае мы применили имя массива number. Функция возвращает целое значение для такой строки. Таким образом, "1234" является строкой из четырех символов и переводится в 1234 - единое число типа int. Функция atoi( ) игнорирует ведущие пробелы, обрабатывает ведущий алгебраический знак, если он есть, и обрабатывает цифры вплоть до первого символа, нс являющегося цифрой. Поэтому наш пример "3 - 4 + 2" был бы превращен в значение 3. Посмотрите "Вопросы" в конце главы для возможного применения этой функции. Функция atof( ) выполняет подобные действия для чисел с плавающей точкой. Она возвращает тип double, поэтому должна быть описана как double в использующей ее программе. Простые версии atof( ) будут обрабатывать числа вида 10.2, 46 и - 124.26. Более мощные версии преобразуют также экспоненциальную запись, т. е. числа, подобные 1.25Е - 13. Ваша система может также иметь обратные функции, работающие в противоположном направлении. Функция itoa( ) будет преобразовывать целое в символьную строку, а функция ftoa( ) преобразовывать число с плавающей точкой в символьную строку. ВЫХОД: exit( ) Далее Содержание Функция exit( ) даст вам удобный способ "покинуть" программу. Она часто используется для прекращения работы программы при появлении ошибки. Если к exit( ) обратились из функции, вызванной главной программой, то прекращает работу вся программа, а не только эта функция. В приведенном выше примере с функцией atoi( ) использование exit( ) позволяет нам избежать включения дополнительного оператора else для обхода остатка программы. 311 Приятная способность exit( ) заключается в том, что она закрывает любые файлы, открытые функцией fopen( ). Это делает наш выход из программы более корректным. Аргументом exit( ) является номер кода ошибки. В некоторых системах он может передаваться другой программе, если исходная прекратила работу. Существует соглашение, что 0 указывает на нормальное завершение, в то время как любое другое значение говорит об ошибке. Есть еще одна тема, которую мы хотим обсудить. РАСПРЕДЕЛЕНИЕ ПАМЯТИ: malloc( ) И са11ос( ) Далее Содержание Ваша программа должна предоставить достаточный объем памяти для запоминания используемых данных. Некоторые из этих ячеек памяти распределяются автоматически. Например, мы можем объявить char place[ ] = "Залив Свиной печенки"; и будет выделена память, достаточная для запоминания этой строки. Или мы можем быть более конкретны и запросить определенный объем памяти: int plates[100]; Это описание выделяет 100 ячеек памяти, каждая из которых предназначена для запоминания целого значения. Язык Си не останавливается на этом. Он позволяет вам распределять дополнительную память во время работы программы. Предположим, например, вы пишете диалоговую программу и не знаете заранее, сколько данных вам придется вводить. Можно выделить нужный вам (как вы считаете) объем памяти, а затем, если понадобится, потребовать еще. На рис. 15.5 дан пример, в котором используется функция malloc( ), чтобы сделать именно это. Кроме того, обратите внимание на то, как такая программа применяет указатели. /* добавляет память, если необходимо */ #include #define STOP " " /* сигнал прекращения ввода */ #define BLOCK 100 /* байты памяти */ #define LIM 40 /* предельная длина вводимой строки */ #define MAX 50 /* максимальное число вводимых строк */ #define DRAMA 20000 /* большая задержка времени */ main( ) { char store[BLOCK]; /* исходный блок памяти */ char symph[LIM]; /* приемник вводимых строк */ char *end; /* указывает на конец памяти */ char *starts[MAX]; /* указывает на начала строк */ int index = 0; /* количество вводимых строк */ int count; /* счетчик */ char *malloc( ); /* распределитель памяти */ starts[0] = store; end = starts[0] + BLOCK - 1; puts(" Назовите несколько симфонических оркестром."); puts(" Вводите по одному: нажмите клавишу [ввод] в начале"); puts(" строки для завершения вашего списка. Хорошо, я готова." ); while(strcmp(fgets(symph, LIM, stdin), STOP) != 0 && index < MAX) { if(strlen(symph) > end - starts[index]) { /* действия при недостатке памяти для запоминания вводимых данных*/ puts(" Подождите секунду. Я попробую найти дополнительную память."); starts[index] = malloc(BLOCK); end = starts[index] + BLOCK - 1; 312 for(count = 0; count < DRAMA; count++); puts(" Нашла немного!" ); } strcpy (starts [index], symph); starts[index + 1] = starts[index] + strlen(symph) + 1; if(++index < MAX) printf("Этo %d. Продолжайте, если хотите.\n", index); } puts(" Хорошо, вот что я получила:"); for(count = 0; count < index; count ++) puts(starts[count]); } РИС. 15.5. Программа, добавляющая память по требованию. Вот образец работы программы: Назовите несколько симфонических оркестров оркестров. Вводите их по одному; нажмите клавишу [ввод] в начале строки для завершения нашего списка. Хорошо, я готова. Сан-франциский симфонический. Это 1. Продолжайте, если хотите. Чикагский симфонический Это 2. Продолжайте, если хотите. Берлинский филармонический Это 3. Продолжайте, если хотите. Московский камерный Это 4. Продолжайте, если хотите. Лондонский симфонический Это 5. Продолжайте, если хотите. Венский филармонический Подождите секунду. Я попробую найти дополнительную память. Нашла немного! Это 6. Продолжайте, если хотите. Питтсбургский симфонический Это 7. Продолжайте, если хотите. Хорошо, вот что я получила: Сан-францизкий симфонический Чикагский симфонический Берлинский филармонический Московский камерный Лондонский симфонический Венский филармонический Питтсбургский симфонический Сначала давайте посмотрим, что делает функция malloc( ). Она берет аргумент в виде целого без знака, которое представляет количество требуемых байтов памяти. Так, malloc(BLOCK) требует 100 байт. Функция возвращает указатель на тип char в начало нового блока памяти. Мы использовали описание char *malloc( ); чтобы предупредить компилятор, что malloc( ) возвращает указатель на тип |