Bỏ qua để đến nội dung

Operator Overloading

Mặc định, toán tử +, -, ==… chỉ hiểu kiểu có sẵn (Int, Double…). Với kiểu tự định nghĩa (struct, class), ta cần định nghĩa lại (overload) các toán tử để dùng chúng một cách tự nhiên.

struct Vector2D {
var x: Double
var y: Double
}
let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
// v1 + v2 // ❌ Lỗi: Binary operator '+' cannot be applied
struct Vector2D {
var x: Double
var y: Double
static func + (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
static func - (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
Vector2D(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
static func * (lhs: Vector2D, scalar: Double) -> Vector2D {
Vector2D(x: lhs.x * scalar, y: lhs.y * scalar)
}
}
let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
let sum = v1 + v2 // Vector2D(x: 4, y: 6)
let diff = v1 - v2 // Vector2D(x: -2, y: -2)
let scaled = v1 * 2.0 // Vector2D(x: 2, y: 4)

+= không tự sinh ra từ + — phải overload riêng, thường dùng mutating:

extension Vector2D {
static func += (lhs: inout Vector2D, rhs: Vector2D) {
lhs = lhs + rhs
}
}
var v = Vector2D(x: 1, y: 1)
v += Vector2D(x: 2, y: 2) // Vector2D(x: 3, y: 3)
extension Vector2D {
// Prefix: -v
static prefix func - (vector: Vector2D) -> Vector2D {
Vector2D(x: -vector.x, y: -vector.y)
}
}
let v = Vector2D(x: 3, y: 4)
let negated = -v // Vector2D(x: -3, y: -4)

Thay vì tự viết ==, đa số trường hợp chỉ cần khai báo conform Equatable và để Swift tự sinh (synthesize) khi tất cả thuộc tính đều Equatable:

struct Point: Equatable {
var x: Int
var y: Int
}
let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 1, y: 2)
print(p1 == p2) // true - tự động sinh bởi compiler

Khi cần logic so sánh tùy chỉnh, tự viết ==:

struct CaseInsensitiveText: Equatable {
var value: String
static func == (lhs: CaseInsensitiveText, rhs: CaseInsensitiveText) -> Bool {
lhs.value.lowercased() == rhs.value.lowercased()
}
}
let a = CaseInsensitiveText(value: "Swift")
let b = CaseInsensitiveText(value: "swift")
print(a == b) // true
struct Product: Comparable {
var name: String
var price: Double
static func < (lhs: Product, rhs: Product) -> Bool {
lhs.price < rhs.price
}
}
let products = [
Product(name: "Bàn phím", price: 500_000),
Product(name: "Chuột", price: 200_000),
Product(name: "Màn hình", price: 3_000_000)
]
let sorted = products.sorted() // Dùng < đã định nghĩa
print(sorted.map { $0.name }) // ["Chuột", "Bàn phím", "Màn hình"]

7. Custom Operators (định nghĩa toán tử mới)

Phần tiêu đề “7. Custom Operators (định nghĩa toán tử mới)”

Swift cho phép tạo toán tử hoàn toàn mới, phải khai báo precedencegroup/infix operator trước:

infix operator : MultiplicationPrecedence // Dot product
struct Vector2D {
var x: Double
var y: Double
static func (lhs: Vector2D, rhs: Vector2D) -> Double {
lhs.x * rhs.x + lhs.y * rhs.y
}
}
let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
print(v1 v2) // 11.0 (1*3 + 2*4)
// Custom precedence group cho toán tử tự định nghĩa phức tạp hơn
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedence
func ** (base: Double, exponent: Double) -> Double {
pow(base, exponent)
}
print(2.0 ** 10.0) // 1024.0
struct Matrix {
var rows: Int
var columns: Int
var data: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
data = Array(repeating: 0.0, count: rows * columns)
}
subscript(row: Int, column: Int) -> Double {
get { data[row * columns + column] }
set { data[row * columns + column] = newValue }
}
static func + (lhs: Matrix, rhs: Matrix) -> Matrix {
var result = Matrix(rows: lhs.rows, columns: lhs.columns)
for i in 0..<lhs.data.count {
result.data[i] = lhs.data[i] + rhs.data[i]
}
return result
}
}

Bài tập: Kiểu Money với overload +, -, so sánh và custom operator

// TODO: struct Money { let amount: Int; let currency: String }
// TODO: Overload + và - (cùng currency mới được cộng/trừ)
// TODO: Conform Comparable để so sánh theo amount

Lời giải:

struct Money: Equatable, Comparable {
let amount: Int
let currency: String
static func + (lhs: Money, rhs: Money) -> Money {
precondition(lhs.currency == rhs.currency, "Không thể cộng 2 loại tiền tệ khác nhau")
return Money(amount: lhs.amount + rhs.amount, currency: lhs.currency)
}
static func - (lhs: Money, rhs: Money) -> Money {
precondition(lhs.currency == rhs.currency, "Không thể trừ 2 loại tiền tệ khác nhau")
return Money(amount: lhs.amount - rhs.amount, currency: lhs.currency)
}
static func < (lhs: Money, rhs: Money) -> Bool {
lhs.amount < rhs.amount
}
}
let a = Money(amount: 100_000, currency: "VND")
let b = Money(amount: 50_000, currency: "VND")
print(a + b) // Money(amount: 150000, currency: "VND")
print(a - b) // Money(amount: 50000, currency: "VND")
print(a > b) // true - tự động có nhờ Comparable + <
  • Ưu tiên conform Equatable/Comparable/Hashable để Swift tự động sinh implementation thay vì tự viết ==/< khi logic đơn giản (so sánh toàn bộ thuộc tính).
  • Chỉ tự định nghĩa custom operator (, **…) khi thực sự làm code rõ ràng hơn (toán học, vector, ma trận) — lạm dụng sẽ khiến code khó đọc với người khác.
  • static prefix func -, static postfix func, static func += là các biến thể thường gặp cần nhớ đúng cú pháp.

  • Overload toán tử bằng static func <toán tử>(...) bên trong type
  • Equatable/Comparable cho phép Swift tự sinh ==/sorted() khi có thể
  • prefix/postfix cho toán tử một ngôi (-x, x!)
  • infix operator + precedencegroup để tạo toán tử hoàn toàn mới

Tiếp theo: Property Wrappers