Главная Случайная страница


Полезное:

Как сделать разговор полезным и приятным Как сделать объемную звезду своими руками Как сделать то, что делать не хочется? Как сделать погремушку Как сделать так чтобы женщины сами знакомились с вами Как сделать идею коммерческой Как сделать хорошую растяжку ног? Как сделать наш разум здоровым? Как сделать, чтобы люди обманывали меньше Вопрос 4. Как сделать так, чтобы вас уважали и ценили? Как сделать лучше себе и другим людям Как сделать свидание интересным?


Категории:

АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника






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





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

тип_функции (* имя_указателя) (список_формальных_параметров);

Например:

int (*funclPtr) (char); - определение указателя funclPtr на функцию с параметром типа char, возвращающую значение типа int.

Если приведенную синтаксическую конструкцию записать без первых круглых скобок, т.е. в виде int *fun (char); то компилятор воспримет ее как прототип функции с именем fun и параметром типа char, возвращающей значение указателя типа int

Второй пример:

char * (*func2Ptr) (char *,int); - определение указателя func2Ptr на функцию с параметрами типа указатель на char и типа int, возвращающую значение типа указатель на char.

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

//Программа 6.13

#include "stdafx.h"

#include <iostream>

void f1(void) { // Определение f 1

std::cout<< "\nExecute f1()";

}

void f2(void) { // Определение f2

std::cout<<"\nExecute f2()";

}

void main(){

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

ptr = f2; // Присваивается адрес f2()

(*ptr)(); // Вызов f2() по ее адресу

ptr = f1; // Присваивается адрес f1()

(*ptr) (); // Вызов f1() no ее адресу

ptr(); // Вызов эквивалентен (*ptr)();

}

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

Выполняется f2 () Выполняется f1() Выполняется f1()

В программе описан указатель ptr на функцию, и ему последовательно присваиваются адреса функций f2 и f1. Заслуживает внимания форма вызова функции с помощью указателя на функцию:

(*имя_указателя)(список_фактических_параметров);

Здесь значением имени_указателя служит адрес функции, а с помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Однако будет ошибкой записать вызов функции без скобок в виде *ptr(). Дело в том, что операция () имеет более высокий приоритет, нежели операция обращения по адресу *. Следовательно, в соответствии с синтаксисом будет вначале сделана попытка обратиться к функции ptr(). И уже к результату будет отнесена операция разыменования, что будет воспринято как синтаксическая ошибка.

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

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

char f1(char) {... } // Определение функции

char f2(int) (...) // Определение функции

void f3(float) (...) // Определение функции

int* f4(char *){...} // Определение функции

char (*ptl)(int); // Указатель на функцию

char (*pt2)(int); // Указатель на функцию

void (*ptr3)(float) = f3; // Инициализированный указатель

void main(){

ptl — f1; // Ошибка - несоответствие сигнатур

pt2 = f3; // Ошибка - несоответствие типов

// (значений и сигнатур)

ptl = f4; // Ошибка - несоответствие типов

ptl = f2; // Правильно

pt2 = ptl; // Правильно


char с = (*ptl)(44); // Правильно

с = (*pt2)('\t'); // Ошибка - несоответствие сиг-натур

}

Указатели на функции могут быть объединены в массивы. Например, float (*ptrArray)(char)[4]; - описание массива с именем ptrArray из четырех указателей на функции, каждая из которых имеет параметр типа char и возвращает значение типа float. Чтобы обратиться, например, к третьей из этих функций, потребуется такой оператор:

float а = (*ptrArray[2])('f');

Как обычно, индексация массива начинается с 0, и поэтому третий элемент массива имеет индекс 2.

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

typedef float (*PTF)(float);

typedef char *(*PTC)(char);

typedef void (*PTFONC)(PTF, int, float);

Здесь ptf - имя типа "указатель на функцию с параметром типа float, возвращающую значение типа float". ptc - имя типа "указатель на, функцию с параметром типа char, возвращающую указатель на тип char”. ptfunc - имя типа "указатель на функцию, возвращающую пустое значение (типа void)". Параметрами для этой функции служат: ptf - указатель на функцию float имя (float), выражение типа int и выражение типа float. (В определение имени типа ptfunc вошел только что определенный тип с именем ptf.)

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

PTF ptfloat1, ptfloat2[5]; //Указатель и массив указателей

//на функции float имя(float)

PTC ptchar; // Указатель на функцию char *(char)

PTFUNC ptfunc[8]; // Массив указателей на функции

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

void qsort(void *base, size_t nelem, size_t width,

int (*fcmp)(const void *pl, const void *p2));

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


base - указатель на начало таблицы сортируемых элементов (адрес 0-го элемента массива);

nelem - количество элементов в таблице (целая величина, не больше размера массива);

width - размер элемента таблицы (целая величина, определяющая в байтах размер одного элемента массива);

fcmp - указатель на функцию сравнения, получающую в качестве параметров два указателя pi, р2 на элементы таблицы и возвращающую в зависимости от результата сравнения целое число:

если *pl < *р2, femp возвращает целое < 0;

если *pl = *p2, fcmp возвращает 0;

если *pl > *p2, fcmp возвращает целое > 0.

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

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

//Программа 6.14

#include "stdafx.h"

#include <iostream>

#include <stdlib.h> // Для функции qsort()

#include <string.h> // Для сравнения строк-.strcmpO

// Определение функции для сравнения:

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[] = {"Ivanov", "Cidorov", "Petrov", "Antonov"};

int i, n = sizeof(pc)/sizeof(pc[0]);

std::cout << "\n before sorter:\n";

for(int i = 0; i < n; i++) std::cout<<"\t"<< pc[i];

qsort((void *)pc, // Адрес начала сортируемой "Таблицы

n, // Количество элементов сортируемой таблицы

sizeof(pc[0]), // Размер одного элементе

sravni); // Имя функции сравнения (указатель)

std::cout << "\n After sorter:\n";

for(i = 0; i < n; i++) std::cout<<"\t"<< pc[i];

getchar();

}

 

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

До сортировки:

Иванов Сидоров Петров Антонов

После сортировки:

Антонов Иванов Петров Сидоров

 

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

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

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


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

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

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

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

 







Date: 2015-05-22; view: 734; Нарушение авторских прав



mydocx.ru - 2015-2024 year. (0.017 sec.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав - Пожаловаться на публикацию