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

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
25
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

А это веселая птица-синица,

Которая

часто ворует пшеницу,

Которая

в темном

чулане хранится

В доме,

построил

Джек.

Который

Вот кот,

пугает и

ловит синицу,

Который

Которая

часто ворует пшеницу,

Которая

в темном

чулане хранится

В доме,

построил

Джек.

Который

ЗАДАНИЕ. Выпишите на листе бумаги последовательность строк, формируемых при препроцессорной обработке файла domPrint.txt. Начало текста:

puts("\t\tМаршак…); puts("Вот дом,\n"

"который построил Джек.\n");

Решающую роль при рекурсивном использовании директивы

#include (как и во всех рекурсиях) играет наличие условия завершения рекурсии. В нашем примере решения задачи 10-03 используется проверка значения препроцессорного идентификатора _T. Начиная с фиксированного значения (равного 5 в нашем примере), текст и сама директива #include не должны больше включаться, и цикл препроцессорной обработки прерывается.

ЗАДАЧА 10-04. Чтобы оценить размеры бедствия, которое грозит произойти при отсутствии условия завершения рекурсии, оформите в виде препроцессорного текста леденящую душу историю о попе и его собаке. (Надеемся, что читатель знаком с этим фольклорным шедевром.)

Файл с решением задачи:

// Fable.txt - бесконечная препроцессорная рекурсия У попа была собака, поп ее любил.

411

Она украла кусок мяса, поп ее убил.

И в землю закопал, и на могиле написал: #include " Fable.txt"

Тестовая программа:

/* 10_04.c - обработка некорректного текста */ #include " Fable.txt"

Директива для препроцессорной обработки:

>gcc -E 10_04.c>pre

Часть результатов из сформированного препроцессором файла

"pre":

#1 "10_04.c"

#1 " Fable.txt" 1

Упопа была собака, поп ее любил. Она украла кусок мяса, поп ее убил.

И в землю закопал, и на могиле написал:

# 1 " Fable.txt" 1

Упопа была собака, поп ее любил.

Она украла кусок мяса, поп ее убил.

................

# 1 " Fable.txt" 1

У попа была собака, поп ее любил. Она украла кусок мяса, поп ее убил.

И в землю закопал, и на могиле написал:

#6 " Fable.txt" 2

..................

#6 " Fable.txt" 2

#2 "10_04.c" 2

Теоретически бесконечный цикл при реальном исполнении программы завершился – компилятор исчерпал свои ресурсы – создал текстовый файл pre (препроцессор из DJGPP создал файл размером 66267 байт) и прекратил циклическое обращение к файлу

Fable.txt.

412

10.3.Препроцессорная "настройка" программ

Директива #define широко используется для внесения в программу заранее запланированных достаточно разнообразных изменений. Например, можно настраивать программу, выполняющую численный расчет, на использование разных типов арифметических (вещественных) данных.

ЗАДАЧА 10-05. Вычислите приближенное значение числа e по формуле Тейлора:

ex = 1+

 

x

+

x2

+

x3

+ L+

xn

+ L,

1!

2!

3!

n!

 

 

 

 

 

прекратив вычисления, как только прибавление очередного слагаемого оставит без изменения предыдущее значение суммы (достигнут "машинный нуль"). Проведите вычисления, исполь-

зуя типы float, double, long double.

В нашем случае x=1, т.е. оценивается значение числа "е". Для вычисления с удвоенной точностью (long double) задачу решает следующая программа:

/* 10_05.c - "настройка" вычислительной программы */ #define REAL long double

#define FORMAT "%27.20Le" #include <stdio.h>

int main()

{

REAL result, member, summa;

int k = 0; /* количество итераций */ member = summa = 1.0;

do { /*цикл с постусловием */ result = summa;

k++;

member/=k;

summa += member;

}

while(summa > result);

printf("Оценка числа е: "FORMAT"\n", result); printf("Число итераций: %d\n", k);

413

printf("Машинный нуль: "FORMAT"\n", member); return 0;

}

Результаты выполнения программы:

Оценка числа е:

2.71828182845904523543e+00

Число итераций:

21

Машинный нуль:

