Указатель на функцию. Использование массива указателей на функции для организации интерфейса. — КиберПедия 

Наброски и зарисовки растений, плодов, цветов: Освоить конструктивное построение структуры дерева через зарисовки отдельных деревьев, группы деревьев...

Индивидуальные очистные сооружения: К классу индивидуальных очистных сооружений относят сооружения, пропускная способность которых...

Указатель на функцию. Использование массива указателей на функции для организации интерфейса.

2017-12-21 416
Указатель на функцию. Использование массива указателей на функции для организации интерфейса. 0.00 из 5.00 0 оценок
Заказать работу

Указатель на функцию. Использование массива указателей на функции для организации интерфейса.

 

Указатель на функцию - переменная, которая содержит адрес некоторой функции. Соответственно, косвенное обращение по этому указателю представляет собой вызов функции.

Определение указателя на функцию имеет вид:

 

int (*pf)(); // без контроля параметров вызова

int (*pf)(void); // без параметров, с контролем по прототипу

int (*pf)(int, char*); // с контролем по прототипу

 

В соответствии с принципом контекстного определения типа данных эту конструкцию следует понимать так: pf - переменная, при косвенном обращении к которой получается функция с соответствующим прототипом, например int_F(int, char*), то есть pf содержит адрес функции или указатель на функцию. Следует обратить внимание на то, что в определении указателя присутствует прототип - указатель ссылается не на произвольную функцию, а только на одну из функций с заданной схемой формальных параметров и результата.

Перед началом работы с указателем его необходимо назначить на соответствующий объект, в данном случае - на функцию. В синтаксисе Си выражение вида &имя_функции имеет смысл - начальный адрес функции или указатель на функцию. Кроме того, по аналогии с именем массива использование имени функции без скобок также интерпретируется как указатель на эту функцию. Указатель может быть инициализирован и при определении.

 

int INC(intа) { return a+1; }

externint DEC(int);

int (*pf)(int);

pf = &INC;

pf = INC; // присваивание указателя

int (*pp)(int) = &DEC; // инициализация указателя

 

Естественно, что функция, на которую формируется указатель, должна быть известна транслятору - определена или объявлена как внешняя. Синтаксис вызова функции по указателю совпадает с синтаксисом ее определения.

 

n = (*pf)(1) + (*pp)(n); // эквивалентно

n = INC(1) + DEC(n);

 

/*Пример определения и использования

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

*/

 

#include<iostream.h>

 

// описание функции f1

void f1(void)

{

cout<< "\n Выполняется f1()";

}

 

// описание функции f2

void f2(void)

{

cout<< "\n Выполняется f2()";

}

 

voidmain()

{

// объявление указателя на функцию,

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

ptr = f2; // ptr инициализируется адресом f2()

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

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

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

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

}

 

Понятие, описание и определение inline функции

 

inline-функция — это такая функция, чье тело подставляется в каждую точ­ку вызова, вместо того, чтобы генерировать код вызова.

Если компилятор C++ перед определением функции встречает ключевое слово inline, он будет заменять обращения к этой функции (вызовы) на последовательность операторов, эквивалентную выполнению функции. Таким образом ваши программы улучшают производительность, избавляясь от издержек на вызов функции и в то же время выигрывая в стиле программы, благодаря использованию функций

При объявлении функции внутри программы C++ позволяет вам предварить имя функции ключевым словом inline. Если компилятор C++ встречает ключевое слово inline, он помещает в выполнимый файл (машинный язык) операторы этой функции в месте каждого ее вызова. Таким образом, можно улучшить читаемость ваших программ на C++, используя функции, и в то же время увеличить производительность, избегая издержек на вызов функций. Следующая программа INLINE.CPP определяет функции тах и min как inline:

 

#include <iostream.h>

 

inlineint max(int a, int b)

{

if (a > b) return(a);

else return(b);

}

inlineint min(int a, int b)

{

if (a < b) return(a);

else return(b);

}

void main(void)

{

cout<< "Минимум из 1001 и 2002 равен " <<min(1001, 2002) <<endl;

cout<< "Максимум из 1001 и 2002 равен " <<max(1001, 2002) <<endl;

}

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

 

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

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

 

floatareaRectangle(float, float) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см)

{

return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение

}

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

 

