для выбора незаданных параметров этих функций. Рассмотрим функцию
ошибки. За ним может следовать произвольное число строк. Нужно
// ...
параметр. Функцию реакции на ошибку можно определить так:
void error(int severity ...)
/*
за "severity" (степень тяжести ошибки) следует
список строк, завершающийся нулем
*/
{
va_list ap;
va_start(ap,severity); // начало параметров
for (;;) {
char* p = va_arg(ap,char*);
if (p == 0) break;
cerr << p << ' ';
}
va_end(ap); // очистка параметров
cerr << '\n';
if (severity) exit(severity);
}
Вначале при вызове va_start() определяется и инициализируется
va_list. Параметрами макроопределения va_start являются имя типа
va_list и последний формальный параметр. Для выборки по порядку
неописанных параметров используется макроопределение va_arg().
В каждом обращении к va_arg нужно задавать тип ожидаемого фактического
параметра. В va_arg() предполагается, что параметр такого типа
присутствует в вызове, но обычно нет возможности проверить это.
Перед выходом из функции, в которой было обращение к va_start,
необходимо вызвать va_end. Причина в том, что в va_start()
могут быть такие операции со стеком, из-за которых корректный возврат
из функции становится невозможным. В va_end() устраняются все
нежелательные изменения стека.
Приведение 0 к (char*)0 необходимо потому, что sizeof(int)
не обязано совпадать с sizeof(char*). Этот пример демонстрирует
все те сложности, с которыми приходится сталкиваться
программисту, если он решил обойти контроль типов, используя
эллипсис.
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); // из
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
for (int j=n-1; i
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
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
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*. Если вы
специально хотите нарушить это условие, то должны использовать
явное преобразование типа.
Достарыңызбен бөлісу: