Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Thành thạo các toán tử Ruby: Hướng dẫn hoàn chỉnh cho nhà phát triển

Ruby có rất nhiều toán tử thú vị.

Ví dụ:

  • Toán tử spaceship (<=>)
  • Toán tử gán modulo (%=)
  • Toán tử triple equals (===)
  • Lớn hơn (>) & nhỏ hơn (<)
  • Khác nhau (!=)

Điều bạn có thể chưa nhận ra là nhiều toán tử trong số này thực chất là các phương thức Ruby.

Điều này có nghĩa là…

Bạn có thể ghi đè hành vi của chúng & sử dụng chúng để định nghĩa hành vi tùy chỉnh trong các lớp của riêng bạn.

Ví dụ, bằng cách định nghĩa == bạn có thể nói cho Ruby biết cách so sánh hai đối tượng cùng lớp.

Bây giờ:

Hãy cùng xem qua một vài ví dụ để bạn có cái nhìn tổng quan vững chắc về cách các toán tử Ruby hoạt động & cách sử dụng chúng trong mã của bạn.

Đây là chủ đề quan trọng nếu bạn thực sự muốn hiểu Ruby.

Mục lục

  • 1 Toán tử Logic trong Ruby
  • 2 Toán tử Toán học trong Ruby
  • 3 Toán tử Gán (==, +=, ||=)
  • 4 Toán tử Unary là gì?
  • 5 Toán tử Splat trong Ruby (Có ví dụ)
  • 6 Toán tử Khớp (=~)
  • 7 Toán tử Ternary trong Ruby (Toán tử dấu hỏi)
  • 8 Toán tử Shovel / Push (<<)
  • 9 Toán tử Triple Equals (Hơn là sự bằng nhau)
  • 10 Toán tử Safe Navigator (&.)
  • 11 Bảng Độ ưu tiên Toán tử
  • 12 Tóm tắt

Toán tử Logic trong Ruby

Trước tiên, chúng ta sẽ xem xét các toán tử logic.

Bạn có thể sử dụng các toán tử này để so sánh hai đối tượng & đưa ra quyết định dựa trên kết quả.

Dưới đây là bảng tóm tắt:

Toán tử Mô tả
< Nhỏ hơn
> Lớn hơn
>= Lớn hơn hoặc bằng
<= Nhỏ hơn hoặc bằng
== Bằng nhau
!= Khác nhau
<=> Lớn hơn, Bằng, Hoặc Nhỏ hơn

Tất cả các toán tử này đều là phương thức, và chúng trả về giá trị boolean, ngoại trừ toán tử spaceship. Toán tử spaceship trả về 1 (lớn hơn), 0 (bằng nhau) hoặc -1 (nhỏ hơn).

Dưới đây là ví dụ sử dụng toán tử >:

if orange.stock > 20 # ... end

Nếu bạn muốn sử dụng toán tử double equals (==) với các lớp của riêng bạn, bạn có thể thấy nó không hoạt động ngay từ đầu…

Ví dụ:

class Fruit
  def initialize(name)
    @name = name
  end
end

orange1 = Fruit.new("orange")
orange2 = Fruit.new("orange")
orange1 == orange2 # false

Lý do là vì triển khai mặc định của == là BasicObject#==, sử dụng phương thức object_id để xác định xem hai đối tượng có phải là cùng một đối tượng hay không.

Bạn có thể khắc phục như sau:

class Fruit
  attr_reader :name
  def initialize(name)
    @name = name
  end
  def ==(other)
    name == other.name
  end
end

Ở đây chúng ta đang định nghĩa ý nghĩa của hai quả trái cây giống nhau:

Chúng phải có cùng tên.

Toán tử Toán học trong Ruby

Tiếp theo là các toán tử toán học.

Không có gì mới…

5 + 5  # 10
10 * 2 # 20
10 ** 2 # 100

Nhưng giống như toán tử ==, đây cũng là các phương thức.

Điều này hữu ích vì bạn có thể định nghĩa ý nghĩa của việc cộng hai đối tượng lại với nhau.

Ví dụ, nếu bạn có hai đối tượng Order, cộng chúng lại có thể cho bạn tổng số tiền cần thanh toán, hoặc bạn nhận được một đơn hàng mới là sự kết hợp của hai đơn hàng đó.

Bạn có thể định nghĩa chính xác cách thức hoạt động bằng cách định nghĩa phương thức +.

