Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

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.size; ++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.size; ++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; // ошибка (использует константный вариант [ ])

double d = v[1]; // отлично (использует неконстантный вариант [ ])

v[1] = 2.0; // отлично (использует неконстантный вариант [ ])

}

Поскольку объекты класса

vector
часто передаются по константной ссылке, эта версия оператора
operator[]
с ключевым словом
const
является существенным дополнением.

18.5. Массивы

До сих пор мы использовали слово массив (array) для названия последовательности объектов, расположенных в свободной памяти. Тем не менее массивы можно размещать где угодно как именованные переменные. На самом деле это распространенная ситуация. Они могут использоваться следующим образом.

• Как глобальные переменные (правда, использование глобальных переменных часто является плохой идеей).

• Как локальные переменные (однако массивы накладывают на них серьезные ограничения).

• Как аргументы функции (но массив не знает своего размера).

• Как член класса (хотя массивы, являющиеся членами класса, трудно инициализировать).

Возможно, вы заметили, что мы отдаем заметное предпочтение классу
vector
по сравнению с массивами. Класс
std::vector
следует использовать при любой возможности. Однако массивы существовали задолго до появления векторов и являлись их приблизительным прототипом во многих языках (особенно в языке C), поэтому их следует знать хорошо, чтобы иметь возможность работать со старыми программами или с программами, написанными людьми, не признающими преимущества класса
vector
.

Поделиться с друзьями: