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;


 

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

77023. Административное право как наука и учебная дисциплина 22.96 KB
  Предметом науки административного права является исследование законов закономерностей и особенностей в управленческой деятельности органов исполнительной власти органов государственного управления администраций органов МСУ их должностных лиц и осуществляемых при этом управленческих отношений в иных сферах эффективности административноправовых норм регулирующих эти отношения правоприменительной практики выявление свойств с целью их совершенствования. Дисциплина курса Административное право представляет собой систему которая...
77024. Понятие системы административно-правового регулирования 29.33 KB
  Понятие системы административно-правового регулирования. Система элементов механизма административно-правового регулирования представляет собой совокупность элементов и правовых средств взаимосвязанных и взаимозависимых и образуемых в информационно-правовое единство в целях упорядочивающего воздействия и устойчивого развития управленческих отношений в сфере деятельности органов исполнительной власти государственного управления администраций органов МСУ уполномоченных должностных лиц и иных регулируемых административными правовыми нормами...
77025. Нормы административного права: понятие, особенности, структура, виды и их характеристика 29.53 KB
  Нормы административного права: понятие особенности структура виды и их характеристика. Административно-правовая норма это мера должного или возможного допускаемого рекомендуемого поведения людей установленная государством и охраняемая специальными средствами...
77026. Реализация и действие административно-правовых норм 27.75 KB
  Реализация административно-правовых норм выражается в практическом проведении в жизнь содержащихся в них правовых предписаний и обеспечении должного поведения участников управленческих отношений.
77027. Административно-правовые отношения: понятие, особенности, содержание. Основания возникновения, изменения и прекращения этих отношений 30.05 KB
  Виды административно-правовых отношений. Административно-правовые отношения это общественные отношения урегулированные нормами административного права возникающие в сфере исполнительной власти государственного управления. Признаки административных правоотношений: являются публичными правоотношениями имеющими в своей основе общественный государственный интерес; носят властный характер так как в процессе возникновения изменения и прекращения данных правоотношений реализуется государственное управление; являются организационными...
77028. Источники административного права: понятие и виды. Кодификация и систематизация административного законодательства 27.38 KB
  Источники административного права: понятие и виды. Кодификация и систематизация административного законодательства. Источники административного права – это внешние формы выражения административно-правовых норм которыми являются различные по юридической силе нормативные правовые акты. Виды источников административного права: 1.