1. Що таке Array і які в нього правила
Якщо ви тільки починаєте, здається, що змінних достатньо: ну, зроблю book1, book2, book3 — і готовий «список книг». Але це швидко перетворюється на головний біль: як додати четверту книгу, як перебрати всі, як знайти потрібний елемент? Масив розв’язує цю проблему: він зберігає багато значень одного типу, у порядку додавання, і дає змогу писати універсальний код.
Уявіть, що ви ведете невеликий консольний застосунок «Список читання». Учора ви могли зберігати одну книгу в String. Сьогодні ви хочете зберігати список назв книг. Ось тут Array і виходить на сцену — як полиця, на яку можна поставити не одну, а багато книг.
Коли ви чуєте слово «масив», не плутайтеся в термінах. Досить запам’ятати дві прості речі.
- Перша: масив зберігає елементи в певному порядку.
- Друга: усі елементи масиву мають один і той самий тип. Ніяких «тут Int, тут String, а тут ще котик» — котик зʼявиться пізніше, коли ви опануєте інші інструменти (і, сподіваюся, не в бойовому середовищі).
Отже, масив можна описати так: «упорядкована колекція значень одного типу». Порядок важливий, бо «перший», «другий», «третій» — це реальна характеристика масиву. Згодом ви зможете отримувати елементи за позицією, тобто за індексом, перебирати їх у циклі та змінювати.
Індекс у масиві
Мало знати, що у вас є масив, — треба ще вміти працювати з його елементами. Щоб отримати елемент із масиву, потрібно знати його позицію. Ця позиція називається індексом.
Індекси у Swift (як і в багатьох мовах програмування) починаються з нуля, а не з одиниці. Спочатку це може виглядати незвично, але швидко стає природним.
Уявіть масив книг:
let books = ["Dune", "Foundation", "The Hobbit"] // три рядки, упаковані у вигляді масиву
Якщо пронумерувати елементи, вийде так:
| Індекс | Елемент |
|---|---|
|
|
|
|
|
|
Тобто:
- перший елемент має індекс 0
- другий — 1
- третій — 2
Кількість елементів можна дізнатися через count. А останній індекс завжди дорівнює count - 1.
print(books.count) // 3
Схема індексів
flowchart LR
A["0 — Dune"] --> B["1 — Foundation"]
B --> C["2 — The Hobbit"]
Головне правило: індекс завжди має бути в діапазоні від 0 до count - 1. Якщо спробувати звернутися до неіснуючого індексу, програма завершиться з помилкою.
Наприклад, у масиву з 3 елементів індекс 3 вже не існує.
var books = ["Dune", "Foundation", "The Hobbit"]
books[1] = "The Hitchhiker's Guide to the Galaxy"
print(books[0]) // Dune
print(books[1]) // The Hitchhiker's Guide to the Galaxy
print(books[2]) // The Hobbit
Саме тому порядок у масиві такий важливий: кожен елемент має свою позицію, і за цією позицією його можна отримати або змінити.
2. Створюємо масиви: основні способи
Літерал масиву
Найзручніший спосіб створити масив — написати його прямо в коді в квадратних дужках. Це називається літерал масиву.
let scores = [10, 20, 30]
print(scores) // [10, 20, 30]
Тут scores — масив цілих чисел. Зверніть увагу: ми не писали тип явно. Swift побачив елементи 10, 20, 30 і дійшов висновку: «ага, це Int, отже масив [Int]».
Приклад із рядками:
let authors = ["Le Guin", "Tolkien", "Asimov"]
print(authors) // ["Le Guin", "Tolkien", "Asimov"]
І знову тип виводиться автоматично: елементи — рядки, отже масив [String].
Виведення типів для масивів
Виведення типу масиву працює просто: Swift дивиться на елементи в квадратних дужках і намагається зрозуміти, якого вони типу. Якщо всі елементи одного типу — чудово, тип масиву визначено.
Складність починається, коли елементи здаються схожими, але насправді мають різні типи. Наприклад, 1 — це Int, а 1.5 — уже Double. В одному масиві тримати їх разом не можна, бо масив має містити елементи одного типу.
Рішення: зробити масив Double, якщо перетворити цілі числа на Double.
let measurements: [Double] = [1.0, 1.5, Double(2)]
print(measurements) // [1.0, 1.5, 2.0]
Тут ми явно підказали тип масиву ([Double]), і Swift погодився. Якби ми написали [1, 1.5, 2], компілятор би зупинив нас і сказав: «визначтеся, що ви хочете зберігати». І це добре: компілятор не дає зробити програму «майже робочою», але з сюрпризом під час виконання.
Невелика схема того, як зазвичай працює виведення типу:
flowchart TD
A["Пишемо літерал масиву: [ ... ]"] --> B["Swift дивиться на елементи"]
B --> C{"Усі елементи одного типу?"}
C -->|Так| D["Тип масиву виводиться автоматично"]
C -->|Ні| E["Потрібна підказка: явний тип або перетворення"]
Порожній масив і чому [] недостатньо
Найчастіший сюрприз для новачків — ви хочете почати зі списку, у якому поки що нічого немає. Логіка проста: пишемо []. Але Swift не телепат (принаймні, офіційно), і йому потрібно розуміти, які елементи будуть у масиві.
Так робити не можна (не компілюється):
- let items = [] — компілятор не знає, що це за елементи.
Правильний спосіб — явно вказати тип. Є кілька рівнозначних варіантів.
Варіант 1: вказати тип ліворуч:
let books: [String] = []
print(books) // []
Варіант 2: використати конструктор без аргументів для типу масиву:
let ids = [Int]()
print(ids) // []
Варіант 3: через форму Array<T>():
let flags = Array<Bool>()
print(flags) // []
Усі три варіанти створюють порожній масив. Відрізняється лише те, як саме ви пояснюєте Swift, що зберігатиметься всередині.
Два записи типу масиву: [Int] і Array<Int>
У Swift є дві рівноправні форми запису типу масиву. Коротка й найпопулярніша — [Int], [String], [Bool]. Довша й більш загальна — Array<Int>, Array<String>. Це один і той самий тип, просто різний запис.
Корисно розуміти обидві форми, бо в документації та в сигнатурах ви часто зустрінете Array<Element>. Загалом Array — це узагальнений (generic) тип, і запис Array<String> якраз показує: «усередині лежить String».
Приклади, які компілюються однаково:
let a: [Int] = [1, 2, 3]
let b: Array<Int> = [1, 2, 3]
print(a) // [1, 2, 3]
print(b) // [1, 2, 3]
Іноді довга форма допомагає не губитися в коді, коли тип стає складнішим. Але на базовому рівні найчастіше достатньо короткої форми [T].
let vs var під час створення масиву
З масивами працює те саме правило, що і з будь-якими значеннями в Swift.
Якщо ви оголосили масив через let, отримуєте константу: не можна змінювати ані сам масив, ані його склад, ані порядок. Якщо через var — масив можна змінювати.
Сьогодні ми ще не користуємося методами append, insert і remove (це буде трохи пізніше), тому різницю відчуємо через просту річ — переприсвоєння.
var tags = ["swift", "basics"]
print(tags) // ["swift", "basics"]
tags = ["swift", "basics", "arrays"]
print(tags) // ["swift", "basics", "arrays"]
А тепер константа:
let roles = ["admin", "editor"]
print(roles) // ["admin", "editor"]
roles можна друкувати, передавати у функції, читати, але не можна змінювати.
Інтуїтивна аналогія: let — це «список, який ви роздрукували та заламінували». Читати можна, дописати маркером уже не вийде. var — це «список у нотатках», який можна редагувати.
Мініпроєкт: заготовка ReadingList
Щоб приклади не виглядали набором випадкових чисел, почнемо мініпроєкт цього дня — консольну заготовку «ReadingList». Поки що вона максимально проста: зберігає список книг як масив рядків і друкує його на екран.
import Foundation
let readingList = [
"The Left Hand of Darkness",
"The Hobbit",
"Foundation"
]
print("Мій список читання:") // Мій список читання:
print(readingList) // ["The Left Hand of Darkness", "The Hobbit", "Foundation"]
Тут import Foundation не обов’язковий для масивів, але в консольних проєктах його часто залишають зі звички: далі зазвичай ідуть рядки, введення, форматування та інші зручності. Якщо хочете, можете прибрати — приклад усе одно скомпілюється.
Масиви різних типів: [Int], [String], [Bool]
Коли ви тільки починаєте, може здаватися, що тип елементів — це «формальність». Але для Swift це основа. Тип визначає, які операції доступні і що взагалі вважається коректним.
Наприклад, масив чисел можна використовувати для статистики, а масив рядків — для виведення повідомлень і обробки тексту. Swift не дасть вам випадково змішати рядок і число в одному масиві без явного перетворення — і це економить години життя, зазвичай ті, які ви витратили б на пошук дурного бага о другій ночі.
let attempts: [Int] = [1, 2, 3, 4]
let names: [String] = ["Ana", "Ben"]
let isCompleted: [Bool] = [true, false, false]
print(attempts) // [1, 2, 3, 4]
print(names) // ["Ana", "Ben"]
print(isCompleted) // [true, false, false]
Масив масивів
Часте питання: «Чи можна зробити масив, де елементи — теж масиви?» Технічно так: тип матиме вигляд [[Int]] або [[String]].
Поки що без індексів і без «матриць» — просто один рядок, щоб сама ідея не виглядала магією:
let shelves: [[String]] = [["A", "B"], ["C"]]
print(shelves) // [["A", "B"], ["C"]]
Запам’ятайте головне: якщо елемент масиву — це інший масив, тип стає «подвійними дужками».
Виведення масиву через print() як інструмент налагодження
На старті програмування важливо мати «ліхтарик», яким ви підсвічуєте те, що справді відбувається в програмі. print() — це ваш ліхтарик. І з масивами він працює зручно: Swift друкує масив у читабельному вигляді, з квадратними дужками та елементами через кому.
Подивімося, як це допомагає бачити порядок:
let queue = ["first", "second", "third"]
print(queue) // ["first", "second", "third"]
Навіть без індексів уже видно: порядок збережено. Це знадобиться пізніше, коли ви почнете додавати та видаляти елементи, і print(queue) швидко покаже, як змінився вміст.
4. Ще кілька корисних нюансів
Іноді масив створюється не лише літералом. Але поки тримаємося простих і читабельних варіантів, без переходу до нових тем.
Створити порожній масив і одразу надрукувати:
var notes: [String] = []
print(notes) // []
Створити масив з одного елемента (так, це нормально):
let single = ["only one"]
print(single) // ["only one"]
Створити масив і дати йому змістовну назву — це не частина синтаксису, але важлива звичка:
let errorMessages = ["Invalid input", "Try again"]
print(errorMessages) // ["Invalid input", "Try again"]
Таблиця-шпаргалка зі створення масивів
Іноді корисно побачити все в одному місці, особливо якщо ви тільки звикаєте до синтаксису. Нижче — невелика шпаргалка саме зі створення, без індексів і мутацій.
| Що хочемо зробити | Як пишемо | Коментар |
|---|---|---|
| Створити масив чисел | |
Тип [Int] буде виведено автоматично |
| Створити масив рядків | |
Тип [String] буде виведено автоматично |
| Створити порожній масив рядків | |
Без типу [] незрозуміло, що це за масив |
| Створити порожній масив Int | |
Зручно, якщо додаватимете елементи пізніше |
| Вказати тип через Array<T> | |
Еквівалент [Int], просто інший запис |
5. Типові помилки під час створення масивів
Помилка №1: писати let a = [] і чекати, що Swift здогадається про тип.
[] — це «нічого», і з цього неможливо зрозуміти, що ви планували зберігати: рядки, числа чи, можливо, назви котиків. Виправлення просте: або вкажіть тип ліворуч (let a: [Int] = []), або створіть через конструктор (let a = [Int]()).
Помилка №2: намагатися змішувати в одному масиві елементи різних типів.
Дуже частий випадок — «хочу [1, 2, 2.5]». Для людини це «числа», а для Swift — Int і Double, тобто різні типи. Правильний шлях: визначити потрібний тип (часто Double) і явно перетворити елементи: [1.0, 2.0, 2.5] або Double(1).
Помилка №3: плутати масив і кортеж через дужки.
[1, 2, 3] — масив, (1, 2, 3) — кортеж. У масиву елементи одного типу, і їх можна зберігати скільки завгодно. У кортежу фіксована кількість елементів, і типи можуть бути різними. Якщо ви випадково написали круглі дужки, код або не скомпілюється там, де ви очікували масив, або поводитиметься не так, як задумано.
Помилка №4: оголосити масив через let, а потім очікувати, що він розширюватиметься.
Навіть якщо вам здається логічним, що «назва списку не змінюється, а елементи додаватимуться», це все одно означає мутацію масиву. Для змін складу потрібен var. Ми докладно змінюватимемо масиви в наступних лекціях, але звичку думати про let/var заздалегідь варто формувати вже зараз.
Помилка №5: давати масиву неінформативну назву й потім плутатися, що в ньому.
let a = ["Tom", "Ben"] компілюється, але за тиждень ви забудете, що це: автори, користувачі, теги? Вдалі назви (authors, usernames, tags) допомагають читати код як текст — і це особливо важливо, коли масиви почнуть мандрувати функціями та умовами.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