Шаг 3.
Указатели на функции (окончание)

    На этом шаге мы рассмотрим использование массивов указателей на функции.

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

    Рассмотрим задачу вычисления корня функции f(x) на интервале локализации [А; В] для иллюстрации особенностей применения указателя функции в качестве параметра. Численный метод (деление пополам интервала локализации) оформляется в виде функции со следующим заголовком:

    float root(указатель_на_функцию, float A, float В, float EPS);

    Здесь А - нижняя граница интервала локализации корня; B - верхняя граница того же интервала; EPS - требуемая точность определения корня. Введем тип "указатель на функцию", для которой нужно определить корень:

    typedef float(*pointPunc)(float);

    Теперь можно определить функцию для вычисления корня заданной функции с указателем pointPunc. Прототип функции будет таким:

    float root(pointPunc F,   float A,   float B,   float EPS);

    Приведем тестовую программу для определения с помощью root() корня функции у = х2 - 1 на интервале [0, 2]:

//RAZN3_1.СРР  - указатель функции как параметр функции.
#include <iostream.h>
#include <stdlib.h>   // Для функции exit(). 
// Определение типа указателя на функцию: 
typedef float (*pointFunc)(float); 
// Определение функции для вычисления корня: 
float root(pointFunc F, float A, float B, float EPS) 
{ 
  float x, y, c, Fx, Fy, Fc;
  x = A; y = B;
  Fx = (*F)(x); // Значение функции на левой границе.
  Fy = (*F)(y); // Значение функции на правой границе.
  if (Fx * Fy > 0.0)
  {
	 cout << "\nНеверен интервал локализации";
	 exit(1); // Аварийное завершение программы.
  }
  do
	{
		 c = (y - x)/2; // Центр интервала локализации.
		 Fc = (*F)(c);  // Значение функции в с.
		 if (Fc * Fx > 0) { x = c; Fx = Fc; }
		 else { y = c; Fy = Fc; }
	} while (Fc != 0 && y - x > EPS);
  return c;
}
#include <math.h>
// Определение тестовой функции у = х * х - 1:
float testfunc(float x)
{ return x * x - 1; }
void main()
{
  float root(pointFunc, float, float, float); // Прототип.
  float result;
  result = root(testfunc, 0.0, 2.0, 1e-5);
  cout << "\nКорень тестовой функции: " << result;
}
Текст этой программы можно взять здесь.

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

    Корень тестовой функции: 1

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

    void qsort(void *base, size_t nelem, size_t width,
                    int (*fcmp)(const void *p1, const void *p2));

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

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

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

