Tải bản đầy đủ (.doc) (39 trang)

Tìm hiểu ngôn ngữ lập trình Ruby

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (333.33 KB, 39 trang )

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
*****************
BÀI TẬP LỚN
MÔN NGUYÊN LÝ CÁC NGÔN NGỮ LẬP TRÌNH
ĐỀ TÀI:
TÌM HIỂU VỀ RUBY
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Mục lục
Chương I: Giới thiệu 4
1. Tổng quát vể Ruby 4
1.1. Ruby là ngôn ngữ lập trình hướng đối tượng 4
1.2. Block và Iterator 4
1.3. Các biểu thức và toán tử trong Ruby 6
1.4. Các phương thức 7
1.5. Phép gán 8
1.6. Các dấu tiền tố và hậu tố 9
1.7. Regexp và Range 9
1.8. Lớp và Module 9
1.9. Những điều mới lạ trong Ruby 12
2. Sử dụug Ruby 13
2.1. Trình thông dịch Ruby 13
2.2. Hiển thị dữ liệu ra 13
2.3. Tương tác Ruby với irb 14
Chương II: Cấu trúc và sự thực thi các chương trình Ruby 15
1. Cấu trúc từ vựng 15
1.1. Chú giải 15
1.2. Literal 15
1.3. Dấu câu 15
1.4. Định danh 16
1.5. Từ khoá 16


2. Cấu trúc cú pháp 17
3. Thực thi chương trình 17
Chương III: Kiểu dữ liệu và đối tượng 19
1. Số 19
2. Xâu 20
3. Các kiểu dữ liệu khác 20
4. Đối tượng 21
4.1. Các tham chiếu đối tượng: 21
4.2. Vòng đời của đối tượng 21
4.3. Định danh đối tượng 22
4.4. Lớp đối tượng và kiểu đối tượng 22
4.5. Sao chép đối tượng 24
Chương IV: Phương thức, Proc, Lambda, và Closure 25
1. Định nghĩa phương thức đơn giản 25
1.1. Giá trị trả về của phương thức 26
1.2. Phương thức và xử lý lỗi 26
1.3. Gọi một phương thức của một đối tượng 26
2. Proc và Lambda 26
2.1. Tạo Proc 27
2.2. Gọi Proc và Lambda 30
3. Closure 30
3.1. Closure và các biến chung 31
2
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
3.2. Closure và Binding 32
Chương V: Lớp và Module 33
1. Tra cứu phương thức 33
2. Tra cứu phương thức của lớp 35
3. Tra cứu hằng số 36
Tài liệu tham khảo 39

3
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Chương I: Giới thiệu
1. Tổng quát vể Ruby
1.1. Ruby là ngôn ngữ lập trình hướng đối tượng
Ruby là ngôn ngữ lập trình hoàn toàn hướng đối tượng. Mọi giá trị đếu là
một đối tượng, thậm chí ngay cả những chuối số và các giá trị true, false, và nil
(nil là một giá trị đặc biệt biểu thị sự không có giá trị; đó là một phiên bản của null
trong Ruby). Trong ví dụ dưới ta sẽ gọi một phương thức có tên là class của các
giá trị này. Chú giải trong Ruby bắt đầu bằng #, và mũi tên => trong chú giải thể
hiện giá trị trả về bởi dòng lệnh:
1.class # => Fixnum: số 1 là một Fixnum
0.0.class # => Float: các số thực có lớp Float
true.class # => TrueClass: true là một thể hiện duy nhất (singleton) của
# TrueClass
false.class # => FalseCalss
nil.class # => NilClass
Trong nhiều ngôn ngữ, lời gọi hàm và phương thức phải có dấu ngoặc đơn,
nhưng trong đoạn code trên không có một dấu ngoặc đơn nào. Trong Ruby, cấu
ngoặc đơn là mặc định và chúng thường được bỏ qua, đặc biệt khi phương thức
không nhận một tham số nào. Việc dấu ngoặc đơn bị bỏ qua trong lời gọi phương
thức làm chúng giống như là các tham chiếu tới các trường hoặc biến của đối
tượng. Điều này là có chủ ý, nhưng sự thật là, Ruby rất khắt khe trong việc đóng
gói các đối tượng; sẽ không có sự truy cập nào đối với trạng thái bên trong của
một đối tượng từ một đối tượng bên ngoài. Những sự truy cập như vậy phải được
thực hiện gián tiếp qua một phương thực truy cập, nhưng là phương thức class ở
trên.
1.2. Block và Iterator
Việc chúng ta có thể gọi các phương thức của các số nguyên chỉ là một mặt
của Ruby. Còn có một vài mặt khác mà lập trình viên Ruby hay sử dụng:

3.times {print “Ruby! ”} # In ra “Ruby! Ruby! Ruby! ”
1.upto(9) {|x| print x} # In ra “123456789”
times và upto là các phương thức của đối tượng số nguyên. Chúng là một loại
phương thức đặc biệt gọi là iterator, và chúng thực hiện giống các vòng lặp. Đoạn
code trong các dấu ngoặc nhọn – gọi là một block – kết hợp với lời gọi phương
thức và được thực hiện trong vòng lặp. Sử dụng các iterator và block là một đặc
4
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
tính đáng chú ý của Ruby; mặc dù ngôn ngữ có vòng lặp while, thì thường người
lập trình sử dụng các vòng lăp với các khởi tạo thực chất là các lời gọi phương
thức.
Số nguyên không phải là các giá trị duy nhất có các phương thức iterator.
Mảng (và các đối tượng “đếm được” tương tự) định nghĩa một iterator bằng each,
phương thức gọi block tương ứng cho mỗi phần tử trong mảng. Mỗi lời gọi block
đi qua một phần tử của mảng:
a = [3, 2, 1] # Đây là một mảng
a[3] = a[2] -1 # Sử dụng dấu ngoặc vuông để lấy và gán giá trị cho phần tử
# mảng
a.each do |elt| # each là một iterator. Block có một tham số elt
print elt+1 # In “4321”
end # Block này được nằm trong trong do/end
# thay vì {}
Có rất nhiều cách sử dụng iterator hữu ích khác:
a = [1, 2, 3, 4] # Khởi tạo một mảng
b = a.map{|x| x*x} # Bình phương các phần tử: b là
# [1,4,9,16]
c = a.select {|x| x%2==0} # Chọn các phần tử chẵn: c là
# [2,4]
a.inject do |sum, x| # Tính tổng của các phần tử => 10
sum + x

