C++ - Страустрап Бьярн
Шрифт:
Интервал:
Закладка:
int amp; vec::operator[](int i) (* if (i«low !! high„i) error(«vec index out of range“); // индекс vec за границами return elem(i); *)
main() (* vector a(10); for (int i=0; i«a.size(); i++) (* a[i] = i; cout „„ a[i] «« " "; *) cout «« «n“; vec b(10,19); for (i=0; i«b.size(); i++) b[i+10] = a[i]; for (i=0; i«b.size(); i++) cout «« b[i+10] «« " "; cout «« «n“; *)
Он выдает 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Это направление развития векторного типа можно разрабатывать дальше. Довольно просто сделать многомерные массивы, массивы, в которых число размерностей задается как параметр конструктора, массивы в стиле Фортрана, к которым можно одновременно обращаться и как к имеющим две размерности, и как к имеющим три, и т.д.
Так класс управляет доступом к некоторым данным. Поскольку весь доступ осуществляется через интерфейс, обеспеченный открытой частью класса, то можно использовать представление данных в соответствие с нуждами разработчика. Например, тривиально можно было бы поменять представление вектора на связанный список. Другая сторона этого состоит в том, что при заданной реализации можно обеспечить любой удобный интерфейс.
1.14 Еще об операциях
Другое направление развития – снабдить вектора операциями:
class Vec : public vector (* public: Vec(int s) : (s) (**) Vec(Vec amp;); ~Vec() (**) void operator=(Vec amp;); void operator*=(Vec amp;); void operator*=(int); //... *);
Обратите внимание на способ определения конструктора производного класса, Vec::Vec(), когда он передает свой параметр конструктору базового класса vector::vector() и больше не делает ничего. Это полезная парадигма. Операция присваивания перегружена, ее можно определить так:
void Vec::operator=(Vec amp; a) (* int s = size(); if (s!=a.size()) error(«bad vector size for =»); // плохой размер вектора для = for (int i = 0; i«s; i++) elem(i) = a.elem(i); *)
Присваивание объектов класса Vec теперь действительно копирует элементы, в то время как присваивание объектов vector просто копирует структуру, управляющую доступом к элментам. Последнее, однако, происходит и тогда, когда vector копируется без явного использования операции присваивания: (1) когда vector передается как параметр и (3) когда vector передается как значение, возвращаемое функцией. Чтобы обрабатывать эти случаи для векторов Vec, вы определяете конструктор Vec(Vec amp;): Vec::Vec(Vec amp; a) : (a.size()) (* int sz = a.size(); for (int i = 0; i«sz; i++) elem(i) = a.elem(i); *) Этот конструктор инициализирует Vec как копию другого Vec, и будет вызываться в отмеченных выше случаях. Выражение в левой части таких операций, как = и +=, безусловно определено, поэтому кажется вполне естественным реализовать их как операции над объектом, который обозначается (денотируется) этим выражением. В частности, тогда они смогут изменять значение своего первого операнда. Левый операнд таких операций, как + и – не требует особого внимания. Вы могли бы, например, передавать оба аргумента по значению и все рано получить правильную реализацию векторного сложения. Однако вектора могут оказаться большими, поэтому чтобы избежать ненужного копирования операнды операции + передаются в operator +() по ссылке:
Vec operator+(Vec amp; a,Vec amp;b) (* int s = a.size(); if (s != b.size()) error(«bad vector size for +»); // плохой размер вектора для + Vec sum(s); for (int i=0; i«s; i++) sum.elem(i) = a.elem(i) + b.elem(i); return sum; *)
Вот пример небольшой программы, которую можно выполнить, если скомпилировать ее вместе с ранее приведенными описаниями vector:
#include «stream.h»
void error(char* p) (* cerr «„ p «« «n“; exit(1); *)
void vector::set_size(int) (* /*...*/ *)
int amp; vec::operator[](int i) (* /*...*/ *)
main() (* Vec a(10); Vec b(10); for (int i=0; i«a.size(); i++) a[i] = i; b = a; Vec c = a+b; for (i=0; i„c.size(); i++) cout «« c[i] «« «n“; *)
1.15 Друзья (friend)
Функция operator+() не воздействует непосредственно на представление вектора. Действительно, она не может этого делать, поскольку не является членом. Однако иногда желательно дать функциям не членам возможность доступа к закрытой части класса. Например, если бы не было функции «доступа без проверки» vector::elem(), вам пришлось бы проверять индекс i на соответствие границам три раза за каждый проход цикла. Здесь мы избежали этой сложности, но она довольно типична, поэтому у класса есть механизм предоставления права доступа к своей закрытой части функциям не членам. Просто в класс помещается описание функции, перед которым стоит ключевое слово friend. Например, если имеется
class Vec; // Vec – имя класса class vector (* friend Vec operator+(Vec, Vec); //... *);
То вы можете написать Vec operator+(Vec a, Vec b) (* int s = a.size(); if (s != b.size()) error(«bad vector size for +»); // плохой размер вектора для + Vec amp; sum = *new Vec(s); int* sp = sum.v; int* ap = a.v; int* bp = b.v; while (s–) *sp++ = *ap++ + *bp++; return sum; *)
Одним из особенно полезных аспектов механизма friend является то, что функция может быть другом двух и более классов. Чтобы увидеть это, рассмотрим определение vector и matrix, а затем определение функции умножения (см. #с.8.8).
1.16 Обобщенные вектора
«Пока все хорошо,» – можете сказать вы, – «но я хочу, чтобы один из этих векторов был типа matrix, который я только что определил.» К сожалению, в С++ не предусмотрены средства для определения класса векторов с типом элемента в качестве параметра. Один из способов – продублировать описание и класса, и его функций членов. Это не идеальный способ, но зачатую вполне приемлемый.
Вы можете воспользоваться препроцессором (#4.7), чтобы механизировать работу. Например, класс vector – упрощенный вариант класса, который можно найти в стандартном заголовочном файле. Вы могли бы написать:
#include «vector.h»
declare(vector,int);
main() (* vector(int) vv(10); vv[2] = 3; vv[10] = 4; // ошибка: выход за границы *)
Файл vector.h таким образом определяет макросы, чтобы макрос declare(vector,int) после расширения превращался в описание класса vector, очень похожий на тот, который был определен выше, а макрос implement(vector,int) расширялся в определение функций этого класса. Поскольку макрос implement(vector,int) в результате расширения превращается в определение функций, его можно использовать в программе только один раз, в то время как declare(vector,int) должно использоваться по одному разу в каждом файле, работающем с этим типом целых векторов.
declare(vector,char); //... implement(vector,char);
даст вам отдельный тип «вектор символов». Пример реализации обобщенных классов с помощью макросов приведен в #7.3.5.
1.17 Полиморфные вектора
У вас есть другая возможность – определить ваш векторный и другие вмещающие классы через указатели на объекты некоторого класса: class common (* //... *); class vector (* common** v; //... public: cvector(int); common* amp; elem(int); common* amp; operator[](int); //... *);
Заметьте, что поскольку в таких векторах хранятся указатели, а не сами объекты, объект может быть "в" нескольких таких векторах одновременно. Это очень полезное свойство подобных вмещающих классов, таких, как вектора, связанные списки, множества и т.д. Кроме того, можно присваивать указатель на производный класс указателю на его базовый класс, поэтому можно использовать приведенный выше cvector для хранения указателей на объекты всех производных от common классов. Например:
class apple : public common (* /*...*/ *) class orange : public common (* /*...*/ *) class apple_vector : public cvector (* public:
cvector fruitbowl(100); //... apple aa; orange oo; //... fruitbowl[0] = amp;aa; fruitbowl[1] = amp;oo; *)