Чем отличаются структуры и классы

В языках программирования, таких как Swift, C++, C#, а также частично в Go, различие между структурами (struct) и классами (class) заключается не только в синтаксисе, но и в поведении, семантике копирования, управлении памятью, наследовании и принципах передачи по значению или по ссылке.

Ниже представлено подробное сравнение между структурами и классами на примере Swift и других языков, где применимы схожие принципы.

📦 Основные различия: значение против ссылки

Характеристика Структура (struct) Класс (class)
Тип передачи По значению По ссылке
--- --- ---
Наследование ❌ Не поддерживается ✅ Поддерживается
--- --- ---
Управление памятью Автоматически (stack/COW) Через reference counting (ARC)
--- --- ---
Может быть изменён в методе Только с mutating Методы могут свободно изменять
--- --- ---
Поддержка ARC
--- --- ---
Identity (сравнение ===) ❌ Нет идентичности ✅ Имеет идентичность
--- --- ---
Поддержка деинициализации ❌ Нет deinit ✅ Есть deinit
--- --- ---

📌 Передача по значению и по ссылке

Структуры: копируются

struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 1, y: 2)
var p2 = p1 // копия p1
p2.x = 10
// p1.x всё ещё равен 1

Классы: передаются по ссылке

class Point {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 1, y: 2)
var p2 = p1 // p2 указывает на тот же объект
p2.x = 10
// p1.x тоже станет 10, потому что они ссылаются на один объект

🧠 Поведение в памяти

Структуры

  • Обычно размещаются в стеке, но при использовании COW (Copy-on-Write) могут быть размещены и в куче.

  • Уничтожаются автоматически по завершению области видимости.

  • Нет необходимости в управлении памятью вручную или через ARC.

Классы

  • Объекты всегда размещаются в куче.

  • Управление памятью осуществляется через ARC (Automatic Reference Counting) в Swift или GC (Garbage Collector) в C# и Java.

  • Использование ссылок требует отслеживания retain count, а значит — риск retain cycle.

🧬 Наследование и полиморфизм

Структуры

  • Не поддерживают наследование.

  • Можно использовать протоколы (интерфейсы) для достижения полиморфизма.

protocol Drawable {
func draw()
}
struct Circle: Drawable {
func draw() { print("Draw circle") }
}

Классы

  • Поддерживают полное наследование: суперклассы, переопределение методов, доступ к super.

  • Позволяют использовать динамическое связывание (dynamic dispatch).

class Shape {
func draw() { print("Shape") }
}
class Circle: Shape {
override func draw() { print("Circle") }
}

⚙️ Изменяемость (mutability)

Структуры

  • Immutable по умолчанию, если переменная объявлена как let, даже внутренние свойства не изменяются.

  • Методы, изменяющие свойства, должны быть помечены как mutating.

struct Counter {
var count = 0
mutating func increment() {
count += 1
}
}

Классы

  • Экземпляры изменяемы, даже если они объявлены через let, если свойства объявлены как var.
class Counter {
var count = 0
func increment() {
count += 1
}
}

🔁 Identity vs Equality

  • Структуры не имеют идентичности — сравниваются по значению.

  • Классы обладают идентичностью — можно проверить, указывает ли ссылка на тот же объект через оператор ===.

let a = SomeClass()
let b = a
print(a === b) // true

🧹 Жизненный цикл: init и deinit

Структуры

  • Могут иметь init, в том числе с дефолтными значениями.

  • Не поддерживают deinit.

Классы

  • Поддерживают деструкторы (deinit), позволяя выполнять действия при освобождении памяти (например, закрытие соединений, удаление наблюдателей и т.п.).

🧰 Применение: когда что использовать

Когда использовать структуры:

  • Данные не должны иметь идентичность.

  • Копирование безопасно и желательно.

  • Структура не будет изменяться многими объектами одновременно.

  • Лёгкие объекты, не обременённые ссылками (например, координаты, векторы, настройки и т.д.).

  • Лучше работает в многопоточном окружении за счёт изоляции данных.

Примеры: CGPoint, CGSize, CGRect, Date, Range, URL.

Когда использовать классы:

  • Объект имеет идентичность, жизненный цикл должен отслеживаться.

  • Объект будет передаваться между многими компонентами по ссылке.

  • Требуется наследование.

  • Нужна слабая ссылка (weak) — возможно только с классами.

  • Требуется деструктор deinit.

Примеры: UIView, UIViewController, URLSession, NSOperation.

🌐 В других языках

C++

  • struct и class почти идентичны по возможностям.

  • Основное отличие — struct имеет public члены по умолчанию, class — private.

struct Point {
int x, y;
}; // x и y  public по умолчанию
class Point {
int x, y; // private по умолчанию
};

C#

  • Имеется чёткое разделение:

    • struct — value type, размещается в стеке;

    • class — reference type, размещается в куче.

  • struct в C# не поддерживает наследование (кроме интерфейсов).

  • C# struct не может иметь деструкторы и инициализаторы по умолчанию.

Go

  • В Go есть только struct, но всё передаётся по значению по умолчанию.

  • Наследование нет, только композиция.

  • Методы можно определять как для значения, так и для указателя (*Type), что приближает поведение к классам.

💡 Расширенные возможности в Swift

  • Struct + Protocols + Generics → альтернатива классической ООП-модели.

  • Copy-on-Write: многие структуры (например, Array, Dictionary, Set, String) используют COW, чтобы совмещать эффективность с безопасностью копирования.

📎 Итоги различий

Категория Struct Class
Тип Value Type Reference Type
--- --- ---
Место хранения Stack (или Heap при COW) Heap
--- --- ---
Передача Копирование Ссылка
--- --- ---
Наследование Нет Да
--- --- ---
ARC Не участвует Участвует
--- --- ---
Mutating Требует mutating Нет требований
--- --- ---
deinit Нет Да
--- --- ---
Сравнение По значению По ссылке
--- --- ---
Поддержка weak/unowned Нет Да
--- --- ---