Một toán tử khác mà bạn có thể chưa quen là toán tử modulo.

Nó trông như dấu phần trăm (%).

Và nó cho bạn phần dư của một phép chia.

Ví dụ:

10 % 2 # 0

Toán tử modulo có nhiều ứng dụng thực tế, như xác định số chẵn/lẻ, kiểm tra tính chia hết, giới hạn giá trị số, v.v.

Toán tử Gán (==, +=, ||=)

Tiếp theo là toán tử gán, và khác với tất cả các toán tử đã xem cho đến nay, chúng không phải là phương thức.

Bạn có toán tử gán cơ bản:

a = 1

Nhưng bạn cũng có các toán tử gán kết hợp:

a += 5 # 6
a *= 2 # 12

Chúng tương đương với việc đọc giá trị hiện tại & sử dụng một trong các toán tử toán học với nó, sau đó lưu kết quả. Bạn có thể làm điều này với tất cả các toán tử toán học, bao gồm cả modulo (%).

Nhưng có hai toán tử gán hoạt động khác biệt!

Đó là ||= và &&=.

Chúng khác biệt vì chúng không tương đương với các phiên bản toán học.

Cái mà a ||= 100 làm là:

“Nếu a không tồn tại hoặc nó là false hoặc nil thì gán 100 cho nó, ngược lại chỉ trả về giá trị của a”

Biểu thức gần nhất tương đương:

(defined?(a) && a) ? a : a = 100

Điều này hữu ích nếu bạn muốn lưu kết quả của một phép tính chậm hoặc yêu cầu API, quá trình gọi là “memoization”.

Toán tử Unary là gì?

Cho đến nay bạn chỉ thấy các toán tử làm việc với 2 giá trị, nhưng cũng có toán tử chỉ làm việc với một giá trị, chúng ta gọi đây là “toán tử unary”.

Ví dụ:

+"abc"

Điều này tạo ra một bản sao có thể thay đổi (mutable) của một chuỗi frozen.

Bạn có thể định nghĩa toán tử unary riêng (+ / -), nhưng cần cú pháp đặc biệt.

Ví dụ:

class String
  def +@
    frozen? ? self.dup : self
  end
end

str = "abc".freeze
p (+str).frozen? # false

Tôi phải dùng dấu ngoặc đơn ở đây do độ ưu tiên của toán tử unary.

Bạn cũng có !!, đây không phải là phương thức:

!!123 # true
!!nil # false

Toán tử này hữu ích vì nó sẽ chuyển bất kỳ giá trị nào thành boolean.

Sau đó bạn có !, cho giá trị boolean ngược lại.

Ví dụ:

!true  # false
!!true # true
!false # true

Toán tử Splat trong Ruby (Có ví dụ)

Toán tử splat (*) thú vị vì nó làm được điều bạn không thể làm nếu không có nó.

Giả sử bạn có một mảng như sau:

attributes = [:title, :author, :category]

Và bạn muốn sử dụng mảng này với một phương thức nhận đối số biến đổi, như attr_reader.

Thì bạn có thể làm:

attr_reader *attributes

Toán tử splat chuyển đổi mảng thành danh sách các phần tử. Nói cách khác, nó loại bỏ mảng & thay thế bằng mọi thứ bên trong nó.

Ví dụ trên tương đương với:

attr_reader :title, :author, :category

Đó chính là sức mạnh của toán tử splat 🙂

Toán tử Khớp (=~)

Toán tử Ruby trông kỳ lạ này (=~) với dấu tilde là gì?

Đó là toán tử khớp (matching operator)!

Nó cho phép bạn thực hiện tìm kiếm chỉ mục nhanh bằng biểu thức chính quy.

Dưới đây là ví dụ:

"3oranges" =~ /[0-9]/ # 0

Điều này tìm kiếm số & trả về chỉ mục bên trong chuỗi nơi khớp đầu tiên được tìm thấy, ngược lại trả về nil.

Ngoài ra, bạn có toán tử !~, là toán tử “KHÔNG khớp”.

Ví dụ:

"abc" !~ /[0-9]/ # false

Bạn sẽ nhận được true hoặc false với cái này, không có chỉ mục, hãy nhớ điều đó.

Toán tử Ternary trong Ruby (Toán tử dấu hỏi)

