Тема: Способы применения встраиваемых функций и их перегрузка

Встраиваемые функции inline

Реализация программы как набора функций хороша с точки зрения разработ­ки программного обеспечения, но вызовы функций приводят к накладным расхо­дам во время выполнения. В С++ для снижения этих накладных расходов на вызо­вы функций — особенно небольших функций — предусмотрены встраиваемые (inline) функции. Спецификация inline перед указанием типа результата в объяв­лении функции «советует» компилятору сгенерировать копию кода функции в со­ответствующем месте, чтобы избежать вызова этой функции. Это эквивалентно объявлению соответствующего макроса. В результате получает­ся множество копий кода функции, вставленных в программу, вместо единствен­ной копии, которой передается управление при каждом вызове функции.

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

Любые изменения функции inline могут потребовать перекомпиляцию всех «потребителей» этой функции. Это может оказаться существенным моментом для развития и поддержки неко­торых программ,

Хороший стиль программирования

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

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

inline double Circ(double R){return 6.28318 * R;}

Обращение в любом месте программы вида Circ(2) приведет к встраиванию в со­ответствующем месте кода 6.28318 * 2 (если компилятор сочтет это целесообразным).

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

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

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

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

char *ToS(char *s, int X)

{

return strcat(strcat(S," "),IntToStr(X).c_str());

}

char *ToS(char *S, double X)

{

return strcat(strcat (S," "),FloatToStr(X).c_str());

}

char *ToS(char *S, bool X)

{

if (X) return strcat(S," true");

else return strcat (S," false");

}

Тогда в своей программе вы можете написать, например, вызовы:

char S[128] = "Значение =";

char S1 = ToS(S,5) ;

или

char S[128] = "Значение =";

char S2 = ToS(S,5.3);

или

char S[128] = "Значение =";

char S3 = ToS(S,true);

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

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

double Area(double R)

{

return 6.28318 * R * R;

}

double Area(double a, double b)

{

return a * b;

}

Тогда операторы вида:

S1 = Area(1);

S2 = Area(1,2);

приведут в первом случае к вызову функции вычисления площади круга, а во вто­ром — к вызову функции вычисления площади прямоугольника.

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

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

Функция с пропущенными аргументами по умолчанию может оказаться вызванной аналогич­но другой перегруженной функции; это синтаксическая ошибка.

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

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

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

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

Приведем пример шаблона функции, возвращающей минимальный из трех передаваемых в нее параметров любого (но одинакового) типа:

template <class Т>

Т min (Т x1, Т х2, Т хЗ)

{

T lmin = x1;

if (x2 < lmin)

lmin = x2;

if (x3 < lmin)

lmin = x3;

return lmin;

}

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

Объявленный таким образом шаблон можно использовать, например, следую­щим образом:

int i1 = 1, i2 = 3, i3 = 2;

double r1 = 2.5, r2 = 1.7, r3 = 3.4;

AnsiString si = "строка 1", s2 = "строка 2", s3 = "строка 3";

Label1->Caption = min(il,i2,i3);

Label2->Caption = min(rl,r2,r3);

Label3->Caption = min(s3,s2,s1);

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

Например, при вызове функции с тремя целыми параметрами компилятор сгенерирует функцию:

int min(int xl, int x2, int x3)

{

int lmin = xl;

if (x2 < lmin)

lmin = x2;

if (x3 < lmin)

lmin = x3;

return lmin;

}

Приведенный шаблон будет работать для любых предопределенных или вве­денных пользователем типов, для которых определена операция отношения.

Каждый формальный параметр в определении шаблона должен хотя бы однажды появиться в списке параметров функции. Каждое имя формального параметра в списке определения шаблона должно быть уникальным. Отсутствие ключевого слова class перед каждым формальным параметрам шаблона функции является ошибкой.

Наши рекомендации