Предположим, что наши исходные данные (стороны прямоугольника) заданы в метрах и сантиметрах, например такие: a = 2м 35 см; b = 1м 86 см. В таком случае, удобно было бы использовать функцию с четырьмя параметрами. То есть, каждая длинна сторон прямоугольника передаётся в функцию по двум параметрам: метры и сантиметры.

 

floatareaRectangle(floata_m, floata_sm, floatb_m, floatb_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм)

{

return (a_m * 100 + a_sm) * (b_m * 100 + b_sm);

}

 

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

 

Теперь, самое главное – у нас есть две функции, с разной сигнатурой, но одинаковыми именами (перегруженные функции). Сигнатура – это комбинация имени функции с её параметрами. Как же вызывать эти функции? А вызов перегруженных функций ничем не отличается от вызова обычных функций, например:

 

areaRectangle(32, 43); // будет вызвана функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см)

areaRectangle(4, 43, 2, 12); // будет вызвана функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм)

 

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

#include<iostream>

usingnamespacestd;

// прототипы перегруженных функций

floatareaRectangle(float a, float b);

floatareaRectangle(float a_m, float a_sm, float b_m, float b_sm);

int main()

{

cout<< "S1 = " <<areaRectangle(32,43) <<endl; // вызовперегруженнойфункции 1

cout<< "S2 = " <<areaRectangle(4, 43, 2, 12) <<endl; // вызовперегруженнойфункции 2

return 0;

}

// перегруженнаяфункция 1

floatareaRectangle(float a, float b) //функция, вычисляющаяплощадьпрямоугольникасдвумяпараметрами a(см) и b(см)

{

return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение

}

// перегруженная функция 2

floatareaRectangle(floata_m, floata_sm, floatb_m, floatb_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм)

{

return (a_m * 100 + a_sm) * (b_m * 100 + b_sm);

}

Строковые функции

Функции для работы со строками объявлены в заголовочном файле string.h. Приведем некоторые из них:

 

char *strcpy(char *dest, const char *src);

 

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

 

char *strdup(const char *s);

 

копирует строку во вновь создаваемую функцией malloc() область памяти. Возвращает указатель на созданную копию или 0 при неудаче. Программист ответственен за освобождение памяти функцией free();

 

size_tstrlen(const char *s);

 

подсчитывает размер строки. Возвращает количество символов строки без нулевого символа. Тип size_t определен в файле string.h и других заголовочных файлах как целое без знака: typedefunsignedsize_t;

 

char *strcat(char *dest, const char *src);

 

присоединяет вторую строку к первой. Возвращает указатель на начало нарощенной строки.

 

char *strchr(const char *s, int c);

 

сканирует строку s в поисках первого вхождения заданного символа с. Нулевой символ можно искать наряду с другими. Возвращает указатель на найденный символ или 0, если символа нет.

 

char *strrchr(constchar *s, int с);

 

то же, что strchr, но находит последнее вхождение символа с в строку s.

 

char *strstr(const char *s1, const char *s2);

 

находит первое вхождение подстроки s2 в строку s1. Возвращает указатель на место первого вхождения или 0, если такового нет.

 

intstrcmp(const char *s1, const char*s2);

 

сравнивает две строки. Возвращает целое меньше нуля, если s1 < s2, равное нулю, если s1 == s2, и большее нуля, если s1 > s2.

 

char *strpbrk(const char *s1, const char *s2);

 

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

 

char *strtok(char *s1, constchar *s2);

 

сканирует первую строку в поисках первого участка, не содержащего символов из s2. Первый вызов функции возвращает указатель на начало первого участка и записывает 0 в s1 сразу после конца участка. Последующие вызовы с 0 в качестве 1-го аргумента обрабатывают строку дальше, пока еще есть такие участки. Если их нет, возвращается 0. Функцию применяют для выделения слов из предложения si. В строке s2 находятся символы-разделители.

 

Перегрузка операторов

Перегрузка операторов – это возможность назначать новый смысл операторам при использовании их с определенным классом.

Во всей литературе для обучения перегрузке операторов скорее всего используется один и тот же пример. Самое стандартное – это перегрузка операторов для работы со строкой. Легко понять, что знак плюс или минус для чисел и знак плюс или минус для строки несут различные смыслы. В первом случае математические вычисления, во втором добавление или удаление символа (символов) или части строки.

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