//RAZN3_2.СРР - упорядочение с помощью библиотечной функции qsort ().
#include <iostream.h>
#include <stdlib.h> // Для функции qsort(). 
#include <string.h> // Для сравнения строк: strcmp(). 
// Определение функции для сравнения: 
int sravni(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[] = {
          "Sine Cura - Синекура", 
          "Pro Forma - Ради формы", 
          "Differentia Specifica -"
                    "\n\t\t Отличительная особенность", 
          "Alea Jacta Est! - Жребий брошен!", 
          "Idem Per Idem -"
                    "\n\t\t Определение через определяемое", 
          "Fiat Lux! - Да будет свет!", 
          "Multa Pan Cis - Многое в немногих словах" }; 
  // Размер таблицы: 
  int n = sizeof(pc)/sizeof(pc[0]); 
  cout << "\n До сортировки:" << hex; 
  for (int i = 0; i < n; i++)
    cout << "\npc[" << i << "]=" <<
          (unsigned long)pc[i] << " -> " << pc[i]; 
  // Вызов функции упорядочения:
  qsort( (void *)pc,    // Адрес начала сортируемой таблицы.
         n,             // Количество элементов сортируемой таблицы.
			sizeof(pc[0]), // Размер одного элемента.
			sravni         // Имя функции сравнения (указатель)ю
		 );
  cout << "\n\n  После сортировки:";
  for (i = 0; i < n; i++)
	 cout << "\npc[" << i << "]=" <<
			 (unsigned long)pc[i] << " -> " << pc[i];
}
Текст этой программы можно взять здесь.

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

      До сортировки:
  pc[0]=2de400ac -> Sine Cura - Синекура 
  pc[1]=2de400c1 -> Pro Forma - Ради формы 
  pc[2]=2de400d8 -> Differentia Specifica -
                                          Отличительная особенность 
  pc[3]=2de4010e -> Alea Jacta Est! - Жребий брошен!
  pc[4]=2de4012f -> Idem Per Idem -
                                         Определение через определяемое 
  pc[5]=2de40162 -> Fiat Lux! - Да будет свет! 
  pc[6]=2de4017d -> Multa Pan Cis - Многое в немногих словах

      После сортировки:
  pc[0]=2de4010e -> Alea Jacta Est! - Жребий брошен! 
  pc[1]=2de400d8 -> Differentia Specifica -
                                         Отличительная особенность
  pc[2]=2de40162 -> Fiat Lux! - Да будет свет! 
  pc[3]=2de4012f -> Idem Per Idem -
                                         Определение через определяемое
  pc[4]=2de4017d -> Multa Pan Cis - Многое в немногих словах 
  рс[5]=2de400c1 -> Pro Forma - Ради формы 
  pc[6]=2de400ac -> Sine Cura - Синекура

    Обратите внимание на значения указателей pc[i]. До сортировки разность между рс[1] и рс[0] равна длине строки "Sine Cura - Синекура" и т.д. После упорядочения рс[0] получит исходное значение рс[3] и т.д.

    Для выполнения сравнения строк (а не элементов массива рс[]) в функции sravni() использована библиотечная функция strcmp(), прототип которой в заголовочном файле string.h имеет вид:

    int strcmp(const char *s1, const char *s2);

    Функция strcmp() поддерживается системой UNIX и выполняет беззнаковое сравнение строк, связанных с указателями s1 и s2. Сравнение выполняется без учета регистров набора букв латинского алфавита. Функция выполняет сравнение посимвольно, начиная с начальных символов строк и до тех пор, пока не встретятся несовпадающие символы либо не закончится одна из строк.

    Прототип функции strcmp() требует, чтобы параметры имели тип (const char *). Входные параметры функции sravni() имеют тип (const void *), как предусматривает определение функции qsort(). Необходимые преобразования для наглядности выполнены в два этапа. В теле функции sravni() определены два вспомогательных указателя типа (unsigned long *), которым присваиваются значения адресов элементов сортируемой таблицы (элементов массива рс[]) указателей. В свою очередь, функция strcmp() получает разыменования этих указателей, т.е. адреса символьных строк. Таким образом, выполняется сравнение не элементов массива char* pc[], a тех строк, адреса которых являются значениями pc[i]. Однако функция qsort() работает с массивом рс[] и меняет местами только значения его элементов. Последовательный перебор массива рс[] позволяет в дальнейшем получить строки в алфавитном порядке, что иллюстрирует результат выполнения программы. Так как pc[i] -указатель на некоторую строку, то его разыменование в операции вывода << в поток cout выполняется автоматически.

    Если не использовать вспомогательных указателей рa, рb, то функцию сравнения строк можно вызвать из тела функции sravni() таким оператором:

    return strcmp((char *)(*(unsigned long *)a), (char *)(*(unsigned long *)b));

    Здесь каждый родовой указатель (void *) вначале преобразуется к типу (unsigned long *). Последующее разыменование "достает" из четырех смежных байтов значение соответствующего указателя pc[i]. И уж затем преобразование (char *) формирует указатель на строку, который нужен функции strcmp().

    Со следующего шага мы начнем рассматривать ссылки на функции.




Предыдущий шаг Содержание Следующий шаг