Функции в С++ : перегрузка, значения по умолчанию

Функции в С++

В отличие от языка C, C++ не предусматривает автоматического преобразования в тех случаях, когда фактические параметры не совпадают по типам с соответствующими им формальными параметрами.

Говорят, что язык C++ обеспечивает строгий контроль типов.

В связи с этой особенностью языка C++, проверка соответствия типов формальных и фактических аргументов выполняется на этапе компиляции.

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

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

 
тип имя (СпецификацияФормальныхАгрументов);

Основное различие – точка с запятой в конце описания (прототипа). Второе отличие – необязательность имен формальных аргументов в прототипе даже тогда, когда они есть в заголовке определения функции.

Спецификация формальных аргументов может быть пустой (void), а может представлять собой список спецификаций отдельных аргументов, разделенных запятыми. Спецификация каждого аргумента в определении функции имеет вид:

тип имя

Значения по умолчанию

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

тип имя=ЗначениеПоУмолчанию

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

Пример Вычислить n в степени k, где чаще всего k=2. Рекурсивная функция со значением k=2 по умолчанию:

1
2
3
4
5
int pow(int n, int k = 2)  // по умолчанию k=2
{
  if (k == 2) return(n*n);
  else return(pow(n, k — 1)*n);
}

Вызывать эту функции можно двумя способами:

 
 
t = pow(i);  // t = i*i;
q = pow(i, 5); // q = i*i*i*i*i;

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

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

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

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

 
 
 
int max(int *a, int n);
float max(float *a, int n);
double max(double *a, int n);

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

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

 
 
int sum(int a, int b=1)  { return(a+b); }
int sum(int a)           { return(a+a); }

то вызов

 
int r = sum(2);  // ошибка

выдаст ошибку из-за неоднозначности толкования sum().

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

В базовом языке Си директива препроцессора #define позволяет использовать макроопределения для записи вызова небольших часто используемых конструкций. Некорректная запись макроопределения может приводить к ошибкам, которые очень трудно найти. Макроопределения не позволяют определять локальные переменные и не выполняют проверки и преобразования аргументов. Если вместо макроопределения использовать функцию, то это удлиняет объектный код и увеличивает время выполнения программы.

Кроме того, при работе с макроопределениями необходимо тщательно проверять раскрытия макросов, например

 
 
#define SUMMA(a, b) a + b
rez = SUMMA(x, y)*10;

После работы препроцессора получим:

 
rez = x + y*10;

В С++ для определения функции, которая должна встраиваться как макроопределение используется ключевое слово inline. Вызов такой функции приводит к встраиванию кода inline-функции в вызывающую программу.

Определение такой функции может выглядеть следующим образом:

1
2
3
4
inline double SUMMA(double a, double b)
{
  return(a + b);
}

При вызове этой функции

 
rez = SUMMA(x,y)*10;

будет получен следующий результат:

 
rez=(x+y)*10;

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

  • Определение и объявление функций должны быть совмещены и располагаться перед первым вызовом встраиваемой функции.
  • Имеет смысл определять inline только очень небольшие функции, поскольку любая inline-функция увеличивает программный код.
  • Различные компиляторы накладывают ограничения на сложность встраиваемых функций. Компилятор сам решает, может ли функция быть встраиваемой. Если функция не может быть встраиваемой, компилятор рассматривает ее как обычную функцию.

Таким образом, использование ключевого слова inline для встраиваемых функций и ключевого слова const для определения констант позволяют практически исключить директиву препроцессора #define из употребления.

Прокрутить вверх