end
Bảng băm, giống như mảng, là một cấu trúc dữ liệu cơ bản của Ruby. Như
tên gọi, chúng dựa trên cấu trúc dữ liệu bảng băm để ánh xạ từ các đối tượng khoá
duy nhất tới các đối tượng giá trị. Bảng băm sử dụng dấu ngoặc vuông, giống như
mảng, để lấy và gán các giá trị. Thay vị sử dụng số nguyên để làm chỉ mục, chúng
sử dụng các đối tượng khoá trong các dấu ngoặc vuông. Giống lớp Array, lớp
Hash cũng định nghĩa một phương thức iterator là each. Phương thức này gọi các
block code tương ứng cho mỗi cặp khoá-giá trị trong bảng băm, và (đây là điểm
khác với Array) truyền các khoá và giá trị như là tham số cho block:
h = { # Một bảng băm ánh xạ từ tên đến các chữ số
:one => 1, # “Mũi tên” thể hiện sự ánh xạ key=>value
:two => 2 # Dấu phẩy thể hiện các Symbol
}
h[:one] # => 1. Lấy một giá trị qua khoá
h[:three] = 3 # Thêm một cặp khoá/giá trị vào bảng băm
h.each do |key,value| # Duyệt qua các cặp khoá/giá trị
print “#{value}:#{key}; ” # Các giá trị được thay thế bằng xâu
5
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
end # In “1:one; 2:two; 3:three; ”
Bảng băm của Ruby có thể sử dụng bất kỳ đối tượng nào làm khoá, nhưng
đối tượng Symbol thường được sử dụng. Symbol là các xâu không đổi. Chúng có
thể được đem so sánh bởi các thực thể hơn nội dung chữ (bởi vì hai đối tượng
Symbol khác nhau không bao giờ có chung nội dung).
Bên cạnh đó, lưu ý rằng Hash.each ở ví dụ trên bao gồm đoạn code:
print “#{value}:#{key}; ”
Xâu trong dấu ngoặc kép có thể bao gồm các biểu thức phân cách bởi
#{ và }. Giá trị của biểu thức trong các dấu phân cách được chuyển thành xâu
(bằng lời gọi phương thức to_s mà mọi đối tượng đều có). Xâu kết quả sau đó
được sử dụng thay thế cho đoạn xâu biểu thức và các dấu phân cách trong xâu. Sự

thay thế các giá trị biểu thức trong xâu này thường được gọi là phép nội suy xâu.
1.3. Các biểu thức và toán tử trong Ruby
Cú pháp của Ruby là hướng biểu thức. Các cấu trúc điều khiển như if mà
được gọi là các câu lệnh trong các ngôn ngữ khác thực chất là các biểu thức trong
Ruby. Chúng có các giá trị giống như các biểu thức đơn giản hơn có, và chúng ta
có thể viết code như sau:
minimum = if x < y then x else y end
Mặc dù tất cả các “câu lệnh” trong Ruby thực chất đều là các biểu thức,
không phải lúc nào chúng cũng trả về giá trị có nghĩa. Ví dụ, vòng lặp while và
các định nghĩa phương thức là các biểu thức mà bình thường sẽ trả về giá trị nil.
Giống như trong hầu hết các ngôn ngữ, các biểu thức trong Ruby được xây
dựng từ các giá trị và các toán tử. Hầu hết, các toán tử của Ruby là quen thuộc với
những người biết C, Java, JavaScript, hoặc bất kỳ ngôn ngữ lập trình tương tự nào.
Sau đây là các ví dụ đơn giản và khác thường của toán tử trong Ruby:
1 + 2 # => 3: phép cộng
1 * 2 # => 2: phép nhân
1 + 2 == 3 # => true: phép kiểm tra tính bằng
2 ** 1024 # => 2 mũ 1024
“Ruby” + “ rocks!” # => “Ruby rocks!”: phép ghép xâu
“Ruby! ” * 3 # => “Ruby! Ruby! Ruby!”: phép lặp xâu
“%d %s” % [3, “rubies”] # => “3 Rubies”: định dạng in, theo kiểu Python
max = x > y > x : y # Toán tử điều kiện
Nhiều toán tử của Ruby được sử dụng như các phương thức, các lớp có thể
định nghĩa (hoặc định nghĩa lại) các phương thức này nếu như chúng muốn. (Tuy
nhiên, chúng không thể định nghĩa một toán tử hoàn toàn mới; chỉ có một tập cố
6
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
định các toán tử được thừa nhận). Theo ví dụ, ta lưu ý là các toán tử + và * thực
hiện với các số nguyên và xâu là khác nhau. Ta có thể định nghĩa các toán tử này
theo cách nào ta muốn trong lớp của mình. Toán tử << là một ví dụ hay. Các lớp

số nguyên Fixnum và Bignum sử dụng các toán tử này cho phép dịch bit trái,
giống ngôn ngữ lập trình C. Bên cạnh đó (giống C++), các lớp khác – như xâu,
mảng và luồng – sử dụng toán tử này cho các phép nối thêm vào. Nếu ta tạo một
lớp mới có thể có giá trị thêm vào nó theo một vài cách nào đấy, định nghĩa << là
một ý hay.
Một trong những toán tử hữu ích nhất được chồng là []. Các lớp Array và
Hashes sử dụng toán tử này để truy cập đến các phẩn tử trong mảng qua chỉ số và
các giá trị qua khoá. Nhưng ta có thể định nghĩa [] trong lớp của minh theo mục
đích mình muốn. Ta thậm chí định nghĩa nó là một phương thức sử dụng nhiều
tham số, phân cách bởi các dấu phẩy trong dấu ngoặc vuông. (Lớp Array chấp
nhận một chỉ số và một độ dài giữa dấu ngoặc vuông để thể hiện mảng con hoặc
“lớp” của mảng). Và nếu ta muốn cho phép các dấu ngoặc vuông được sử dụng
bên trái các phép gán, ta có thể định nghĩa toán tử []= tương ứng. Giá trị ở bên trái
phép gán sẽ được truyền như là tham số cuối cùng cho phương thức mà thực hiện
toán tử đấy.
1.4. Các phương thức
Các phương thức được định nghĩa với từ khoá def. Giá trị trả về của một
phương thức là giá trị cuối cùng của biều thức trong thân của nó:
def square(x) # Định nghĩa một phương thức tên là square với tham số x
x*x # Trả về bình phương của x
end # Kết thúc phương thức
Khi một phương thức, như ở trên, được định nghĩa ngoài một lớp hoặc một
module, thì nó là một hàm toàn cục hơn là một phương thức của một đối tượng.
(Tuy nhiên, về mặt kỹ thuật, một phương thức giống như thế trở thành một
phương thức private của lớp Object.) Các phương thức có thể cũng được định
nghĩa trong các đối tượng riêng biết bằng việc thêm vào đầu tên của phương thức
đối tượng mà có phương thức đó. Các phương thức như thế được gọi là các
phương thức singleton, và chúng là cách Ruby định nghĩa các phương thức lớp:
def Math.square(x) # Định nghĩa một phương thức lớp của module Math
x*x

end
Module Math là một phần của thư viện Ruby, và đoạn code này thêm vào
một phương thức mới cho nó. Đây là đặc điểm quan trọng của Ruby – các lớp và
module là “mở” và có thể được thay đổi và mở rộng trong lúc chạy.
7
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Các tham số của phương thức có thể có giá trị mặc định cụ thể, và các
phương thức có thể nhận một số số cố định các tham số.
1.5. Phép gán
Phép gán = (không thể chồng được) trong Ruby sẽ gán một giá trị vào một
biến:
x = 1
Phép gán có thể kết hơp với các toán tử khác như + và -:
x += 1 # Tăng x lên 1: lưu ý là Ruby không có ++.
y -= 1 # Giảm y đi 1: và cũng không có
Ruby hỗ trợ phép gán song song, cho phép nhiều hơn một giá trị và nhiều
hơn một biến trong các biểu thức gán:
x, y = 1, 2 # Tương tự như x = 1; y =2
a, b = b, a # Hoán đổi giá trị của hai biến
x, y, z = [1,2,3] # Các phần tử của mảng được gán giá trị một cách tự động
Các phương thức trong Ruby được phép trả về hơn một giá trị, và phép gán
song song là một sự kết hợp hữu ích với các phương thức này. Ví dụ :
# Định nghĩa một phương thức để chuyển hệ toạ độ Cartesian(x,y) thành Polar
def polar(x,y)
theta = Math.atan2(y,x) # Tính góc
r = Math.hypot(x,y) # Tính khoảng cách
[r, theta] # Biểu thức cuối cùng là giá trị trả về
end
# Đây là cách ta sử dụng phương thức này với phép gán song song
distance, angle = polar(2,2)

