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

ЖАНРЫ

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

}

double* p = new double[a.sz]; // выделяем новую память

for (int i = 0; i<a.sz; ++i) p[i] = a.elem[i]; // копируем

// элементы

delete[] elem; // освобождаем старую память

space = sz = a.sz; // устанавливаем новый размер

elem = p; // устанавливаем указатель на новые

// элементы

return *this; // возвращаем ссылку на целевой объект

}

В этом фрагменте кода мы сначала проверяем самоприсваивание (например,

v=v
);
в этом случае ничего делать не надо. С логической точки зрения эта проверка лишняя, но иногда она позволяет значительно оптимизировать программу. Эта проверка демонстрирует использование указателя
this
, позволяющего проверить, является ли аргумент a тем же объектом, что и объект, из которого вызывается функция-член (т.е.
operator=
). Убедитесь, что этот код действительно работает, если из него удалить инструкцию
this==&a
. Инструкция
a.sz<=space
также включена для оптимизации. Убедитесь, что этот код действительно работает после удаления из него инструкции
a.sz<=space
.

19.2.6. Предыдущая версия класса vector

Итак, мы получили почти реальный класс

vector
для чисел типа
double
.

// почти реальный вектор чисел типа double

class vector {

/*

инвариант:

для 0<=n<sz значение elem[n] является n- м элементом

sz<=space;

если sz<space, то после elem[sz–1] есть место

для (space–sz) чисел типа double

*/

int sz; // размер

double* elem; // указатель на элементы (или 0)

int space; // количество элементов плюс количество слотов

public:

vector:sz(0),elem(0),space(0) { }

explicit vector(int s):sz(s),elem(new double[s]),space(s)

{

for (int i=0; i<sz; ++i) elem[i]=0; // элементы

// инициализированы

}

vector(const vector&); // копирующий конструктор

vector& operator=(const vector&); // копирующее присваивание

~vector { delete[] elem; } // деструктор

double& operator[ ](int n) { return elem[n]; } // доступ

const double& operator[](int n) const { return elem[n]; }

int size const { return sz; }

int capacity const { return space; }

void resize(int newsize); // увеличение

void push_back(double d);

void reserve(int newalloc);

};

Обратите внимание на то, что этот класс содержит все основные операции (см. раздел 18.3): конструктор, конструктор по умолчанию, копирующий конструктор, деструктор. Он также содержит операции для доступа к данным (индексирование

[]
), получения информации об этих данных (
size
и
capacity
), а также для управления ростом вектора (
resize
,
push_back
и
reserve
).

19.3. Шаблоны

Однако нам мало

иметь вектор, состоящий из чисел типа
double
; мы хотим свободно задавать тип элементов наших векторов. Рассмотрим пример.

vector<double>

vector<int>

vector<Month>

vector<Window*> // вектор указателей на объекты класса Window

vector< vector<Record> > // вектор векторов из объектов класса Record

vector<char>

Для этого мы должны научиться определять шаблоны. На самом деле мы с самого начала уже использовали шаблоны, но до сих пор нам не приходилось определять их самостоятельно. Стандартная библиотека содержит все необходимое, но мы не должны полагаться на готовые рецепты, поэтому следует разобраться, как спроектирована и реализована стандартная библиотека, например класс
vector
и функция
sort
(разделы 21.1 и Б.5.4). Это не просто теоретический интерес, поскольку, как обычно, средства и методы, использованные при создании стандартной библиотеки, могут помочь при работе над собственными программами. Например, в главах 21-22 мы покажем, как с помощью шаблонов реализовать стандартные контейнеры и алгоритмы, а в главе 24 продемонстрируем, как разработать класс матриц для научных вычислений.

По существу, шаблон (template) — это механизм, позволяющий программисту использовать типы как параметры класса или функции. Получив эти аргументы, компилятор генерирует конкретный класс или функцию.

19.3.1. Типы как шаблонные параметры

Итак, мы хотим, чтобы тип элементов был параметром класса
vector
. Возьмем класс
vector
и заменим ключевое слово
double
буквой
T
, где
T
— параметр, который может принимать значения, такие как
double
,
int
,
string
, vector<Record> и Window*. В языке С++ для описания параметра
T
, задающего тип, используется префикс
template<class T>
, означающий “для всех типов
T
”.

Рассмотрим пример.

// почти реальный вектор элементов типа T

template<class T> class vector {

// читается как "для всех типов T" (почти так же, как

// в математике)

int sz; // размер

T* elem; // указатель на элементы

int space; // размер + свободная память

public:

vector:sz(0),elem(0),space(0) { }

explicit vector(int s);

vector(const vector&); // копирующий
конструктор

vector& operator=(const vector&); // копирующее
присваивание

~vector { delete[] elem; } // деструктор

T& operator[](int n) { return elem[n]; } // доступ: возвращает

// ссылку

const T& operator[](int n) const { return elem[n]; }

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