До сих пор (см. раздел 17.6) для доступа к элементам вектора мы использовали функции-члены set()
и get()
. Но этот способ слишком громоздок и некрасив. Мы хотим использовать обычную индексацию: v[i]
. Для этого следует определить функцию-член с именем operator[]
. Вот ее первая (наивная) версия.
class vector {
int sz; // размер
double* elem; // указатель на элементы
public:
// ...
double operator[](int n) { return elem[n]; } // возвращаем
// элемент
};
Все выглядит хорошо и просто, но, к сожалению, слишком просто. Разрешив оператору индексирования (operator[]()
) возвращать значение, мы разрешили чтение, но не запись элементов.
vector v(10);
int x = v[2]; // хорошо
v[3] = x; // ошибка: v[3] не может стоять в левой
// части оператора =
Здесь выражение v[i]
интерпретируется как вызов оператора v.operator[](i)
, который возвращает значение элемента вектора v
с номером i
. Для такого слишком наивного варианта класса vector
значение v[3]
является числом с плавающей точкой, а не переменной, содержащей число с плавающей точкой.
ПОПРОБУЙТЕ
Создайте вариант класса vector
, скомпилируйте его и посмотрите на сообщение об ошибке, которое ваш компилятор выдаст для инструкции v[3]=x;
.
В следующей версии мы разрешим оператору operator[]
возвращать указатель на соответствующий элемент:
class vector {
int sz; // размер
double* elem; // указатель на элемент
public:
// ...
double* operator[](int n) { return &elem[n]; } // возвращаем
// указатель
};
При таком определении мы можем записывать элементы.
vector v(10);
for (int i=0; i
// некрасиво
*v[i] = i;
cout << *v[i];
}
Здесь выражение v[i]
интерпретируется как вызов оператора v.operator[](i)
и возвращает указатель на элемент вектора v
с номером i
. Проблема в том, что теперь мы должны написать оператор *
, чтобы разыменовать указатель, ссылающийся на этот элемент. Это так же некрасиво, как и функции set()
и get()
. Проблему можно устранить, если вернуть из оператора индексирования ссылку.
class vector {
// ...
double& operator[ ](int n) { return elem[n]; } // возвращаем
// ссылку
};
Теперь можем написать следующий вариант.
vector v(10);
for (int i=0; i
v[i] = i; // v[i] возвращает ссылку на элемент с номером i
cout << v[i];
}
Мы обеспечили традиционные обозначения: выражение v[i]
интерпретируется как вызов оператора v.operator[](i)
и возвращает ссылку на элемент вектора v
с номером i
.
18.4.1. Перегрузка ключевого слова const
Функция operator[]()
, определенная выше, имеет один недостаток: ее нельзя вызвать для константного вектора. Рассмотрим пример.
void f(const vector& cv)
{
double d = cv[1]; // неожиданная ошибка
cv[1] = 2.0; // ожидаемая ошибка
}
Причина заключается в том, что наша функция vector::operator[]()
потенциально может изменять объект класса vector
. На самом деле она этого не делает, но компилятор об этом не знает, потому что мы забыли сообщить ему об этом. Для того чтобы решить эту проблему, необходимо предусмотреть функцию-член со спецификатором const
(см раздел 9.7.4). Это легко сделать.
class vector {
// ...
double& operator[](int n); // для неконстантных векторов
double operator[](int n) const; // для константных векторов
};
Очевидно, что мы не могли бы вернуть ссылку типа double&
из версии со спецификатором const
, поэтому возвращаем значение типа double
. С таким же успехом мы могли бы вернуть ссылку типа const double &
, но, поскольку объект типа double
невелик, не имеет смысла возвращать ссылку (см. раздел 8.5.6), и мы решили вернуть значение. Теперь можно написать следующий код:
void ff(const vector& cv, vector& v)
{
double d = cv[1]; // отлично (использует константный вариант [ ])
cv[1] = 2.0; // ошибка (использует константный вариант [ ])