Operatorul de indexare
Operatorul de indexare [] (subscript operator) permite interpretarea unui obiect ca un tablou sau ca un container de alte obiecte și permite selectarea unor elemente individuale din obiect. Funcția operatorului [] trebuie să primească ca argument un indice condițional, după care, în obiectul-container, se poate găsi elementul dorit. Să analizăm un exemplu simplu:
#include <iostream>
class Person
{
public:
Person(std::string p_name, unsigned p_age, std::string p_company)
{
name = p_name;
company = p_company;
age = p_age;
}
std::string operator[](unsigned index) const
{
switch (index)
{
case 0: return name;
case 1: return std::to_string(age); // convertim în tipul std::string
case 2: return company;
default: return "Bad Index";
}
}
private:
std::string name;
unsigned age;
std::string company;
};
int main()
{
Person tom{"Tom", 38, "Microsoft"};
std::cout << tom[0] << "\n" << tom[1] << "\n" << tom[2] << std::endl;
}
Aici, clasa Person definește trei variabile private – name, age și company, care nu sunt accesibile din exterior. De fapt, putem considera obiectul Person ca un container deasupra acestor variabile. Pentru a accesa aceste variabile, definim operatorul de indexare:
std::string operator[](unsigned index) const
{
switch (index)
{
case 0: return name;
case 1: return std::to_string(age);
case 2: return company;
default: return "Bad Index";
}
}
Operatorul primește ca operand un indice numeric, în acest caz de tip unsigned (un număr natural), și în funcție de acest index returnează valoarea unei variabile. În acest caz returnăm o valoare de tip std::string, iar valoarea câmpului age este convertită în șir cu ajutorul funcției std::to_string. Alegerea ce variabilă se întoarce pentru un anumit index este convențională. În acest exemplu este logic (dar nu obligatoriu) să fie în ordinea în care sunt declarate în clasă. Apoi, în funcția main, folosind acest operator, putem accesa variabilele prin index:
Person tom{"Tom", 38, "Microsoft"};
std::cout << tom[0] << "\n" << tom[1] << "\n" << tom[2] << std::endl;
Ieșirea în consolă:
Tom
38
Microsoft
Nu este obligatoriu să folosim indici numerici. Ei pot reprezenta orice tip, de exemplu, std::string:
#include <iostream>
class Person
{
public:
Person(std::string p_name, unsigned p_age, std::string p_company)
{
name = p_name;
company = p_company;
age = p_age;
}
// index pe bază de șir
std::string operator[](const std::string& prop) const
{
if(prop=="name") return name;
else if(prop== "age") return std::to_string(age);
else if(prop=="company") return company;
else return "Bad Index";
}
private:
std::string name;
unsigned age{};
std::string company;
};
int main()
{
Person tom{"Tom", 38, "Microsoft"};
std::cout << tom["name"] << ": " << tom["company"] << std::endl; // Tom: Microsoft
}
Operatorul de indexare poate fi folosit pentru accesul la elementele unui set care există în obiect. De exemplu, o companie are un set de angajați:
#include <iostream>
class Company
{
public:
std::string& operator[](unsigned index)
{
return employees[index];
}
private:
std::string employees[10]{"Tom", "Sam", "Bob"};
};
int main()
{
Company company;
std::cout << company[0] << std::endl; // Tom
company[0] = "Tomas";
std::cout << company[0] << std::endl; // Tomas
}
Pentru simplitate, clasa Company conține un set de angajați sub forma unui tablou de șiruri. Cu ajutorul funcției de indexare putem accesa unul dintre elementele tabloului employees. Pentru a putea modifica obiectele returnate, operatorul returnează nu doar un obiect std::string, ci o referință std::string&.
Totuși, principala problemă aici constă în organizarea logicii de returnare a elementului. În primul caz (cu clasa Person) este necesar să aducem valori de tipuri diferite la un tip comun. În al doilea caz, ne putem confrunta cu situația în care este transmis un index invalid. Există diverse strategii pentru a trata aceste cazuri, dar în orice caz, aceste situații trebuie luate în considerare atunci când definim operatorul.