Создание собственных функций вставки и извлечения

Собственная функция вставки имеет следующий вид:

ostream &operator<<(ostream &stream, класс obj)

{ //Тело функции вставки

return stream; }

Нужно обратить внимание на то, что функция возвращает ссылку на поток типа ostream (класс ostream является производным от класса ios). Первый параметр функции является ссылкой на поток вывода. Второй параметр представляет собой объект, подлежащий вставке, функция вставки должна вернуть ссылку на поток. Это позволяет использовать функции внутри сложных выражений.

Внутри функции вставки можно выполнять любые операции. Именно они определяют сущность функции. Однако функцию вставки не следует слишком усложнять.

 

Собственная функция извлечения имеет следующий вид:

istream &operator>>(istream &stream, класс &ob)

{ // Тело функции извлечения

return stream; }

Функция извлечения возвращает ссылку на поток типа istream, т.е. на поток ввода. Ее первым параметром должна быть ссылка на поток типа istream. Вторым параметром должна быть ссылка на объект класса, для которого перегружена функция извлечения. Это позволяет модифицировать объект при выполнении операции ввода (извлечения).

Перегруженные функции вставки и извлечения не могут быть членами класса. Чтобы эти функции могли обращаться к закрытым членам класса, лучше сделать эти функции дружественными по отношению к указанному класса.

 

Рассмотрим пример: класс массив (Array), который выполняет проверку диапазона, чтобы гарантировать, что индексы остаются в пределах границ массива. Класс допускает присваивание одного объекта массива другому с помощью операции присваивания. Объекты этого класса автоматически узнают свой размер, так что при передаче массива функции передавать отдельно размер массива в качестве аргумента не требуется.

//Листинг 16.2

#include<iostream>

#include<fstream>

#include <cstdlib>

using namespace std;

class Array{

public:

Array(int n=10); //конструктор с умолчанием

Array(const Array &); //конструктор копии

~Array(); //деструктор

int& operator [](int);//операция индексации

//операция присваивания массивов

const Array &operator=(const Array &);

//дружественные функции:

//операция вставки в поток

friend ostream &operator<<(ostream &stream,Array ob);

//операция извлечения из потока

friend istream &operator>>(istream &stream,Array &ob);

private:

//указатель на нулевой элемент массива

int *ptr;

int size; // размер массива

};

 

//Конструктор с умолчанием

Array::Array(int n)

{ size=n;

//выделение памяти для массива

ptr=new int[size];

for(int i=0;i<size;i++)

//задание массиву начальных значений

ptr[i]=0; }

 

//Конструктор копирования

Array::Array(const Array &ob)

{ size=ob.size;

//выделение памяти для массива

ptr=new int[size];

//копирование ob в объект

for(int i=0;i<size;i++)

ptr[i]=ob.ptr[i]; }

 

//Деструктор

Array::~Array()

{

//возвращение области памяти массива

delete [] ptr; }

 

//Перегруженная операция индексации

int & Array::operator[](int index)

{

//проверка ошибочного выхода

//индекса из диапазона

if(index<0 || index>=size)

{cout<<"Error index!\n"; exit(1);}

//возвращение ссылки создает L-величину

return ptr[index];

}

 

// Перегруженная операция присваивания

const Array & Array::operator=(const Array &right)

{ if (&right!=this)//проверка самоприсваивания

{

//возвращение области памяти

delete [] ptr;

//изменение размера массива

size=right.size;

//выделение памяти для копии массива

ptr=new int[size];

//массив копии в объект

for (int i=0;i<size; i++)

ptr[i]=right.ptr[i]; }

return *this; } //позволяет =y=z;

 

//Перегруженная операция извлечения из потока

istream &operator>>(istream &stream,Array &ob)

{

//ввод элементов массива

for(int i=0;i<ob.size;i++)

stream>>ob.ptr[i];

return stream; }

 

//Перегруженная операция вставки в поток

ostream &operator<<(ostream &stream,Array ob)

{ //вывод числа элементов в массиве

stream<<"size= "<<ob.size<<": ";

//вывод элементов массива

for(int i=0;i<ob.size;i++)

stream<<ob.ptr[i]<<" ";

stream<<"\n";

return stream; }

 

int main()

