Бьерн Страуструп - Язык программирования С++. Главы 2-4
Страница 66. Указатель на функцию


 

4.6.9 Указатель на функцию

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

         void error(char* p) { /* ... */ }

         void (*efct)(char*);   // указатель на функцию

         void f()
         {
           efct = &error;       // efct настроен на функцию error
           (*efct)("error");    // вызов error через указатель efct
         }

    Для вызова функции с помощью указателя (efct в нашем примере)
 надо вначале применить операцию косвенности к указателю - *efct.
 Поскольку приоритет операции вызова () выше, чем приоритет
 косвенности *, нельзя писать просто *efct("error"). Это будет
 означать *(efct("error")), что является ошибкой. По той же
 причине скобки нужны и при описании указателя на функцию. Однако,
 писать просто efct("error") можно, т.к. транслятор понимает, что
 efct является указателем на функцию, и создает команды, делающие
 вызов нужной функции.
    Отметим, что формальные параметры в указателях на функцию описываются
 так же, как и в обычных функциях. При присваивании указателю на функцию
 требуется точное соответствие типа функции и типа присваиваемого
 значения. Например:

          void (*pf)(char*);          // указатель на void(char*)
          void f1(char*);             // void(char*);
          int f2(char*);              // int(char*);
          void f3(int*);              // void(int*);

          void f()
          {
            pf = &f1;                 // нормально
            pf = &f2;                 // ошибка: не тот тип возвращаемого
                                      // значения
            pf = &f3;                 // ошибка: не тот тип параметра

            (*pf)("asdf");            // нормально
            (*pf)(1);                 // ошибка: не тот тип параметра

            int i = (*pf)("qwer");    // ошибка: void присваивается int
          }

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

  typedef int (*SIG_TYP)(int);    // из <signal.h>
  typedef void (SIG_ARG_TYP)(int);
  SIG_TYP signal(int, SIG_ARG_TYP);

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

         typedef void (*PF)();

         PF edit_ops[] = { // команды редактора
             &cut, &paste, &snarf, &search
         };

         PF file_ops[] = { // управление файлом
            &open, &reshape, &close, &write

         };

   Далее надо определить и инициализировать указатели, с помощью которых
   будут запускаться функции, реализующие выбранные из меню команды.
   Выбор происходит нажатием клавиши мыши:

         PF* button2 = edit_ops;
         PF* button3 = file_ops;

   Для настоящей программы редактора надо определить большее число
   объектов, чтобы описать каждую позицию в меню. Например, необходимо
   где-то хранить строку, задающую текст, который будет выдаваться для
   каждой позиции. При работе с системой меню назначение клавиш мыши
   будет постоянно меняться. Частично эти изменения можно представить
   как изменения значений указателя, связанного с данной клавишей. Если
   пользователь выбрал позицию меню, которая определяется, например,
   как позиция 3 для клавиши 2, то соответствующая команда реализуется
   вызовом:

          (*button2[3])();

       Чтобы полностью оценить мощность конструкции указатель на функцию,
   стоит попытаться написать программу без нее. Меню можно изменять
   в динамике, если добавлять новые функции в таблицу команд.
   Довольно просто создавать в динамике и новые меню.
       Указатели на функции помогают реализовать полиморфические
   подпрограммы, т.е. такие подпрограммы, которые можно применять
   к объектам различных типов:

           typedef int (*CFT)(void*,void*);

           void sort(void* base, unsigned n, unsigned int sz, CFT cmp)
           /*
              Сортировка вектора "base" из n элементов
              в возрастающем порядке;
              используется функция сравнения, на которую указывает cmp.
              Размер элементов равен "sz".

              Алгоритм очень неэффективный: сортировка пузырьковым методом
            */
            {
              for (int i=0; i<n-1; i++)
                  for (int j=n-1; i<j; j--) {
                     char* pj = (char*)base+j*sz;  // b[j]
                     char* pj1 = pj - sz;          // b[j-1]
                     if ((*cmp)(pj,pj1) < 0) {
                     // поменять местами b[j] и b[j-1]
                        for (int k = 0; k<sz; k++) {
                            char temp = pj[k];
                            pj[k] = pj1[k];
                            pj1[k] = temp;
                        }
                      }
                    }
             }

   В подпрограмме sort неизвестен тип сортируемых объектов; известно
   только их число (размер массива), размер каждого элемента и функция,
   которая может сравнивать объекты. Мы выбрали для функции sort()
   такой же заголовок, как у qsort() - стандартной функции сортировки
   из библиотеки С. Эту функцию используют настоящие программы.
   Покажем, как с помощью sort() можно отсортировать таблицу с такой
   структурой:

            struct user {
               char* name;     // имя
               char* id;       // пароль
               int dept;       // отдел
            };

            typedef user* Puser;

            user heads[] = {
                 "Ritchie D.M.",      "dmr",   11271,
                 "Sethi R.",          "ravi",  11272,
                 "SZYmanski T.G.",    "tgs",   11273,
                 "Schryer N.L.",      "nls",   11274,
                 "Schryer N.L.",      "nls",   11275
                 "Kernighan B.W.",    "bwk",   11276
            };

            void print_id(Puser v, int n)
            {
              for (int i=0; i<n; i++)
                  cout << v[i].name << '\t'
                       << v[i].id << '\t'
                       << v[i].dept << '\n';
            }

 Чтобы иметь возможность сортировать, нужно вначале определить
 подходящие функции сравнения. Функция сравнения должна возвращать
 отрицательное число, если ее первый параметр меньше второго,
 нуль, если они равны, и положительное число в противном случае:

            int cmp1(const void* p, const void* q)
            // сравнение строк, содержащих имена
            {
              return strcmp(Puser(p)->name, Puser(q)->name);
            }

            int cmp2(const void* p, const void* q)
            // сравнение номеров разделов
            {
              return Puser(p)->dept - Puser(q)->dept;
            }

 Следующая программа сортирует и печатает результат:

           int main()
           {
             sort(heads,6,sizeof(user), cmp1);
             print_id(heads,6);    // в алфавитном порядке
             cout << "\n";
             sort(heads,6,sizeof(user),cmp2);
             print_id(heads,6);    // по номерам отделов
           }

    Допустима операция взятия адреса и для функции-подстановки, и для
 перегруженной функции ($$R.13.3).
    Отметим, что неявное преобразование указателя на что-то в
 указатель типа void* не выполняется для параметра функции, вызываемой
 через указатель на нее. Поэтому функцию

         int cmp3(const mytype*, const mytype*);

 нельзя использовать в качестве параметра для sort().
 Поступив иначе, мы нарушаем заданное в описании условие, что
 cmp3() должна вызываться с параметрами типа mytype*. Если вы
 специально хотите нарушить это условие, то должны использовать
 явное преобразование типа.

 
« Предыдущая статья   Следующая статья »