БлогNot. C++: как правильно переопределить << и >> в классе?

C++: как правильно переопределить << и >> в классе?

Мне требовался несложный шаблон класса, работающий более чем с одним скалярным типом данных, но гораздо более компактный, чем вот этот код.

В данном случае класс - простое "кольцо" элементов с фиксированным размером и двумя базовыми действиями - "положить элемент в кольцо и сдвинуть указатель", "забрать элемент из кольца и сдвинуть указатель", например:

было 0 0 0 0 0 (размерность кольца равна 5 элементов)
положили 1 2 3
стало 0 0 1 2 3
положили 4 5 6
стало 2 3 4 5 6 (указатель перед выводом стоял на "2", а "1" уже затёрлась)
взяли 3 элемента, выводя их, вывелись 2 3 4
снова вывели кольцо, вывелось 5 6 2 3 4

Ring <int> A(5);
A.put(1); A.put(2); A.put(3);
cout << A;
A.put(4); A.put(5); A.put(6);
cout << A;
cout << endl << A.get(); 
cout << endl << A.get();
cout << endl << A.get();
cout << A;

Выше показан код из main, который мог бы дать такую картину (модифицированной версией программы!), а ниже - полный листинг программы (консоль актуальной сборки Visual Studio 2019).

#include <iostream>
#include <cstdlib>
using namespace std;

template <typename T> class Ring { //Шаблон класса "кольцо"
private:
 T* data;
 unsigned size, pointer;
public:
 Ring(int size = 2) {
  if (size < 2) size = 2;
  this->data = new T[size];
  if (!data) { cout << "Memory allocation error!"; exit(1); }
  this->size = size;
  this->pointer = 0;
  for (unsigned u = 0; u < size; u++) this->data[u] = (T)0;
 }
 void put(T val) {
  this->data[this->pointer] = val;
  this->pointer++;
  if (this->pointer == this->size) this->pointer = 0;
 }
 T get() {
  T val = this->data[this->pointer];
  this->pointer++;
  if (this->pointer == this->size) this->pointer = 0;
  return val;
 }
};

int main() {
 Ring <int>* R1 = new Ring <int>(5); //Кольцо целых чисел в "куче"
 R1->put(1);
 R1->put(2);
 R1->put(3);
 cout << endl;
 for (unsigned u = 0; u < 6; u++) cout << R1->get() << " ";  //0 0 1 2 3 0
 
 Ring <double> R2 (3); //Кольцо вещественных чисел в "стеке"
 R2.put(1.5);
 R2.put(2.5);
 cout << endl;
 for (unsigned u = 0; u < 3; u++) cout << R2.get() << " "; //0 1.5 2.5

 return 0;
}

В этом коде нет возможности вывести всё кольцо в поток "сразу", да ещё и конструкции вида

cout << endl << R2.get() << " " << R2.get() << " " << R2.get();

нормально работать не будут из-за пресловутых точек следования.

Поэтому ниже приводится расширенный шаблон, в который включены шаблоны операторных функций << и >> для поточного вывода и ввода.

#include <iostream>
#include <cstdlib>
using namespace std;

template <typename T> class Ring { //Шаблон класса "кольцо"
private: //Данные и шаблоны операторов ввода-вывода
 T* data;
 unsigned size, pointer;
 template <typename T1> friend ostream& operator << (ostream& os, const Ring <T1>&);
 template <typename T1> friend istream& operator >> (istream& is, Ring <T1>&);
public:
 Ring(unsigned size = 2) { //Конструктор
  if (size < 2) size = 2;
  this->data = new T[size];
  if (!data) { cout << "Memory allocation error!"; exit(1); }
  for (unsigned u = 0; u < size; u++) this->data[u] = (T)0;
  this->size = size;
  this->pointer = 0;
 }
 void put(T val) { //Положить элемент в кольцо и сдвинуть указатель
  this->data[this->pointer] = val;
  this->pointer++;
  if (this->pointer == this->size) this->pointer = 0;
 }
 T get() { //Забрать элемент из кольца и сдвинуть указатель
  T val = this->data[this->pointer];
  this->pointer++;
  if (this->pointer == this->size) this->pointer = 0;
  return val;
 }
};

template <typename T> ostream& operator << (ostream& os, const Ring <T>& dt) {
 //Шаблон оператора вывода <<
 os << std::endl;
 unsigned index = dt.pointer;
 for (unsigned k = 0; k < dt.size; k++) {
  os << dt.data[index] << (k == dt.size - 1 ? "" : " ");
  index++;
  if (index == dt.size) index = 0;
 }
 return os;
}

template <typename T> istream& operator >> (istream& is, Ring <T>& dt) {
 //Шаблон оператора ввода >>
 is >> dt.data[dt.pointer];
 dt.pointer++;
 if (dt.pointer == dt.size) dt.pointer = 0;
 return is;
}

int main() {
 Ring <int>* R1 = new Ring <int>(3); //Создали объект в куче
 R1->put(1);
 R1->put(2);
 R1->put(3);
 cout << *R1;

 Ring <double> R2(3); //Создали объект в стеке
 cout << endl << "Input the double number: ";
 cin >> R2;
 R2.put(1.5);
 R2.put(2.5);
 cout << R2;

 return 0;
}

Обратите внимание, что в описании шаблонов операторов << и >> для класса указан "другой" тип данных T1, без этого ничего работать не будет. Модификатор const и оператор ссылки & также имеют значение.

Важно, что тем же кодом можно обмениваться данными с файлами, например:

int main() {
 Ring <int> R (5);
 ifstream ("input.txt") >> R >> R >> R >> R >> R; //добавить #include <fstream>
 ofstream("output.txt") << R;
 return 0;
}

В файле input.txt из текущей папки было

1 2 3 4
5

В файл output.txt, созданный в текущей папке, записалось


1 2 3 4 5


теги: c++ программирование список учебное

21.01.2021, 20:09; рейтинг: 77