Operator Overloading
1. Vì sao cần Operator Overloading?
Phần tiêu đề “1. Vì sao cần 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 applied2. Overload toán tử số học
Phần tiêu đề “2. Overload toán tử số học”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)3. Compound Assignment (+=, -=)
Phần tiêu đề “3. Compound Assignment (+=, -=)”+= 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)4. Unary Operators (-, !)
Phần tiêu đề “4. Unary Operators (-, !)”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)5. So sánh - Equatable
Phần tiêu đề “5. So sánh - Equatable”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 compilerKhi 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) // true6. Sắp xếp - Comparable
Phần tiêu đề “6. Sắp xếp - Comparable”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ĩaprint(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ơnprecedencegroup ExponentiationPrecedence { higherThan: MultiplicationPrecedence associativity: right}
infix operator **: ExponentiationPrecedence
func ** (base: Double, exponent: Double) -> Double { pow(base, exponent)}
print(2.0 ** 10.0) // 1024.08. Subscript kết hợp Operator Overloading
Phần tiêu đề “8. Subscript kết hợp Operator Overloading”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 }}🛠️ Thực hành
Phần tiêu đề “🛠️ Thực hành”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 amountLờ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 + <⚠️ Lưu ý quan trọng
Phần tiêu đề “⚠️ Lưu ý quan trọng”- Ư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.
📝 Tóm tắt
Phần tiêu đề “📝 Tóm tắt”- Overload toán tử bằng
static func <toán tử>(...)bên trong type Equatable/Comparablecho phép Swift tự sinh==/sorted()khi có thểprefix/postfixcho 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