В попередньому уроці ми визначили змінну типу vector<float>, як еквівалент масиву, розмір якого ми зможемо довільно змінювати по ходу виконання коду. Але це не означає (так само як і для класичних векторів C ++), що ми можемо таким чином створювати тільки динамічні масиви найпростіших вбудованих типів. Тип елемента вектора може бути довільним і як завгодно складним! Наприклад, ми могли б описати студентську групу так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <iostream> #include <vector> // подключаем необходимую библиотеку using namespace std; struct student { char fio[80]; short age; // конструктор объекта student(const char* fio, short age) : age(age) { // присваиваем полю age,значение параметра age // в поле fio структуры копируем параметр fio, который указывается при создании объекта strncpy_s(this->fio, fio, sizeof(this->fio)); } // ... }; // перегружаем оператор для форматирования вывода объектов inline ostream& operator <<(ostream& out, const student& obj) { return out << obj.fio << "\t: " << obj.age; } int main(void) { setlocale(LC_ALL, "rus"); vector<student> group; // метод push_back() добавляет новую запись в конец вектора group.push_back(student("Иванов И.И.", 20)); group.push_back(student("Петров П.П.", 21)); group.push_back(student("Сидоров С.С.", 19)); for (int i = 0; i < group.size(); i++) cout << group[i] << endl; } |
У цьому прикладі, не зважаючи на все інше, ми застосували покажчик this і перевантаження операторів. Залишаю посилання для вас, якщо комусь необхідно освіжити це в пам'яті.
Шаблонним класом вектора (і будь-якого контейнера STL) може бути, в свою чергу, STL контейнер. Наприклад vector< vector<int> > или vector< vector< vector<int> > > (не забудьте пробіл між закриваються дужками ‘>‘ - Це особливість синтаксичного розбирача). Таким чином ми можемо, например, створити клас трикутних матриць:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <vector> #include <cstring> #include <iostream> using namespace std; struct trimatrix : vector< vector<double> > { // треугольная матрица trimatrix(int n) { // n - размер while (n > 0) { vector<double> row(n); for (int i = 0; i < n; i++) row[i] = n - i; push_back(row); n--; } } }; inline ostream& operator <<(ostream& out, const trimatrix& obj) { for (int i = 0; i < obj.size(); i++) { for (int j = 0; j < obj[i].size(); j++) cout << obj[i][j] << " "; cout << endl; } return out; } int main(void) { cout << trimatrix(4); } |
Наступним рівнем нашого поглиблення в техніку векторів, і контейнерів STL взагалі, буде поняття итератор. Итератор - центральне поняття для роботи з контейнерами STL. итератор – це деяка абстракція, яка застосовується для виконання ітерації (перебору) елементів в контейнері STL і надання доступу до окремих елементів.
итератор p не є покажчиком, але, спершу, ви можете умовно вважати його як щось подібне з вигляду: *p позначатиме значення даних під поточним ітератором, p переводить итератор на наступний елемент контейнера, а p– (коли це допустимо) - На попередній елемент. Для різних типів контейнерів, відповідні їм ітератори можуть належати до однієї з 5-ти категорій: вхідні, вихідні, односпрямовані, двонаправлені і довільного доступу.
Ітератори векторів - це ітератори прямого доступу. Саме тому для векторів можлива операція індексації. цих, досить поверхневих, знань про ітератори нам досить для того, щоб почати працювати з ними.
Воспроізведём в термінах ітераторів завдання знаходження всіх простих чисел, що не перевищують N (решето Ератосфена), яку ми вже вирішували раніше в техніці масивів C ++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <iostream> #include <vector> #include <string> using namespace std; int main(int argc, char **argv) { int k, n; // верхняя граница vector<bool> a(n = stoi(argv[1]) + 1, true); a[0] = a[1] = false; for (k = 2; k < n; k++) for (vector<bool>::iterator j = a.begin() + k + k; j < a.end(); j += k) *j = false; const int line = 10; // выводить чисел в строку k = 0; for (vector<bool>::iterator j = a.begin(); j < a.end(); j++) if (*j) { cout << j - a.begin() << "\t"; if (0 == ++k % line) cout << endl; } if (k % line != 0) cout << endl; return 0; } |
Як легко бачити з опису vector<bool>::итератор, що итератор зберігає в собі вид контейнера, до якого відноситься, і тип елементів цього контейнера. Це вимагає досить громіздкою записи з точним описом типу ітератора. Але останній стандарт C ++ 11 ввів поняття виводимості типу: якщо необхідний тип об'єкта виводиться з контексту його використання, то тип об'єкта може бути оголошений описателем автоматичний (виведений тип). тоді рядок 16 показаного вище коду може бути записана так:
1 | for( auto j = a.begin(); j < a.end(); j++ ) |
нарешті, для векторів (і для всіх контейнерів, мають двонаправлені iteratorи, як згадувалося вище) можуть бути визначені реверсні ітератори, які переміщуються не від початку контейнера до кінця, а навпаки - з кінця в початок. Такий итератор повинен оголошуватися як зовсім інший тип, например:
1 | vector<bool>::reverse_iterator i; |
Але і тут ми можемо покластися на виведення типів, як в наступному прикладі:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> #include <vector> using namespace std; int main(void) { float data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int n = sizeof(data) / sizeof(data[0]); // кол-во элементов vector<float> array(data, data + n); // вектор из массива for (auto j = array.begin(); j < array.end(); j++) cout << *j << (j + 1 == array.end() ? "\n" : " "); for (auto j = array.rbegin(); j < array.rend(); j++) cout << *j << (j + 1 == array.rend() ? "\n" : " "); } |
Питання, що виникають по уроку задавайте в коментарях.
Нарешті то що сучасне по срр, так тримати
Пристрасть - співчуття чудове , але люта ілюзія змушує нас засумніватися в то , що однією пристрасті для милого розлучення занадто . Зважившись на такий істотний ривок , як заручини , потрібно постаратися методологічно оцінити обґрунтованість свого ҏешенія .
зтвоз