Такие контейнеры стандартной библиотеки шаблонов обеспечивают эффективный последовательный доступ к списку объектов. Стандартная библиотека C++ предоставляет три вида последовательных контейнеров: векторы, списки и двухсторонние очереди.
Вектор
Массивы часто используются для хранения ряда элементов и обеспечивают возможность прямого доступа к ним. Элементы в массиве имеют один и тот же тип, а обратиться к ним можно с помощью индекса. Библиотека STL обеспечивает класс- контейнер vector, который ведет себя подобно массиву, но его использование отличается большей мощностью и безопасностью по сравнению со стандартным массивом C++.
Вектор — это контейнер, оптимизированный таким образом, чтобы обеспечить быстрый доступ к его элементам по индексу. Класс-контейнер vector определен в файле заголовка <vector> в пространстве имен std (подробнее об использовании пространств имен см. главу 17). Вектор можно наращивать по мере необходимости. Предположим, был создан вектор для 10 элементов. После того как в вектор поместили 10 объектов, он оказался целиком заполненным. Если затем к вектору добавить еще один объект, он автоматически увеличит свою вместимость так, что сможет разместить одиннадцатый объект. Вот как выглядит определение класса vector:
template <class T, class А = allocator<T>> class vector
{
// члены класса
};
Первый аргумент (class T) означает тип элементов в векторе. Второй аргумент (class А) — это класс распределения, который берет на себя функции диспетчера памяти, ответственного за распределение и освобождение памяти для элементов контейнеров. Принципы построения и выполнения классов распределения затрагивают более сложные темы, которые выходят за рамки этой книги.
По умолчанию элементы создаются с помощью оператора new() и освобождаются с помощью оператора delete(), т.е. для создания нового элемента вызывается стандартный конструктор класса Т. Это служит еще одним аргументом в пользу явного определения стандартного конструктора для ваших собственных классов. Если этого не сделать, то нельзя будет использовать стандартный векторный контейнер для хранения объектов пользовательского класса.
Определить векторы для содержания целых и вещественных чисел можно следующим образом:
vector<int> vInts; // вектор для хранения целых элементов
vector<float> vFloats; // вектор для хранения вещественных элементов
Обычно пользователь имеет представление о том, сколько элементов будет содержаться в векторе. Предположим, на курс прикладной математики в институте набирается не более 50 студентов. Прежде чем создавать вектор для массива студентов, следует побеспокоиться о том, чтобы он был достаточно большим и мог содержать 50 элементов. Стандартный класс vector предоставляет конструктор, который принимает число элементов в качестве параметра. Так что можно определить вектор для 50 студентов следующим образом:
vector<Student> MathClass(50);
Компилятор автоматически выделит достаточный объем памяти для хранения записей о 50 студентах. Каждый элемент вектора создается с использованием стандартного конструктора Student::Student().
Количество элементов в векторе можно узнать с помощью функции-члена size(). В данном примере функция-член vStudent.size() возвратит значение 50.
Другая функция-член, capacity(), сообщает, сколько в точности элементов может принять вектор, прежде чем потребуется увеличение его размера. Но об этом речь впереди.
Вектор называется пустым, если он не содержит ни одного элемента, т.е. если его размер равен нулю. Чтобы определить, не является ли вектор пустым, в классе вектора предусмотрена функция-член empty(), которая принимает значение, равное истине, если вектор пустой.
Чтобы записать студента Гарри на курс прикладной математики, т.е. (говоря языком программирования) чтобы назначить объект Harry класса Student вектору MathClass, можно использовать оператор индексирования ([]):
MathClass[5] = Harry;
Индексы начинаются с нуля. Для назначения объекта Harry шестым элементом вектора MathClass здесь используется перегруженный оператор присваивания класса Student. Аналогично, чтобы определить возраст объекта Harry, можно получить доступ к соответствующей записи, используя следующее выражение:
MathClass[5].GetAge();
Как упоминалось выше, при добавлении в вектор большего числа элементов, чем было указано при создании вектора, дополнительное место для нового элемента будет добавлено автоматически. Предположим, курс прикладной математики стал таким популярным, что количество принятых студентов превысило число 50. Возможно, за 51- го студента кто-то замолвил словечко, и декану не осталось ничего другого, как увеличить число студентов на курсе. Так вот, если на курс (в вектор MathClass) захочет записаться 51-я студентка Салли (объект Sally), компилятор спокойно расширит пределы вектора, чтобы "впустить" новое молодое дарование.
Добавлять элемент в вектор можно различными способами. Один из них — с помощью функции-члена push_back():
MathClass.push_back(Sally);
Эта функция-член добавляет новый объект Sally класса Student в конец вектора MathClass. И теперь в векторе MathClass содержится уже 51 элемент, причем к объекту Sally можно обратиться по индексу MathClass[50].
Чтобы функция push_back() была работоспособной, в классе Student нужно определить конструктор-копировщик. В противном случае эта функция не сможет создать копию объекта Sally.
В векторе из библиотеки STL не задается максимальное число элементов, так как это решение лучше переложить на плечи создателей компиляторов. Векторный класс предоставляет функцию-член max_size(), которая способна сообщить это магическое число, определенное в вашем компиляторе.
В листинге 19.8 демонстрируется использование векторного класса. Вы увидите, что для упрощения обработки строк в этом листинге используется стандартный класс string. Для получения более подробной информации о классе string обратитесь к документации, прилагаемой к вашему компилятору.
Листинг 13.8. Создание вектора и обеспечение доступа к его элементам
1: #include <iostream>
2: #include <string>
3: #include <vector>
4: using namespace std;
5:
6: class Student
7: {
8: public:
9: Student();
10: Student(const string& name, const int аде);
11: Student(const Student& rhs);
12: ~Student();
13:
14: void SetName(const string& name);
15: string GetName() const;
16: void SetAge(const int age);
17: int GetAge() const;
18:
19: Student& operator=(const Student& rhs);
20:
21: private:
22: string itsName;
23: int itsAge;
24: };
25:
26: Student::Student()
27: : itsName("New Student"), itsAge(16)
28: { }
29:
30: Student::Student(const string& name, const int agе)
31: : itsName(name), itsAge(age)
32: { }
33:
34: Student::Student(const Student& rhs)
35: : itsName(rhs.GetName()), itsAge(rhs.GetAge())
36: { }
37:
38: Student::~Student()
39: { }
40:
41: void Student::SetName(const string& name)
42: {
43: itsName = name;
44: }
45:
46: string Student::GetName() const
47: {
48: return itsName;
49: }
50:
51: void Student::SetAge(const int age)
52: {
53: itsAge = age;
54: }
55:
56: int Studsnt::GitAge() const
57: {
58: return itsAge;
59: }
60:
61: Student& Student::operator=(const Student& rhs)
62: {
63: itsName = rhs,GetName();
64: itsAge = rhs.GetAge();
65: return *this;
66: }
67:
68: stream& operator<<(ostream& os, const Student& rhs)
69: {
70: os << rhs.GetName() << " is " << rhs.GetAge() << " years old";
71: return os;
72: }
73:
74: template<class T>
75: void ShowVector(const vector<T>& v); // Отображает свойства вектора
76:
77: typedef vector<Student> SchoolClass;
78:
79: int main()
80: {
81: Student Harry;
82: Student Sally("Sally", 15);
83: Student Bill("Bill", 17);
84: Student Peter("Peter", 16);
85:
86: SchoolClass EmptyClass;
87: cout << "EmptyClass:n";
88: ShowVector(EmptyClass);
89:
90: SchoolClass GrowingClass(3);
91: cout << "GrowingClass(3):n";
92: ShowVector(GrowingClass);
93:
94: GrowingClass[0] = Harry;
95: GrowingClass[1] = Sally;
96: GrowingClass[2] = Bill;
97: cout << "GrowingClass(3) after assigning students:n";
98: ShowVector(GrowingClass);
99:
100: GrowingClass.push_back(Peter);
101: cout << "GrowingClass() after added 4th student:n";
102: ShowVector(GrowingClass);
103:
104: GrowingClass[0].SetName("Harry");
105: GrowingClass[0].SetAge(18);
106: cout << "GrowingClass() after Setn:";
107: ShowVector(GrowingClass);
108:
109: return 0;
110: }
111:
112: //
113: // Отображает свойства вектора
114: //
115: template<class T>
116: void ShowVector(const vector<T>& v)
117: {
118: cout << "max_size() = " << v,max_size();
119: cout << "tsize() = " << v,size();
120: cout << "tcapaeity() = " << v,capacity();
121: cout << "t" << (v.empty()? "empty": "not empty");
122: cout << "n";
123: