Операторы ввода >> и вывода << прекрасно работают для примитивных типов данных, таких как int или double. В то же время для использования их с объектами классов необходимо переопределять эти операторы.
Стандартный выходной поток cout имеет тип std::ostream. Поэтому первый параметр (левый операнд) операции << представляет ссылку на неконстантный объект ostream. Данный объект не должен представлять константу, так как запись в поток изменяет его состояние. Причем параметр представляет именно ссылку, так как нельзя копировать объект класса ostream.
Второй параметр оператора определяется как ссылка на константный объекта класса, который надо вывести в поток.
Для совместимости с другими операторами переопределяемый оператор должен возвращать значение параметра std::ostream.
Также следует отметить, что операторы ввода и вывода не должны быть членами в классе, а определяются вне класса как обычные функции.
#include <iostream>
#include <string>
class Person
{
public:
Person(std::string name, unsigned age): name{name}, age{age} {}
std::string getName() const {return name;}
unsigned getAge() const {return age;}
void setName(std::string personName){ name = personName;}
void setAge(unsigned personAge){ age = personAge;}
private:
std::string name;
unsigned age;
};
std::ostream& operator << (std::ostream &os, const Person &person)
{
return os << person.getName() << " " << person.getAge();
}
int main()
{
Person tom{"Tom", 38};
std::cout << tom << std::endl;
Person bob{"Bob", 42};
std::cout << bob << std::endl;
}
В данном случае оператор вывода определяется для объектов структуры Person. Сам оператор по сути просто выводит имя и возраст пользователя через пробел. Консольный вывод программы:
Tom 38 Bob 42
Первый параметр оператора >> представляет ссылку на объект istream, с которого осуществляется чтение. Второй параметр представляет ссылку на неконстантный объект, в который надо считать данные. В качестве результата оператор возвращают ссылку на поток ввода istream из первого параметра.
#include <iostream>
#include <string>
class Person
{
public:
Person(std::string name, unsigned age): name{name}, age{age} {}
std::string getName() const {return name;}
unsigned getAge() const {return age;}
void setName(std::string personName){ name = personName;}
void setAge(unsigned personAge){ age = personAge;}
private:
std::string name;
unsigned age{};
};
std::istream& operator >> (std::istream& in, Person& person)
{
std::string name;
unsigned age;
in >> name >> age;
person.setName(name);
person.setAge(age);
return in;
}
int main()
{
Person bob{"",0};
std::cout << "Input name and age: ";
std::cin >> bob;
std::cout << "Name: " << bob.getName() << "\tAge: " << bob.getAge() << std::endl;
}
Оператор ввода последовательно считывает из потока данные в переменные name и age и затем использует их для установки имени и возраста пользователя.
std::istream& operator >> (std::istream& in, Person& person)
{
std::string name;
unsigned age;
in >> name >> age;
person.setName(name);
person.setAge(age);
return in;
}
При этом в данном случае предполагается, что имя представляет одно слово. Если надо считать сложное имя, которое состоит из нескольких слов, или имя и фамилию, то естественно надо определять более сложную логику.
Пример работы программы:
Input name and age: Bob 42 Name: Bob Age: 42
Однако что если мы введем для возраста вместо числа строку? В этом случае переменная age получит неопределенное значение. Существуют различные варианты, как обрабатывать подобные ситуации. Но в качестве примера мы можем в случае некорректного ввода устанавливать значение по умолчанию:
std::istream& operator >> (std::istream& in, Person& person)
{
std::string name;
unsigned age;
in >> name >> age;
if (in)
{
person.setName(name);
person.setAge(age);
}
return in;
}
С помощью выражения if(in) проверяем, является ли ввод удачным. Если он завершился успешно, то устанавливаем введенные значения. Если же ввод не удался, у объекта Person
остаются те значения, которые у него было до ввода.
Определив операторы ввода и выводы, мы можем их использовать также и для чтения и записи файла:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
class Person
{
public:
Person(std::string name, unsigned age): name{name}, age{age} {}
std::string getName() const {return name;}
unsigned getAge() const {return age;}
void setName(std::string personName){ name = personName;}
void setAge(unsigned personAge){ age = personAge;}
private:
std::string name;
unsigned age{};
};
std::ostream& operator << (std::ostream &os, const Person &person)
{
return os << person.getName() << " " << person.getAge();
}
std::istream& operator >> (std::istream& in, Person& person)
{
std::string name;
unsigned age;
in >> name >> age;
// если ввод не удался, устанавливаем некоторые значения по умолчанию
if (in)
{
person.setName(name);
person.setAge(age);
}
return in;
}
int main()
{
// начальные данные - вектор объектов Person
std::vector<Person> people =
{
Person{"Tom", 23},
Person{"Bob", 25},
Person{"Alice", 22},
Person{"Kate", 31}
};
// запись данных в файл
std::ofstream out("people.txt");
if (out.is_open())
{
for (const Person& person: people)
{
out << person << std::endl;
}
}
out.close();
// вектор для считываемых данных
std::vector<Person> new_people;
// чтение ранее записанных данных из файла
std::ifstream in("people.txt");
if (in.is_open())
{
Person person{"",0};
while (in >> person)
{
new_people.push_back(person);
}
}
in.close();
// вывод считанных данных на консоль
std::cout << "All people:" << std::endl;
for (const Person& person: new_people)
{
std::cout << person << std::endl;
}
}
Здесь для класса Person определены операторы ввода и вывода. С помощью оператора вывода данные будут записываться в файл users.txt, а с помощью оператора ввода - считываться из файла. В конце считанные данные выводятся на консоль:
Результат работы программы:
All users: Tom 23 Bob 25 Alice 22 Kate 31