книги / Программирование на языке Си
..pdf232 |
Программирование на языке Си |
там анализа некоторых условий. Для этого все ветви обработки (например, 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