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;


 

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

39113. Ноосферная концепция В.И. Вернадского 52.5 KB
  Осознавая огромную роль и значение человека в жизни и преобразовании планеты русский ученый употреблял понятие ноосфера в разных смыслах: 1.[2] Таким образом понятие ноосфера предстаёт в двух аспектах: 1. Ноосфера в стадии становления развивающаяся стихийно с момента появления человека; 2. Ноосфера развитая сознательно формируемая совместными усилиями людей в интересах всестороннего развития всего человечества и каждого отдельного человека.
39114. Устойчивое развитие и эколого – правовой режим охраны природных ресурсов и объектов 71.5 KB
  Человек всегда использовал окружающую среду в основном как источник ресурсов, однако в течение очень длительного времени его деятельность не оказывала заметного влияния на биосферу. Лишь в конце дошлого столетия изменения биосферы под влиянием хозяйственной деятельности обратили на себя внимание ученых. В первой половине нынешнего века эти изменения нарастали и в настоящее время лавиной обрушились на человеческую цивилизацию
39115. ИССЛЕДОВАНИЕ КОРОННОГО РАЗРЯДА 305.5 KB
  Если к двум электродам между которыми находится газовый промежуток приложить электрическое поле то при определенной разности потенциалов между электродами которую назовем критической и обозначим через U0 возникает коронный разряд. При прочих равных условиях вероятность появления свечения вокруг электрода а следовательно короны тем больше чем меньше радиус кривизны электродов. Свечение возникающее при коронном разряде около электрода связано с элементарными процессами происходящими на границе электрод воздух или в объеме...
39116. ИССЛЕДОВАНИЕ КОРОННОГО РАЗРЯДА. Отрицательный коронный разряд 85 KB
  Кроме того критические потенциалы коронного разряда и искрового пробоя Uп неодинаковы. Возникновение коронного разряда объясняется появлением вблизи коронирующего электрода резкой неоднородности электрического поля значительно превосходящей напряженность электрического поля на других участках воздушного промежутка между электродами. Для возникновения коронного разряда напряженность поля у электрода должна превосходить электрическую прочность воздуха.
39117. ПСИХОФИЗИОЛОГИЧЕСКИЕ ПОДХОДЫ И ТЕОРИИ, ОКАЗАВШИЕ ЗНАЧИТЕЛЬНОЕ ВЛИЯНИЕ НА РАЗВИТИЕ ОТЕЧЕСТВЕННОЙ ПСИХОЛОГИИ 102.5 KB
  Основным фактором детерминирующим развитие и функционирование живых организмов а также отдельных физиологических систем в составе организма выступает не прошлое событие а подготовка к еще не наступившим событиям которая обеспечивается прогнозированием результатов предстоящих действий на основе опережающего отражения и механизмами целеполагания. Любой поведенческий акт живого организма обеспечивается рядом системных механизмов и процессов которые организуются в функциональную систему: механизм афферентного синтеза...
39118. АЛЬТЕРНАТИВНЫЕ ОБЩЕПСИХОЛОГИЧЕСКИЕ ТЕОРИИ И ТЕОРЕТИЧЕСКИЕ ПОДХОДЫ К ОБЪЯСНЕНИЮ ПСИХИЧЕСКИХ ЯВЛЕНИЙ 63.5 KB
  Строгий естественнонаучный подход к анализу и объяснению психических явлений в психологии поведения. В качестве одного из основателей объективной психологии поведения называют российского физиолога И. Вместе с тем это направление имеет множество научных достижений которые составляют золотой фонд мировой психологии: исследована эффективность различных типов подкрепления поощрения и наказания в процессах научения разработано множество тонких методов позволяющих регистрировать различные формы поведения животных установлено...
39119. КУЛЬТУРНО-ИСТОРИЧЕСКИЙ И СИСТЕМНО-ДЕЯТЕЛЬНОСТНЫЙ ПОДХОДЫ К АНАЛИЗУ И ОБЪЯСНЕНИЮ ПСИХИЧЕСКИХ ЯВЛЕНИЙ 172 KB
  Культурное развитие человека представляет собой формирование и развитие в совместной деятельности и общении высших психических функций ВПФ. Источник развития человеческой психики находится во внешней идеальной форме – в фиксированных в человеческой культуре средствах и способах деятельности и общения которыми необходимо овладеть. Формирование ВПФ выделяет человека из животного мира и заключается в присвоении культурноисторического опыта человечества что обеспечивает изменение структуры деятельности и психики человека. При этом...
39120. ТЕОРИИ ЭМОЦИОНАЛЬНЫХ ЯВЛЕНИЙ. ТЕОРИИ МОТИВАЦИОННОЙ И ВОЛЕВОЙ РЕГУЛЯЦИИ 155 KB
  Состояние когнитивного диссонанса возникает тогда: когда человек воспринимает самого себя в качестве причины возникшей когнитивной несогласованности; когда действия субъекта основаны на свободном выборе и разрушают Яконцепцию; когда люди чувствуют личную ответственность за свои неверные действия и поступки; когда такие действия и поступки имеют серьезные последствия. Когда свои действия или поступки которые вызывают когнитивный диссонанс человек не может оправдать и объяснить внешними факторами уменьшение диссонанса...
39121. СТРУКТУРНЫЕ, ФУНКЦИОНАЛЬНЫЕ И ГЕНЕТИЧЕСКИЕ ТЕОРИИ МЫШЛЕНИЯ (ИНТЕЛЛЕКТА) ЧЕЛОВЕКА 163.5 KB
  Такие возможности реализуются на основе эмпирического опыта и воздействий социального окружения; эмпирическим опытом который включает: формирование двигательных навыков путем упражнения извлечение информации из опыта взаимодействия с физическим миром логикоматематический опыт организации и координации познавательных действий; воздействием социального окружения. Взаимодействие субъекта и объекта – это источник любого знания который складывается из двух типов опыта: координации действий и операций которые строятся...