MM2 TP
.pdfFLOAT X;
CHAR NAME[50];
SCANF("&D %F %S", &I, &X, NAME);
со строкой на вводе
25 54.32E-1 THOMPSON
приводит к присваиванию I значения 25,X - значения 5.432 и NAME - строки
"THOMPSON", надлежащим образом законченной символом \ 0. Эти три поля ввода мож-
но разделить столькими пробелами, табуляциями и символами новых строк, сколько по-
требуется с точки зрения дизайна.
Выполнение следующего фрагмента: INT I;
FLOAT X;
CHAR NAME[50];
SCANF("%2D %F %*D %2S", &I, &X, NAME);
с вводом
56789 0123 45A72
присвоит I значение 56, X - 789.0, пропустит 0123 и поместит в NAME строку "45".
При следующем обращении к любой процедуре ввода рассмотрение начнется с буквы A.
В этих двух примерах NAME является именем массива и, следовательно, указателем. По этой причине перед ним ненужно помещать знак &.
В качестве примера рассмотрим элементарный калькулятор:
/* EX0022.C */ #include <stdio.h> main() {
double sum, v; sum =0;
while (scanf("%lf", &v) != EOF) printf("\t%.2f\n", sum = sum + v);
return(0);
}
Выполнение функции SCANF заканчивается либо тогда, когда она исчерпывает свою управляющую строку, либо когда некоторый элемент ввода не совпадает с управляющей спецификацией. В качестве своего значения она возвращает число правильно совпадаю-
щих и присвоенных элементов ввода. Это число может быть использовано для определе-
ния количества найденных элементов ввода. при выходе на конец файла возвращается
EOF. Подчеркнем, что это значение отлично от 0, что следующий вводимый символ не удовлетворяет первой спецификации в управляющей строке. При следующем обращении к SCANF поиск возобновляется непосредственно за последним введенным символом.
Заключительное предостережение: аргументы функции SCANF должны быть указате-
лями. Несомненно наиболее распространенная ошибка состоит в написании scanf("%d", n);
вместо scanf("%d", &n);
4.5Форматное преобразование в памяти
Возврат_к_оглавлению
От функции SCANF и PRINTF происходят функции SSCANF и SPRINTF, которые осуществляют аналогичные преобразования, но оперируют со строкой, а не с файлом. Об-
ращения к этим функциям имеют вид: SPRINTF(STRING, CONTROL, ARG1, ARG2, ...) SSCANF(STRING, CONTROL, ARG1, ARG2, ...)
Подобно PRINTF функция SPRINTF преобразует свои аргументы ARG1, ARG2 и т.д. в
соответствии с форматом, указанным в CONTROL, но помещает результаты в STRING, а
не в стандартный вывод. Существенно, чтобы строка STRING имела достаточный размер для размещения результата.
Рассмотрим пример:
NAME - символьный массив
N - целое
Тогда SPRINTF(NAME, "TEMP%D", N); создает в NAME строку вида TEMPNNN, где
NNN - значение N.
Функция SSCANF выполняет обратные преобразования - она просматривает строку
STRING в соответствии с форматом в аргументе CONTROL и помещает результирующие значения в аргументы ARG1, ARG2 и т.д.эти аргументы должны быть указателями. В ре-
зультате работы оператора
SSCANF(NAME, "TEMP%D", &N);
переменная N получает значение строки цифр, следующих за TEMP в NAME.
Последствия появления в строке формата непустых символов, кроме символов специ-
фикации преобразования очень плохо документированы. Поэтому использование конст-
рукций, подобных предыдущему примеру для SCANF, должно сопровождаться достаточ-
ным количеством экспериментов. С точки зрения надежности получения ожидаемых ре-
зультатов лучше таких конструкций избегать.
4.6Доступ к файлам
Возврат_к_оглавлению
До сих пор рассматривались примеры, в которых программы читали из стандартного ввода и писали в стандартный вывод, относительно которых предполагалось, что они пре-
доставлены программе местной операционной системой.
Следующим шагом в вопросе ввода-вывода является написание программы, работаю-
щей с файлом, который не связан заранее с программой.
Прежде чем можно считывать из некоторого файла или записывать в него, этот файл должен быть открыт с помощью функции FOPEN из стандартной библиотеки.
Функция FOPEN берет внешнее имя (подобное X.C или Y.C), проводит некоторые об-
служивающие действия и переговоры с операционной системой (детали которых здесь не рассматриваются) и возвращает внутреннее имя файла, которое должно использоваться при последующих чтениях из файла или записях в него.
Это внутреннее имя, называемое "указателем файла", фактически является указателем структуры, которая содержит информацию о файле, такую как место размещения буфера,
текущая позиция символа в буфере, происходит ли чтение из файла или запись в него и тому подобное. Пользователи не обязаны знать эти детали, потому что среди определений для стандартного ввода-вывода, получаемых из файла STDIO.H, содержится определение структуры с именем FILE.
Далее приведен фрагмент программы, в котором выполняется открытие файла. file *fp;
fp = fopen(name,mode);
Первая строка описывает переменную FP, как указатель на структуру, хранящую ин-
формацию об открываемом файле.
Вторая строка это вызов функции FOPEN, которая открывает файл. Первый аргумент
FOPEN – символьная строка, содержащая спецификацию файла (адрес в файловой систе-
ме). Второй аргумент также является символьной строкой, которая указывает, как этот файл будет использоваться. Допустимыми режимами являются: чтение ("R"), запись ("W")
и добавление ("A").
При попытке открыть файл, который еще не существует, для записи или добавления,
то такой файл будет создан (если это возможно). Открытие существующего файла на за-
пись приводит к отбрасыванию его старого содержимого. Попытка чтения несуществую-
щего файла является ошибкой. Ошибки могут быть обусловлены и другими причинами
(например, попыткой чтения из файла, не имея на то разрешения). При наличии какой-
либо ошибки функция возвращает нулевое значение указателя NULL (которое для удоб-
ства также определяется в файле STDIO.H).
После открытия файла по отношению к нему могут быть выполнены действия, реали-
зуемые через вызовы функций. Хороший стиль программирования предусматривает еще одну операцию с файлом – закрытие. Эта операция необходима при работе на современ-
ных компьютерах, т.к. в них основная работа с файлами выполняется после копирования данных в оперативную память. Следовательно существует опасность того, что изменен-
ные данные не будут перенесены обратно во внешнюю память. Операция закрытия файла не является обязательной, т.к. исполняющая система C автоматически закрывает все фай-
лы при завершении задачи. Желательность операции определяется тем, что автоматика может не сработать при авариях программ или оборудования. Кроме того открытые фай-
лы потребляют ресурсы компьютера и своевременное закрытие файлов повышает эффек-
тивность вычислительного процесса.
Итак обращаем внимание на существование функции закрытия файла: fclose(fp);
Параметром функции является указатель на файл, который сформирован соответст-
вующей функцией FOPEN.
Из функций, обрабатывающих содержимое файла прежде всего рассмотрим GETC и PUTC, которые являются простейшими. Функция GETC возвращает следующий символ из файла. Ей необходим указатель файла, чтобы знать, из какого файла читать. Таким об-
разом,
c=getc(fp)
помещает в C следующий символ из файла, указанного посредством FP, и EOF, если достигнут конец файла.
Функция PUTC putc(c,fp)
помещает символ C в файл FP и возвращает C.
При запуске любой программы автоматически открываются три файла, которые снаб-
жены определенными указателями файлов. Этими файлами являются стандартный ввод,
стандартный вывод и стандартный вывод ошибок; соответствующие указатели файлов на-
зываются STDIN, STDOUT и STDERR. Обычно все эти указатели связаны с терминалом,
но STDIN и STDOUT могут быть перенаправлены на файлы с при запуске программы из командной строки.
Функции GETCHAR и PUTCHAR могут быть определены в терминалах GETC, PUTC, STDIN и STDOUT следующим образом:
#define getchar() getc(stdin) #define putchar(c) putc(c, stdout)
Следует помнить, что GETC, как и GETCHAR используют буферизацию, следователь-
но при использовании GETC для чтения с клавиатуры (из STDIN) фактический возврат символа происходит только после нажатия ENTER.
Для примера рассмотрим программу копирования символов из одного файла в другой
(EX0023.C). Этот пример целесообразно сравнить с примером EX0005.C, где выполнялось копирование символов с клавиатуры на экран.
/* EX0023.C */ #include <stdio.h>
int main() /* копирование символов */
{
FILE *f1, *f2; char c;
if ((f1 = fopen("c:\\pronc\\ex\\ex0023.tx1","r")) != NULL) {
if ((f2 = fopen("c:\\pronc\\ex\\ex0023.tx2","w")) != NULL) { c = getc(f1);
while (c != EOF) { putc (c,f2); c = getc(f1);
}
fclose(f1);
fclose(f2);
}
else
printf("ERROR ON OPENING OUTPUT FILE\n");
}
else
printf("ERROR ON OPENING INPUT FILE\n"); return(0);
}
Впримере EX0023.C спецификации открываемых файлов содержат двойные символы
\\вместо одинарных. Дело в том, что в языке C символ \, входящий в строку символов, яв-
ляется специальным. Он является началом специальных последовательностей, таких как
\n и т.п. Дублирование символа "отключает" его специальную функцию.
Для нормальной работы примера EX0023.C необходимо до его запуска сформировать файл EX0023.TX1, либо с помощью редактора NOTEPAD, либо WORD. В последнем слу-
чае необходимо сохранить набранный текст в файле типа ОБЫЧНЫЙ ТЕКСТ. В обоих случаях необходимо позаботиться чтобы сформированный файл имел требуемое расши-
рение (TX1). Особенности подготовки файла EX0023.TX1 с помощью WORD определя-
ются тем, что стандартная структура документа WORD (файлы с расширением DOC) со-
держат кроме текстовой информации большое количество служебной. Эта служебная ин-
формация представлена байтами, значение которых может не входить в диапазон значе-
ний представляющих текст. Следовательно при копировании файла программа EX0023.C
может принять один из служебных байтов за признак EOF и преждевременно завершить работу.
Для обеспечения копирования документов WORD преобразуем предыдущий пример к виду EX0024.C.
В этом примере изменен режим открытия файлов. Использованное обозначение (RB и WB) приводит к "байтовому" режиму работы с файлами при котором функции GETC и PUTC переписывают каждый байт информации без изменений. В обычном (текстовом)
режиме возможна некоторая самодеятельность со стороны GETC и PUTC. Дело в том, что в процессе работы программы признак конца строки существует в виде одного байта, а в текстовых файлах для этой цели используются два байта.
В примере EX0024.C присутствует еще одно изменение. Оно связано с использовани-
ем функции FEOF для обнаружения конца файла. Эта функция срабатывает не по нали-
чию специального признака в теле файла, а по длине файла, хранящейся в служебных об-
ластях внешнего носителя данных.
Символ ! встречающийся в условном выражении для WHILE означает логическое от-
рицание.
/* EX0024.C */ #include <stdio.h>
int main() /* копирование символов из одного документа
WORD в другой*/
{
FILE *f1, *f2; char c;
if((f1 = fopen("c:\\pronc\\ex\\ex00241.doc","rb")) != NULL) {
if((f2 = fopen("c:\\pronc\\ex\\ex00242.doc","wb")) != NULL) { c = getc(f1);
while (! feof(f1)) { putc(c,f2); c = getc(f1);
}
fclose(f1);
fclose(f2);
}
else
printf("ERROR ON OPENING OUTPUT FILE");
}
else
printf("ERROR ON OPEING INPUT FILE");
return(0);
}
Кроме ввода/вывода символов для обработки файлов можно использовать форматные функции FSCANF и FPRINTF. Они идентичны функциям SCANF и PRINTF, за исключе-
нием того, что первым аргументом является указатель файла, определяющий тот файл,
который будет читаться или куда будет вестись запись; управляющая строка будет вторым аргументом.
Для иллюстрации применения FPRINTF рассмотрим программу, реализующую чис-
ленное решение дифференциального уравнения. Для примера возьмем уравнения, описы-
вающие электрическую цепь, состоящую из последовательного соединения активного со-
противления R, емкости C и индуктивности L.
Зависимость тока в такой цепи I от приложенного напряжения U описывается сле-
дующим уравнением:
|
|
dI |
|
|
|
|
|
1 |
t |
|
|
|
|
|
|
|
|
L |
+ RI + |
òIdt =U |
|
||||||||||||
dt |
C |
|
||||||||||||||
|
|
|
|
|
|
|
0 |
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
избавляемся от интеграла и делим обе части на L. |
|
|||||||||||||||
|
d 2 I |
|
+ |
R dI |
+ |
1 |
I = |
1 dU |
(4.6.1) |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
dt 2 |
|
L dt |
LC |
L dt |
|||||||||||
|
|
|
|
|
Для решения уравнения используется метод Эйлера. Этот метод обеспечивает невысо-
кую точность вычислений, но его применение в данном случае оправдано наглядностью.
Для обеспечения возможности применения метода Эйлера необходимо преобразовать дифференциальное уравнения второго порядка в систему из двух дифференциальных уравнений первого порядка. Для этого вводим новые переменные
|
x1 = I |
|
|
|
|
|
(4.6.2) |
||||
|
x2 |
= |
dI |
- |
1 |
U |
(4.6.3) |
||||
|
|
|
|
||||||||
|
|
|
dt |
|
|
|
L |
|
|||
из (4.6.2) |
|
||||||||||
|
dI |
= |
|
dx1 |
(4.6.4) |
||||||
|
dt |
|
dt |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|||
из (4.6.3) |
|
||||||||||
|
dI |
= x2 |
+ |
1 |
U |
(4.6.5) |
|||||
|
dt |
|
|||||||||
|
|
|
|
|
|
|
|
L |
|
|
d 2 I |
= |
|
dx |
2 |
+ |
|
1 dU |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(4.6.6) |
|||||||||||||||
|
dt 2 |
|
|
|
|
|
|
|
L |
|
dt |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||||||
|
|
|
|
dt |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||||||||
после подстановки (4.6.4) в (4.6.5) |
|
|
|
|
|
|
||||||||||||||||||||||||||||||||
|
dx1 |
|
|
= x2 + |
1 |
U |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(4.6.7) |
||||||||||||||
|
dt |
L |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
после подстановки (4.6.6), (4.6.6) и (4.6.2) в (4.6.1) получаем |
|
|||||||||||||||||||||||||||||||||||||
|
dx |
2 |
|
|
|
|
1 dU |
|
|
|
|
|
R æ |
|
|
|
1 |
ö |
|
1 |
|
|
1 dU |
|
||||||||||||||
|
|
|
|
|
+ |
|
|
|
|
|
|
|
|
|
|
|
+ |
|
|
|
ç x |
|
+ |
|
U ÷ + |
|
x |
= |
|
|
|
|
(4.6.8) |
|||||
|
dt |
L dt |
|
|
|
|
|
|
L |
|
L dt |
|||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
L è |
|
2 |
|
ø |
|
LC 1 |
|
|
||||||||||||||||||||||
после преобразований |
|
|
|
|
|
|
|
|
|
|
|
|||||||||||||||||||||||||||
|
|
dx2 |
|
+ |
R |
x |
|
|
+ |
1 |
x = - |
R |
U |
|
|
|
|
|
|
|
(4.6.9) |
|||||||||||||||||
|
|
|
|
|
|
|
LC |
|
|
|
|
|
|
|
|
|||||||||||||||||||||||
|
|
|
dt |
|
|
|
L |
2 |
|
|
|
|
|
|
1 |
|
|
L2 |
|
|
|
|
|
|
|
|
объединяя (4.6.7) и (4.6.9) окончательно получаем систему из двух дифференциальных уравнений первого порядка, эквивалентную уравнению (4.6.1)
{ |
dx1 |
|
= x2 |
+ |
1 |
U |
|
|
|
|
|
|
||
dt |
|
|
|
|
|
|
|
|
||||||
|
|
|
|
L |
|
|
|
(4.6.10) |
||||||
dx2 |
|
|
|
|
|
|
|
R |
|
|||||
= - |
1 |
x1 |
- |
x2 |
- |
R |
U |
|||||||
|
|
|
2 |
|||||||||||
|
dt |
|
|
|
LC |
|
L |
|
L |
Метод Эйлера заключается в замене производной отношением приращений. После та-
кой операции можно записать:
x |
|
= x |
|
+ (x |
2(k -1) |
+ |
1 |
U |
(k -1) |
)Dt |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|||||||||||
1(k ) |
1(k -1) |
|
|
L |
|
|
|
|
|
|
|
|||||||
{x |
|
|
|
|
|
1 |
|
|
R |
|
|
|
R |
|
|
(4.6.11) |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||
|
= x |
|
+ (- |
x |
- |
x |
|
- |
U |
|
)Dt |
|||||||
|
|
LC |
|
|
L2 |
|
||||||||||||
|
2( k ) |
|
2(k -1) |
|
|
1(k -1) |
|
L |
|
2(k -1) |
|
|
(k -1) |
|
В программе EX0025.C рассчитывается переходный процесс установления тока в RLC
цепи при подаче на нее постоянного напряжения. Полученные значения тока в зависимо-
сти от времени записываются в файл. Содержимое этого файла может быть использовано для построения графика средствами EXCEL.
/* EX0025.C */ #include <stdio.h> int main() {
FILE *fp;
float t, tmax, dt, r, l, c, x1, x2;
tmax = 30.0;
dt |
= 0.1; |
r |
= 1.0; |
l |
= 1; |
c |
= 1; |
x1 |
= 0.0; |
x2 |
= 0.0; |
if((fp = fopen("c:\\pronc\\ex\\ex0025.txt","w")) != NULL) { for(t = 0.0; t <= tmax; t = t + dt) {
x1 = x1 + (x2 + 1.0/l) * dt;
x2 = x2 + ((-1.0/(l * c)) * x1 - (r/l) * x2 - r/(l * l)) * dt; fprintf(fp,"%f\t%f\t%f\n", t, x1, x2);
}
fclose(fp);
}
else
printf("ERROR ON OPEING OUTPUT FILE"); return(0);
}
Упражнение 19.
Написать программу сравнения двух файлов. Программа должна вывести на экран первые отличающиеся строки и номер позиции, где зафиксировано изменение.
Упражнение 20.
Написать программу, которая генерирует файл, содержащий таблицу значений "за-
шумленной" синусоиды, т.е. таблицу значений функции SIN (t) + NOISE(t) . Программа должна запрашивать шаг таблицы, количество строк таблицы и амплитуду шума. На ос-
новании полученных данных построить график средствами EXCEL.
Упражнение 21.
Написать программу сглаживания методом скользящего среднего таблично заданной функции времени. Программа обрабатывает файл, полученный в результате работы про-
граммы, созданной в упражнении 21. Программа должна запрашивать количество строк,
по которым выполняется осреднение. В результате работы программа должна сформиро-