Значения режимов в Турбо-Си

 

Режим Смысл
"r" "w" "а" "r+" "w+" ''а+" Открыть файл для чтения Создать файл для записи Добавлять в файл Открыть файл для чтения/записи Создать файл для чтения/записи Открыть или создать файл для чтения/записи

 

Например, для того чтобы открыть файл с именем test для записи, можно написать

 

fp = fopen("test", "w");

 

где fp -переменная типа FILE*. Переменная fp является указателем файла.

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

if((fp = fopen("tesf, "w"))==NULL)

{

рuts("Нельзя открыть файл!\n");

exit(l);

}

 

NULL - это макро, которое определяется в файле заголовка stdio.h.

Функция putc() в виде

 

рuts(<символ>, fp);

 

используется для записи символа в поток, который предварительно открыт для записи с помощью функции fopen(); fp - возвращаемый функцией fopen() указатель файла.

Функция getc() в виде

 

getc(fp)

 

используется для чтения символов, которые она возвращает из потока, открытого в режиме чтения функцией fopen(). fp является указателем файла типа FILE, который возвращается функцией fopen(). В тех случаях, когда достигается конец файла, функция getc() возвращает маркер его конца EOF. Например, для чтения текстового файла до маркера конца файла можно использовать следующие операторы:

ch = setc(fp);

while(ch!=EOF)

{

ch = getc(fp);

}

 

Функция feof() использует аргумент указателя-файла и возвращает 1, если достигнут конец файла, или 0, если не достигнут. Например, приведенная ниже программа читает двоичный файл до тех пор, пока ЭВМ не встречает маркер конца файла:

 

while(!feof(fp)) ch = getc(fp);

 

Функциюfdose() используют для закрытия потока, который был открыт с помощью функции foреn(). Все потоки необходимо закрыть перед завершением программы. Аргументом функции является указатель файла, который закрывается.

Функции foреn(), getc(), putc() и fdose() составляют минимальный набор функций ввода-вывода. Простым примером использования функций putc(), foреn() и fdose() является программа, которая приведена ниже. Эта программа просто читает символы с клавиатуры и записывает иx в дисковый файл до тех пор, пока не введен знак $. Имя выходного файла задается из командной строки. Например, если вы назовете программу ktod («клавиша - на диск»), то набор на клавиатуре ktod test будет позволять вам вводить строки текста в файл с именем test.

Программа 108

#include .h"

main(argc,argv) /*ktod - клавиша на диск */

int argc;

char *argv[];

(

FILE *fp;

char ch;

if(argc!=2)

{

printf("Bы забыли ввести имя файла\n);

exit(l);

)

if((fp=fopen(argv[l], "w"))== NULL)

(

printf("He может открыть файл\n);

exit(l);

}

do

(

ch = getchar();

putc(ch, fp);

)

while (ch!='s');

fclose (fp) ;

}

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

 

Программа 109

#include "studio.h"

main (argc, argv) /*dtos-wicK на экран*/

int argc;

char *argv[] ;

(

FILE *fp;

char ch;

if(argc!=2) {

printf("Вы забыли ввести имя файла\n"};

exit(l);

}

if((fp=fopen(argv[l], "r"))==NOLL)(

printfC'He может открыть файл\n"};

exit(l);

}

ch=getc(fp); /* читать один символ */

while(ch!=EOF)

{

putchar(ch); /* печать на экран */

ch=getc(fp);

}

fclose(fp) ;

}

 

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

Например, для чтения 234-го байта в файле с именем test можно использовать следующую функцию:

funcl()

{

FILE *fp;

if((fp=fopen("test" ,"r"))==NULL)

{

printf("He могу открыть фаил\n");

exit(l);

}

fseek(fp,234L,0);

return getc(fp); /*читать один символ в 234-й позиции*/

}

 

В дополнение к рассмотренным до сих пор основным функциям ввода-вывода буферизованная система ввода-вывода включает функции fprintf() и fscanf(). Эти функции ведут себя точно так же, как функции printf() и scanf(), за исключением того, что они работают с дисковыми файлами. Обращения к функциям fprintf() и fscanf() имеют следующий вид:

 

fprintf(1р,<формат>,<список аргументов>);

fscanf(1р,<формат>,<список аргументов>);

 

где fp является файловым указателем, который возвращается вызовом функции fopen().

 

ДИРЕКТИВЫ ПРЕПРОЦЕССОРА

 

Препроцессор - это программа, которая производит некоторые, иногда весьма значительные, манипуляции с первоначальным текстом программы перед тем, как он подвергается трансляции.

Оператор препроцессора - это одна строка исходного текста, начинающаяся с символа #, за которым следуют название оператора и операнды.

Операторы препроцессора могут появляться в любом месте программыи их действие распространяется на весь исходный файл.

Весьма часто используют следующие операторыпрепроцессора:

 

#include

#define

 

Более специфичными являются директивы #pragma, #if, #error и др.

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

Например, чрезвычайно часто в начале программы на языке Си встречается препроцессорная конструкция

 

#include <stdio.h>

 

Когда исходный текст программы обрабатывается препроцессором, на место этой инструкции ставится содержимое расположенного в некоем стандартном месте файла stdio.h, содержащего макроопределения и объявления данных, необходимых для работы функций из стандартной библиотеки ввода-вывода. Для использования различных математических функций необходимо подключать файл описаний math.h. Функции, оперирующие со строками, описаны в файле string.h, функции общего назначения - в stdlib.h, функции даты и времени - в time.h, диагностика - в assert, h и т.д.

Директива #define позволяет дать в программе макроопределения (или задать макросы). Оператор макроопределения имеет вид

 

#define <макроимя> <строка лексем> или

#define <макроимя(<список параметров>)> <строка лексем>

 

Макроимя - это идентификатор. Строка лексем - это последовательность лексем от Макроимени до конца строки. Точка с запятой в конце макроопределения не ставится.

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

Например, прочитав определения

 

#defmePI3.14159

#defineE2.711828

 

препроцессор заменит в программе все имена PI и Е на соответствующие числовые константы.

Препроцессор языка Си позволяет переопределять не только константы, но и целые программные конструкции. Например, можно написать определение

 

#define forever for(;;)

 

и затем всюду писать бесконечные циклы в виде forever

Определение макроимени с параметрами имеет некоторую специфику. Список параметров макроимени - это список идентификаторов, разделенных запятыми. Далее после списка параметров строка лексем также может содержать эти параметры, которые при макровызове будут заменены на соответствующие аргументы.

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

В следующих программах иллюстрируются некоторые применения операторов препроцессора.

Пример 1: найти большее из двух чисел.

 

Программа 110

#include<stdio.h>

#define MAX(X,Y) ((X)>(Y) ? (X) : (Y))

main()

{

int x,y;

scanf ("%d %d", &x, &y); printf ("%d", MAX(x, y) );

)

Результат работы программы:

3 5

Пример 2.

Программа 111

 

#include<stdio.h>

#define S (x) x*x

#define P(x) printf("x равен %d.\n",x)

main()

{

int x=4;

int z ;

z = S(x); P(z); z = 3(2);

P(z);

P(S(x));

P(S(x+2));

P(100/S(2));

P(S(++x)) ;

}

Результат работы программы:

 

x равен16.

x равен 4.

x равен 16.

x равен 14.

x равен 100.

x равен 30.

 

Оператор препроцессора #pragma позволяет записывать самые различные указания компилятору (зависящие от конкретного компилятора). Например, следующие два предложения препроцессора

 

#pragma recursive

#pragma nonrec

 

устанавливают режим всех функций программы по умолчанию рекурсивным или нерекурсивным. Указание препроцессора

 

#pragma optimize time

 

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

 

СИ И ПАСКАЛЬ

 

При знакомстве с языком Си, особенно после изучения Паскаля и Бейсика, погружение в детали его изобразительных средств может затушевать важную мысль: хотя на Си можно написать практически любую прикладную программу, он изначально для этого не предназначен. Си является результатом эволюционного развития языков создания системных программных средств. Если в прикладном программировании эволюция шла от Фортрана к Алголу, Коболу, Паскалю и т.д., то в системном - от Ассемблеров, привязанных к архитектуре ЭВМ, к Си, для которого созданы трансляторы, делающие его хоть и независимым от архитектуры, но не меняющим основного предназначения.

С помощью Си можно сделать то, что на Паскале сделать невозможно (или почти невозможно) - например, написать фрагмент операционной системы (или новую операционную систему), утилиты и т.п. Так, ряд трансляторов с Паскаля написаны на Си; обратное невозможно представить. В то же время, не раз отмечалось, что прикладные программы, написанные на Паскале, отличаются большей надежностью, чем написанные на Си; их легче читать, передавать от одного программиста другому для совершенствования и сопровождения. Это связано с тем, что Паскаль содержит существенно больше ограничений и является языком более высокого уровня с сильной типизацией данных. Для языка же. который предназначен для разработки системного программного обеспечения, чем меньше ограничений, тем лучше; так, в Си возможны неявные преобразования всех базовых типов данных и указателей друг в друга, что крайне желательно при создании системных средств, но при невнимательности программиста приводит к ошибкам, не улавливаемым транслятором с Си (Паскаль же подобные недопустимые операции пресекает немедленно).

Разумеется, сказанное выше не следует абсолютизировать. Программисты, привыкшие к Си, успешно пишут на нем программы различных классов. Это касается не только Си - вспомните об экспертных системах, написанных на Бейсике. В то же время, при массовом программировании придерживаться «разделения труда» между языками представляется более естественным.

 

Контрольные вопросы и задания

 

1. Охарактеризуйте назначение и особенности языка Си.

2. Какие символы образуют алфавит языка Си?

3. Что называется лексемами, идентификаторами, литералами? Приведите примеры.

4. Какие типы данных используются в Си? Приведите примеры описания переменных.

5. Охарактеризуйте арифметические, логические и битовые операции Си.

6. Какие разновидности оператора присваивания имеются в Си?

7. Как на языке Си можно описать ветвление?

8. Охарактеризуйте возможности цикла for. Приведите примеры.

9. Какие логические циклы имеются в Си? Приведите примеры их использования.

10. Какие операторы управления имеются в Си?

11. Какова структура программы на Си? Что такое функция?

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

13. Для чего в качестве аргументов функций используются указатели? Приведите примеры.

14. Для чего в Си существуют классы памяти?

15. Что такое потоки и файлы в Си?

16. Охарактеризуйте стандартные функции ввода и вывода в Си.

17. Что такое препроцессор Си? Приведите примеры директив препроцессора.