Использование шаблонов классов

По мере того как количество создаваемых классов растет, обнаруживается, что некоторый класс, созданный для одной программы (или, возможно, для этой), очень похож на требующийся сейчас. Во многих случаях классы могут отличаться только типами. Другими словами, один класс работает с целочисленными значениями, в то время как требующийся сейчас должен работать со значениями типа float. Чтобы увеличить вероятность повторного использования существующего кода, C++ позволяет программам определять шаблоны классов. Если сформулировать кратко, то шаблон класса определяет типонезависимый класс, который в дальнейшем служит для создания объектов требуемых типов. Если компилятор C++ встречает объявление объекта, основанное на шаблоне класса, то для построения класса требуемого типа он будет использовать типы, указанные при объявлении. Позволяя быстро создавать классы, отличающиеся только типом, шаблоны классов сокращают объем программирования, что, в свою очередь, экономит ваше время.

Основные концепции:

· Используя ключевое слово template и символы типов (например, Т, T1 и Т2), программы могут создать шаблон класса — определение шаблона класса может использовать эти символы для объявления элементов данных, указания типов параметров и возвращаемого значения функций-элементов и т.д.

  • Для создания объектов класса с использованием шаблонов ваши программы просто ссылаются на имя класса, за которым внутри угловых скобок следуют типы (например, <int, float>), каждому из которых компилятор назначает символы типов и имя переменной.
  • Если у класса есть конструктор, с помощью которого вы инициализируете элементы данных, вы можете вызвать этот конструктор при создании объекта с использованием шаблона, например class_name<int,float>values(200);.
  • Если компилятор C++ встречает объявление объекта, он создает класс из шаблона, используя соответствующие типы.

Предположим, к примеру, вы создаете класс массива, в котором есть методы для вычисления суммы и среднего значения хранимых в массиве чисел. Предположим, что вы работаете с массивом типа int, и ваш класс мог бы выглядеть так:

class array

{
public:
array(int size);
long sum(void);
int average_value(void);
void show_array(void);
int add_value(int);
private:
int *data;
int size;
int index;
};

Следующая программа I_ARRAY.CPP использует класс array ддя работы со значениями типа int.

#include <iostream.h>

#include <stdlib.h>

class array

{
public:
array(int size);
long sum(void);
int average_value(void);
void show_array(void);
int add_value(int) ;
private:
int *data;
int size;
int index;
};

array::array(int size)

{
data = new int [size];
if (data == NULL)

{
cerr << "Недостаточно памяти - программа завершается " << endl;
exit(l);
}

array:: size = size;
array::index = 0;
}

long array::sum(void)

{
long sum = 0;
for (int i = 0; i < index; i++) sum += data[i];
return(sum);
}

int array::average_value(void)

{
long sum = 0;
for (int i = 0; i < index; i++) sum += data[i];
return (sum / index);
}

void array::show_array(void)

{
for (int i = 0; i < index; i++) cout << data[i] << ' ';
cout << endl;
}

int array::add_value(int value)

{
if (index == size) return(-1); // массив полон
else

{
data[index] = value;
index++;
return(0); // успешно
}
}

void main(void)

{
array numbers (100); // массив из 100 эл-тов
int i;
for (i = 0; i < 50; i++) numbers.add_value(i);
numbers.show_array();
cout << "Сумма чисел равна " << numbers.sum () << endl;
cout << "Среднее значение равно " << numbers.average_value() << endl;
}

Как видите, программа распределяет 100 элементов массива, а затем заносит в массив 50 значений с помощью метода add_value. В классе array переменная index отслеживает количество элементов, хранимых в данный момент в массиве. Если пользователь пытается добавить больше элементов, чем может вместить массив, функция add_value возвращает ошибку. Функция average_value использует переменную index для определения среднего значения массива. Программа запрашивает память для массива, используя оператор new, который подробно рассматривается далее.

Теперь предположим, что вашей программе необходимо работать с массивом значений с плавающей точкой, кроме того, что она работает с целочисленным массивом. Один из способов обеспечить поддержку массивов различных типов состоит в создании разных классов. С другой стороны, используя шаблоны классов, вы можете избавиться от необходимости дублировать классы. Ниже представлен шаблон класса, который создает общий класс array:

template<class T, class T1> class array

{
public:
array(int size);
T1 sum (void);
T average_value(void);
void show_array(void);
int add_value(T);
private:
T *data;
int size;
int index;
};

Этот шаблон определяет символы типов T и T1. В случае массива целочисленных значений Т будет соответствовать int, а T1 — long. Аналогичным образом для массива значений с плавающей точкой значения Т и Т1 равны float. Теперь потратьте время, чтобы убедиться, что вы поняли, как компилятор С++ будет подставлять указанные вами типы вместо символов Т и Т1.

Далее, перед каждой функцией класса вы должны указать такую же запись со словом template. Кроме того, сразу же после имени класса вы должны указать типы класса, например array <T, T1>::average_value. Следующий оператор иллюстрирует определение функции average_value для этого класса:

template<class Т, class T1> Т array<T, T1>::average_value(void)

{
T1 sum = 0;
int i;
for (i = 0; i < index; i++) sum += data[i] ;
return (sum / index);
}

После создания шаблона можно создавать класс требуемого типа, указывая имя класса, а за ним в угловых скобках необходимые типы, как показано ниже:

Имя шаблона //----> array <int, long> numbers (100); <------//Типы шаблона
array <float, float> values(200);

Следующая программа использует шаблон класса array для создания двух классов, один из которых работает со значениями типа int, а второй — со значениями типа float.

#include <iostream.h>

#include <stdlib.h>

template<class T, class T1> class array

{
public:
array(int size);
T1 sum(void);
T average_value(void);
void show_array(void);
int add_value(T);
private:
T *data;
int size;
int index;
};

template<class T, class T1> array<T, t1>::array(int size)

{
data = new T[size];
if (data == NULL)

{
cerr << "Недостаточно памяти - программа завершается" << endl;
exit(l);
}

array::size = size;
array::index = 0;
}

template<class T, class T1> Tl array<T, Tl>::sum(void)

{
T1 sum = 0;
for (int i = 0; i < index; i++) sum += data[i];
return(sum);
}

template<class T, class T1> T array<T, T1>::average_value(void)

{
Tl sum =0;
for (int i = 0; i < index; i++) sum += data[i];
return (sum / index);
}

template<class T, class T1> void array<T, T1>::show_array(void)

{
for (int i = 0; i < index; i++) cout << data[i] << ' ';
cout << endl;
}

template<class T, class T1> int array<T, T1>::add_value(T value)

{
if (index == size)
return(-1); // Массив полон
else

{
data[index] = value;
index++;
return(0); // Успешно
}
}

void main(void)

{
// Массив из 100 элементов
array<int, long> numbers(100)7
// Массив из 200 элементов
array<float, float> values(200);
int i;
for (i = 0; i < 50; i++) numbers.add_value(i);
numbers.show_array();
cout << "Сумма чисел равна " << numbers.sum () << endl;
cout << "Среднее значение равно " << numbers.average_value() << endl;
for (i = 0; i < 100; i++) values.add_value(i * 100);
values.show_array();
cout << "Сумма чисел равна." << values.sum() << endl;
cout << "Среднее значение равно " << values.average_value() << endl;
}

Для создания объектов с использованием шаблона класса просто нужно указать имя шаблона класса, за которым между левой и правой угловыми скобками укажите типы, которыми компилятор заменит символы Т, T1, T2 и т. д. Затем программа должна указать имя объекта (переменной) со значениями параметров, которые следует передать конструктору класса, как показано ниже:

template_class_name<typel, type2> object_name( parameter1, parameter2);

Когда компилятор C++ встречает такое объявление, он создает класс, основанный на указанных типах. Например, следующий оператор использует шаблон класса array для создания массива типа char, в котором хранится 100 элементов:

array<char, int> small_numbers(100) ;

Использование свободной памяти в С++

Как известно, если программа объявляет массив, компилятор C++ распределяет память для хранения его элементов. Однако представляется возможным, что до некоторого времени размер массива может быть не так велик, чтобы вместить все необходимые данные. Например, предположим, что создан массив для хранения 100 акций. Если позже потребуется хранить более 100 акций, нужно будет изменить программу и перекомпилировать ее. С другой стороны, вместо распределения массива фиксированного размера программы могут запрашивать необходимое количество памяти динамически, т.е. во время выполнения. Например, если программе необходимо следить за акциями, она могла бы запросить память, достаточную для хранения 100 акций. Аналогично, если программе необходимы только 25 акций, она могла бы запросить меньше памяти. Распределяя подобным образом память, программы непрерывно изменяют свои потребности без дополнительного программирования. Если программы запрашивают память во время выполнения, они указывают требуемое количество памяти, а C++ возвращает указатель на эту память. C++ распределяет память из областей памяти, которые называются свободной памятью. В этом разделе рассматриваются действия, которые должна выполнить программа для динамического распределения, а впоследствии освобождения памяти во время выполнения.

Основные концепции:

· Чтобы запросить память во время выполнения, ваши программы должны использовать оператор C++ new.

· При использовании оператора new программы указывают количество требуемой памяти. Если оператор new может успешно выделить требуемый объем памяти, он возвращает указатель на начало области выделенной памяти.

· Если оператор new не может удовлетворить запрос на память вашей программы (возможно, свободной памяти уже не осталось), он возвращает указатель NULL.

· Чтобы позже освободить память, распределенную с помощью оператора new, ваши программы должны использовать оператор C++ delete.

ОПЕРАТОР new

Оператор C++ new позволяет программам распределять память во время выполнения. Для использования оператора new необходимо указать количество байтов памяти, которое требуется программе. Предположим, например, что программе необходим 50-байтный массив. Используя оператор new, можно «заказать» эту память, как показано ниже:

char *buffer = new char[50];

Говоря кратко, если оператор new успешно выделяет память, он возвращает указатель на начало области этой памяти. В данном случае, поскольку программа распределяет память для хранения массива символов, она присваивает возвращаемый указатель переменной, определенной как указатель на тип char. Если оператор new не может выделить запрашиваемый объем памяти, он возвратит NULL-указатель, который содержит значение 0. Каждый раз, когда программы динамически распределяют память с использованием оператора new, они должны проверять возвращаемое оператором new значение, чтобы определить, не равно ли оно NULL.

Следующая программа использует оператор new для получения указателя на 100-байтный массив:

#include <iostream.h>

void main(void)

{
char *pointer = new char[100];
if (pointer != NULL) cout << "Память успешно выделена" << endl;
else cout << "Ошибка выделения памяти" << endl;
}

Как видите, программа сразу проверяет значение, присвоенное оператором new переменной-указателю. Если указатель содержит значение NULL, значит new не смог выделить запрашиваемый объем памяти. Если же указатель содержит не NULL, следовательно, new успешно выделил память и указатель содержит адрес начала блока памяти.

Следующая программа запрашивает у пользователя количество байт памяти, которое необходимо выделить, и затем распределяет память, используя оператор new:

#include <iostream.h>

void main(void)

{
int size;
char *pointer;
cout << "Введите размер массива, до 30000: ";
cin >> size;
if (size <= 30000)

{
pointer = new char[size];
if (pointer != NULL) cout << "Память выделена успешно" << endl;
else cout << "Невозможно выделить память" << endl;
}
}

Когда программы используют оператор new для динамического распределения памяти, то вполне вероятно, что они сами знают, сколько памяти необходимо выделить. Например, если программа распределяет память для хранения информации о служащих, она, возможно, сохранила количество служащих в файле. Следовательно, при запуске она может прочитать количество служащих из файла, а затем выделить соответствующее количество памяти.

Следующая программа выделяет каждый раз память для 10000 символов до тех пор, пока оператор new не сможет больше выделить память из свободной памяти. Другими словами, эта программа удерживает выделенную память, пока не использует всю доступную свободную память. Если программа успешно выделяет память, она извещает об этом сообщением. Если память больше не может быть выделена, программа выводит сообщение об ошибке и завершается:

#include <iostream.h>

void main(void)

{
char * pointer;
do

{
pointer = new char[10000];
if (pointer != NULL) cout << "Выделено 10000 байт" << endl;
else cout << "Больше нет памяти" << endl;
} while (pointer 1= NULL);
}

Замечание: Если выработаете в среде MS-DOS, то, возможно, будете удивлены тем, что свободная память исчерпается после того, как программа выделит 64 Кбайт, Большинство работающих в MS-DOS компиляторов C++ по умолчанию используют малую модель памяти, которая обеспечивает только 64 К6aйт свободной памяти. Аналогично, если вы используете среду MS-DOS, то наибольшая область памяти, к которой могут обратиться ваши программы, может быть ограничена 64Кбайт.

ОСВОБОЖДЕНИЕ ПАМЯТИ

Если программе больше не нужна выделенная память, она должна ее освободить, используя оператор delete. Для освобождения памяти с использованием оператора delete следует просто указать этому оператору указатель на данную область памяти, как показано ниже:

delete pointer;

Следующая программа использует оператор delete для освобождения выделенной с помощью оператора new памяти:

#include <iostream.h>

#include <string.h>

void main(void)

{
char *pointer = new char[100];
strcpy(pointer, "Учимся программировать на языке C++");
cout << pointer << endl;
delete pointer;
}

По умолчанию, если программа не освобождает выделенную ей память до своего завершения, операционная система автоматически освобождает эту память после завершения программы. Однако если программа использует оператор delete для освобождения памяти по мере того, как она (память) становится ненужной, то эта память вновь становится доступной для других целей (возможно, для этой программы, которая опять будет использовать оператор new, или для операционной системы).

Следующая программа выделяет память для хранения массива из 1000 целочисленных значений. Затем она заносит в массив значения от 1 до 1000, выводя их на экран. Потом программа освобождает эту память и распределяет память для массива из 2000 значений с плавающей точкой, занося в массив значения от 1.0 до 2000.0:

#include <iostreain.h>

void main(void)

{
int *int_array = new int[1000];
float *float_array;
int i;
if (int_array 1= NULL)

{
for (i = 0; i < 1000; i++) int_array[i] = i + 1;
for (i = 0; i < 1000; i++) cout << int_array[i] << ' ';
delete int_array;
}
float_array = new float[2000];
if (float_array != NULL)

{
for (i = 0; i < 2000; i++) float_array[i] = (i + 1) • 1.0;
for (i = 0; i < 2000; i++) cout << float_array[i] << ' ' ;
delete float_array;
}
}

Как правило, программы должны освобождать память с помощью оператора delete по мере того, как память становится не нужна.

Наши рекомендации