Các phương thức kết thúc bởi một bằng (=) là đặc biệt bởi Ruby cho phép
chúng được gọi bằng cách sử dụng cú pháp gán. Nếu một đối tượng o có một
phương thức x=, thì hai dòng code sau thực hiện cùng một việc:
o.x=(1) # Cú pháp gọi phương thức bình thường
o.x = 1 # Lời gọi phương thức thông qua phép gán
8
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
1.6. Các dấu tiền tố và hậu tố
Ta đã thấy các phương thức có tên kết thúc bằng = có thể được gọi bằng biểu
thức gán. Các phương thức Ruby cũng có thể kết thúc bằng dấu hỏi hoặc dấu chấm
than. Dấu hỏi được sử dụng để đánh dấu một xác nhận – các phương thức trả về
giá trị Boolean. Ví dụ, các lớp Array và Hash đểu có phương thức tên là empty?
để kiểm tra cấu trúc dữ liệu đó có phần tử nào hay không. Dấu chấm than ở cuối
tên phương thức được dùng để biểu thị sự thận trọng khi sử dụng phương thức đó.
Một số số các lớp nòng cốt của Ruby định nghĩa các cặp phương thức có cùng tên,
ngoại trừ một phương thức có dấu chấm than ở cuối và phương thức còn lại thì
không. Thông thường, phương thức không có dấu chấm than trả về bản sao đã
thay đổi của đối tượng gọi nó, và phương thức có dấu chấm than là phương thức
làm thay đổi chính đối tượng. Ví dụ, lớp Array định nghĩa các phương thức sort và
sort !.
Ngoài các ký tự dấu câu ở cuối các tên phương thức, ta còn thấy các ký tự
dấu câu còn xuất hiện ở đầu tên các biến của Ruby: biến toàn cục được bắt đầu
bằng $, các biến tham chiếu bắt đầu bằng @, và các biến của lớp được bắt đầu
bằng @@. Các tiền tố này có thể mất một thời gian để quen dùng, nhưng nhờ nó
bạn sẽ biết được phạm vi của biến. Các tiền tố là bắt buộc để không bị nhập nhằng
với cú pháp rất uyển chuyển của Ruby. Một cách để nói về công dụng của các tiền
tố trong tên biến là giúp ta có thể bỏ qua các dấu ngoặc trong các lời gọi hàm.
1.7. Regexp và Range
Ta đã nói về mảng và bảng băm như là các cấu trúc đối tượng cơ bản của
Ruby. Ta cũng đã mô cách sử dụng của số và xâu. Có hai loại dữ liệu khác cũng

rất đáng để nói đến ở đây. Một đối tượng Regexp (biểu thức chính quy) môt tả một
định dạng xâu và có các phương thức để xác định một xâu đầu vào có hợp với
định dạng đó hay không. Và một đối tượng Range biểu diễn các giá trị (thường là
số) giữa hai đầu mút. Các biểu thức chính quy và các đoạn có cú pháp riêng :
/[Rr]uby/ # Khớp "Ruby" hoặc “ruby”
/\d{5}/ # Khớp 5 chữ số liên tiếp
1 3 # Tất cả x trong đoạn 1 <= x <= 3
1…3 # Tất cả x trong đoạn 1 <= x < 3
Các đối tượng Regexp và Range định nghĩa toán tử == thông thường để kiểm
tra tính bằng nhau. Ngoài ra chúng cũng định nghĩa toán tử == để kiểm tra tính so
khớp và sự có mặt. Cấu trúc case của Ruby (giống như switch trong C hoặc Java)
kiểm tra các biểu thức của nó vói từng trường hợp bằng cách sử dụng ==, vì thế
toán tử này thường được gọi là toán tử kiểm tra bằng trường hợp.
1.8. Lớp và Module
Lớp là một tập hợp các phương thức liên quan thực hiện trên trạng thái của
một đối tượng. Trạng thái của một đối tượng được xác định bằng các biến của nó:
9
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
các biến có thể bắt đầu bằng @ và có giá trị xác định riêng cho đối tượng cụ thể.
Đoạn code sau định nghĩa một lớp tên là Sequence và mô tả cách viết một phương
thức duyệt và định nghĩa các toán tử:
# Lớp dưới đây mô tả một chuỗi các số được xác định bởi bat ham số from, to và
# by. Các số x trong chuỗi thoả mãn hai tính chất sau:
#
# from <= x <= to
# x = from + n*by, với n là một số nguyên
#
class Sequence
# Đây là lớp có thể liệt kê đựơc, nó định nghĩa từ iterator ở dưới
include Enumerable # Tham chiếu các phương thức của module này trong lớp

# Phương thức khởi tạo rất đặc biệt; nó được gọi một cách tự động để khởi tạo
# một thể hiện mới được tạo của lớp
def initialize(from, to, by)
# Lưu giữ các tham số vào trong các biến để sử dụng sau này
@from, @to, @by = from, to, by # Lưu ý đến phép gán song song và tiền
# tố
End
# Đây là iterator đòi hỏi bởi module Enumerable
def each
x = @from # Bắt đầu từ điểm đầu tiên
while x <= @to # Đến khi chúng ta vẫn chưa đến điểm cuối cùng
yield x # Truyền x cho block tương ứng với iterator
x += @by # Tăng x lên
end
end
# Định nghĩa phương thức length (giống như mảng) trả về số lượng giá trị trong
# chuỗi
def length
return 0 if @from > @to
Integer((@to-@from)/@by) + 1 # Tính và trả về độ dài của chuỗi
End
# Định nghĩa một tên khác cho cùng phương thức.
# Trong Ruby việc một phương thức có nhiều tên là điều bình thường
alias size length # size bây giờ đồng nghĩa với length
# Chồng lại toán tử truy cập mảng để truy cập ngẫu nhiên đến chuỗi
def[](index)
return nil if index < 0 # Trả về nil đếu chỉ số âm
10
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
v = @from + index*@by # Tính giá trị

if v <= @to # Nếu nằm trong chuỗi
v # thì trả về nó
else # Ngược lại
nil # trả về nil
end
end
# Chồng lại các toán tử toán học để trả về đối tượng Sequence mới
def *(factor)
Sequence.new(@from*factor, @to*factor, @by*factor)
end
def +(offset)
Sequence.new(@from+offset, @to+offset, @by)
end
end
Đây là đoạn code sử dụng lớp Sequence:
s = Sequence.new(1, 10, 2) # Từ 1 đến 10 bước nhảy 2
s.each {|x| print x} # In “13579”
print s[s.size-1] # In 9
t = (s+1)*2 # Từ 4 đến 22 bước nhảy 4
Đặc tính quan trọng của lớp Sequence là nó có iterator each. Nếu chúng ta
chỉ quan tâm tới phương thức iterator, ta không cần phải định nghĩa cả lớp. Thay
vào đó, ta có thể đơn giản viết một phương thức iterator nhận các tham số from, to
và by. Thay vì làm một hàm toàn cục, hãy định nghĩa nó trong một module riêng:
module Sequences # Bắt đầu một module mới
def self.fromtoby(from, to, by)# Một phương thức singleton của
# module
x = from
while x <= to
yield x
x += by

