Перегрузка функций
Перегрузка является отличительной особенностью яп С++ в сравнении с Си. Перегрузка выполняется на этапе компиляции.
double square(double d) {return d * d;}
int square(int i) {return i * i;}
int a = square(4); // square(int)
int b = square(3.14); // square(double)
double c = square(5); // square(int)
Стоит отметить что перегрузка не будет работать если изменить только возвращаемый тип:
int getRandomValue();
double getRandomValue();
// Error
Почему не происходит конфликт имён?
int min(int a, int b)
int min(int a, double b)
Потому что во время компиляции имена этих функций поменяются на _Z3minii и _Z3minid, где:
- 3 - длина названия функции
- min - название функции
- id - параметры функции: int и double.
Такая подстановка называется декорирование имени функции.
Обратите внимание, что перегрузка доступна только для С++ и не существует в С, соответственно компилятор С не декорирует имя функции, а оставляет как есть. Для того что бы иметь возможность использовать С функции в С++ стоит указать перед функцией extern “C”, это сообщит компилятору не декорировать имя функции. Очень частая ошибка:
g++ -o test1 test1a.o test1b.o
test1a.o(.text+0x18): In function `main':
: undefined reference to `findmax(int, int)'
collect2: ld returned 1 exit status
Т.е. линковщик не нашел функцию findmax, обратите внимание, именно findmax без декорации! Это означает что вероятно компилятор C++ декорировал findmax до _Z2findmaxii, а компилятору С была обещана (была объявлена в коде) функция findmax без декорации, которую он и не может найти в итоге.
Наглядный пример
Возьмем .c файл, который будет вызывать функцию, реализованную в .срр:
// main.c
int func(int x, int y, int z);
int main()
{
func(3, 2, 1);
return 0;
}
// func.cpp
int func(int x, int y, int z)
{
return 1;
}
Следовательно, при компиляции .с (сишным компилятором) на этапе линковки он будет ждать функцию с названием func (без декорации), а .срр (с++ компилятором), который знает о перегрузке, как обычно декорирует название функции: _Z4funciii (сгенерированные названия можно посмотреть в АСМ файле: g++ -S filename
). Итого: линковщик не найдёт функцию, произойдет ошибка линковки.
ld.exe: main.o:main.c:(.text+0x1e): undefined reference to `func'
error: ld returned 1 exit status
Но если мы добавим extern "C"
к определению функции, то все успешно скомпилируется:
// func.cpp
extern "C"
{
int func(int x, int y, int z)
{
return 1;
}
}
Потому что extern “C” говорит с++ компилятору не декорировать название функции, оставить в стиле Си.
Стоит обратить внимание, что добавляя extern “C”:
// Так перегружать нельзя, что логично: какую из двух вызывать с Си кода?
extern "C"
{
int func(int x, int y, int z)
{
return 1;
}
int func(int x, int y)
{
return 1;
}
}
// А так можно (первая будет участвовать в С коде, а в С++ будет перегрузка):
extern "C"
{
int func(int x, int y, int z)
{
return 1;
}
}
int func(int x, int y)
{
return 1;
}
extern “C” можно добавлять:
1. К чему-то одному:
// только одна функция
extern "C" void func() {}
2. К блоку:
extern "C"
{
// много всего
}
Так же существует extern "C++"
(включена по умолчанию)
Перегрузка методов
struct Vector2D
{
Vector2D(double x, double y) : x(x), y(y) {}
// произведение вектора на число
Vector2D mult(double d) const
{
return Vector2D(x * d, y * d);
}
// произведение вектора на вектор
double mult(Vector2D const &p) const
{
return x * p.x + y * p.y;
}
double x, y;
};
Vector2D p(1, 2);
Vector2D q = p.mult(10);
double r = p.mult(q);
Перегрузка при наследовании
struct File
{
void write(char const * s);
...
};
struct Formatted_File : File
{
void write(int i);
void write(double d);
using File::write; // без этой строки методы дочернего класса просто перекроют одноименные родительские методы
...
};
Formatted_File f;
f.write(4); // вызовется соотв. функция дочернего класса
f.write("Hello"); // не будет работать без using File::write;