Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЯП - ПОИТ (Бахтизин) часть 1 редакт.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
1.76 Mб
Скачать

13.6. Перегрузка

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

void print(int a); // Вывести на печать число

void print(char c); // Вывести на печать символ

void print(char* s); // Вывести на печать строку

Каноническим является пример функции print. Это очень удобное имя для функции, выводящей что-то на печать. Но если в программе используется несколько функций, одна из которых выводит число, другая – символ и т.д., то в Си придется создать функции с разными именами (printa(int), printc(char), prints(char*), …). Это, в свою очередь, вносит некоторые неудобства при работе с функциями (иногда необходимо иметь достаточно богатую фантазию, чтобы придумывать новые имена, сложно вспомнить, какое имя имеет нужная модификация и т.д.).

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

void f(int a, char c, char* s)

{

// ...

print(a); // Будет вызвана void print(int a)

print(c); // Будет вызвана void print(char c)

print(s); // Будет вызвана void print(char* s)

// ...

}

В случае, когда ни один из прототипов не подходит для вызова (например, при попытке вызова print с параметром типа double или int*) компилятор попытается выполнить неявное преобразование к нужному типу, или, если оно не возможно, констатирует ошибку.

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

13.7. Ссылочный тип

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

int a = 5; // Переменная целого типа

int *pa = &a; // Указатель на переменную целого типа

int &sa = a; // Ссылка на переменную a (целого типа)

Из данного примера виден синтаксис объявления ссылок. В отличие от объявления указателя идентификатору ссылки предшествует не звёздочка, а амперсанд. Кроме того, как уже было сказано, ссылка должна быть инициализирована при объявлении (заметьте, что в инициализации ссылки, перед идентификатором «a», нет амперсанда).

Работать со ссылочной переменной можно точно так же, как с переменной на которую она ссылается:

void f(int a)

{

int *pa = &a;

int &sa = a;

sa = 3; // Эквивалентно a = 3; или *pa = 3;

// ...

f(sa); // Эквивалентно f(a); или f(*pa);

}

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

void s_add(int &sa, int b)

{

sa += b;

}

При передаче параметров, переменная b будет передана в функцию через стек по значению, а переменная sa станет ссылкой на первый фактический параметр функции add(). Таким образом, при изменении sa реально изменяется переменная, на которую sa ссылается. Можно говорить что add() является аналогом такой функции использующей указатель:

void p_add(int *pa, int b)

{

*pa += b;

}

Применение ссылки в качестве формального параметра функции изменит и синтаксис ее вызова:

void f(int a, int b)

{

// Вызов с передачей адреса переменной

p_add(&a, b);

// Вызов с «передачей ссылки» на переменную

s_add(a, b);

}

В приведенном примере обе функции изменят значение a, прибавляя к нему b. Не следует, также, забывать и о том, что во втором случае также произошла передача адреса, хотя и в неявной форме.

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

struct TItem *create_item(struct TItem **item,

struct TItem *nxt,

int num)

{

*item = (struct TItem *) malloc(sizeof(struct TItem));

(*item)->number = num;

(*item)->next = nxt;

return *item;

}

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

struct TItem *create_item(struct TItem *&item,

struct TItem *nxt,

int num)

{

item = (struct TItem *) malloc(sizeof(struct TItem));

item->number = num;

item->next = nxt;

return item;

}

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