end
end
end
Với định nghĩa iterator như trên, ta có thể viết code như sau:
Sequences.fromtoby(1, 10, 2) {|x| print x} # In “13579”
11
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Một iterator như trên làm cho việc tạo một đối tượng để duyệt qua một chuỗi
các số là không cần thiết. Nhưng tên của phương thức lại dài, và cú pháp của lời
gọi nó không làm thoả mãn chúng ta. Điều ta muốn là một cách để duyệt qua đối
tượng Range của các số theo từng bước không phải là 1. Một trong những tính
chất đáng ngạc nhiên của Ruby là các lớp, thậm chí các lớp có sẵn, của nó là mở:
bất cứ chương trình nào cũng có thể thêm các phương thức cho các lớp. Do đó ta
hoàn toàn có thể định nghĩa một phương thức iterator mới cho các khoảng:
class Range # Mở một lớp đã có
def by(step) # Định nghĩa một iterator tên là by
x = self.begin # Bắt đầu từ phần tử cuối của đoạn
if exclude_end? # Với đoạn bỏ phần tử cuối
while x < self.end # Kiểm tra với toán tử <
yield x
x += step
end
else # Ngược lại, với đoạn lấy cả phần tử cuối
while x <= self.end # Kiểm tra với toán tử <=
yield x
x += step
end
end
end
end

# Ví dụ
(0 10).by(2) {|x| print x} # In “0246810”
(0…10).by(2) {|x| print x} # In “02468”
Phương thức by này tiện lợi nhưng không cần thiết, lớp Range đã định nghĩa
một iterator tên step phục vụ cho trường hợp này. Lõi của Ruby API rất lớn, và
cần nhiều thời gian để tìm hiểu.
1.9. Những điều mới lạ trong Ruby
Xâu trong Ruby có thể biến đổi đựơc, điều này có thể gây ngạc nhiên đặc biệt
với các lập trình viên Java. Toán tử []= cho phép ta có thể thay đổi các chữ cái của
một xâu hoặc thêm vào, xoá, và thay thế các xâu con. Toán tử << cho phép ta nối
thêm một xâu, và lớp String đinh nghĩa rất nhiều phương thức khác có thể làm
thay đổi bản thân xâu. Bởi vì xâu dễ dàng bị thay đổi, xâu chữ trong một chương
trình không phải là đổi tượng duy nhất. Nếu ta sử dụng một xâu chữ trong một
vòng lặp, nó sẽ tạo ra một đối tượng mới trong mỗi vòng lặp. Sử dụng phương
thức freeze của một xâu (hoặc của bất cứ đối tượng nào) để ngăn ngừa bất cứ thay
đổi nào lên đối tượng.
12
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Các điều kiện và vòng lặp trong Ruby (ví dụ như if và while) tính các biểu
thức điều kiện để xác định nhánh nào được thực hiện hoặc vòng lặp có được tiếp
tục hay không. Các biểu thức điều kiện thường có giá trị true hoặc false, nhưng
điều đó không bắt buộc. Giá trị nil được coi như giá trị false, và bất cứ giá trị nào
khác đều là true. Điều này có vẻ sẽ gây ngạc nhiên có các lập trình viên C khi họ
đã quen với 0 là false, và lập trình viên Javacript coi xâu rỗng “” là false.
2. Sử dụug Ruby
Trước khi sử dụng Ruby, điều ta cần là một trình thông dịch Ruby, và ta cũng
sẽ muốn biết cách sử dụng ba công cụ - irb, ri, và gem.
2.1. Trình thông dịch Ruby
Một khi ta đã cài Ruby, ta có thể gọi trình thông dịch Ruby bằng câu lệnh
ruby sau:

% ruby –e ‘puts “hello world!”’
hello world!
Tham số -e trong dòng lệnh bắt trình thông dịch thực hiện một dòng code
Ruby cụ thể. Thông thường, ta viết chương trình Ruby vào một file và bảo trình
thông dịch dịch nó:
% ruby hello.rb
hello world!
2.2. Hiển thị dữ liệu ra
Để có thể sử dụng các đặc tính của Ruby, ta cần phải biết một cách để hiển
thị dữ liệu ra để chương trình thử có thể in các kết quả của nó. Hàm puts – được sử
dụng trong đoạn code “hello world” ở trên- là một trong những cách đó. Nói một
cách không chính xác, puts in một xâu chữ ra màn hình và thêm vào một ký tự
xuống dòng (trừ khi xâu đó đã kết thúc bởi ký tự đó). Nếu ta truyền một đối tượng
không phải là xâu, puts gọi phương thức to_s của đối tượng đó và in ra xâu trả về
bởi phương thức. print cũng thực hiện tương tự, nhưng nó không thêm ký tự
xuống dòng. Ví dụ, gõ chương trình có 2 dòng code dưới đây vào một trình soạn
thảo và lưu nó vào một file tên là count.rb:
9.downto(1) {|n| print n} # Không có dấu xuống dòng giữa các số
Puts “ blastoff!” # Kết thúc bằng dấu xuống dòng
Chạy chương trình với trình thông dịch của Ruby:
%ruby count.rb
13
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Nó sẽ cho ra kết quả như sau:
987654321 blastoff!
Ta có thể sử dụng hàm p thay cho hàm puts. Nó không chỉ ngắn hơn, mà còn
chuyển đổi các đối tượng thành xâu với phương thức inspect mà thường trả về kết
quả thân thiện cho các lập trình viên hơn là to_s.
2.3. Tương tác Ruby với irb
irb (viết tắt của “interactive Ruby) là một Ruby shell. Gõ một biểu thức Ruby

tại dấu nhắc và nó sẽ thực hiện và hiển thị giá trị cho bạn. Đây thường là cách dễ
dàng nhất để thử các tính năng của ngôn ngữ mà ta đọc được từ các sách. Đây là
một ví dụ của irb:
$ irb –simple-promt # Khởi động irb từ terminal
>> 2**3 # Thử hàm mũ
=> 8 # Đây là kết quả
>> “Ruby! ” * 3 # Thử phép lặp xâu
=> “Ruby! Ruby! Ruby! ” # Kết quả
>> 1.upto(3){|x| puts x} # Thử một iterator
1 # Vì chúng ta gọi hàm put 3 lần
2
3
=> 1 # Giá trị trả về của 1.upto(3)
>> quit # Thoát khỏi irb
$ # Trở về dấu nhắc
Phiên ví dụ trên đã mô tả tất cả những gì cần biết về irb để ta có thể sử dụng
nó một cách hiệu quả khi khám phá Ruby.
14
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Chương II: Cấu trúc và sự thực thi các
chương trình Ruby
Phần này giải thích cấu trúc cả chương trình Ruby, bao gồm cấu trúc từ vựng,
các token (ký tự đại diện)… Phần tiếp theo sẽ là cấu trúc cú pháp của chương trình
Ruby, giải thích cách các biểu thức, cấu trúc điều khiển, phương thức, lớp và
những thứ tương tự được viết theo một chuỗi các token. Cuối cùng, phần này sẽ
mô tả các file code Ruby.
1. Cấu trúc từ vựng
Trình thông dịch của Ruby phân tách một chương trình thành một dãy các
token. Token bao gồm các lời chú giải (comment), literal, dấu câu, định danh, và
các từ khoá.

1.1. Chú giải
Chú giải trong Ruby bắt đầu bằng ký tự # và tiếp tục đến hết dòng. Trình
thông dịch của Ruby sẽ bỏ quả ký tự # và các đoạn text sau nó. Nếu một ký tự #
xuất hiện trong một xâu hoặc một biểu thức chính quy, thì nó chỉ đơn giản là một
phần của xâu hoặc biểu thức chính quy dó và không phải là chú giải:
# Toàn bộ dòng này là một chú giải
x = “#This is a string” # Và đây là một chú giải
y = /#This is a regular expression/ # Đây là một chú giải nữa
1.2. Literal
Các literal là các giá trị xuất hiện trực tiếp trong đoạn mã của Ruby. Chúng
bao gồm các số, xâu, và các biểu thức chính quy. (Các literal khác, như mảng hay
các giá trị băm, không phải là các token mà là các biểu thức phức tạp hơn.). Ví
dụ :
1 # Một literal số nguyên
1.0 # Một literal số phẩy động
‘one’ # Một literal xâu
"two" # Một literal xâu khác
/three/ # Một literal biểu thức chính quy
1.3. Dấu câu
Ruby sử dụng các ký tự dấu câu cho nhiều lí do. Hầu hết các toán tử của
Ruby được viết bằng cách sử dụng các ký tự dấu câu, như + cho phép cộng, * cho
phép nhân, và || cho toán tử OR. Các ký tự dấu câu cũng được sử dụng để phân
15
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
tách xâu, biểu thức chính quy, mảng, và các literal băm và để nhóm và phân tách
các biểu thức, các tham số của phương thức, và các chỉ số của mảng.
1.4. Định danh
Một định danh đơn giản chỉ là một tên. Ruby sử dụng các định danh để đặt
tên cho các biến, phương thức, lớp và những thứ khác. Các định danh của Ruby
bao gồm các chữ cái, số và các ký tự gạch dưới, nhưng nó không thể bắt đầu bằng

số. Các định danh không thể có dấu cách hoặc các ký tự không in được, và không
thể có dấu câu. Các định danh bắt đầu bằng ký tự viết hoa A-Z là các hằng số, và
trình thông dịch của Ruby sẽ đưa ra một lời cảnh báo (nhưng không phải là lỗi)
nếu ta thay đổi giá trị của các định danh đó. Tên của lớp và module phải bắt đầu
bằng một chữ cái viết hoa.
1.4.1 Phân biệt hoa thường
Ruby là ngôn ngữ có phân biệt hoa thường. Các chữ cái viết thường và viết
hoa là khác nhau. Ví dụ, từ khoá end là hoàn toàn khác với từ khoá END.
1.4.2 Dấu câu trong các định danh
Các ký tự dấu câu có thể xuất hiện ở đầu và cuối các định danh của Ruby.
Chúng có các ý nghĩa như sau:
$ Các biến toàn cục được bắt đầu bằng dấu dollar.
@ Các biến được bắt đầu bằng dấu @, và biến của lớp được bắt đầu bằng hai
dấu @.
? Các phương thức mà trả về giá trị Boolean thường có tên kết thúc bằng
dấu hỏi.
! Tên của phương thức có thể kết thúc bằng dấu chấm than để thể hiện rằng
chúng phải được sử dụng một cách cẩn thận. Quy ước này là để phân biệt
các phương thức khác trả về đối tượng gọi nó đã được thay đổi so với đổi
tượng ban đầu
= Phương thức có tên bắt đầu bằng dấu bằng có thể được gọi bằng cách đặt
tên phương thức, không có đấu bằng, bên phía trái của toán tử gán.
1.5. Từ khoá
Các từ khoá sau có ý nghĩa đặc biệt trong Ruby và được xử lý riêng biệt bởi
bộ phân tích của Ruby:
__LINE__ case ensure not then
__ENCODING__ class false or true
__FILE__ def for redo undef
BEGIN defined? if rescue unless
END do in retry until

alias else module return when
and elsif next self while
16
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
begin end nil super yield
break
2. Cấu trúc cú pháp
Chúng ta sẽ mô tả ngắn gọn cách các token từ vựng liên kết thành một cấu
trúc cú pháp lớn hơn để tạo thành một chương trình Ruby.
Đơn vị cơ bản của cú pháp trong Ruby là biểu thức. Trình thông dịch của
Ruby tính các biểu thức, và trả về kết quả. Biểu thức đơn giản nhất là biểu thức sơ
cấp, biểu diễn trực tiếp giá trị. Các literal số và xâu là các biểu thức sơ cấp. Các
biểu thức sơ cấp khác bao gồm các từ khoá như true, false, nil và self. Các biến
cũng là các biểu thức sơ cấp chúng tính giá trị của một biến.
Các giá trị phức tạp hơn có thể được viết như các biểu thức kết hợp:
[1,2,3] # Một literal Array
{1=>"one", 2=>"two"} # Một literal Hash
1 3 # Một literal Range
Các toán tử được sử dụng để thực hiện các phép tính trên giá trị, và các biểu
thức kết hợp được xây dựng bằng việc kết hợp các biểu thức con đơn giản hơn với
các toán tử:
1 # Một biểu thức cơ bản
x # Một biểu thức cơ bản khác
x = 1 # Một biểu thức gán
x = x + 1 # Một biểu thức với hai toán tử
Các biểu thức có thể được kết hợp với các từ khoá của Ruby để tạo ra các câu
lệnh điều khiển, như if cho các đoạn code thực hiện có điều kiện và while cho
đoạn code thực hiện lặp.
if x < 10 then # Nếu biểu thức này đúng
x = x + 1 # thì thực hiện lệnh này

end # Đánh dấu kết thúc điều kiện
while x < 10 do # Trong khi biểu thức này còn đúng
print x # Thực hiện lệnh này
x = x + 1 # Rồi thực hiện lệnh này
end # Đánh dấu kết thúc vòng lặp
3. Thực thi chương trình
Ruby là một ngôn ngữ kịch bản (script). Điều đó có nghĩa là các chương trình
Ruby chỉ đơn giản là các danh sách, hoặc các script, của các lệnh được thực hiện.
Theo mặc định, các lệnh này sẽ được thực hiện tuần tự, theo thứ tự chúng xuất
17
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
hiện. Cấu trúc điều khiển cả Ruby sẽ làm thay đổi thứ tự thực hiện ban đầu và cho
phép các lệnh được thực thi theo điều kiện hoặc lặp lại.
Các lập trình viên mà đã quan thuộcvới các ngôn ngữ biên dịch như C hay
Java có thể sẽ gặp một số khác biệt. Không có phương thức main nào trong Ruby
để bắt đầu cho chương trình thực thi. Trình thông dịch của Ruby được cung cấp
một kịch bảng để thực hiện, và nó bắt đầu thực thi ngay từ dòng đầu tiên cho đến
dòng cuối cùng.
Một khác biệt nữa giữa Ruby và các ngôn ngữ biên dịch liên quan đến
module, lớp, và định nghĩa phương thức. Trong các ngôn ngữ biên dịch, các cấu
trúc cú pháp này được xử lý bởi trình biên dịch. Trong Ruby, chúng là các lệnh
như các cấu trúc khác. Khi Ruby thông dịch một định nghĩa của lớp, nó thực hiện
nó, tạo ra một lớp mới. Tương tự, khi trình thông dịch của Ruby gặp một định
nghĩa phương thức, nó thực hiện nó, và một phương thức đã được định nghĩa.
Phần sau của chương trình, trình thông dịch có thể gặp và thực thi một biểu thức
gọi phương thức này, và lời gọi này sẽ thực hiện các lệnh có trong thân phương
thức.
Trình thông dịch của Ruby được gọi từ dòng lệnh và được cung cấp một kịch
bản để thực hiện. Một kịch bản 1 dòng đơn giản thường được viết trực tiếp từ dòng
lệnh. Tuy nhiên, thường gặp hơn, tên của file chứa kịch bản sẽ được cung cấp.

