Функция может возвращать ссылку. В результате такая функция может использоваться в левой части оператора присваивания! В качестве примера рассмотрим следующую простую программу:
#include <iostream.h>
char& replace(int i, char *s) // возврат ссылки
{
return s[i];
}
char s[80] = "Hello There";
int main ()
{
replace(5) = 'X'; // присвоение Х пробелу после Hello
cout << s;
return 0;
}
Эта программа заменяет пробел между словами “Hello” и “There” символом ‘Х’. В результате программа выводит на экран «HelloXThere».
Функция replace() в соответствии со своим объявлением возвращает ссылку на символ. В соответствии со своей реализацией функция replace() возвращает ссылку на элемент массива s, определяющийся индексом i. Далее возвращаемая функцией replace() ссылка используется функцией main() для присвоения элементу буквы «X».
Хотя в первую очередь ссылки включены в C++ для поддержки передачи параметров по ссылке и возвращения результата функции по ссылке, можно объявлять переменные ссылочного типа и как самостоятельные переменные. В таком случае они называются независимыми ссылками (independent references). Однако в таком случае их возможности серьезно ограничиваются.
Переменные ссылочного типа всегда должны указывать на какой-либо объект, поэтому они должны быть инициализированы при своем объявлении. В общем случае это означает присвоение им адреса уже объявленного объекта. После этого переменная ссылочного типа может использоваться всюду, где используется переменная, на которую она ссылается. Фактически между обеими переменными нет разницы. В качестве примера рассмотрим программу:
#include <iostream.h>
int main()
{
int j, k;
int &i = j; // независимая ссылка на j
j = 10;
cout << j << " " << i; // выводит 10 10
k = 121;
i = k; // копирование значения, а не адреса к в j
cout << "\n" << j; // выводит 121
return 0;
}
Программа выдаст следующие данные:
10 10
Адрес, на который указывает ссылочная переменная i, фиксирован и не может меняться. Так при выполнении присвоения i = k величина к копируется в j.
Вот другой пример. Инструкция i++ не вызывает изменения адреса. Вместо этого значение переменной k увеличивается на 1. (Надо помнить, что ссылки не являются указателями.)
Ссылочные переменные могут указывать на константы:
const int &i = 100;
Используя знания относительно ссылочных переменных, теперь можно легко понять, как унарные операторы перегружаются с помощью дружественных функций. Для начала вернемся к исходной версии оператора ++ по отношению к классу point. Для удобства он представлен ниже:
point point::operator++()
{
х++; У++; z++;
return *this;
}
Как известно, всякая функция-член имеет неявный аргумент, соответствующий ключевому слову this и являющийся указателем на объект, членом которого служит данная функция. По этой причине при перегрузке унарного оператора с помощью функции-члена нет необходимости для этой функции иметь какие-либо аргументы. Единственным необходимым аргументом в такой ситуации является указатель на объект, осуществляющий вызов функции-оператора. Поскольку this является указателем на объект, то любые изменения данных объекта влияют на объект, вызывающий функцию-оператор. В отличие от этого функции-друзья не получают указателя this и поэтому не могут ссылаться на объект, вызывающий их.
Для того чтобы выполнить перегрузку унарных операторов ++ или -- с помощью функции-друга, необходимо использовать параметр ссылочного типа. Ниже представлен вариант перегрузки оператора ++ с помощью дружественной функции:
class point
{
int x, y, z; // трехмерные координаты
public:
friend point operator+(point p1, point p2);
point operator=(point p2); // p1 подразумевается
friend point operator++(point &p1);
void show();
void set(int mx, int my, int mz);
};
point operator++(point &p1)
{
p1.x++; p1.y++; p1.z++;
return p1;
}
ПАМЯТКА: Для перегрузки операторов следует пользоваться функциями-членами. Функции-друзья предназначаются в C++ большей частью для специальных ситуаций.
4.10 Перегрузка оператора []
Кроме нескольких вышеперечисленных операторов, можно перегрузить и многие другие операторы C++. Большей частью требуется перегружать стандартные операторы, такие как арифметические, логические или операторы отношения. Тем не менее, имеется один достаточно экзотичный оператор, который бывает полезно перегружать: []. В C++ оператор [] при перегрузке рассматривается как бинарный оператор. Его следует перегружать с помощью функции-члена. Нельзя использовать дружественную функцию. Общая форма функции-оператора operator[]() имеет следующий вид:
тип имя_класса::operator[](int i)
Параметр не обязан иметь тип int, но поскольку функция operator[]() обычно используется для индексации массива, то в таком качестве обычно используется целое значение. Для заданного объекта A выражение
A[3]
преобразуется в вызов функции operator[]():
operator[](3)
В таком случае значение индекса передается функции operator[]() в качестве явного параметра. Указатель this указывает на объект A, тот самый, который вызывает функцию.
В следующей программе класс array содержит массив из трех переменных целого типа. Конструктор инициализирует каждый элемент массива заданным значением. Перегруженная функция-оператор operator[]() возвращает величину элемента массива, определяемого индексом, передаваемым в качестве параметра.
#include <iostream.h>
class array
{
int a[3];
public:
array(int i, int j, int k) { a[0] = i; a[1] = j; a[2] = k; }
int operator[](int i) { return a[i]; }
};
int main()
{
array A(1, 2, 3);
cout << A[1]; // выводит 2
return 0;
}
Можно создать функцию-оператор operator[]() таким образом, чтобы оператор [] можно было использовать как с левой, так и с правой стороны оператора присваивания. Для этого достаточно в качестве возвращаемой величины для operator[]() задать ссылку.
class array
{
int a[3];
public:
int& operator[](int i) { return a[i]; }
//…
};
Поскольку operator[]() возвращает ссылку на элемент массива, отвечающий индексу i, то он может быть использован с левой стороны операции присваивания для модификации элемента массива. Разумеется, этот оператор может быть использован и с правой стороны оператора присваивания.
Как известно, в C++ во время выполнения программы можно выйти за пределы массива, и при этом не будет выдаваться сообщение об ошибке. Одним из достоинств перегрузки оператора [] служит то, что с его помощью можно устранить такую возможность.
Если создается класс, содержащий массив, и разрешен доступ к этому массиву только через перегруженный оператор [], то можно перехватывать значение, содержащее величину индекса за пределами допустимых значений. Например, следующая программа осуществляет проверку на принадлежность значений допустимой области:
#include <iostream.h>
#include <stdlib.h>
class array
{
int a[3];
public:
array(int i, int j, int k) { a[0] = i; a[1] = j; a[2] = k; }
int& operator[](int i);
};
int& array::operator[](int i)
{
if(i<0 || i>2)
{
cout << "Boundary Error\n";
exit(1);
}
return a[i];
}
int main()
{
array A(1, 2, 3) ;
cout << A[1]; // выводит 2
A[3] = 44; // генерируется ошибка времени выполнения поскольку 3 выходит за допустимые пределы
return 0;
}
При выполнении инструкции
A[3] = 44;
оператор перехватывает ошибку выхода за границу массива.