посмотрите на сообщение об ошибке, которое ваш компилятор выдаст для инструкции 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.
Итак, что такое массив? Как его определить и как использовать? Массив — это однородная последовательность объектов, расположенных в смежных ячейках памяти; иначе говоря, все элементы массива имеют один и тот же тип, и между ними нет пробелов. Элементы массива нумеруются, начиная с нуля в возрастающем порядке. В объявлении массив выделяется квадратными скобками.
const int max = 100;
int gai[max]; // глобальный массив (из 100 чисел типа int);
// "живет всегда"
void f(int n)
{
char lac[20]; // локальный массив; "живет" до конца области
// видимости
int lai[60];
double lad[n]; // ошибка: размер массива не является константой
// ...
}
Обратите внимание на ограничение: количество элементов именованного массива должно быть известно на этапе компиляции. Если мы хотим, чтобы количество элементов массива было переменным, то должны разместить его в свободной памяти и обращаться к нему через указатель. Именно так поступает класс vector с массивами элементов.
Как и к элементам массивов, размещенных в свободной области, доступ к элементам именованных массивов осуществляется с помощью операторов индексирования и разыменования ([ ] и *). Рассмотрим пример.
void f2()
{
char lac[20]; // локальный массив; "живет" до конца области
// видимости
lac[7] = 'a';
*lac = 'b'; // эквивалент инструкции lac[0]='b'
lac[–2] = 'b'; // ??
lac[200] = 'c'; // ??
}
Эта функция компилируется, но, как мы знаем, не все скомпилированные функции работают правильно. Использование оператора [ ] очевидно, но проверка выхода за пределы допустимого диапазона отсутствует, поэтому функция f2() компилируется, а результат записи lac[–2] и lac[200] приводит к катастрофе (как всегда, при выходе за пределы допустимого диапазона). Не делайте этого. Массивы не