Trình thông dịch của Ruby đọc file đó và thực thi kịch bản đó. Đầu tiên nó thực
hiện mọi block BEGIN. Sau đó nó bắt đầu từ dòng đầu tiên của file chó đến khi
một trong những điều sau xảy ra:
- Nó thực hiện một lệnh là cho chương trình Ruby dừng lại.
- Nó đến dòng cuối của file.
- Nó đọc một dòng mà có token _END_ đánh dấu là hết phần logic của file.
Trước khi thoát, trình thông dịch Ruby thực hiện phần thân của lệnh END mà
nó gặp và bất cứ code “shutdown hook” được đăng ký bằng hàm at_exit.
18
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Chương III: Kiểu dữ liệu và đối tượng
Chương này giới thiệu về các giá trị sử dụng trong chương trình Ruby.
Chương bắt đầu bằng cái nhìn tổng quát về số và các giá trị xâu. Tiếp theo chương
giải thích ngắn gọn về các cấu trúc dữ liệu quan trọng của Ruby. Tất cả các giá trị
của Ruby đều là đối tượng, và chương này cũng sẽ bao gồm cái nhìn tổng quá về
các đặc tính mà mọi đối tượng đều có.
Lớp được mô tả trong chương này là kiểu dữ liệu cơ bản của ngôn ngữ Ruby.
Chương này sẽ trình bày các thao tác cơ bản của loại dữ liệu này: cách các giá trị
literal được viết trong chương trình, cách các phép tính số nguyên và số phẩy động
được thực hiện, cách dữ liệu xâu được mã hoá, cách các giá trị được xử lý như
khoá băm…
1. Số
Ruby có 5 lớp số có sẵn để biểu diễn các số, và thư viện chuẩn có thêm ba
lớp số khác. Hình dưới mô tả hệ thống cấp bậc của các lớp
Hình 1: Cấp bậc của lớp số
Mọi đối tượng số trong Ruby đều là thể hiện của Numeric. Mọi số nguyên
đều là thể hiện của Integer. Nếu một giá trị số nguyên vừa trong 31 bit, nó là một
thể hiện của Fixnum. Ngược lại, nó là một Bignum. Đối tượng Bignum biểu diễn
các số nguyên có kích thước không cố định, và nếu kết quả của một phép tính trên
Fixnum lớn hơn một số Fixnum, kết quả đó sẽ được chuyển về kiểu Bignum.

Tương tự, nếu kết quả của một phép toán trên đối tượng Bignum nằm trong giới
hạn của Fixnum, thì kết quả đó sẽ là một số Fixnum. Các số thực trong Ruby
thuộc lớp Float, biểu diễn cách biểu diễn dấu phẩy động.
Các lớp Complex, BigDecimal, và Rational không phải là các lớp có sẵn cho
Ruby nhưng được cung cấp như một phần của thư viện chuẩn. Lớp Complex biểu
19
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
diễn các số phức, BigDecimal biểu diễn các số thực có độ chính xác thay đổi, sử
dụng các biểu diễn thập phân chứ không phải là biểu diễn nhị phân. Và Rational
biểu diễn các phân số: một số nguyên chia cho một số nguyên khác.
Mọi đối tượng số là cố định; không có phương thức nào cho phép ta thay đổi
giá trị của đối tượng. Nếu ta truyền một thể hiện của một đối tượng số cho một
phương thức, ta không cần phải lo lắng rằng phương thức sẽ làm thay đổi đối
tượng.
2. Xâu
Xâu trong Ruby được biểu diễn bởi đối tượng của lớp String. Các xâu là các
đối tượng có thể thay đổi được, và lớp String định nghĩa một tập các toán tử và
phương thức để lấy xâu con, chèn và xoá xâu, tìm kiếm, thay đổi, Ruby cung cấp
nhiều cách để thể hiện các literal xâu trong chương trình, và một trong số đó hỗ trợ
cú pháp nội suy xâu. Các định dạng xâu được biểu diễn trong các đối tượng
Regexp, và Ruby định nghĩa cú pháp cho các biểu thức chính quy. Ví dụ, đoạn
code /[a-z]\d+/, biểu diễn một chữ cái thường theo sau bới một hoặc nhiều chữ số.
Các biểu thức chính quy làm một tính chất thường được sử dụng trong Ruby,
nhưng Regexp không phải phải là kiểu dữ liệu cơ bản như các số, xâu, và mảng.
Trong phạm vi báo cáo này, ta sẽ không đi sâu vào xâu.
3. Các kiểu dữ liệu khác
Ngoài số và xâu, Ruby còn định nghĩa rất nhiều các kiểu dữ liệu khác:
- Array: một Array là một chuỗi các giá trị mà cho phép các giá trị được truy cập
bởi vị trí của nó, hay là chỉ số, trong chuỗi. Giá trị đầu tiên có chỉ số bằng 0.
- Bảng băm: là một cấu trúc dữ liệu lưu trữ một tập các đối tượng được gọi là

khoá, và một giá trị tương ứng với từng khoá. Bảng băm cũng đựơc gọi là map bởi
nó ánh xạ khoá tới giá trị
- Đoạn: là cấu trúc dữ liệu biểu diễn các giá trị nằm giữa một giá trị bắt đầu và giá
trị kết thúc. Khi khai báo một đối tượng đoạn, nếu sử dụng hai dấu chấm, thì đoạn
đó sẽ bao gồm cả giá trị kết thúc, nếu sử dụng ba dấu chấm, thì đoạn đó sẽ không
bao gồm cả giá trị kết thúc.
- Ký hiệu: là một xử lý riêng của trình thông dịch Ruby lưu trữ một bảng các ký
hiệu chứa tên của mọi lớp, phương thức, và các biến mà nó biết. Nó cho phép trình
thông dịch tránh phải so sánh xâu: ví dụ nó tham khảo các tên phương thức bởi vị
trí của chúng trong bảng ký hiệu. Điều này sẽ chuyển các thao tác phức tạp trên
xâu thành các thao tác trên số tương đối đơn giản.
- true, false, nil: đây là các từ khoá của Ruby, và cũng là các đối tượng đặc biệt, là
thể hiện singleton của các lớp TrueClass, FalseClass, và NilClass. Lưu ý rằng
không có lớp Boolean nào trong Ruby. TrueClass và FalseClass đều có lớp cha là
Object.
20
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
4. Đối tượng
Ruby là ngôn ngữ thuần đối tượng: mọi giá trị đều là các đối tượng, và không
có sự khác nhau giữa các kiểu dữ liệu chính và kiểu đối tượng như các ngôn ngữ
lập trình khác. Trong Ruby, mọi đối tượng đều thừa kế từ một lớp tên là Object và
cùng có chúng các phương thức định nghĩa bởi lớp này. Phần này sẽ mô tả những
đặc tính chung của mọi đối tượng trong Ruby.
4.1. Các tham chiếu đối tượng:
Khi ta làm việc với các đối tượng trong Ruby, ta đang làm việc với các tham
chiếu. Bản thân chúng không phải là các đối tượng mà là một tham chiếu tới nó.
Khi gán một giái trị vào một biến, ta không sao chép một đối tượng “vào” biến đó,
ta chỉ đơn thuần lưu một tham chiếu tới một đối tượng trong biến đó. Một vài đoạn
code sẽ giải thích điều này:
s = "Ruby" # Tạo một đối tượng string. Lưu giữ tham chiếu tới nó trong s

