C++ Программирование в среде С++ Builder 5

Перегруженные функции


В программировании то и дело случается писать функции для схожих действий, выполняемых над различными типами и наборами данных. Возьмите, например, функцию, которая должна возвращать квадрат своего аргумента. В C/C++ возведение в квадрат целого и числа с плавающей точкой — существенно разные операции. Вообще говоря, придется написать две функции — одну, принимающую целый аргумент и возвращающую целое, и вторую, принимающую тип double и возвращающую также double. В С функции должны иметь уникальные имена. Таким образом, перед программистом встает задача придумывать массу имен для различных функций, выполняющих аналогичные действия. Например, Square-Int() и SquareDbl() .

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

Рассмотрите следующий пример с вышеупомянутыми квадратами. Мы предусмотрели еще “возведение в квадрат” строки, когда результатом функции должна быть строка, в которой любые символы, кроме пробелов, удваиваются.

#include <stdio.h>

int Square(int arg)

{

return arg*arg;

}

double Square(double arg)

{

return arg*arg;

char *Square(const char *arg, int n)

{



static char res[256];

int j = 0;

while (*arg && j < n) { if (*arg != ' ') res[j++] = *arg;

res[j++] = *arg++;

}

res[j] = 0;

return res;

}

int main(void)

{

int x = 11;

double у = 3.1416;

char msg[] = "Output from overloaded Function!";

printf("Output: %d, %f, %s\n", Square (x) , Square (y) , Square (msg, 32) ) ;

return 0 ;

}

}

Результат работы программы показан на рис. 6.1.

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

Рис. 6.1 Пример с тремя перегруженными функциями

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


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



Как это делается



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

Вот несколько примеров того, как для различных прототипов производится декорирование имени функции:

void Func(void); // @Func$qv

void Func(int); // @Func$qi

void Func(int, int); // @Func$qii

void Func(*char); // 8Func$qpc

void Func(unsigned); // @Func$qui

void Func(const char*); // @Func$qpxc

Тип возвращаемого значения никак не отражается на декорировании имени.



Спецификация связи



Если функция, написанная на C++, должна вызываться из программы на С, Pascal или языке ассемблера (и наоборот, что часто бывает при использовании существующих динамических библиотек), то механизм декорирования имен C++ создает некоторые трудности. Они могут быть причиной сообщений об ошибках при компоновке вроде “Неопределенный символ ххх в модуле уyу”.

Чтобы обойти возникающие из-за декорирования проблемы, используется спецификация extern "С". Она применяется в двух случаях: во-первых, когда следует запретить декорирование имени определяемой вами функции, и во-вторых, когда вы хотите информировать компилятор, что вызываемая вами функция не соответствует спецификации связи C++.

Вот примеры:

// Функция, которую можно вызывать из С (прототип):

extern "С" void FuncForC(void);

// Прототип функции из некоторой библиотеки не на C++:

extern "С" void SomeExtFunc (int);

// Определение - extern "С" не требуется, если включен

// прототип:



void FuncForC(void)

{

printf("Hello!\n");

}

// Вызов внешней функции:

SomeExtFunc(count);



Следует отличать спецификацию связи от соглашений o вызове. Функция, например, может вызываться в соответствии с соглашением Pascal (что означает определенный порядок передачи параметров и имя в верхнем регистре), но иметь спецификацию связи C++, т. е. декорированное имя. Алгоритм здесь такой: сначала формируется (если не указано extern "С") декорированное имя, а затем оно подвергается модификации в соответствии с соглаЕсли вы вызываете функции библиотек, написанных на С или языке ассемблера и у вас есть необходимые заголовочные файлы на С, то соответствующие директивы включения можно поместить в блок extern "С", как показано ниже. В противном случае нужно было бы модифицировать эти файлы, добавив спецификации связи к прототипам функций.

extern "С" { #include "asmlib.h" #include "someclib.h" }


Содержание раздела