Операторы ввода >> и вывода << прекрасно работают для примитивных типов данных, таких как int или double. В то же время для использования их с объектами классов необходимо переопределять эти операторы.
Стандартный выходной поток cout имеет тип std::ostream. Поэтому первый параметр (левый операнд) операции << представляет ссылку на неконстантный объект ostream. Данный объект не должен представлять константу, так как запись в поток изменяет его состояние. Причем параметр представляет именно ссылку, так как нельзя копировать объект класса ostream.
Второй параметр оператора определяется как ссылка на константный объекта класса, который надо вывести в поток.
Для совместимости с другими операторами переопределяемый оператор должен возвращать значение параметра std::ostream.
Также следует отметить, что операторы ввода и вывода не должны быть членами в классе, а определяются вне класса как обычные функции.
#include <iostream> 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> 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> 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