Перейти к содержимому

Практика 7. Практика к лекции 7

Цель: закрепить работу со структурами и классами в Swift — разницу между типами-значениями и ссылочными типами, хранимые и вычисляемые свойства, наблюдатели свойств (willSet/didSet), мутирующие методы, инициализаторы и статические свойства — через серию упражнений от базовых к проектным.

Рекомендации по выполнению:

  • Выполняйте задания в отдельных файлах .swift (например, task1.swift, task2.swift).
  • Запускайте через терминал: swift task1.swift (или swift имя_файла.swift).
  • Для проектов из нескольких файлов используйте Swift Package Manager (swift package init --type executable).
  • Работа ведётся на Linux — Xcode не требуется, достаточно установленного Swift toolchain.
  • Для каждого задания добавляйте проверочные print/assert-выражения, демонстрирующие работу.

A. Разминка

  1. Создайте структуру Temperature с хранимым свойством celsius: Double и вычисляемым свойством fahrenheitget и set). Формула: F = C × 9/5 + 32.
var temp = Temperature(celsius: 100)
print(temp.fahrenheit) // 212.0
temp.fahrenheit = 32
print(temp.celsius) // 0.0
  1. Создайте структуру Vector2D со свойствами x: Double, y: Double. Добавьте:
    • Вычисляемое свойство magnitude (длина вектора: sqrt(x*x + y*y)).
    • Мутирующий метод mutating func scale(by factor: Double) — умножает x и y на factor.
    • Статический метод static func add(_ a: Vector2D, _ b: Vector2D) -> Vector2D.
var v1 = Vector2D(x: 3, y: 4)
print(v1.magnitude) // 5.0
v1.scale(by: 2)
print(v1) // Vector2D(x: 6.0, y: 8.0)
let v2 = Vector2D(x: 1, y: 1)
let v3 = Vector2D.add(v1, v2)
print(v3) // Vector2D(x: 7.0, y: 9.0)

B. Свойства и наблюдатели

  1. Создайте структуру Circle с хранимым свойством radius: Double и двумя вычисляемыми свойствами:
    • area: Double (только для чтения) — площадь круга (Double.pi * radius * radius).
    • diameter: Doubleget и set) — при установке diameter пересчитывается radius.
var c = Circle(radius: 5)
print(c.area) // ≈78.54
print(c.diameter) // 10.0
c.diameter = 6
print(c.radius) // 3.0
  1. Создайте структуру StepCounter со свойством totalSteps: Int с наблюдателем didSet, который выводит количество добавленных шагов и текущее общее количество.
var stepper = StepCounter(totalSteps: 0)
stepper.totalSteps = 100 // Шагов добавлено: 100. Всего: 100.
stepper.totalSteps = 360 // Шагов добавлено: 260. Всего: 360.

Дополнительно: добавьте willSet, который выводит "Значение изменится с X на Y".


C. Типы-значения vs ссылочные типы

  1. Создайте структуру PointStruct и класс PointClass, оба с var x: Int, y: Int. Выполните следующий эксперимент:
var structA = PointStruct(x: 1, y: 2)
var structB = structA
structB.x = 99
print(structA.x) // Что выведет?
print(structB.x) // Что выведет?
let classA = PointClass(x: 1, y: 2)
let classB = classA
classB.x = 99
print(classA.x) // Что выведет?
print(classB.x) // Что выведет?
print(classA === classB) // Что выведет?

Объясните разницу в поведении комментарием в коде. Почему classA.x изменился, хотя мы меняли classB?

  1. Напишите функцию modifyPoint, принимающую PointClass и увеличивающую x на 10. Вызовите её и убедитесь, что оригинальный объект изменился. Затем напишите аналогичную функцию для PointStruct с ключевым словом inout и сравните поведение.
func modifyClass(_ p: PointClass) {
p.x += 10
}
func modifyStruct(_ p: inout PointStruct) {
p.x += 10
}
let obj = PointClass(x: 5, y: 5)
modifyClass(obj)
print(obj.x) // 15
var val = PointStruct(x: 5, y: 5)
modifyStruct(&val)
print(val.x) // 15

