Тема: Способы применения встраиваемых функций и их перегрузка
Встраиваемые функции 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 перед каждым формальным параметрам шаблона функции является ошибкой.