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

Функции в С++

В отличие от языка 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 из употребления.

4 комментария к “Функции в С++”

  1. Поправьте меня, если ошибаюсь, при inline функция\процедура загружается в память с запуском программы, без inline — в момент вызова. Т.е. если функция вызывается откуда-нибудь из цикла, то для избежания постоянных выделений\освобождений памяти для нее ее желательно инлайнить. НО, inline может быть проигнорирован компилятором, т.к. он по своему принципу определяет, что нужно инлайнить, а что нет(сугубо мое субъективное мнение).

    1. Елена Вставская

      Как раз наоборот. Если функция вызывается из цикла, инлайнить ее желательно.
      inline — это встраивание функции в код, то есть обращение к такой функции не происходит, а ее тело встраивается в место вызова. Это подобно макрокомандам. Такой метод увеличивает объем кода, но сокращает время его выполнения.

  2. Дмитрий

    Например, если в одной программе перегружены функции

    1
    2
    3
    4
    int sum(int a, int b=1) { return(a+b); }
    int sum(int a) { return(a+a); }
    то вызов
    int r = sum(2); // ошибка

    выдаст ошибку из-за неоднозначности толкования sum().
    ***
    Елена, не могли бы вы пояснить в чем заключается неоднозначность толкования..?

    1. Елена Вставская

      Если мы вызываем функцию sum(5), компилятор не сможет принять решение, вызывать функцию

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

      или

      1
      sum(5) {return (5+5);}

      В этом и неоднозначность. Поэтому при такой перегрузке возникнет ошибка компиляции.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

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