Код С++ Перегрузка операторов

  #include <stdlib.h> #include <iostream.h> #include<string.h> //для работы со строковыми функциями   /*СОЗДАЕМ СОБСТВЕННЫЙ КЛАСС*/ classstring { public: string (char*); //конструктор класса принимающий один параметр voidoperator +(char*); //определение оператора + voidoperator -(char); //определение оператора - voidshow_string(void); //метод класса для отображения строки private: chardata[256]; //символьный массив, доступный только классу };   string::string(char *str) //Транзитом через конструктор { strcpy(data,str); //копируем в символьный массив класса данные из принимаемой извне строки }   void string::operator +(char *str) //Определяемоператор + { strcat(data,str); //как функцию сложения двух строк }   void string::operator -(char letter)//Определяемоператор - { chartemp[256]; // будем создавать новую строку inti,j; //счетчики циклов //Проходим по всей строке класса с помощью цикла и если символ строки не равен принятому символу (параметру), то копируем его в новую строку for (i=0,j=0;data[i];i++) if (data[i]!=letter) temp[j++]=data[i]; temp[j]=NULL;   strcpy(data,temp); //Копируем новую строку в символьный массив класса }   void string::show_string(void) { cout<<data<<endl; //Показываем символьный массив класса }   voidmain() { system("cls"); //Очистка экрана   char *stroka,*stroka2; //Объявление двух указателей для строк cin.getline(stroka,256); //Считывание первой строки с клавиатуры cin.getline(stroka2,256); //Считывание второй строки с клавиатуры   stringtitle(stroka); //Объявление переменной типа нашего класса и передача вконструктор первой строки title+" "; //С помощью перегрузки операторов добавили к строке пробел title+stroka2; //C помощью перегрузки операторов добавили к строке вторую строку title.show_string(); //Отобразили результирующую строку на экране title-'в'; //При помощи перегрузки операторов пытаемся удалить символ в title.show_string(); //Отобразили результирующую строку cin.get(); }

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

Нельзя перегружать:

. (точка)

* (звездочка)

:: (два двоеточия подряд)

?:(вопрос с двоеточием)

sizeof (тоже нельзя)

Нужно знать:

Чтобы перегрузить оператор, нужно определить класс, к которому оператор будет назначен

Когда перегружается оператор, перегрузка действует только для класса в котором он определяется. Если оператор использует неклассовые переменные, то используется стандартное определение оператора

Для перегрузки оператора используется ключевое слово С++ operator. Это слово определяет метод класса, который С++ вызывает каждый раз, когда переменная класса вызывает оператор

С++ позволяет перегружать все операторы кроме вышеуказанных

Кроме уже описанных ограничений на перегрузку операторов имеют место другие ограничения.

Старшинство операций не может быть изменено перегрузкой

Ассоциативность операций не может быть изменена перегрузкой

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

Каждая из операций & * + - может иметь и унарный и бинарный варианты. Эти унарные и бинарные варианты могут перегружаться отдельно

Создавать новые операции невозможно. Возможно только использовать существующие.

Нельзя изменить операцию для объекта встроенного типа (уже упоминалось чуть другими словами)

Неявной перегрузки не существует (например object1=object1+object2 не равноobject1+=object2 если явно перегрузки не прописано)

Нужно помнить, что перегрузка операторов нужна для облегчения читаемости кода и злоупотребление этим вместо пользы может приносить вред.

Дружественные функции

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

Дружественная функция объявляется внутри класса с модификатором friend

Казалось бы легкое и понятное определение. Объявил функцию внутри класса, дописал перед ней модификатор friend и используй как обычную и независимую от класса функцию вне класса. Если вы заинтересованы в этой теме, то попробуйте применить такую теорию на практике. Думаю подавляющее большинство попробовавших столкнутся с проблемами.

В качестве обучающего примера я приведу Код C++, в котором происходит сравнение обычной функции класса с дружественной функцией. Сравнение идет только по тому как пишется код в одном случае и как в другом. Вместе с этим вы можете прекрасно видеть синтаксис написания дружественной функции для класса

