На попередньому занятті ми оголошували змінні для зберігання поодинокого значення типу int, char або string, навіть якщо використовувалося декілька їх екземплярів. Проте можна оголосити колекцію об'єктів, наприклад, 20 цілих чисел або зграя котів.
На сьогоднішньому зайнятті.
■ Що таке масиви, як їх оголошувати і використовувати.
■ Що таке рядки і як використати для їх створення символьні масиви.
■ Короткий вступ в тип std::string.

 Що таке масив

Визначення слова масив (array) в словнику досить близько до того, що ми хочемо зрозуміти. Згідно із словником Вебстера, масив - це "група елементів, що формують повний набір, наприклад масив сонячних панелей".

Нижче приведені характеристики масиву.

■ Масив - це колекція елементів.

■ Усі елементи, що містяться в масиві, мають однаковий вигляд.

■ Така колекція формує повний набір.

У мові C++   масиви дозволяють зберегти в пам'яті елементи даних однакового типу в послідовному порядку.

Необхідність в масивах

Припустимо, ви пишете програму, де користувач може ввести п'ять цілих чисел і відобразити їх на екрані. Один із способів мав на увазі б оголошення в програмі п'яти окремих цілочисельних змінних і збереження в них значень, що відображалися. Оголошення виглядали б таким чином:

int FirstNumber = 0;

int SecondNumber = 0;

int ThirdNumber = 0;

int FourthNumber = 0;

int FifthNumber = 0;

Якби користувачеві знадобилося зберігати і згодом відображати 500 і ціліших чисел, то довелося б оголосити 500 таких цілочисельних змінних, використовуючи приведену вище систему. Це зажадає величезної роботи і терпіння на її виконання. А що робити, якщо користувач попросить забезпечити 500 000 цілих чисел замість 5?

Правильно було б оголосити масив з п'яти цілих чисел, кожне з яких ініціалізувалося б нулем:

int MyNumbers [5] = {0};

Таким чином, якби вас попросили забезпечити 500 000 цілих чисел, то ваш масив без проблем збільшився б так:

int ManyNumbers [500000] = {0};

Масив з п'яти символів був би визначений таким чином:

char MyCharacters [5];

Такі масиви називаються статичними (static array), оскільки кількість елементів, що містяться в них, а також розмір його області в пам'яті залишаються незмінними під час компіляції.

Оголошення і ініціалізація статичних масивів

У приведених вище рядках коду ми оголосили масив MyNumbers, який містить п'ять елементів типу int (тобто цілих чисел), що ініціалізували значенням 0. Таким чином, для оголошення масиву в мові C   використовується наступний синтаксис:

тип_элемента имя_массива [количество_элементов] = {необов'язкові початкові значення}

Можна навіть оголосити масив і ініціалізувати вміст усіх його елементів. Так, цілочисельний масив з п'яти цілих чисел можна ініціалізувати п'ятьма різними цілочисельними значеннями:

int MyNumbers [5] = {34, 56, - 21, 5002, 365};

Усі елементи масиву можна також ініціалізувати одним значенням:

int MyNumbers [5] = {100}; // ініціалізувати усі елементи

// значенням 100

Ви можете також ініціалізувати тільки частину елементів масиву :

int MyNumbers [5] = {34, 56}; // ініціалізувати перші два елементи

Ви можете визначити довжину масиву (тобто кількість елементів в нім) як константу і використати її при визначенні масиву :

const int ARRAY _ LENGTH = 5;

int MyNumbers [ARRAY _ LENGTH] = {34, 56, - 21, 5002, 365};

Це особливо корисно, коли необхідно мати доступ і використати довжину масиву в декількох місцях. Наприклад, при переборі елементів в декількох місцях можна уникнути необхідності з'ясування його довжини кожного разу - досить лише виправити значення, що ініціалізувало, при оголошенні const int.

ПРИМІТКА   При частковій ініціалізації масивів деякі компілятори ініціалізували проігноровані вами елементи початковим значенням 0.

Якщо початкова кількість елементів в масиві невідома, можна не вказувати його:

int MyNumbers [] = {2011, 2052, - 525};

Приведений вище код створює масив з трьох цілих чисел з початковими значеннями

2011, 2052 и- 525.

ПРИМІТКА   Масиви, які ми оголошували досі, називаються статичними масивами (static array), оскільки їх довжина фіксується програмістом під час компіляції. Такий масив не може прийняти більше даних, ніж визначив програміст. Він також не може задіяти менше пам'яті, якщо використовується тільки наполовину або взагалі не використовується.

Як дані зберігаються в масиві

Розглянемо книги, що стоять поряд на полиці. Це приклад одновимірного масиву, оскільки він розташовується тільки в одній розмірності, якою є кількість книг в ній. Кожна книга - це елемент масиву, а полиця схожа на область пам'яті, яка була зарезервована для зберігання цієї колекції книг (Рис. 4.1).

array book

Рис. 4.1. Книги на полиці: одновимірний масив

Це не помилка, ми починаємо нумерувати книги з нуля. Оскільки, як ви побачите пізніше, індекси в мові C   починаються з 0, а не з 1. Подібно до п'яти книг на полиці, масив MyNumbers, що містить п'ять цілих чисел, виглядає дуже схоже на мал. 4.2.

array book 2

Рис. 4.2. Організація масиву MyNumbers з п'яти цілих чисел

Зверніть увагу, що зайнята масивом область пам'яті складається з п'яти блоків рівного розміру, визначуваного типом що зберігаються масивом даних, в даному випадку типом int. Якщо ви пам'ятаєте, ми розглядали розмір цілочисельних типів на зайнятті 3, "Використання змінних, оголошення констант". Об'єм пам'яті, зарезервованої компілятором для масиву MyNumbers, складе sizeof (int)  * 5. У загальному вигляді об'єм пам'яті у байтах, що резервується компілятором для масиву, складає:

Байти масиву = sizeof (тип елементу)  * количество_элементов

Доступ до даних, що зберігаються в масиві

Для звернення до елементів масиву можна використати відлічуваний від нуля індекс (index). Індекси називаються відлічуваними від нуля тому, що перший елемент масиву має індекс 0. Так, перше цілочисельне значення, що зберігається в масиві MyNumbers, - це MyNumbers [0], друге - MyNumbers [1] і так далі. П'ятий елемент - MyNumbers [4]. Іншими словами, індекс останнього елементу в масиві завжди на одиницю менше його довжини.

Коли проситься доступ до елементу по індексу N, компілятор використовує адресу області пам'яті першого елементу (позиція нульового індексу) як відправну точку, а потім пропускає N елементів, додаючи до нього зміщення, вичислене як N*sizeof (тип_ елементу), щоб отримати адресу області, N, що містить, 1-й елемент. Компілятор C   не перевіряє, чи знаходиться індекс в межах фактично певних елементів масиву.  Ви можете спробувати вибрати елемент по індексу 1001 в масиві, що містить тільки 10 елементів, поставивши під загрозу безпеку і стабільність вашої програми. Відповідальність за відвертання звернення до елементів за межами масиву лежить виключно на програмістові.

УВАГА!   Результат доступу до масиву за його межами непередбачуваний. Як правило, це порушує роботу програми. Цього треба уникати за всяку ціну.

Лістинг 4.1 демонструє оголошення масиву цілих чисел, ініціалізацію його елементів цілочисельними значеннями і звернення до них для відображення на екрані.

ЛІСТИНГ 4.1. Оголошення масиву цілих чисел і доступ до його елементів

1: #include <iostream>
2:   
3: using namespace std;
4:   
5: int main ()
6: {
7:     int MyNumbers [5] = {34, 56, -21, 5002, 365};
8:
9:     cout << "First element at index 0: " << MyNumbers [0] << endl;
10:    cout << "Second element at index 1: " << MyNumbers [1] << endl;
11:    cout << "Third element at index 2: " << MyNumbers [2] << endl;
12:    cout << "Fourth element at index 3: " << MyNumbers [3] << endl;
13:    cout << "Fifth element at index 4: " << MyNumbers [4] << endl;
14:
15:    return 0;
16: }

Результат

First element at index 0 : 34

Second element at index 1 : 56

Third element at index 2 : - 21

Fourth element at index 3 : 5002

Fifth element at index 4 : 365

Аналіз

У рядку 6 оголошується масив з п'яти цілих чисел з початковими значеннями, визначеними для кожного з них. Подальші рядки просто відображають цілі числа, використовуючи оператор cout і змінну типу масиву MyNumbers з відповідним індексом.

ПРИМІТКА   Щоб ви краще ознайомилися з концепцією відлічуваних від нуля індексів, використовуваних для доступу до елементів масиву, з лістингу 4.1 рядків коду нумеруються починаючи з нуля, а не з одиниці.

Зміна даних, що зберігаються в масиві

У коді попереднього лістингу призначені для користувача дані не вводилися в масив. Синтаксис привласнення цілого числа елементу в цьому масиві дуже схожий на привласнення значення цілочисельній змінній.

Наприклад, привласнення значення 2011 цілочисельній змінній виглядає так:

int AnlntegerValue;

AnlntegerValue = 2011;

Привласнення значення 2011 четвертому елементу в даному масиві виглядає так:

MyNumbers [3] = 2011; // Привласнення 2011 четвертому елементу

Лістинг 4.2 демонструє використання констант в оголошенні довжини масиву, а також привласнення значень окремим елементам масиву під час виконання програми.

ЛІСТИНГ 4.2. Привласнення значень елементам масиву

1:  #include <iostream>
2:  using namespace std;
3: 
4:  int main()
5:  {
6:  const int ARRAY_LENGTH = 5;
7: 
8:  // Массив из 5 целых чисел, инициализированных нулями
9:  int MyNumbers [ARRAY_LENGTH] = {0};
10:
11: cout << "Enter index of the element to be changed: ";
12: int nElementlndex = 0;
13: cin » nElementlndex;
14:
15: cout << "Enter new value: ";
16: cin » MyNumbers [nElementlndex];
17:
18: cout << "First element at index 0: " << MyNumbers [0] << endl;
19: cout << "Second element at index 1: " << MyNumbers [1] << endl;
20: cout << "Third element at index 2: " << MyNumbers [2] << endl;
21: cout << "Fourth element at index 3: " << MyNumbers [3] << endl;
22: cout << "Fifth element at index 4: " << MyNumbers [4] << endl;
23:
24: return 0;
25: }

Результат

Enter index of the element to be changed: 2

Enter new value: 2011

First element at index 0 : 0

Second element at index 1 : 0

Third element at index 2 : 2011

Fourth element at index 3 : 0

Fifth element at index 4 : 0

Аналіз

Синтаксис оголошення масиву в рядку 8 використовує константу const integer ARRAY LENGTH, що ініціалізувала заздалегідь значенням п'ять. Оскільки це статичний масив, його довжина фіксується під час компіляції. Компілятор замінює константу ARRAY LENGTH значенням 5 і компілює код, вважаючи, що МуАггау - це цілочисельний масив, що містить п'ять елементів. У рядках 10-12 користувача запитують, який елемент масиву він хоче змінити, а введений індекс зберігається в цілочисельній змінній Elementlndex.  Це значення використовується в рядку 14 для зміни утримуваного масиву. Висновок демонструє, що змінився елемент по індексу 2, а фактично це був третій елемент масиву, оскільки індекси відлічуються від нуля. Ви повинні звикнути до цього.

ПРИМІТКА

Багато новачків в програмуванні на мові C   привласнюють значення п'ятому елементу масиву з п'яти цілих чисел по індексу п'ять. Зверніть увагу: це вже за межами масиву, і код, що відкомпілювався, спробує звернутися до шостого елементу масиву, що складається з п'яти елементів.

Цей вид помилки викликається помилкою поста охорони (fence - post error). Ця назва походить з того факту, що кількість постів для побудови охорони завжди на один більше, ніж кількість ділянок, що охороняються.

УВАГА!

У лістингу 4.2 відсутнє щось фундаментальне : перевірка введеного користувачем індексу на відповідність межам масиву. Фактично попередня програма повинна перевіряти, чи знаходиться значення змінної nElementindex в межах від 0 до 4, і відкидати усі інші значення. Відсутність цієї перевірки дозволяє користувачеві присвоїти значення поза межами масиву. Потенційно це може привести до порушення роботи додатка, а в найгіршому випадку і операційної системи. Детальніша інформація про перевірки приведена на зайнятті 6, "Галуження процесу виконання програм".

Використання циклів для доступу до елементів масиву

При роботі з елементами масиву в послідовному порядку для звернення до них (перебору) використовуються цикли. Щоб швидко навчитися ефективно працювати з елементами масиву, використовуючи цикл for, звернетеся до лістингу 6.10 зайняття 6, "Галуження процесу виконання програм".

РЕКОМЕНДУЄТЬСЯ   Ініціалізуйте масиви завжди, інакше вони міститимуть непередбачені значення. Перевіряйте завжди, чи використовуються ваші масиви в межах їх меж

НЕ РЕКОМЕНДУЄТЬСЯ  Ніколи не звертайтеся до елементу номер N, використовуючи індекс N, в масиві з N елементів. Не забувайте, що до першого елементу в масиві звертаються по індексу 0

Багатовимірні масиви

Масиви, які ми розглядали досі, нагадували книги на полиці. Може бути більше книг на довшій полиці або менше на коротшій. Таким чином, довжина полиці - єдина розмірність, визначальна її місткість, отже, вона одновимірна. Але що якщо тепер треба використати масив для моделювання сонячних панелей, як показано на мал. 4.3? Сонячні панелі, на відміну від книжкових полиць, поширюються в двох розмірності: по довжині і по ширині.

array 4.3

Рис. 4.3. Масив сонячних панелей на даху

Як можна помітити на Рис. 4.3, шість сонячних панелей розташовуються в двовимірному порядку: два ряди по три стовпці. З одного боку ви можете розглядати таке розташування як масив з двох елементів, кожен з яких сам є масивом з трьох панелей, іншими словами, як масив масивів. У мові C   ви можете створювати двовимірні масиви, але ви не обмежені тільки двома розмірністю. Залежно від необхідності і характеру додатка ви можете також створити в пам'яті багатовимірні масиви.

Оголошення і ініціалізація багатовимірних масивів

Мова C++   дозволяє оголошувати багатовимірні масиви, вказавши кількість елементів, яку необхідно зарезервувати по кожній розмірності. Отже, двовимірний масив цілих чисел, що представляє сонячні панелі на мал. 4.3, можна оголосити так:

int SolarPanellDs [2][3];

Зверніть увагу, що на мал. 4.3 кожною з шести панелей присвоєний також ідентифікатор в межах від 0 до 5. Якби довелося ініціалізувати цілочисельний масив в тому ж порядку, то це виглядало б так:

int SolarPanellDs [2][3] = {{0, 1, 2}, {3, 4, 5}};

Як бачите, синтаксис ініціалізації подібний до використовуваного при ініціалізації двох одновимірних масивів. Зверніть увагу: це не два масиви, оскільки масив двовимірний, це два його ряди. Якби цей масив складався з трьох лав і трьох стовпців, його оголошення і ініціалізація виглядали б таким чином:

int ThreeRowsThreeColumns [3][3] = {{- 501, 206, 2011}, {989, 101, 206},

{303, 456, 596}};

ПРИМІТКА   Навіть при тому, що мова C++   дозволяє використати модель багатовимірних масивів, в пам'яті масив міститься як одновимірний. Компілятор розкладає багатовимірний масив в області пам'яті, яка розширюється тільки в одному напрямі. Якщо ініціалізувати той же масив SolarPanellDs таким чином, то результат був би тим же :

int SolarPanellDs [2] [3] = {0, 1, 2, 3, 4, 5};

Проте попередній спосіб наочніший, оскільки так простіше представити і зрозуміти, що багатовимірний масив - це масив масивів.

Доступ до елементів у багатовимірному масиві

Вважайте багатовимірний масив масивом масивів. Оскільки розглянутий раніше двовимірний масив включав три ряди і три стовпці, що містять цілі числа, ви можете представити його як масив, що складається з трьох елементів, кожен з яких є масивом, що складається з трьох цілих чисел.

Тому, коли необхідно отримати доступ до цілого числа в цьому масиві, слід використати перший індекс для вказівки номера масиву, що зберігає цілі числа, а другий індекс, - для вказівки номера цілого числа в цьому масиві. Розглянемо наступний масив:

int ThreeRowsThreeColumns [3][3] = {{- 501, 206, 2011}, {989, 101, 206},

{303, 456, 596}};

Він ініціалізував способом, який можна розглядати як три масиви, кожен з яких містить три цілі числа. Тут цілочисельний елемент зі значенням 206 знаходиться в позиції [0][1], а елемент зі значенням 456 - в позиції [2][2].

 Лістинг 4.3 демонструє як можна звертатись до цілочисленних елементів в цьому масиві.

Лістинг 4.3. Доступ до елементів в багатомірному масиві

1: #include <iostream>
2: using namespace std;
3:
4:   int main()
5:   {
6:      int ThreeRowsThreeColumns [3][3] = \
7:      {{-501, 206, 2011},{989, 101, 206}, {303, 456, 596}};
8:
9:      cout << "Row 0: " << ThreeRowsThreeColumns[0][0]<< " " \
10:                       << ThreeRowsThreeColumns[0][1]<< " " \
11:                       << ThreeRowsThreeColumns[0][2]<< endl;
12:
13:     cout << "Row 1: " << ThreeRowsThreeColumns[1][0]<< " " \
14:                       << ThreeRowsThreeColumns[1][1]<< " " \
15:                       << ThreeRowsThreeColumns[1][2]<< endl;
16:
17:     cout << "Row 2: " << ThreeRowsThreeColumns[2][0]<< " " \
18:                       << ThreeRowsThreeColumns[2][1]<< " " \
19:                       << ThreeRowsThreeColumns[2][2]<< endl;
20:   
21:     return 0;
22:  }

Результат

Row 0 : - 501 206 2011

Row 1 : 989 101 206

Row 2 : 303 456 596

Аналіз

Зверніть увагу на спосіб звернення до елементів у відрядковому масиві, що розпочинається з масиву Row 0 (перший ряд з індексом 0) і масивом Row 2 (третій ряд з індексом 2), що закінчується. Оскільки кожен з лав - це масив, синтаксис для звернення до третього елементу в першому ряду такий, як в рядку 10.

ПРИМІТКА

Довжина коду в лістингу 4.3 істотно збільшується при збільшенні кількості елементів в масиві або його розмірності. Фактично в професійному середовищі розробки такий код непридатний.

Ефективніший спосіб звернення до елементів багатовимірного масиву приведений в лістингу 6.14 зайняття 6, "Галуження процесу виконання програм". Там для доступу до усіх елементів подібного масиву використовується вкладений цикл for. Код із застосуванням циклу for істотно коротше і менше схильний до помилок, а крім того, на його довжину не впливає зміна кількості елементів в масиві.

Динамічні масиви

Розглянемо додаток, який зберігає медичні записи лікарні. Програміст ніяк не може заздалегідь знати, скільки записів повинне зберігати і обробляти його застосування. Він може зробити припущення про розумну межу кількості записів для маленької лікарні, перевищення якого маловірогідне. В цьому випадку він безглуздо резервує величезні об'єми пам'яті і зменшує продуктивність системи.

У такому разі треба використати не статичні масиви, які ми розглянули тільки що, а динамічні, які оптимізують використання пам'яті і при необхідності збільшують розмір займаних ними ресурсів і пам'яті під час виконання. Мова C   надає дуже зручний в роботі динамічний масив у формі типу std:: vector, як показано в лістингу 4.4.

ЛІСТИНГ 4.4. Створення динамічного масиву цілих чисел і заповнення його значеннями

1: #include <iostream>
2: #include <vector>
3:
4: using namespace std;
5:
6: int main()
7: {
8:    vector DynArrNums (3);
9:
10:   DynArrNums[0] = 365;
11:   DynArrNums[1] = -421;
12:   DynArrNums[2]= 789;
13:
14:   cout << "Number of integers in array: " << DynArrNums.size()
           << endl;
15:
16:   cout << "Enter another number for the array" << endl;
17:   int AnotherNum = 0;
18:   cin » AnotherNum;
19:   DynArrNums.push_back(AnotherNum);
20:
21:   cout << "Number of integers in array: " << DynArrNums.size()
           << endl;
22:   cout << "Last element in array: ";
23:   cout << DynArrNums[DynArrNums.size() - 1] << endl;
24:
25:   return 0;
26: }

Результат

Number of integers in array: 3

Enter another number for the array

2011

Number of integers in array: 4 Last element in array: 201182 ЗАЙНЯТТЯ 4. Масиви і рядки

Аналіз

Не хвилюйтеся про синтаксис векторів і шаблонів в лістингу 4.4, - вони доки ще не були пояснені. Спробуйте подивитися виведення і співвіднести воно з кодом. Згідно з виведенням, початковий розмір масиву складає три елементи, що узгоджується з оголошенням вектору в рядку 7. Знаючи це, ви все ж можете в рядку 15 попросити користувача ввести четверте число і, що найцікавіше, в рядку 18 можете додати його у вектор, використовуючи метод push back (). Вектор динамічно змінить свої розміри так, щоб пристосуватися до зберігання більшого об'єму даних.  Це помітно по подальшому збільшенню розміру масиву до 4. Зверніть увагу на використання знайомого по статичному масиву синтаксису доступу до даних у векторі. У рядку 22 здійснюється доступ до останнього елементу (яким би він не був по рахунку, оскільки його позиція обчислюється під час виконання) за допомогою індексу, який для останнього елементу має значення розмір, - 1, а метод size () якраз і повертає загальну кількість елементів вектору.

ПРИМІТКА

Для використання класу динамічного масиву std:: vector в код необхідно включити заголовок vector, як це зроблено в рядку 1 лістингу 4.4.

#include <vector>

Детальніша інформація про вектори приведена на зайнятті 17, "Класи динамічних масивів бібліотеки STL".

Рядки в стилі C++

Рядки в стилі C++ (C - style string) - це окремий випадок масиву символів. Ви вже бачили декілька прикладів таких рядків у вигляді строкових літералів, коли писали такий код:

std::cout << "Hello World";

Це еквівалентно такому оголошенню масиву :

char SayHello[] = {'Н', 'е', 'l*, 'l', 'о', ' 'W', 'o', 'r', 'l*, 'd', '\0'};

std::cout << SayHello << std::endl;

Зверніть увагу: останній символ в масиві - нульовий символ ' \0'. Він також називається знаком закінчення рядка (string - terminating character), оскільки вказує компілятору, що рядок закінчився. Такі рядки в стилі З - це окремий випадок символьних масивів, останнім символом яких завжди є нульовий символ ' \ 0 *. Коли ви використовуєте в коді строковий літерал, компілятор сам додає після нього символ ' \ 0'.

Якщо вставити символ ' \ 0' в середину масиву, то це не змінить його розмір; проте обробка рядка, що зберігається в цьому масиві, зупиниться на цій точці. Це демонструє лістинг 4.5.

ПРИМІТКА

Символ ' \0 ' може здатися двома символами, і, насправді, для його введення слід натиснути на клавіатурі дві клавіші. Проте обернена коса риска - це спеціальний код, що управляє, який компілятор розуміє і сприймає \0 як нуль, тобто це спосіб вказати компілятору вставити нульовий символ або нуль.

Ви не можете ввести нульовий символ безпосередньо, оскільки літерал ' 0' буде сприйнятий як символьний нуль з кодом ASCII 48, а не 0.

Щоб побачити цей і інші значення кодів ASCII, звернетеся до таблиці в додатку Д, "Коди ASCII".

ЛІСТИНГ 4.5. Аналіз рядка, що завершується нулем, в стилі C

1:  #include <iostream>
2:  using namespace std;
3:
4:  int main()
5:  {
6:     char SayHello[] = {'H', 'e', *l', 'l', 'o', ' ',
                                    'W', 'o', 'r', 'l', 'd','\0'};;
7:     cout << SayHello << endl;
8:     cout << "Size of array: " << sizeof(SayHello) << endl;
9:
10:    cout << "Replacing space with null" << endl;
11:    SayHello[5] = '\0'; 
12:    cout << SayHello << endl;
13:    cout << "Size of array: " << sizeof(SayHello) << endl; 
14:    return 0;
15: }

Результат

Hello World Size of array: 12 Replacing space with null Hello

Size of array: 12

Аналіз

Код рядка 10 замінює пропуск в рядку Hello World нульовим символом. Тепер у масиву є два нульові завершальні символи, але використовується перший, що і створює ефект. Коли пропуск замінюється нульовим символом, рядок, що відображається, усікається до частини Hello. Метод sizeof () в рядках 7 і 12 вказує, що розмір масиву не змінився, незважаючи на зміну даних, що відображаються.

УВАГА!

Якщо при оголошенні і ініціалізації символьного масиву в рядку 5 лістингу 4.5 ви забуваєте додати символ ' \0', те після частини Hello World виведення міститиме випадковий набір символів. Річ у тому, що оператор std : :cout не зупиниться на виведенні масиву, він продовжуватиме виведення, поки не досягне нульового символу, навіть якщо для цього доведеться перейти межі масиву.

Ця помилка може порушити вашу програму, а в деяких випадках поставити під загрозу стабільність системи.

Рядки в стилі З багаті небезпекою. Лістинг 4.6 демонструє риски, пов'язані з їх застосуванням.

ЛІСТИНГ 4.6. Ризик використання рядків в стилі С і призначеного для користувача введення

1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6:    cout << "Enter a word NOT longer than 20 characters:" << endl;
7:
8:    char Userlnput [21] = {'\0 ' };
9:    cin >> Userlnput;
10:
11:   cout << "Length of your input was: " << strlen (Userlnput)
           << endl;
12:
13:   return 0;
14: }

Результат

Enter a word NOT longer than 20 characters:
Don'tUseThisProgram
Length of your input was: 19

Аналіз

Небезпека видно у виведенні. Програма просить користувача не вводити більше двадцяти символів. Причина в тому, що оголошений в рядку 7 символьний буфер, призначений для зберігання призначеного для користувача введення, має фіксовану статичну довжину в 21 символ. Оскільки останній символ в рядку має бути нульовим ' \0', максимальна довжина тексту, що зберігається буфером, обмежується двадцятьма символами. Зверніть увагу на застосування оператора strlen в рядку 10 для обчислення довжини рядка.  Він перебирає символьний буфер і підраховує кількість символів, поки не досягає нульового, який означає кінець рядка. Цей символ був вставлений в кінець введених користувачем даних оператором cin. Подібна поведінка оператора strlen робить це небезпечним, оскільки він може легко перетнути межі символьного масиву, якщо користувач введе текст довше згаданої межі. Щоб дізнатись як реалізувати перевірку, що гарантує, що запис в масив не вийде за його межі, звернетеся до лістингу 6.2 зайняття 6, "Галуження процесу виконання програм".

Рядки C++  : використання типу std:: string

Стандартний рядок C++   - найефективніший спосіб роботи і з текстовим введенням, і при маніпуляції рядками, наприклад, при їх конкатенації.

УВАГА!

Додатки, написані на мові С (чи на мові C++   програмістами з великим досвідом в мові С), частенько використовують у своєму коді функції копіювання рядків, такі як strcpy, функції конкатенації, такі як street, і визначення довжини рядків, такі як strlen.

Ці функції використовують рядки в стилі З і тому небезпечні, оскільки шукають завершальний нульовий символ і можуть здолати межі символьного масиву, якщо програміст не гарантував наявність завершального нульового символу.

Мова C++   надає потужний і в той же час безпечний засіб маніпулювання рядками - клас std:: string, представлений в лістингу 4.7. Клас std:: string реалізований не на статичному масиві типу char незмінного розміру, як рядки в стилі З, і допускає збільшення розміру, коли в нім необхідно зберегти більше за даних.

ЛІСТИНГ 4.7 Використання типу std:: string для ініціалізації і зберігання призначеного для користувача введення, а також копіювання, конкатенація і визначення довжини рядка

1:  #include <iostream>
2:  #include <stream>
3:  using namespace std;
4:  int main()
5:  {
6:     string Greetings ("Hello std::string!"); 
7:     cout << Greetings << endl;
8:
9:     cout << "Enter a line of text: " << endl; 
10:    string FirstLine; 
11:    getline(cin, FirstLine);
12:
13:    cout << "Enter another: " << endl; 
14:    string SecLine; 
15:    getline(cin, SecLine);
16:  
17:    cout << "Result of concatenation: " << endl; 
18:    string Concat = FirstLine + " " + SecLine; 
19:    cout << Concat << endl;
20:
21:    cout << "Copy of concatenated string: " << endl;
22:    string Copy;
23:    Copy = Concat;
24:
25:    cout << Сору << endl;
26:
27:    cout << "Length of concat string: " << Concat.length() << endl;
28:
29:    return 0;
30: }

Результат

Hello std::string!
Enter a line of text:
I love
Enter another:
C++ strings
Result of concatenation:
I love C++ strings
Copy of concatenated string:
I love C++ strings
Length of concat string: 18

Аналіз

Намагайтеся зрозуміти виведення і зв'язати воно з відповідними елементами в коді. Не турбуйтеся доки про нові синтаксичні засоби. Програма розпочинається з відображення такою, що ініціалізувала в рядку 7 рядків Hello std:: string!. Потім, в рядках 12 і 16, вона просить користувача ввести два рядки тексту, які зберігаються в змінних FirstLine і SecLine. Фактично конкатенація дуже проста і виглядає як арифметична сума в рядку 19, де навіть пропуск був доданий до першого рядка. Дія копіювання - це просте привласнення в рядку 24.  Визначення довжини рядка здійснюється при виклику методу length () в рядку 27.

ПРИМІТКА

Для використання рядків C   в код необхідно включити заголовок string : #include <string>

Це можна помітити в рядку 1 лістингу 4.7.

Щоб детальніше вивчити різні функції класу std : : string, звернетеся до зайняття 16, "Класи рядків бібліотеки STL". Оскільки ви ще не вивчали класи і шаблони, ігноруйте доки відповідні розділи і приділіть увагу суті прикладів.

Резюме

На цьому зайнятті ви познайомилися з основами масивів і способами їх застосування. Ви навчилися оголошувати і ініціалізувати їх елементи, діставати доступ до значень елементів масиву і записувати їх. Ви дізналися, як важливо не виходити за межі масиву. Це називається переповнюванням буфера (buffer overflow), і перевірка введення перед його використанням для індексації елементів дозволяє гарантувати знаходження в межах масиву без їх перетину.

Динамічні масиви дозволяють програмістові не хвилюватися про установку максимальної довжини масиву під час компіляції, а також забезпечують краще управління пам'яттю у разі, якщо розмір масиву менше очікуваного максимуму.
Ви також дізналися, що рядки в стилі З - це окремий випадок символьного масиву, де кінець рядка відзначається нульовим завершальним символом 1 \0 '. Крім того, ви дізналися, що мова C   забезпечує набагато кращу можливість - клас std:: string, - що надає зручні допоміжні функції і дозволяє