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

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

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

232

Программирование на языке Си

там анализа некоторых условий. Для этого все ветви обработки (например, N+1 штук) оформляются в виде однотипных функ­ ций (с одинаковым типом возвращаемого значения и одинако­ вой спецификацией параметров). Определяется массив указа­ телей из N+1 элементов, каждому элементу которого присва­ ивается адрес конкретной функции обработки. Затем формиру­ ются условия, на основе которых должна выбираться та или иная функция (ветвь) обработки. Вводится индекс, значение ко­ торого должно находиться в пределах от 0 до 1S включительно, где (N+1) - количество ветвей обработки. Каждому условию ставится в соответствие конкретное значение индекса. По кон­ кретному значению индекса выполняются обращение к элемен­ ту массива указателей на функции и вызов соответствующей функции обработки:

имя массива [индекс) {список фактическихпараметров'): (*имя массива [индекс]) {список фактических параметров);

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

#±nclude <stdlib.h> /* Для функции exit ( ) */ #include <stdio.h>

#define N 2

void actO(char * name)

{

printf("%s: Работа завершена!\n",name); exit(0);

}

void actl(char * name)

{

printf("%s: работа l\n",name);

)

void act2(char * name)

{

printf("%s: работа 2\n",name);

}

void main()

{ / * Массив указателей на функции: */

Глава 5. Функции

233

void (* pact[])(char *)={actO,actl,act2}; char string[12];

int number; printf("\п\пВводите имя: "); scanf("%s",string);

printf("Вводите номера работ от 0 до %d:\n",N); While (1)

{

scanf("%d",(number);

/* Ветвление по условию */ pact[number](string);

>

>

Пример выполнения программы:

Вводите имя: Peter

Вводите номера работы от 0 до 2: 1

Peter: работа 1 1

Peter: работа 1

2

Peter: работа 2

О

Peter: Работа Завершена!

В программе для упрощения нет защиты от неверно введен­ ных данных, т.е. возможен выход индекса за пределы, опреде­ ленные для массива pact[ ] указателей на функции . При такой ситуации результат непредсказуем.

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

double rectangle

(double (* pf)(double), double a, double b)

234 Программирование на языке Си

{

i n t

N = 2 0 ;

i n t

i ;

d o u b l e h , s = 0 . 0 ; h = ( b - a ) / N ;

f o r ( i = 0 ; i < N ; i + + ) s + = p f ( a + h / 2 + i * h ) ;

r e t u r n h * s ;

}

Параметры функции rectangle(): p f- указатель на функцию с параметром типа double, возвращающую значение типа double. Это указатель на функцию, вычисляющую значение подынте­ гральной функции при заданном значении аргумента. Парамет­ ры а, b - пределы интегрирования. Число интервалов разбиения отрезка интегрирования фиксировано: N=20. Пусть текст функ­ ции под именем rect.c сохранен в каталоге пользователя.

Предложим, что функция rectangle() должна быть использо­ вана для вычисления приближенных значений интегралов (Абрамов С.А., Зима Е.В. Начала информатики. - М.: Наука, 1989.- С . 83):

2

1/2

Программа для решения этой задачи может иметь следую­ щий вид:

#±nclude <math.h> #include <stdio.h>

#include "rect.c" /* Включение определения функции rectangle! ) */

double ratio(double x) /* Подынтегральная функция */

{

double z ; /* Вспомогательная переменная */ z=x*x+l;

return x/(z*z);

}

double cos4_2(double v) /* Подынтегральная функция */

Глава 5.- Функции

235

{

double w; /* Вспомогательная переменная */ w=cos(v);

return 4*w*w;

}

void ma±n()

{

double a,b,c; a=-l.0;

b=2.0; c=rectangle(ratio,a,b);

printf("\n Первый интеграл: %f",c); printf("\n Второй интеграл: %f",

rectangle(C O B 4_2,0.0,0.5));

)

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

Первый интеграл: 0.149847 Второй интехрал: 1.841559

Комментарии к тексту программы могут быть следующими. Директива ^include "rect.c" включает на этапе препроцессорной обработки в программу определение функции rectangle(). Предполагается, как упомянуто выше, что текст этого опреде­ ления находится в файле rect.c. В языке Си, как говорилось в главе 3, существует соглашение об обозначении имен включае­ мых файлов. Если имя файла заключено в угловые скобки '< >', то считается, что это один из файлов стандартной библиотеки компилятора. Например, файл <math.h> содержит средства свя­ зи с библиотечными математическими функциями. Файл, назва­ ние которого помещено в кавычках " ", воспринимается как файл из текущего каталога. Именно там в этом примере препро­ цессор начинает разыскивать файл rect.c.

Определения функций ratio() и cos4_2(), позволяющих вы­ числять значения подынтегральных выражений по заданному значению аргумента, ничем не примечательны. Их прототипы соответствуют требованиям спецификации первого параметра функции rectangle():

double имя (double)

236

Программирование на языке Си

В основной программе m ain() функция rectangle() вызыва­ ется дважды с разными значениями параметров. Для разнообра­ зия вызовы выполнены по-разному, но каждый раз первый пара­ метр - это имя конкретной функции, т.е. константный указатель на функцию.

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

Рассмотрим программу, демонстрирующую особенности та­ кой организации меню.

В программе определены три функции: первые две функции f1 () и f2() с прототипом вида

int f (void);

(пустой список параметров, возвращается значение типа int) и третья функция menu() с прототипом

int (*menu(void)) (void);

которая возвращает значение указателя на функции с пустыми списками параметров, возвращающие значения типа int.

При выполнении функции menu() пользователю дается воз­ можность выбора из двух пунктов меню. Пунктам меню соот­ ветствуют определенные выше функции fl () и f2(), указатель на одну из которых является возвращаемым значением. При не­ верном выборе номера пункта возвращаемое значение становит­ ся равным NULL.

В основной программе определен указатель г, который мо­ жет принимать значения адресов функций f l ( ) и f2(). В беско­ нечном цикле выполняются обращения к функции menu(), и если результат равен NULL, то программа печатает "The End" и завершает выполнение. В противном случае вызов

t=(*r) (.);

Глава б. Функции

237

обеспечивает исполнение той из функций fl () или f2(), адрес которой является значением указателя г.

Текст программы1:

#include <stdio.h> int f1 (void)

{

printf(" The first actions: "); return 1;

}

int f2(void)

{

printf(" The second actions: "); return 2;

}

int (* menu(void)) (void)

{

int choice; /* Номер пункта меню */

/* Массив указателей на

функции: */

int (* menu_items[])() = (fl, f2);

printf("\n Pick the menu

item (1 or 2): ");

scanf("%d",fichoice);

> 0)

if (choice < 3 && choice

return menu_iterns[choice - 1];

else

 

return NULL;

 

}

void main()

{

int (*r)(void); /* Указатель на функции */ int t;

while (1)

( /* Обращение к меню: */ r=menu();

if (r == NULL)

(

printf("\nThe End!"); return;

}

1 Исходный вариант программы предложен С.МЛавреновым.

238

Программирование на языке Си

/* Вызов выбранной функции */ t=(*r) () ;

printf("\tt= %d",t);

}

}

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

Pick

the menu

item

(1

or 2):

2

The

second actions: t=2

1

Pick

the menu

item

(1

or 2):

The

first actions:

t=l

24

Pick

the menu

item

(1 or 2):

The End!

В функции menu() определен массив menu_items[ ] указате­ лей на функции. В качестве инициализирующих значений в списке использованы имена функций fl () и f2():

int (* menu_items[ ]) () = {fl, f2};

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

typedef int (*Menu_action) (void);

M enuaction menu items [ ] = {fl, f2};

Здесь typedef вводит обозначение Menu action для типа "ука­ затель на функции с пустым списком параметров, возвращаю­ щие значения типа int".

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

qsort ()

- функция быстрой сортировки массива;

search ()

- функция поиска в массиве элемента с заданны­

 

ми свойствами.

Глава 5. Функции

239

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

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

void qsort(void * base, s iz e t nelem, s iz e t width, int (*fcmp)(const void * pi, const void * p2));

Это прототип функции быстрой сортировки, входящей в стандартную библиотеку функций. Прототип находится в заго­ ловочном файле stdlib.h. Функция qsort() сортирует содержи­ мое таблицы (массива) однотипных элементов, неоднократно вызывая функцию сравнения, подготовленную пользователем. Для вызова функции сравнения ее адрес должен заместить ука­ затель fcmp, специфицированный как формальный параметр. При использовании qsort() программист должен подготовить таблицу сортируемых элементов в виде одномерного массива фиксированной длины и написать функцию, позволяющую сравнивать два любых элемента сортируемой таблицы. Остано­ вимся на параметрах функции qsort():

base

-

указатель на начало таблицы (массива) сорти-

 

 

. руемых элементов (адрес нулевого элемента

 

 

массива);

nelem

-

количество сортируемых элементов в таблице

 

 

(целая величина, не большая размера массива) -

 

 

сортируются первые nelem элементов от начала

 

 

массива;

width

-

размер элемента таблицы (целая величина, оп­

 

 

ределяющая в байтах размер одного элемента

 

 

массива);

240

Программирование на языке Си

fcmp

- указатель на функцию сравнения, получающую

 

в качестве параметров два указателя pi, р2 на

 

элементы таблицы и возвращающую в зависи­

 

мости от результата сравнения целое число:

 

если *р1 < *р2, функция fcmp () возвращает

 

отрицательное целое < 0;

 

если *р1 == *р2, fcmp () возвращает 0;

 

если *pl > *р2, fcmp () возвращает положи­

 

тельное целое > 0.

При сравнении символ "меньше, чем" (<) означает, что после сортировки левый элемент отношения *р1 должен оказаться в таблице перед правым элементом *р2, т.е. значение *р1 должно иметь меньшее значение индекса в массиве, чем *р2. Аналогич­ но (но обратно) определяется расположение элементов при вы­ полнении соотношения "больше, чем" (>).

В следующей программе функция qsort() используется для упорядочения массива указателей на строки разной длины. Упорядочение должно быть выполнено таким образом, чтобы последовательный перебор массива указателей позволял полу­ чать строки в алфавитном порядке. Сами строки в процессе сор­ тировки не меняют своих положений. Изменяются только значения указателей в массиве.

#include <stdio.h>

♦include <stdlib.h> /* Для функции qsort() */ ♦include <string.h> /* Для сравнения строк:

strcmp ()*/

/* Определение функции для сравнения: */ int compare(const void *a, const void *b)

{

unsigned long *pa = (unsigned long *)a, *pb = (unsigned long *)b;

Return strcmp((char *)*pa, (char *)*pb);

}

void main()

(

char *pc[] = { "One - 1", "Two - 2",

Глава 5. Функции

 

 

 

241

 

"Three

-

3",

 

 

"Four

-

4

",

 

 

"Five

- 5",

 

 

"Six -

6",

 

 

 

"Seven

- 7",

 

/*

"Eight - 8" };

*/

Размер таблицы:

int n = sizeof(pc)/sizeof(pc[0]);

int

i;

До

сортировки:");

printf("\n

for

(i = 0;

i <

n; i++)

printf("\npc

[%d]

= %p -> %s",

i#pc[i],pc[i]);

/* Вызов функции упорядочения: */ qsort((void *)

pc,/* Адрес начала сортируемой таблицы */

п ,/* Число элементов сортируемой таблицы */ sizeof(pc[0J), /* Размер одного элемента */ compare /* Имя функции сравнения

(указатель) */

) ;

printf("\n\n После сортировки:"); for (i = 0; i < n; i++)

printf("\npc [%d] = %p -> %s", i ipc ti1,pc[i]);

}

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

до сортировки:

рс

СО] = 00В8 -> One - 1

рс

tl] = 00С0 -> Two - 2

рс

[2] = 00С8 -> Three - 3

рс

[3] = 00D2 -> Four - 4

рс

[4] = 00DC -> Five - 5

рс

[5] .= 00Е5 -> Six - 6

рс

(6)

= 00ED -> Seven - 7

рс

(7]

= 00F7 -> Eight - 8

после сортировки:

рс [0] = 00F7 -> Eight - 8 рс [1] = 00DC -> Five - 5 рс (2] = 00D2 -> Four - 4

16'3'24

Соседние файлы в папке книги