{ Array vect1(7),vect2;

//заполнение массива vect1 – ввод с клавиатуры

cin>>vect1;

//открытие файла для ввода

ifstream inarray("vector.txt");

if(!inarray)

{cout<<"File can not be open\n";

exit(1);} //выход из программы

 

//заполнение массива vect2 - ввод из файла

inarray>>vect2;

inarray.close();

 

//вывод информации о массиве vect2 на экран

cout<<vect2;

 

//открытие файла для вывода

ofstream outarray("vector1.txt");

if(!outarray)

{cout<<"File can not be open\n";

exit(1);} //выход из программы

//вывод информации о массиве vect1 в файл

outarray<<vect1;

outarray.close();

Array vect3(vect1);

cout<<vect3;

//присваивание одного массива другому

vect1=vect2;

cout<<vect1;

 

//использование перегруженной операции

//индексации для создания R-величины

cout<<vect1[5]<<"\n";

 

//использование перегруженной операции

//индексации для создания L-величины

vect1[5]=100;

cout<<vect1;

vect1[15]=10; // ошибка вне диапазона

return 0;

}

Результат работы программы:

Вводим с клавиатуры: 15 16 17 18 19 20 21

Вводится из файла vector.txt: 1 2 3 4 5 6 7 8 9 10

size= 10: 1 2 3 4 5 6 7 8 9 10

size= 7: 15 16 17 18 19 20 21

size= 10: 1 2 3 4 5 6 7 8 9 10

size= 10: 1 2 3 4 5 100 7 8 9 10

Error index!

Создается файл vector1.txt и в него записывается следующая информация:

size= 7: 15 16 17 18 19 20 21

 

В программе мы использовали стандартную библиотечную функцию exit(), вызов которой приводит к прекращению работы программы и передаче управления операционной системе. Она выглядит следующим образом

void exit(int код_возврата);

Значение переменной код_возврата передается вызывающему процессу, в роли которого чаще всего выступает операционная система. Нулевое значение кода возврата соответствует нормальному завершению работы. Другие значения аргумента указывают на вид ошибки. Для вызова функции exit() необходим заголовочный файл <cstdlib>.

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

Функция-элемент operator= сначала осуществляет проверку самоприсваивания. Если имеет место попытка самоприсваивания, присваивание пропускается (т.е. объект уже есть сам по себе). Если бы проверки самоприсваивания не было, функция-элемент должна была бы начать с уничтожения пространства массива-адресата. Поскольку при самоприсваивании это также и исходный массив, то массив оказался бы разрушенным. Если самоприсваивания нет, то функция-элемент использует delete, чтобы освободить память, ранее выделенную в массиве-адресате, копирует size исходного массива в size массива-адресата, использует new, чтобы выделить требуемую память массиву-адресату, и помещает указатель, возвращенный new, в элемент ptr массива. Затем используется цикл for для копирования элементов исходного массива в массив-адресат. Независимо от того, есть самоприсваивание или нет, функция-элемент затем возвращает текущий объект (т.е. *this) как константную ссылку; это делает возможным сцепленное присваивание, такое как x=y=z.

Когда компилятор встречает в main выражение vect1[5] он активизирует перегруженную функцию-элемент operator[], генерируя вызов vect1.operator[](5). Функция-элемент operator[] проверяет, находится ли индекс в допустимом диапазоне. Если индекс в заданном диапазоне, то соответствующий элемент возвращается как ссылка, так что он может быть использован как L-величина (например, с левой стороны оператора присваивания).

Покажем, как можно перегрузить операцию () для индексирования в классе Array.

//Перегруженная операция () индексации

int & Array::operator()(int index){

//проверка ошибочного выхода

//индекса из диапазона

if(index<0 || index>=size)

{cout<<"Error index!\n";

exit(1);}

//возвращение ссылки создает L-величину

return ptr[index];

}

Тогда обращение к элементам массива будет выглядеть так:

//использование перегруженной операции //индексации ()для создания R-величины

cout<<vect1(5)<<"\n";

//использование перегруженной операции //индексации ()для создания L-величины

vect1(5)=100;

Однако применение операции () для индексирования одномерного массива не дает никаких преимуществ по сравнению с обычной операцией [].

Задание:

Описать класс вещественная матрица.

Элементы-данные класса:

**matr – указатель на указатель на double;

n – число строк в матрице;

m - число столбцов в матрице.

Класс должен реализовывать следующие методы:

- конструктор, создающий матрицу требуемого размера с нулевыми значениями элементов;

- конструктор копирования;

- деструктор;

- функцию транспонирования матрицы;

- перегруженные операции:

= - операция присваивания;

+ - сложение двух матриц;

* - умножение двух матриц;

() – операция индексирования;

<< - операция вставки в поток;

>> - операция извлечения из потока.

Другие методы класса ввести по желанию.

Написать программу, демонстрирующую работу с этим классом. Программа должна содержать меню, позволяющее осуществить проверку всех методов класса.

Создать многофайловый проект.



Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: