Suprascrierea operatorilor de intrare și ieșire
Operatorii de intrare >> și ieșire << funcționează perfect pentru tipurile primitive de date, cum ar fi int sau double. În schimb, pentru a-i utiliza cu obiecte de tipul claselor, este necesar să suprascriem acești operatori.
Operatorul <<
Fluxul standard de ieșire cout are tipul std::ostream. De aceea, primul parametru (operandul din stânga) al operației << reprezintă o referință la un obiect non-constant de tip ostream. Acest obiect nu trebuie să fie constant, deoarece scrierea în flux modifică starea sa. De asemenea, parametrul reprezintă o referință, deoarece nu se poate copia un obiect de tipul ostream.
Al doilea parametru al operatorului este definit ca o referință la un obiect constant al clasei care trebuie să fie afișat în flux.
Pentru compatibilitate cu alți operatori, operatorul suprascris trebuie să returneze valoarea parametrului std::ostream.
De asemenea, trebuie menționat că operatorii de intrare și ieșire nu trebuie să fie membri ai clasei, ci sunt definiți în afara clasei, ca funcții obișnuite.
#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;
}
În acest caz, operatorul de ieșire este definit pentru obiectele structurii Person. Operatorul, de fapt, pur și simplu afișează numele și vârsta utilizatorului, separate printr-un spațiu. Iată rezultatul pe consolă:
Tom 38
Bob 42
Operatorul >>
Primul parametru al operatorului >> reprezintă o referință la un obiect istream, din care se face citirea. Al doilea parametru reprezintă o referință la un obiect non-constant, în care se vor citi datele. Ca rezultat, operatorul returnează o referință la fluxul de intrare istream din primul parametru.
#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;
}
Operatorul de intrare citește succesiv din flux datele în variabilele name și age, iar apoi le folosește pentru a seta numele și vârsta utilizatorului.
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;
}
În acest caz, se presupune că numele reprezintă un singur cuvânt. Dacă dorim să citim un nume complex, care conține mai multe cuvinte sau un nume și un prenume, atunci, desigur, trebuie să definim o logică mai complexă.
Exemplu de lucru al programului:
Input name and age: Bob 42
Name: Bob Age: 42
Totuși, ce se întâmplă dacă pentru vârstă se introduce un șir de caractere în loc de un număr? În acest caz, variabila age va primi o valoare nedefinită. Există diverse moduri de a trata aceste situații, dar ca exemplu, putem seta o valoare implicită în caz de intrare incorectă:
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;
}
Folosind expresia if(in), verificăm dacă intrarea a avut loc cu succes. Dacă a fost un succes, setăm valorile introduse. Dacă intrarea nu a reușit, obiectul Person va păstra valorile pe care le avea înainte de introducere.
Citirea și scrierea fișierelor
După ce am definit operatorii de intrare și ieșire, îi putem folosi și pentru citirea și scrierea fișierelor:
#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()
{
// date inițiale - vector de obiecte Person
std::vector<Person> people =
{
Person{"Tom", 23},
Person{"Bob", 25},
Person{"Alice", 22},
Person{"Kate", 31}
};
// scrierea datelor într-un fișier
std::ofstream out("people.txt");
if (out.is_open())
{
for (const Person& person: people)
{
out << person << std::endl;
}
}
out.close();
// vector pentru datele citite
std::vector<Person> new_people;
// citirea datelor din fișier
std::ifstream in("people.txt");
if (in.is_open())
{
Person person{"", 0};
while (in >> person)
{
new_people.push_back(person);
}
}
in.close();
// afișarea datelor citite pe consolă
std::cout << "All users:" << std::endl;
for (const Person& person: new_people)
{
std::cout << person << std::endl;
}
}
Aici, pentru clasa Person sunt definite operatorii de intrare și ieșire. Cu ajutorul operatorului de ieșire, datele vor fi scrise într-un fișier people.txt, iar cu ajutorul operatorului de intrare vor fi citite din fișier. La final, datele citite sunt afișate pe consolă.
Rezultatul programului:
All users:
Tom 23
Bob 25
Alice 22
Kate 31