Код C++ Дружественная функция и обычная функция Класса

  #include <conio.h> #include <iostream.h>   class A { int x; //Приватный элемент из класса A friendvoidget_x(int, A &); //Прототип дружественной функции для занесение в приватный x значения public: voidget_x(int);//Прототип обычного метода для занесения значения в приватный x voidshow(); //Прототип функции для отображения x из приватного поля }; /*Прототипы функций определены внутри класса. Сами функции описаны вне*/   voidget_x(int N, A &obj_A) //Функция не является частью класса, но работает словно является { obj_A.x=N; //в элемент x класса A передается принимаемый параметр N }   void A::get_x(int N) //функция является частью класса A { x=N; //в приватный элемент x класса A заносится принимаемый в N параметр }   void A::show() //Функция является частью класса A и играет роль посредника { cout<<x<<endl; //Отображаем приватный элемент x из класса A }   voidmain() { clrscr(); intvalue=100; //value будет передаваться как параметр вовнутрь класса в приватный x A obj_A;   get_x(value, obj_A); //Работаем как с обычной функцией. obj_A.show(); //Отображаем результаты value=999; //Изменили значение в value   obj_A.get_x(value); //Работаем как с методом объекта obj_A.show(); //Отображаем результаты   getch(); return; }

Сразу можно обратить внимание, что прототип дружественной функции описан внутри поля private. На самом деле можно его описывать и в других полях, но так видно одно важное отличие от обычных методов класса. В программе два абсолютно одинаковых по смыслу метода и оба выполняют одну и ту же задачу. Один метод дружественный, второй метод обычный. Так вот обычный метод из поля private был бы недоступен и без посредника он бы не знал что от него хотят, а дружественному методу посредник не нужен. Об этом и говорит часть теории:
Дружественная функция – это функция, которая не являясь частью класса имеет доступ ко всем элементам из дружественного себе класса.

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

Так как элемент x был объявлен внутри private, то для отображения этого x необходим посредник, в качестве посредника работает функция show(). Функция show() объявлена внутри класса и значит все поля класса ей доступны. Я надеюсь вы изучили, что такое поле private и дальнейшее описание будет лишним

Коротко говоря:
Внутри класса объявлены

Приватный элемент x

Дружественная функция ввода в x значения

Обычная функция ввода в x значения

Функция для отображения приватного элемента

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

Второй описывается функция, которая принимает только один параметр. При этом функция опирается на класс A и следовательно заносит принимаемый параметр вовнутрь того класса на который и опирается.

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

Функция friend может быть объявлена даже внутри private и посредники ей не нужны

Обычная функция класса требует посредников для работы с элементами из private

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

Осталось только то, что находится внутри главной функции. Собственно там не должно возникнуть вопросов. Разница вызовов видна невооруженным глазом.

В первом случае обычная функция принимает параметры и обрабатывает их

Во втором случае обрабатываем класс через соответствующий объект

При этом первый вариант работы то же самое, что и второй.

 

Определение шаблона класса

Шаблон класса

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

Общая форма объявления параметризованного класса:

template<classтип_данных>classимя_класса {... };

Основные свойства шаблонов классов

· Компонентные функции параметризованного класса автоматически являются параметризованными. Их не обязательно объявлять как параметризованные с помощью template.

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

· Если friend-функция содержит в своем описании параметр типа параметризованного класса, то для каждого созданного по данному шаблону класса имеется собственнаяfriend-функция.

· В рамках параметризованного класса нельзя определить friend-шаблоны (дружественные параметризованные классы).

· С одной стороны, шаблоны могут быть производными (наследоваться) как от шаблонов, так и от обычных классов, с другой стороны, они могут использоваться в качестве базовых для других шаблонов или классов.

· Шаблоны функций, которые являются членами классов, нельзя описывать как virtual.

· Локальные классы не могут содержать шаблоны в качестве своих элементов.

Понятие абстрактного класса

Класс в котором определен хотя бы один чисто виртуальный метод называется абстрактным. Нельзя создавать объекты абстрактного класса. Абстрактный класс может использоваться только в качестве базового класса для построения других классов.

 

Класс, порожденный от абстрактного класса, должен переопределять описанные в нем чисто виртуальные методы. В противном случае этот класс также будет абстрактным.

 

В качестве примера абстрактного класса мы приведем класс Abstract, в котором описан чисто виртуальный метод PureFunc. Обратите внимание, что этот метод не определен в классе Abstract. Определение метода содержится только в порожденном классе Fact.

// Абстрактный класс Abstract

 

