Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

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*. Если вы       специально хотите нарушить это условие, то должны использовать       явное преобразование типа.