Помогни ни да направим Uroci.net по - богат! Добави урок

C++ част.5 (Свободна памет)

C++ » C++
fix3d   трудност:    видян: 24136



5.4. Указатели към функции

Нека да трябва да напишем една обобщена функция за сортиране. В повечето случаи особено подходящ се оказва алгоритъма за бързото сортиране. От потребителя се изисква само да напише следното

sort( array, lowBound, highBound );

При някои обстоятелства, обаче, бързото сортиране не е подходящо. Ако масивът е подреден в низходящ ред, например, методът със сливането ще работи по-добре. Ако разполагате с повече памет, по-подходящо може да се окаже сортирането с натрупване.

За малко множество от елементи обикновено достатъчно добър е сортирането по метода на мехурчето. Функцията sort() трябва да предлага някакви улеснения за избиране и на алтернативни сортиращи алгоритми. Проблемът се състои в това как да осигурим необходимата гъвкавост за едно малко множество от случаи без да усложним особено много общото използване на функцията. Обикновено, решение предлага една подразбираща се стойност. В нашият случай подразбиращата се стойност е функцията qickSort().

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

Този раздел разглежда как могат да бъдат удовлетворени тези две изисквания.



Типът указател към функция

Какво представлява един указател към функция? Какъв ще бъде типа му? Как може да бъде деклариран? Ето декларацията на quickSort()

void quickSort( int*, int, int );

Разбира се, името на функцията не е част от типа й. Типът на функцията се определя от стойността, която връща, и сигнатурата на списъка от аргументите й. Един указател към quickSort() може да бъде определен като се използват същата сигнатура и тип за връщане

void pf( int, int, int );

Написаното е почти правилно. Проблемът се състои в това, че компилаторът интерпретира оператора като дефиниция на функция pf(), която има три аргумента и тип за връщане void*. Операторът * се свързва със спецификатора на тип, а не с pf. Необходимо е задаването на скоби за да бъде свързан оператора * с pf

void (*pf) ( int*, int, int );

Този оператор декларира pf като указател към функция, която има три аргумента и тип за връщане void - т.е., указател от типа на quickSort().

Някои от останалите функции могат да имат или да нямат същия тип. Следните три функции за сортиране имат един и същ тип

void bubbleSort( int*, int, int );
void mergeSort( int*, int, int );
void heapSort( int*, int, int );

Обаче, двойката функции min() и max() декларират един друг тип

int min( int*, int sz );
int max( int*, int sz );

Указател към тези функции може да бъде дефиниран по следния начин

int (*pfi) ( int*, int );

Съществуват толкова различни типове на функции, колкото са комбинациите от сигнатури на функции и типове за връщане.



Инициализация и присвояване

Нека да припомним, че един идентификатор на масив съдържа стойността на указателя към първия елемент на масива когато не е изменен чрез индексен оператор. Едно име на функция, когато не е видоизменяно чрез оператор за обръщение, получава като стойност указател към функция от нейния тип. Например,quickSort;

получава стойност като не именуван указател от тип

void (*)( int*, int, int );

Прилагането на оператора адрес към името на функцията също дава като резултат указател към типа на функцията. По такъв начин както quickSort, така и &quickSort сочат към един и същ тип. Един указател към функция се инициализира по следния начин

void (*pfv) ( int*, int, int ) = quickSort;
void (*pfv2)( int*, int, int ) = pfv;

Присвояването на стойност се извършва по подобен начин

pfv = quickSort;
pfv2 = pfv;

Инициализацията и присвояването се считат за правилни само ако списъкът от аргументи и типът за връщане съвпадат точно. В противен случай се издава съобщение за грешка по време на компилация. Например,

extern int min( int*, int );
extern void (*pfv) ( int*, int, int ) = 0;
extern (*pfi)( int*, int ) = 0;

main() {
pfi = min; // ok
pfv = min; // error
pfv = pfi; // error
}

Един указател към функция може да бъде инициализиран и чрез присвояване на стойност 0.

Указателите могат също така да адресират и представители на презаредими функции. Когато една функция е презаредима, компилаторът свързва този представител, чийто тип за връщане и сигнатура съответствуват точно на типа на указателя. Ако такъв представител не бъде намерен се издава съобщение за грешка по време на компилация. Например,

extern void ff( char );
extern void ff( unsigned );

void (*pf)(char) = ff; // ok
void ff(char)
void (*pf2)
(int) = ff; // error no exact match



Извикване

Операторът * не е необходим за да бъде извикана една функция чрез указател. Както директното, така и индиректното обръщение към функция се прави по един и същ начин. Например,

#include <stream.h>

extern min( int*, int );

int (*pf)
( int, int ) = min;
const int iaSize = 5;
int ia[ iaSize ] = { 7, 4, 9, 2, 5 };

main() {
cout << "Direct call min "<< min( ia, iaSize ) << "n";
cout << "Indirect call min "<< pf( ia, iaSize ) << "n";
}

min( int* ia, int sz) {
int minVal = ia[ 0 ];
for ( int i = 0; i< sz; ++i )
if ( minVal > ia[ i ] )
minVal = ia[ i ];
return minVal;
}

Обръщението pf( ia, iaSize ); е кратък запис на явното указване на указателя.

(*pf)( ia, iaSize );

Двете форми са еквивалентни.
Масиви, аргументи и типове за връщане
Може да бъде деклариран масив от указатели към функции. Например,