classAbstract

 

{

 

public:

 

// Чисто виртуальный метод, не имеет определения

 

virtualintPureFunc(void) = 0;

 

voidSetValue(inti) {iValue = i;}

 

intiValue;

 

};

 

// Класс Fact

 

class Fact: public Abstract

 

{

 

intPureFunc(void) {return iValue * iValue;}

 

};

Принципы ООП

Основные принципы ООП

 

Объектно-ориентированное программирование основано на «трех китах» - трех важнейших принципах, придающих объектам новые свойства. Этими принципами являются инкапсуляция, наследование и полиморфизм.

 

Инкапсуляция

 

Инкапсуляция есть объединение в единое целое данных и алгоритмов обработки этих данных. В рамках ООП данные называются полями объекта, а алгоритмы - объектными методами.

 

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

 

Другим немаловажным следствием инкапсуляции является легкость обмена объектами, переноса их из одной программы в другую. Можно сказать, что ООП «провоцирует» разработку библиотек объектов, таких как TurboVision.

 

Наследование

 

Наследование есть свойство объектов порождать своих потомков. Объект-потомок автоматически наследует от родителя все поля и методы, может дополнять объекты новыми полями и заменять (перекрывать) методы родителя или дополнять их.

 

Принцип наследования решает проблему модификации свойств объекта и придает ООП в целом исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее близкий по своим свойствам для решения конкретной задачи, и создает одного или нескольких потомков от него, которые «умеют» делать то, что не реализовано в родителе.

 

Последовательное проведение в жизнь принципа «наследуй и изменяй» хорошо согласуется с поэтапным подходом к разработке крупных программных проектов и во многом стимулирует такой подход.

 

Полиморфизм

 

Полиморфизм - это свойство родственных объектов (т.е. объектов, имеющих одного общего родителя) решать схожие по смыслу проблемы разными способами. В рамках ООП поведенческие свойства объекта определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках объекта, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющие разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.

Указатель на функцию. Использование массива указателей на функции для организации интерфейса.

 

Указатель на функцию - переменная, которая содержит адрес некоторой функции. Соответственно, косвенное обращение по этому указателю представляет собой вызов функции.

Определение указателя на функцию имеет вид:

 

int (*pf)(); // без контроля параметров вызова

int (*pf)(void); // без параметров, с контролем по прототипу

int (*pf)(int, char*); // с контролем по прототипу

 

В соответствии с принципом контекстного определения типа данных эту конструкцию следует понимать так: pf - переменная, при косвенном обращении к которой получается функция с соответствующим прототипом, например int_F(int, char*), то есть pf содержит адрес функции или указатель на функцию. Следует обратить внимание на то, что в определении указателя присутствует прототип - указатель ссылается не на произвольную функцию, а только на одну из функций с заданной схемой формальных параметров и результата.

Перед началом работы с указателем его необходимо назначить на соответствующий объект, в данном случае - на функцию. В синтаксисе Си выражение вида &имя_функции имеет смысл - начальный адрес функции или указатель на функцию. Кроме того, по аналогии с именем массива использование имени функции без скобок также интерпретируется как указатель на эту функцию. Указатель может быть инициализирован и при определении.

 

int INC(intа) { return a+1; }

externint DEC(int);

int (*pf)(int);

pf = &INC;

pf = INC; // присваивание указателя

int (*pp)(int) = &DEC; // инициализация указателя

 

Естественно, что функция, на которую формируется указатель, должна быть известна транслятору - определена или объявлена как внешняя. Синтаксис вызова функции по указателю совпадает с синтаксисом ее определения.

 

n = (*pf)(1) + (*pp)(n); // эквивалентно

n = INC(1) + DEC(n);

 

/*Пример определения и использования

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

*/

 

#include<iostream.h>

 

// описание функции f1

void f1(void)

{

cout<< "\n Выполняется f1()";

}

 

// описание функции f2

void f2(void)

{

cout<< "\n Выполняется f2()";

}

 

voidmain()

{

// объявление указателя на функцию,

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

ptr = f2; // ptr инициализируется адресом f2()

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

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

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

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

}

 


Поделиться с друзьями:

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Биохимия спиртового брожения: Основу технологии получения пива составляет спиртовое брожение, - при котором сахар превращается...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.17 с.