1.95729410633912612322e-20

ЗАДАНИЕ. Посмотрите на результаты препроцессорных подстановок в форматную строку функции printf() значений идентификатора FORMAT. (Используйте команду >gcc –E…).

ЗАДАНИЕ. Изменяя в программе строки замещения для препроцессорных идентификаторов REAL и FORMAT, вычислите оценки числа e для типа float (спецификация %27.20e) и double (%27.20e).

В связи со сложностями реализации в классическом языке Си многомерных массивов с переменными размерами препроцессорные подстановки широко используются для "превращения" переменных размеров массивов в постоянные. Следующая задача иллюстрирует указанную возможность.

ЗАДАЧА 10-06. Напишите функцию для транспонирования квадратной матрицы, передаваемой в функцию через параметры в виде двумерного массива. Размеры матрицы задавайте препроцессорной константой. В основной программе определите и инициализируйте двумерный массив, представляющий матрицу, обратившись к функции, транспонируйте матрицу и распечатайте ее по строкам.

// 10_06.c – задание размеров многомерных массивов

#include <stdio.h> #define N 3

void transp(float d[N][N])

{

float r;

414

int i, j;

for (i = 0; i < N - 1; i++) for (j = i + 1; j < N; j++)

{ r = d[i][j]; d[i][j] = d[j][i]; d[j][i] = r;

}

}

int main()

{

float x[N][N] = { 0, 1, 1, 2, 0, 1, 2, 2, 0 };

int i, j; transp(x);

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

{printf("\nline %d :",i+1); for (j = 0; j < N; j++)

printf("\t%f",x[i][j]);

}

return 0;

}

Результаты выполнения программы:

line 1

:

0.000000

2.000000

2.000000

line

2

:

1.000000

0.000000

2.000000

line

3

:

1.000000

1.000000

0.000000

ЗАДАЧА 10-07. Определите функцию, присваивающую вещественные псевдослучайные значения из диапазона от 0 до 1 элементам прямоугольной матрицы X={xij}. Напишите функцию печати (вывода на дисплей) значений элементов прямоугольной матрицы. Размеры матрицы (n и m), а также двумерный массив элементов матрицы должны служить параметрами функций. Задав с помощью препроцессорных идентификаторов N и M размеры матрицы, определите в основной программе соответствующий массив ее элементов. Заполните матрицу и распечатайте значения ее элементов, используя вышеназванные функции.

/* 10_07.c – матрица с переменными размерами как параметр */

#include <stdio.h>

415

#include <stdlib.h> /* для rand(void), RAND_MAX */ #define N 3

#define M 4

void matrixPrint(int n, int m, float x[][M])

{

int i, j;

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

{printf("\nline %d :",i+1); for (j = 0; j < m; j++)

printf("\t%5.3f",x[i][j]);

}

}

int main()

{

void elementValues(int, int, float [][M]); float matrix[N][M]; elementValues(N,M,matrix); matrixPrint(N,M,matrix);

return 0;

}

void elementValues(int n, int m, float array[][M])

{

float r; int i, j;

for (i = 0; i < n; i++) for (j = 0; j < m; j++)

{r = (float)(RAND_MAX - rand())/RAND_MAX; array[i][j] = r;

}

}

Результаты выполнения программы:

line 1

:

1.000

0.998

0.917

0.647

line

2

:

0.271

0.817

0.815

0.026

line

3

:

0.946

0.091

0.534

0.337

В функции elementValues() используется функция стандартной библиотеки int rand(), возвращающая целое значение в диапазоне от 0 до RAND_MAX. Это значение приводится к интервалу [0–1) и присваивается вспомогательной переменной r, а затем оче-

416

редному элементу матрицы. Функция rand() и препроцессорная константа специфицированы в заголовочном файле stdlib.h.

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

void matrixPrint(int n, int m, float x[][])

Компилятор ответит:

10_07.c: In function `matrixPrint':

10_07.c:20: arithmetic on pointer to an incomplete type

Действительно, float X[][M] – это одномерный массив, каждый элемент которого есть массив из M элементов, а float X[][] воспринимается как незавершенный тип, не пригодный для использования.

Попытка заменить в списке параметров обозначение двумерного массива указателем на массив указателей float **x также не удается. Компилятор выдаст предупреждение:

10_04.c: In function `main':

10_04.c:28: warning: passing arg 3 of `matrixPrint' from incompatible pointer type

При исполнении будет выдано сообщение об ошибке (можете проверить).

Утешает лишь возможность применения в новых компиляторах целочисленного параметра функции для указания размера элементов массива-параметра другого параметра функции. Заголовок вида

void matrixPrint(int n, int m, float[][m])...

будет благополучно воспринят транслятором DJGPP, что соответствует современному Стандарту языка Си.

417

10.4.Макросредства препроцессора

ЗАДАЧА 10-08. Определите макрос для вывода на экран дисплея изображения и значения арифметического выражения с возможностью задания спецификации преобразования.

В пособии [3] приведено решение задачи для того случая, когда тип формируемого выражением значения заранее известен и фиксирован:

#defin PRINT(A) printf(#A "=%f",A)

В теме 3 (подразд. 3.4) приведены макросы для вывода изображений выражений и их значений типов int, float, double, long double. Эти макросы мы уже неоднократно использовали в программах из тем 4 – 9.

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

/* 10_08.c - макроподстановки и операция # */ #include <stdio.h>

#define DISPLAY(EXPRESSION, FORMAT) \ printf(#EXPRESSION"="#FORMAT,EXPRESSION)

int main()

{

DISPLAY(33.22,%7.3f); return 0;

}

Результат препроцессорной обработки:

.....

int main()

{

printf("33.22""=""%7.3f", 33.22 ) ; return 0;

}

418

Результаты выполнения программы:

33.22= 33.220

Отметим еще раз роль препроцессорной операции # перед именем параметра макроса в строке замещения. Аргумент при подстановке помещен в кавычки. Обратите внимание, что обратная косая черта (\) позволяет переносить длинную строку, чем мы воспользовались, разместив строку замещения макроса на следующей строке после DISPLAY().

ЗАДАЧА 10-09. Используя предложенный в предыдущей задаче макрос, выведите на экран предельные значения разных арифметических типов, определенные в заголовочных файлах <limits.h> и <float.h>.

Для нескольких констант из float.h следующая программа решает сформулированную задачу:

// 10_09.c - макроподстановки и предельные значения

#include <stdio.h> #include <float.h>

#define DISPLAY(EXPRESSION, FORMAT) \ printf(#EXPRESSION"="#FORMAT,EXPRESSION)

int main()

{

DISPLAY(FLT_EPSILON,%e\n); DISPLAY(FLT_MIN,%e\n); DISPLAY(FLT_MAX,%e\n); DISPLAY(DBL_EPSILON,%le\n); DISPLAY(DBL_MIN,%le\n); DISPLAY(DBL_MAX,%le\n); return 0;

}

Результаты выполнения программы:

FLT_EPSILON=1.192093e-07

FLT_MIN=1.175494e-38

FLT_MAX=3.402823e+38

DBL_EPSILON=2.220446e-16

DBL_MIN=2.225074e-308

DBL_MAX=1.797693e+308

419

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

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

ЭКСПЕРИМЕНТ. Отделите пробелом имя макроса DISPLAY от скобки, открывающей список параметров макроса. Результат лучше изучать, не прибегая к трансляции, а только выполнив препроцессорную обработку:

/* 10_09_1.c - ошибка в макросе */ //#include <stdio.h>

#define DISPLAY (EXPRESSION, FORMAT) \ printf(#EXPRESSION"="#FORMAT,EXPRESSION)

int main()

{

DISPLAY(33.22,%f); return 0;

}

Результат препроцессорной обработки:

.....

int main()

{

(EXPRESSION, FORMAT) printf(#EXPRESSION"="#FORMAT, EXPRESSION) (33.22,%f);

return 0;

}

В программе заключенная в скобки последовательность параметров воспринята как часть замещающей строки. DISPLAY считается препроцессорным идентификатором (не именем макроса). Остальное очевидно из результата препроцессорной обработки. В книге из-за размеров страницы строка, заменившая в результате препроцессор-

420