4881

Указатели на функции. Перегрузка функций. Шаблоны функций

Лекция

Исторические личности и представители мировой культуры

Указатели на функции. Перегрузка функций. Шаблоны функций. Предположим, что нужно реализовать функцию сортировки массива строк с примерно таким прототипом: void sort( char beg, char end ) здесь beg и end являются указателями на начало и конец...

Русский

2012-11-28

61 KB

7 чел.

Указатели на функции. Перегрузка функций. Шаблоны функций.

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

void sort( char ** beg, char ** end );

здесь beg и end являются указателями на начало и конец диапазона элементов массива, который необходимо отсортировать. Для сортировки должно быть определено правило упорядочивания строк (предикат) и различных вариантов выбора этого правила может быть много, при этом сам алгоритм сортировки слабо зависит от выбора предиката. Конечно, можно было бы реализовать несколько разных функций для различных правил сравнения: сортировка строк по длине, в лексикографическом (словарном) порядке, по возрастанию, убыванию и т.п., однако это привело бы к массовому дублированию однотипного кода, и в случае обнаружения какой-либо ошибки в реализации алгоритма, пришлось бы исправлять её во всех вариантах сортировки. В подобных случаях удобно использовать указатель на функцию в качестве одного из параметров функции сортировки.

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

int compare( const char * s1, const char * s2 );

в случае равенства строк compare возвращает 0, иначе 1, если строка s1 «больше» s2, и  -1, если s1 «меньше» s2. В этом случае, указатель на любую функцию с таким прототипом будет выглядеть так:

int ( * pf ) ( const char *, const char * );

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

int compareLen( const char * s1, const char * s2 )

{

 int len1 = strlen( s1 );

 int len2 = strlen( s2 );

 

 return ( len1 == len2 ) ? 0 : ( len1 > len2 ? 1 : -1 );

}

bool compareAlpha( const char * s1, const char * s2 )

{

 return s1[0] != s2[0];

}

int ( * pf ) ( const char *, const char * );

void main(int argc, char* argv[])

{

pf = compareLen;   // допустимо

pf = & compareLen; // допустимо, эквивалентно предыдущему

pf = strcmp;       // допустимо

 

pf = compareAlpha; // ошибка, несовпадение типов

}

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

оператор разыменования при этом необязательно. И прямой вызов функции по имени, и косвенный вызов по указателю записываются одинаково:

compareLen( "a", "b" ); // прямой вызов

 pf( "a", "b" );         // косвенный вызов

( * pf )( "a", "b" );   // косвенный вызов с использованием

                        // явного синтаксиса указателя

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

Можно объявить массив указателей на функции. Например:

int ( * funcArray[5] )( double );

Здесь funcArray – это массив из пяти элементов, каждый из которых является указателем на функцию, принимающую один параметр типа double и возвращающую значение типа int. Объявления такого типа достаточно трудно читать, поэтому часто используют директиву typedef для объявления вспомогательного типа, соответствующего указателю на функцию:

typedef int ( * t_funcPtr )( double );

t_funcPtr funcArray[5];

Присваивание значений элементам массива и вызов функций осуществляется с использованием обычного оператора [], например:

int f1( double d )

{

 return static_cast< int >( d );

}

funcArray[0] = f1;

... // инициализация остальных элементов массива funcArray

double values[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };

double results[5];

for ( int i = 0; i < 5; ++i )

results[i] = funcArray[i]( values[i] );

В результате, функция сортировки строк с учетом произвольного правила сортировки может быть реализована, например, так (использован алгоритм сортировки «пузырьком»):

// Определение типа указателя на функцию сравнения строк

typedef int ( * t_compareFunc ) ( const char *, const char * );

void sortStrings( char ** beg, char ** end, t_compareFunc compare )

{

 // Временный буфер для обмена строк

 const int BUFSIZE = 128;

 char tmp[BUFSIZE];

   

 bool swapped = true;

 while ( swapped )

{

 swapped = false;

 for ( char ** strPtr = beg; strPtr != end; ++strPtr )

 {

  if ( compare( * strPtr, * ( strPtr + 1 ) ) > 0 )

  {

   strcpy( tmp, * strPtr );

   strcpy( * strPtr, * ( strPtr + 1 ) );

   strcpy( * ( strPtr + 1 ), tmp );

   swapped = true;

  }

 }

}

}

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

