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.

Thử thách

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:

  • Tạo biến kết quả, khởi tạo giá trị đầu tiên – chuỗi mặc định
  • Chạy vòng từ 2 cho tới số lần nối chuỗi và mỗi vòng lặp chỉ thực hiện một lệnh gán chuỗi và kết quả.
  • In kết quả

Về cơ bản thuật toán là như thế, bây giờ đến phần thực hiện

Nạp chồng toán tử cơ bả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.

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.

Các toán tử Custom

Hãy thêm dòng sau vào playground để bắt đầu:

infix operator ** {associativity left precedence 150}

Chú giải:

  1. Tên toán tử nhân custom: **
  2. Kiểu của nó là infix do do nó là toán tử nhị phân với 2 toán hạng
  3. Nó đánh giá từ trái sang phải, do đó có left associativity
  4. Độ ưu tiên của nó là 150 – giống như toán tử nhân chuẩn

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:

Toán tử hỗn hợp

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.

Kết luận

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 bài viết.

Bạn nghĩ thế nào về hướng dẫn này và nạp chồng toán tử? Hãy để lại comment phía dưới nhé!

Khóa học lập trình di động tại Techmaster:

Để cài đặt MacOSX lên phần cứng không phải Apple liên hệ chuyên gia cài Hackintosh:

  • Nguyễn Minh Sơn: 01287065634
  • Huỳnh Minh Sơn: 0936225565
  • Website: caidatmacos.com
Tham gia ngay khoá học lập trình iOS, hình thức học tập rất linh hoạt cho bạn lựa chọn và sẽ có mức học phí khác nhau tuỳ theo bạn chọn học Online, Offline hoặc FlipLearning(Kết hợp giữa Online và Offline). Ngoài ra bạn có thể tham gia thực tập toàn thời gian tại Techmaster để rút ngắn thời gian học và tăng cơ hội việc làm.