Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
кр1 вариант 27 / Теоретическая часть.doc
Скачиваний:
5
Добавлен:
01.04.2014
Размер:
117.76 Кб
Скачать

Содержание

Теоретическая часть. 3

1. Указатели на функции. Массивы указателей на функции. Указатель на функцию в качестве аргумента другой функции. 3

Практическая часть. 12

Список использованных источников 16

Теоретическая часть.

1. Указатели на функции. Массивы указателей на функции. Указатель на функцию в качестве аргумента другой функции.

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

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

функция как аргумент

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

double add(double a, double b){return a+b;}

double mul(double a, double b){return a*b;}

// чтобы по короче

typedef double (*OPfunc)(double,double);

double op(OPfunc f, double a, double b){return f(a,b);}

...

cout << op(add,23.5,23.2);

аргументы с атрибутом const

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

//объекты на которые ссылаются a и b

//внутри функции доступны только для чтения

void anyfunc(const int *a, const int &b);

значения по умолчанию

C++ позволяет задать аргументам функции значения по умолчанию. Такие аргументы должны быть в конце списка. В качестве значения может выступать любое корректное для инициализации выражение этого же типа.

// обычно значения задают в объявлениях функциях

int anyfunc(int a, int b, float c=3.14, bool d=true);

перегрузка функций

Функциям можно задавать одно и тоже имя, если они имеют разное число аргументов или разные типы аргументов. Это называется перегрузкой функций. Иногда совместное использование перегрузки и значений аргументов по умолчанию может привести к конфликту имен.

Реализуется перегрузка генерацией нового имени функции на основе числа и типов аргументов. У каждого компилятора собственный алгоритм генерации имен. Поэтому при разработки библиотек для их переносимости используют только C функции. А по стандарту языка С, к именам функции добавляется только символ подчеркивания. Ниже приведены имена функций, которые сгенерировал компилятор MinGw. Посмотреть имена можно в объектных файлах (*.obj или *.o)

//__Z4funci

void func(int a){ a*456;}

//__Z4funcdPKc

int func(double a,const char* b){return a+456;}

// для статических (*.lib,*.a) или

// динамических (*.dll для windows) библиотек

// _func

extern "C" double func(int*a){return 456;}

Соседние файлы в папке кр1 вариант 27