Механизм перегрузки функций позволяет иметь несколько одноименных функций, выполняющих схожие операции над аргументами разных типов. Например, именно этот механизм позволяет осуществлять сложение аргументов разных типов одним и тем же оператором сложения +. Для вычисления выражения 1 + 2 вызывается операция целочисленного сложения, в то время как вычисление выражения 1.0 + 2.0 осуществляет сложение с плавающей точкой. Выбор конкретной версии этого оператора зависит от типов аргументов и производится незаметно для программиста. Ответственность за распознавание контекста и применение операции берет на себя компилятор.

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

int max_pair( int a, int b );

int max_array( const int * A, int size );

int max_matrix( const int ** M, int rows, int cols );

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

int max( int a, int b );

int max( const int * A, int size );

int max( const int ** M, int rows, int cols );

Шаблоны функций.

Механизмы указателей на функции и перегрузки функции позволяют во многих случаях избежать дублирования кода и «засорения» области видимости разными именами для функций, выполняющих однотипную работу, однако строгая типизация в C++ создает дополнительные препятствия: в рассмотренном выше примере функций max необходимо было бы реализовать перегруженную версию функции для всех типов, к которым она может применяться, при том, что по существу, реализации этих версий функции не будут иметь различий:

int max( int a, int b );

double max( double a, double b );

unsigned long max( unsigned long a, unsigned long b );

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

template < class T >

T max( T a, T b )

{

  return ( a > b ) ? a : b;

}

Как объявление, так и определение шаблона функции всегда должны начинаться с ключевого слова template, за которым следует список разделенных запятыми идентификаторов, заключенный в угловые скобки < и >, – список параметров шаблона, обязательно непустой. У шаблона могут быть параметры-типы, представляющие некоторый тип, и параметры-константы, представляющие фиксированное константное выражение.

Параметр-тип состоит из ключевого слова class или ключевого слова typename, за которым следует идентификатор. Эти слова всегда обозначают, что последующее имя относится к встроенному или определенному пользователем типу. Имя параметра шаблона выбирает программист. В приведенном примере мы использовали имя T, но могли выбрать и любое другое.

Процесс подстановки типов и значений вместо параметров называется конкретизацией шаблона. При конкретизации (порождении конкретного экземпляра) шаблона вместо параметра-типа подставляется фактический встроенный или определенный пользователем тип. Любой из типов int, double, char* является допустимым аргументом шаблона. Параметр-константа выглядит как обычное объявление. Он говорит о том, что вместо имени параметра должно быть подставлено значение константы из определения шаблона. Выполняется конкретизация неявно, как побочный эффект вызова или взятия адреса шаблона функции. Например, в следующей программе шаблон max конкретизируется дважды  – один раз для массива из пяти элементов типа int, а другой – для четырех элементов типа double:

// Определение шаблона функции max()

// с параметром-типом T и параметром-константой size

template < typename T, int size >

T max( T ( & refArr ) [ size ] )

{

  T maxVal = refArr[ 0 ];

  for ( int i = 1; i < size; ++i )

     if ( refArr[i] > maxVal )

        maxVal = refArr[i];

  return maxVal;

}

// размер определится по числу элементов в списке инициализации

int ia[] = { 10, 7, 14, 3, 25 };

double da[6] = { 10.2, 7.1, 14.5, 3.2, 25.0, 16.8 };

void main()

{

  // конкретизация max() для массива из 5 элементов типа int

  // подставляется T => int, size => 5

  int i = max( ia );

  

  // конкретизация max() для массива из 6 элементов типа double

  // подставляется T => double, size => 6

  double d = max( da );

}

Для определения фактического типа и значения константы, которые надо подставить в шаблон, исследуются фактические аргументы, переданные при вызове функции. В нашем примере для идентификации аргументов шаблона при конкретизации используются тип ia (массив из пяти int) и da (массив из шести double). Процесс определения типов и значений аргументов шаблона по известным фактическим аргументам функции называется выведением (deduction) аргументов шаблона.

Шаблон конкретизируется либо при вызове, либо при взятии адреса функции. В следующем примере указатель pf инициализируется адресом конкретизированного экземпляра шаблона. Его аргументы определяются путем исследования типа параметра функции, на которую указывает pf:

template < typename T, int size >

T max( T ( & refArr ) [ size ] )

{/*…*/}

// pf указывает на int max( int ( & )[10] )

int ( * pf )( int ( & )[10] ) = max;


 

А также другие работы, которые могут Вас заинтересовать

44732. Elliptic Sentences 33.81 KB
  It ws believed on theoreticl grounds tht gs should be nonconducting in the bsence of rdition provided tht the potentil grdient cross it ws not so high tht sprking could tke plce. Curiously enough experiments undertken to test this hypothesis showed tht smple of ir in closed vessel lwys exhibited smll electricl conductivity in spite of every precution to eliminte rdition nd prevent lekge long the insultors. Tht these explntions were not sufficient to ccount for the observed phenomen ws shown by the experiments of some scientists who in...
44733. Word Order in the Simple Sentence. Types of Questions. The Noun: the Category of Number. The Use of Articles. Present, Past, Future Simple (Active Voice) 56 KB
  Mn is lso the cretor of the innumerble spiritul tresures of mnkind: the wonderful works of rt literture nd science. The mchine system mde it possible to include science in production on lrge scle. We live in the epoch when science becomes direct productive force of society. spred of informtion of knowledge of science поширення інформації знань науки 12.
44734. Past Participle, Simple Tenses (Passive Voice); Pronouns: demonstrative, personal and possessive 37.5 KB
  Potentil nd Kinetic Energy The cpcity of body to perform work is clled its energy. This energy is designted potentil if it is due to the position of the body. Energy in this form is designted s kinetic. t this point the body possesses no potentil energy t ll for its distnce bove the ground is zero but it does hve kinetic energy becuse of its motion.
44735. Present Participle. Continuous Tenses (Active, Passive). Quantifiers: some, any, no, much, many, little, a little, few, a few 82 KB
  Prctice reding the following wordcombintions: Tken into ccount for exmple in order to in fct in the lnguge of science in everydy life connected with the ide of time stte of rest stte of motion from motion to rest in scientific sense of the word the force is pplied to result in no work the mount of performed work the product of the force by the distnce the bility to work different kinds of energy ll moving bodies to drive the wterwheels of turbines. TEXT 3 FORCE WORK ENERGY ND POWER In the lnguge of science few words...
44736. Perfect Tenses (Active, Passive). Numerals. Cleft sentences: it is (was)…that (who) 66.5 KB
  In our scientific ge there is generl belief tht ll science s it grows to perfection becomes mthemticl in its ides. It is generlly true tht in the development of lgebr three stges hve been pssed successively: verbl bbrevited nd symbolic. Verbl lgebr is chrcterized by the complete bsence of ny symbols except of course tht the words themselves re used in their symbolic sense.
44737. Роль мультимедиа в повышении эффективности процесса обучения экономике 136.5 KB
  Практические аспекты использования мультимедиа в процессе обучения экономике. Особенности проведения медиа-урока: доходы и расходы семьи. Сравнительный анализ традиционного и нетрадиционного (с использованием мультимедиа) урока...
44738. РАЗВИТИЕ БАНКОВСКИХ ПРОДУКТОВ И УСЛУГ 459 KB
  Изучить основные направления в части оценки состояния номенклатуры и ассортимента продуктов и услуг российских банков, предоставляемых клиентам. Оценить эффективность приоритетов развития и перспектив внедрения новых продуктов и услуг, путей и способов реализации. Разработать рекомендации по внедрению новых продуктов и услуг в банковской среде, предоставляемых клиентам.
44739. Основы психологии и педагогики 615 KB
  Основной целью изучения учебной дисциплины «Основы психологии и педагогики» является формирование у будущих специалистов универсальных психолого-педагогических компетенций, обеспечивающих эффективное решение широкого круга социально-личностных и профессиональных задач в сфере любой профессии.
44740. Горная выработка. Проведение горных выработок 446.5 KB
  Горнодобывающая промышленность является одной из отраслей экономики России, на основе которой развиваются металлургия, химическая промышленность, машиностроение, электродобывающая и другие отрасли.