t = s # Sao chép tham chiếu vào t. s và t để trỏ và cùng đối tượng.
t[-1] = "" # Thay đổi đối tượng thông qua tham chiếu tại t.
print s # Truy cập tới đối tượng đã thay đổi qua s. In ra "Rub".
t = "Java" # giờ t trỏ đến đối tượng khác t
print s,t # In ra "RubJava".
Khi ta truyền một đối tượng cho một phương thức trong Ruby, thì một tham
chiếu đối tượng sẽ được truyền cho phương thức đó. Đó không phải là đối tượng,
và cũng không phải là một tham chiếu tới tham chiếu tới đối tượng. Nói cách khác
là các tham số của phương thức được truyền bằng giá trị chứ không phải bằng
tham chiếu, nhưng các giái trị được truyền là các tham chiếu đối tượng.
Bởi vì các tham chiếu đối tượng được truyền cho phương thức, các phương thức
có thể sử dụng các tham chiếu này để thay đổi bên trong đối tượng. Những thay
đổi này sẽ vẫn còn khi phương thức kết thúc.
4.2. Vòng đời của đối tượng
Các lớp sẵn có của Ruby có các cú pháp literal, và các thể hiện của các lớp
này được tạo ra đơn giản bằng cách sử dụng các giá trị của chúng trong code. Các
đối tượng của các lớp khác cần phải được tạo ra một cách tường mình, và chúng
thường được tạo bằng phương thức tên là new :
myObject = myClass.new
new là một phương thức của lớp Class. Nó cấp phát bộ nhớ để lưu trữ đối tượng
mới, sau đó khởi tạo trạng thái cho đối tượng “rỗng” vừa mới được cấp phát này
bằng việc gọi phương thức initialize của nó. Các tham số cho new được truyền
trực tiếp vào initialize. Đầu hếu các lớp đều định nghĩa một phương thức initialize
để sử dụng khi sự khởi tạo là cần thiết cho các thể hiện.
21
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Phương thức new và initialize cung cấp các kỹ thuật mặc định cho việc tạo
lớp mới, nhưng các lớp có thể định nghĩa các phương thức khác, còn gọi là các
phương thức “công xưởng”, để trả về các thể hiện.
Các đối tượng trong Ruby không bao giờ cần phải được giải phóng một cách

tường minh, nhưng trong C và C++. Ruby sử dụng kỹ thuật dọn rác để tự động
huỷ đổi tượng khi chúng không cần nữa. Một đối tượng sẽ có khả năng bị thu dọn
nếu nó không thể truy cập đến được nữa – khi không còn tham chiếu nào đến đối
tượng ngoại trừ từ các đối tượng không thể truy cập tới khác.
Việc Ruby sử dụng kỹ thuật dọn rác làm cho các chương trình Ruby bớt nhạy
cảm với việc rò rỉ bộ nhớ hơn là các ngôn ngữ đòi hỏi các đối tượng và bộ nhớ
cần được phải xoá và giải phóng tường minh. Nhưng dọn rác không có nghĩa là
không thể rò rỉ bộ nhớ được: bất cứ đoạn code nào và tạo các tham chiếu tới các
đối tượng tồn tại lâu sẽ gây ra rò rỉ bộ nhớ. Giả sử ta dùng một bảng băm như một
bộ nhớ đệm. Nếu bộ nhớ đệm không được xoá bớt bằng một vài thuật toán loại bỏ
phần tử ít sử dụng nhất, thì đối tượng bộ nhớ đệm sẽ vẫn truy cập được miễn là
bảng băm vẫn truy cập được. Nếu bảng băm được tham chiếu thông qua biến toàn
cục, thì nó vẫn truy cập được miễn là trình thông dịch Ruby vẫn chạy.
4.3. Định danh đối tượng
Mọi đối tượng có một định danh đối tượng, một Fixnum, mà ta có thể lấy
được bằng phương thức object_id. Giá trị trả về bởi phương thức này là hằng số và
duy nhất trong suốt vòng đời của đối tượng. Khi đối tượng có thể truy cập được,
nó luôn có cùng ID, và không có đối tượng nào khác có cùng ID đó.
4.4. Lớp đối tượng và kiểu đối tượng
Có vài cách để xác định lớp của một đối tượng trong Ruby. Các tốt nhất là hỏi nó:
o = "test" # Đây là một giá trị
o.class # Trả về một đối tượng biểu diễn lớp String
Nếu ta quan tâm tới cấp lớp của một đối tượng, ta có thể hỏi bất cứ lớp nào lớp
cha của nó là gì:
o.class # String: o là một đối tượng String
o.class.superclass # Object: Lớp cha của String là Object
o.class.superclass.superclass # nil: Object không có lớp cha
Vậy một cách đơn giản nhất để kiểm tra lớp của một đối tượng bằng các so sánh:
o.class == String # true nếu o là một String
Phương thức instance_of? có chức năng tương tự:

22
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
o.instance_of? String # true nếu o là một String
Thông thường khi ta kiểm tra lớp của một đối tượng, ta cũng muốn biết đối
tượng đó có là thể hiện của một lớp con nào của lớp đó không. Để làm điều đó, ta
sử dụng phương thức is_a?, hoặc đồng nghĩa với nó kind_of?:
x = 1 # Đây là giá trị mà ta làm việc với
x.instance_of? Fixnum # true: là một thể hiện của Fixnum
x.instance_of? Numeric # false: instance_of? không kiểm tra sự kế thừa
x.is_a? Fixnum # true: x là một Fixnum
x.is_a? Integer # true: x là một Integer
x.is_a? Numeric # true: x là một Numeric
x.is_a? Comparable # true: cũng dùng được với các module mixin
x.is_a? Object # true với mọi giá trị của x
Mọi lớp có một lớp được định nghĩa đầy đủ trong Ruby, và lớp đó không
thay đổi trong suốt vòng đời của đối tượng. Nói cách khác, kiểu của một đối tượng
là lỏng hơn. Kiểu của một đối tượng liên quan đế lớp của nó, nhưng lớp đó chỉ là
một phần của kiểu của đối tượng. Khi ta nói về kiểu của một đối tượng, ta nói đến
tập các ứng xử đặc thù của đối tượng đó. Nói cách khác kiểu của một đối tượng là
tập các phương thức mà nó có thể thực hiện. (Định nghĩa này là đệ qui bởi vì
không chỉ bao gồm tên của các phương thức, mà kiểu của các tham số cho các
phương thức đó).
Trong lập trình Ruby, ta thường không quan tâm tới lớp của một đối tượng,
ta chỉ muốn biết liệu có thể gọi một phương thức nào với nó không. Ví dụ, giả sử
với toán tử <<. Mảng, xâu, file, và các lớp liên quan tới I/O khác định nghĩa toán
tử này như một thao tác thêm vào sau. Nếu ta viết phương thức này để sinh ra một
xâu đầu ra, ta có thể viết tổng quát để sử dụng toán tử này. Do đó phương thức của
ta có thể được gọi với bất cứ tham số nào thực hiện <<. Ta không quan tâm tới lớp
của tham số, chi cần biết là ta có thể thêm vào sau nó. Ta có thể kiểm tra điều này
với phương thức respond_to?:

o.respond_to? :"<<" # true nếu o có toán tử <<
Nhược điểm của hướng tiếp cận này là ta chỉ kiểm tra tên của phương thức,
không kiểm tra các tham số cho phương thức đó. Ví dụ, Fixnum và Bignum đều
thực thi << như là thao tác dịch bit trái và chờ nhận một số thay vì một xâu. Các
đối tượng số có vẻ như “thêm vào sau được” khi ta sử kiểm tra bằng respond_to?,
nhưng chúng sẽ gây ra lỗi khi đoạn code của ta thêm một xâu vào sau. Không có
giải pháp chung để giải quyết vấn đề này, nhưng cách đối phó với nó là loại bỏ các
đối tượng Numeric với phương thức is_a?:
o.respond_to? :"<<" and not o.is_a? Numeric
23
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Một ví dụ khác cho sự khác nhau giữa kiểu và lớp là lớp StringIO (trong thư
viện chuẩn của Ruby). StringIO cho phép đọc hoặc ghi vào các đối tượng xâu như
thể chúng là các đối tượng IO. StringIO mô phỏng các API IO – các đối tượng
StringIO định nghĩa các phương thức giống như của các đối tượng IO. Nhưng
StringIO không phải là một lớp con của IO. Nếu ta viết một phương thức nhận
tham số là một dòng dữ liệu, và kiểm tra lớp của phương thức đó với is_a? IO, thi
phương thức đó không thành công với các tham số của StringIO.
4.5. Sao chép đối tượng
Lớp Object định nghĩa hai phương thức khá gần với nhau để sao chép các đối
tượng. Cả clone và dup đều trả về một sao chép nông của đối tượng mà gọi nó.
Nếu đối tượng được sao chép bao gồm một trạng thái bên trong trỏ đến đối tượng
khác, chỉ có các tham chiếu đối tượng được sao chép, mà không sao chép các bản
thân đối tượng được tham chiếu.
Nếu đối tượng được sao chép định nghĩa phương thức initialize_copy, thì
clone và dup đơn giản chỉ là cấp phát một thể hiện mới và rỗng của lớp và gọi
phương thức initialize_copy của thể hiện rỗng này. Đối tượng được sao chép được
truyền như một tham số, và phương thức “khởi tạo sao chép này” có thể khởi tạo
sao chép theo cách mà ta mong muốn. Ví dụ, phương thức initialize_copy có thể
được sao chép đệ qui các dữ liệu bên trong của một đối tượng để đối tượng trả về

không chỉ là một bản sao nông của đối tượng gốc.
Các lớp cũng có thể chồng phương thức clone và dup để có được các phương
thức sao chép như mong muốn.
Có hai điểm khác nhau quan trọng giữa clone và dup được định nghĩa bởi
Object. Thứ nhất, clone sao chép cả trạng thái đóng bằng và bị hỏng của một đối
tượng, trong khia dup chỉ sao chép trạng thái bị hỏng; gọi dup của một đối tượng
đóng băng sẽ trả về một bản sao không đóng băng. Thứ hai, clone sao chép tất cả
các phương thức singleton của đối tượng, trong khi dup thì không.
24
Bài tập lớn Nguyên lý các ngôn ngữ lập trình – Tìm hiểu về Ruby
Chương IV: Phương thức, Proc, Lambda, và
Closure
Một phương thức là một block (được đặt tên) của đoạn code có tham số
tương ứng với một hoặc một vài đối tượng. Một lời gọi phương thức xác định tên
phương thức, đối tượng gắn với nó (thường được gọi là đối tượng nhận), và một
vài hoặc không có giá trị tham số nào được gán cho phương thức. Giá trị của biểu
thức cuối cùng trong phương thức sẽ là giá trị trả về của biểu thức gọi phương
thức.
Nhiều ngôn ngữ phân biệt giữa hàm, không gắn với một đối tượng nào, và
phương thức, gắn với một đối tượng nhận. Bởi vì Ruby là một ngôn ngữ thuần
hướng đối tượng, tất cả các phương thức đều là phương thức thực sự và đều gắn
với ít nhất một đối tượng.
Phương thức là một phần cơ bản của cú pháp Ruby, nhưng nó không phải là
giá trị mà Ruby có thể thực hiện được. Bởi vì phương thức trong Ruby không phải
là đối tượng như số, xâu và mảng. Tuy nhiên, ta có thể lấy một đối tượng Method
để biểu diễn một phương thức, và ta có thể gọi phương thức một cách không trực
tiếp thông qua đối tượng Method.
Phương thức không phải là dạng code duy nhất có tham số có thể thực thi
được của Ruby. Block là một chuỗi code có thể thực thi được và có thể có tham
số. Không giống các phương thức, block không có tên, và chúng chỉ có thể được

gọi một cách không trực tiếp thông qua phương thức iterator.
Block, giống như phương thức, không phải là đối tượng. Nhưng ta có thể tạo
một đối tượng để biểu diễn một block, và thực tế là điều này rất hay được dùng
trong chương trình Ruby. Một đối tượng Proc biểu diễn một block. Giống đối
tượng Method, ta có thể thực thi đoạn code trong một block thông qua Proc biểu
diễn nó. Có hai loại đối tượng Proc, gọi là proc và lambda, và có sự khác nhau
nhỏ giữa hai loại này. Cả proc và lambda đều là hàm chứ không phải là các
phương thức của một đối tượng. Một đặc tính quan trọng của proc và lambda là
chúng đều đóng kín: chúng ngăn chặn sự truy cập vào các biến cục bộ trong phạm
vi mà chúng được định nghĩa, thậm chí khi proc và lambda được gọi trong những
phạm vi khác nhau.
1. Định nghĩa phương thức đơn giản
Các phương thức được định nghĩa bằng từ khoá def. Tiếp theo là tên phương
thức và danh sách tên các tham số có thể có nằm trong dấu ngoặc. Đoạn code
Ruby tạo nên thân phương thức ở sau danh sách tham số, và cuối phương thức là
từ khoá end. Tên của các tham số có thể được sử dụng như là các biến trong thân
phương thức, và giá trị của các tham số này sẽ được truyền trong lời gọi phương
thức. Đây là một ví dụ về một phương thức:
25

×