Nạp chồng toán tử là một trong những tính năng rất mạnh của bất kỳ ngôn ngữ lập trình nào, do đó Apple đã quyết định cho tính năng này vào trong Swift. Bằng cách sử dụng nó, bạn có thể dễ dàng tạo ra những trường hợp kỳ cục ví dụ như làm cho toán tử trừ (“-“) thành cộng các số, hay là chia (“/”) thì lại thành nhân các số. Tuy nhiên chắc chắn đó không phải là điều bạn thực sự muốn làm khi sử dụng toán tử này đâu.
Trong phần hướng dẫn này, bạn có một bài tập đơn giản như sau: mở rộng chức năng cơ bản của toán tử nhân dùng cho các số sao cho các chuỗi cũng có thể dùng được. Ở đây, bạn sẽ sử dụng toán tử nối chuỗi, bạn có thể hình dung bài này như sau:
"abc" * 5 = "abc" + "abc" + "abc" + "abc" + "abc" = "abcabcabcabcabc"
Trước khi bắt tay vào code, hãy nghĩ xem bạn sẽ giải quyết bài này như thế nào và trình tự sẽ làm là gì. Còn đây là cách tôi sẽ làm:
Về cơ bản thuật toán là như thế, bây giờ đến phần thực hiện
Hãy bật Xcode lên và mở playground. Bạn hãy xóa các code cũ trong đó đi và thêm mẫu hàm toán tử nhân như sau:
func *(lhs: String, rhs: Int) -> String {
}
Hàm này có 2 tham số - toán hạng bên trái kiểu String và toán hạng bên phải kiểu Int – vào kiểu trả về là String.
Có 3 bước bạn sẽ làm trong phần thân hàm. Đầu tiên, tạo biến kết quả và khởi tạo giá trị - đó là đối số kiểu chuỗi của hàm:
var result = lhs
Tiếp theo chạy vòng lặp từ 2 cho tới đối số Int với kiểu vòng lặp “for in” như sau:
for _ in 2...rhs {
}
Chú ý: bạn để biến chạy trong vòng lặp là gạch dưới “_” do bạn không cần sử dụng đến giá trị của nó – tìm hiểu thêm về vòng lặp ở đây.
Trong vòng lặp, bạn chỉ thực hiện đúng một lệnh update kết quả với giá trị chuỗi:
result += lhs
Cuối cùng trả về kết quả:
return result
Bây giờ, chúng ta đã có thể sử dụng toán tử này:
let u = "abc"
let v = u * 5
Thế là xong. Tuy nhiên có một vấn đề khác là chúng ta chỉ có thể sử dụng toán tử này để nhân chuỗi. Còn các kiểu khác thì sao. Hãy cùng giải quyết vấn đề này với các toán tử generic.
Các kiểu generic mặc định là không hoạt động với các toán tử, do đó bạn cần sử dụng protocol. Thêm hàm nguyên mẫu sau vào playground:
protocol Type {
}
Tiếp đến, thêm nguyên mẫu hàm toán tử gán giá trị cộng vào protocol:
func +=(inout lhs: Self, rhs: Self)
Hàm này có cả hai toán hạng trái, phải thuộc kiểu Self, đây là một cách để thể hiện chúng là bất cứ kiểu nào thực hiện protocol. Toán hạng trái được đánh dấu là inout do giá trị của nó được thay đổi và trả lại từ hàm.
Một cách khác mà bạn có thể định nghĩa nguyên mẫu hàm toán tử cộng :
func +(lhs: Self, rhs: Self) -> Self
Hàm này có cả toán hạng bên trái và phải thuộc kiểu Self và trả về kết quả cộng thuộc kiểu Self. Trong trường hợp này, bạn không cần sử dụng tên tham số inout nữa.
Tiếp đến, tạo extensions cho các kiểu String, Int, Double, Float mà thực hiện protocol “Type”
extension String: Type {}
extension Int: Type {}
extension Double: Type {}
extension Float: Type {}
Chú ý, việc thực hiện các extension là rỗng bởi vì chúng ta không muốn thêm giá nào vào các kiểu mặc định này. Chúng đơn giản được tạo ra để phù hợp với protocol.
Bây giờ thêm nguyên mẫu hàm toán tử nhân vào playground:
func *<T: Type>(lhs: T, rhs: Int) -> T {
}
Hàm này có 2 tham số: toán hạng trái thuộc kiểu T và toán hạng phải thuộc kiểu Int, giá trị trả về là kiểu T. Bạn sử dụng giới hạn kiểu để làm kiểu generic “T” phù hợp với protocol “Type”, do đó, nó hiểu được toán tử gán cộng.
Chú ý: Bạn cũng có thể định nghĩa giới hạn kiểu với từ khóa “where” – cách này dài hơn cách trên:
func *<T where T: Type>(lhs: T, rhs: Int) -> T
Việc thực hiện hàm này cũng giống hệt như trường hợp trước:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result
Chú ý: Bạn cũng có thể sử dụng toán tử cộng – nếu thế, hãy đảm bảo thêm nguyên mẫu hàm của nó vào protocol.
Bây giờ hãy thử thực hiện các toán tử generic:
let x = "abc"
let y = x * 5
let a = 2
let b = a * 5
let c = 3.14
let d = c * 5
let e: Float = 4.56
let f = e * 5
Có một vấn đề ở đây: bạn đang sử dụng toán tử nhân chuẩn. Nó có thể gây ra một chút lẫn lộn. Tốt hơn là chúng ra thay đổi nó thành các toán tử khác. Bây giờ hãy xem chúng ta có thể sửa lại điều này bằng các toán tử custom như thế nào.
Hãy thêm dòng sau vào playground để bắt đầu:
infix operator ** {associativity left precedence 150}
Chú giải:
Chú ý: Bạn có thể đọc thêm về độ ưu tiên toán tử và associativity tại đây.
Nguyên mẫu của hàm toán tử custom tương tự như nguyên mẫu hàm toán tử chuẩn – chỉ khác tên:
func **<T: Type>(lhs: T, rhs: Int) -> T {
}
Các bước thực hiện trong hàm cũng giống hệt như lần trước:
var result = lhs
for _ in 2...rhs {
result += lhs
}
return result
Sau đây là sử dụng toán tử custom:
let g = "abc"
let h = g ** 5
let i = 2
let j = i ** 5
let k = 3.14
let l = k ** 5
let m: Float = 4.56
let n = m ** 5
Bây giờ còn một vấn đề nữa – phiên bản toán tử hỗn hợp cho toán tử nhân custom này chưa được định nghĩa, chúng ta sẽ tiếp tục ở phần sau:
Kiểu toán tử hỗn hợp này có độ ưu tiên và associativity giống hệt như trường hợp trước, chỉ khác tên:
infix operator **= {associativity left precedence 150}
Tiếp đến thêm, nguyên mẫu hàm toán tử hỗn hợp vào playground:
func **=<T: Type>(inout lhs: T, rhs: Int) {
}
Hàm này không có kiểu trả về, do nó có toán hạng trái được đánh dấu là: “inout”
Nội dung của hàm này chỉ có một dòng sau: (sử dụng toán tử custom được định nghĩa lúc trước để trả về kết quả nhân)
lhs = lhs ** rhs
Giờ là sử dụng toán tử này như sau:
var o = "abc"
o **= 5
var q = 2
q **= 5
var s = 3.14
s **= 5
var w: Float = 4.56
w **= 5
Thế là xong – rất đơn giản phải không nào.
Nạp chồng toán tử khi được sử dụng với sự cẩn trọng có thể trở thành công cụ rất mạnh – tôi hy vọng bạn có thể tự tìm cách sử dụng chúng cho dự án của riêng mình.
Tham khảo thêm, bạn có thể tải file Playground trên GitHub. Tôi đã thử code này trên Xcode 7.3 và Switft 2.2.
Nguồn: Techmaster
Location: Hà Nội
Salary: 8 Mil - 15 Mil VND