int (*testCases[10])();

декларира testCases като масив от десет елемента. Всеки елемент е указател към функция, която няма аргументи и има тип за връщане int. Изпълнението на елементи от testCases може да има вида

extern const SIZE = 10;
extern int (*testCase[SIZE])();
extern int testResults[SIZE];

void runtest() {
for ( int = 0; i < SIZE; ++i )
testResults[ i ] = testCases[ i ]();
}

Един масив от указатели може да бъде инициализиран по следния начин

extern void quickSort( int*, int, int );
extern void mergeSort( int*, int, int );
extern void heapSsort( int*, int, int );
extern void bubbleSort( int*, int, int );

void ( sortFuncs[] )( int, int, int ) = {
quickSort,
mergeSort,
heapSort,
bubbleSort };

Към sortFuncs също може да бъде деклариран указател, който ще бъде от типа “указател към масив от указатели към функции”. Дефиницията ще изглежда така

void ( **pfSort ) ( int*, int, int ) = sortFuncs;

Двата оператора ** декларират pfSort като указател към указател.

**pSort; има стойност адреса на sortFuncs.
**pfSort; има стойност адреса на quickSort(), първият елемент на sortFuncs - еквивалентно на записа *pfSort[ 0 ];

За да изпълни quickSort() чрез pfSort, програмистът трябва да напише едно от следните две неща

// equivalent invocation
pfSort[ 0 ]( ia, 0, iaSize-1);

// shorthand
(*pfSort[ 0 ])( ia, 0, iaSize-1 );

// explicit

Указатели към функции могат също да бъдат декларирани и като аргументи на функции и за тях могат да се задават стойности по подразбиране.

exetrn void quickSort( int*, int, int );
void sort( int*, int, int,void (*)(int*, int, int) = quickSort );

Една проста дефиниция на sort() може да изглежда така:

void sort( int *ia, int low, int high,void (*pf)(int*, int, int )) {
if ( !ia ) return;
if ( !pf ) return;
if ( high < low + 2 ) return;
pf( ia, low, high );
}

sort() може да бъде извикана във всички от следните начини

// normally, these would be in a header file
extern int *ia;
extern const iaSize;
extern void quickSort( int*, int, int );
extern void bubbleSort( int*, int, int );
typedef void (*PFV)( int*, int, int );
extern void sort( int*, int, int, PFV=quickSort );
extern void setSortPointer ( PFV& );
PFV mySort;

void ff(){
sort( ia, 0, iaaSize );
sort( ia, 0, iaSsize, bubbleSort );
setSortPointer( mySort );
sort( ia, 0, iaSize, mySort );
}

Един указател към функция може също да бъде деклариран като тип за връщане на функция. Например,

int ( ff( int ))( int, int );

декларира ff() като функция, която получава един аргумент от тип int. Тя връща указател към функция от тип int (*)( int*, int );
Използуването на име typedef може да направи използването на указател към функция значително по-лесно за четене. Например,

typedef int (*PFI)( int*, int );

PFI ff( int );

е една еквивалентна декларация на ff().

Упражнение 5-9. В раздел 4.9 беше дефинирана функцията fibonacci(). Дефинирайте указател към функция, който да може да сочи към fibonacci(). Извикайте функцията чрез този указател за да генерирате редица на Фибуначи от осем елемента.

Упражнение 5-10. В раздел 4.10 беше дефинирана функцията binSearch(). Дефинирайте функция search(), която да може да бъде извиквана така

extern int size, val, ia[>int index = search( ia, size, val, binSearch );
_new_handler

_new_handler представлява указател към функция, предлагана от една от стандартните библиотеки на С++, които се разпространяват заедно с езиковата система на AT&T и има стойност 0 по подразбиране. Декларацията на _new_handler изглежда така

void ( *_new_handler ) ();
_new_handler е указател към функция, която има тип за връщане void и не получава аргументи.

Когато изпълнението на функцията new не приключи успешно, се проверява дали _new_pointer не сочи към функция. Ако _new_handler съдържа стойността по подразбиране, new връща 0; иначе се извиква функцията, към която сочи _new_handler. Функцията, към която сочи _new_pointer трябва да бъдедефинирана от потребителя. Освен това, той трябва явно да даде на _new_handler стойност - указател към функция. Това може да бъде направено директно по следния начин

void freeStoreExeption()// to be invoked if new failsextern
StoreExeption_new_handler = freeStoreExeption;// set_new_handler to free

или чрез библиотечната функция set_new_handler(). Последното може да бъде извършено по следния начин

#include <new.h>// new.h contains a declaration of set_new_handler
set_new_handler( freeStoreExeption );// set _new_handler with library function

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

Най-простата функция, с която се свързва _new_handler издава съобщение за грешка и осигурява изход от програмата

#include <stream.h>
#include <stdlib.h>

extern char *progName; // current file
enum Exeptions { FS_EXHAUST = 1, /* ... */ };
void freeStoreExeption() {
cerr << progName<< " free store exhausted!n";
// do any clean-up here ...
exit( FS_EXHAUST );
}


Страници: «3 4 5 6 7 »

Сподели урока:



Регистрирайте се, за да добавите коментар


Калдейта Ком ЕООД - © 2003-. Всички права запазени.
Препоръчваме: IT Новини