Nếu bạn thích mã gọn gàng & ngắn gọn thì bạn sẽ yêu thích toán tử ternary của Ruby.

Đó là cách viết câu lệnh if/else ngắn gọn.

Nó trông như sau:

condition ? true : false

Ví dụ:

"".size == 0 ? "Chuỗi rỗng" : "Chuỗi không rỗng"

Cách nó hoạt động:

Phần đầu tiên của toán tử ternary định nghĩa điều kiện ("".size == 0).

Sau đó là dấu hỏi (?).

Tiếp theo là giá trị trả về khi điều kiện đúng.

Sau đó là dấu hai chấm (:).

Và phần cuối là giá trị trả về khi điều kiện sai, đây sẽ là else trong biểu thức điều kiện đầy đủ.

Toán tử Shovel / Push (<<)

Toán tử này (<<) cũng là một phương thức, nên nó thay đổi hành vi tùy thuộc vào đối tượng bạn đang làm việc.

Ví dụ, với mảng nó chỉ là bí danh cho phương thức push.

animals = []
animals << "cat"

Với chuỗi nó sẽ nối vào cuối:

"" << "cat"

Và với Integers, nó sẽ thực hiện “left shift”, tức là xoay tất cả các bit sang trái.

2 << 1 # 4
2 << 2 # 8
2 << 3 # 16

Toán tử Triple Equals (Hơn là sự bằng nhau)

Toán tử cuối cùng hôm nay là toán tử triple equals (===).

Đây cũng là một phương thức, và nó xuất hiện ngay cả ở những nơi bạn không ngờ tới.

Ví dụ, trong câu lệnh case:

case "bacon"
when String
  puts "Đây là chuỗi!"
when Integer
  puts "Đây là số nguyên!"
end

Ruby đang gọi phương thức === ở đây trên lớp.

Như sau:

String === "bacon"

Điều này so sánh lớp hiện tại với lớp của đối tượng khác.

Vì vậy mục đích của toán tử này là định nghĩa sự bằng nhau trong bối cảnh câu lệnh case.

Toán tử Safe Navigator (&.)

Bạn có thể muốn gọi một phương thức trên một đối tượng, nhưng đối tượng đó có thể là nil, điều này không tốt vì gọi phương thức trên nil thường dẫn đến lỗi.

Một giải pháp:

if user && user.active # ... end

Cách tốt hơn:

if user&.active # ... end

Toán tử &. này là toán tử safe navigator (được giới thiệu trong Ruby 2.3), chỉ gọi phương thức active trên user nếu nó không phải nil.

Rất hữu ích!

Bảng Độ ưu tiên Toán tử

Ruby đánh giá mã nguồn của bạn theo danh sách優先次, giống như trong toán học với phép nhân & dấu ngoặc.

Điều này có thể trở thành nguồn gốc của nhiều lỗi nếu bạn không hiểu cách nó hoạt động.

Dưới đây là bảng, từ ưu tiên cao đến thấp:

Toán tử
!, ~, unary +
**
unary -
*, /, %
+, -
<<, >>
&
|, ^
>, >=, <, <=
<=>, ==, ===, !=, =~, !~
&&
||
?, :
modifier-rescue
=, +=, -=, *=, /=, %=
defined?
not
or, and
modifier-if, modifier-unless, modifier-while, modifier-until
{ } blocks
do ... end blocks

Với modifier-something nghĩa là phiên bản one-liner của các từ khóa này.

Ví dụ:

puts "awesome" if blog_name == "rubyguides"

Dưới đây là ví dụ nơi độ ưu tiên block có thể khiến bạn ngạc nhiên:

# Trả về mảng các ký tự in hoa
p ["a", "b", "c"].map { |character| character.upcase }
# Trả về đối tượng Enumerator
p ["a", "b", "c"].map do |character| character.upcase end

Trong trường hợp đầu tiên nó hoạt động như mong đợi, trong trường hợp thứ hai block có độ ưu tiên thấp hơn nên map nghĩ không có block & trả về enumerator.

Tóm tắt

Bạn đã học về nhiều toán tử của Ruby, từ toán tử toán học, logic đến các toán tử unary ít phổ biến hơn.

Nhiều trong số các toán tử này thực chất là phương thức mà bạn có thể triển khai trong các lớp của riêng mình.

Hy vọng bạn thấy hữu ích & thú vị!

Cảm ơn bạn đã đọc 🙂