D. Методы и инициализаторы

  1. Создайте структуру BankCard со свойствами:

    • let number: String — номер карты.
    • let holder: String — имя владельца.
    • private(set) var balance: Double — баланс (изменяемый только внутри структуры).

    Добавьте методы:

    • mutating func deposit(_ amount: Double) — пополнение (с проверкой amount > 0).
    • mutating func withdraw(_ amount: Double) -> Bool — снятие (возвращает false, если недостаточно средств).
    • Кастомный инициализатор, принимающий number, holder и необязательный initialBalance (по умолчанию 0).
var card = BankCard(number: "4276-XXXX-XXXX-1234", holder: "Иван Петров", initialBalance: 1000)
card.deposit(500)
print(card.balance) // 1500.0
print(card.withdraw(2000)) // false
print(card.withdraw(300)) // true
print(card.balance) // 1200.0
  1. Создайте структуру Counter со:
    • Статическим свойством static var totalCreated: Int = 0 — общее количество созданных экземпляров.
    • Хранимым свойством var count: Int = 0.
    • Методом mutating func increment(by amount: Int = 1).
    • Инициализатором, увеличивающим Counter.totalCreated.
var c1 = Counter()
var c2 = Counter()
c1.increment(by: 5)
c2.increment()
print(c1.count) // 5
print(c2.count) // 1
print(Counter.totalCreated) // 2

E. Связный список (class)

  1. Создайте класс LinkedNode с var value: Int и var next: LinkedNode?. Постройте цепочку из 4–5 узлов и реализуйте:
    • Функцию printList(_ head: LinkedNode?) — выводит значения в формате 1 -> 2 -> 3 -> nil.
    • Функцию sum(_ head: LinkedNode?) -> Int — возвращает сумму всех значений.
    • Функцию count(_ head: LinkedNode?) -> Int — возвращает количество узлов.

Объясните в комментарии, почему для связного списка необходим class, а не struct.

let node3 = LinkedNode(value: 30, next: nil)
let node2 = LinkedNode(value: 20, next: node3)
let node1 = LinkedNode(value: 10, next: node2)
printList(node1) // 10 -> 20 -> 30 -> nil
print(sum(node1)) // 60
print(count(node1)) // 3

F. Мини-проект

  1. «Библиотека геометрических фигур» — реализуйте систему работы с фигурами, используя структуры:
struct Point {
var x: Double
var y: Double
}
struct Size {
var width: Double
var height: Double
}
struct Rectangle {
var origin: Point
var size: Size
var area: Double { ... } // вычисляемое свойство
var perimeter: Double { ... } // вычисляемое свойство
var center: Point { ... } // вычисляемое свойство (get и set)
func contains(_ point: Point) -> Bool { ... }
mutating func translate(dx: Double, dy: Double) { ... }
}

Реализуйте:

  • Все вычисляемые свойства (area, perimeter, center).
  • Установку center должна пересчитывать origin.
  • Метод contains — проверяет, лежит ли точка внутри прямоугольника.
  • Метод translate — сдвигает прямоугольник на (dx, dy).

Создайте массив из нескольких Rectangle и используя функциональные методы:

  • Найдите прямоугольник с наибольшей площадью.
  • Отфильтруйте прямоугольники, содержащие точку (5, 5).
var rect = Rectangle(origin: Point(x: 0, y: 0), size: Size(width: 10, height: 5))
print(rect.area) // 50.0
print(rect.center) // Point(x: 5.0, y: 2.5)
rect.center = Point(x: 0, y: 0)
print(rect.origin) // Point(x: -5.0, y: -2.5)
print(rect.contains(Point(x: -3, y: -1))) // true

Критерии оценивания

  • Корректность и полнота реализации: 0–5 баллов
  • Правильное использование struct и class, понимание value/reference семантики: 0–5 баллов
  • Качество и читаемость кода (именование, вычисляемые свойства, mutating, private(set)): 0–3 балла
  • Наличие проверочных print/assert выражений: 0–3 балла

Максимум: 16 баллов. Бонус до +2 за дополнительные задания.


Дополнительно (по желанию)

  • Добавьте к Vector2D реализацию протокола CustomStringConvertible для красивого вывода (аналог __repr__ в Python).
  • Реализуйте структуру Stack<Element> с методами push, pop, peek и вычисляемым свойством isEmpty.
  • Добавьте в мини-проект структуру Circle с аналогичным интерфейсом и функцию, которая определяет, пересекаются ли два прямоугольника.