Hàm là cách các ngôn ngữ lập trình gom nhóm các lệnh thành một khối, thực hiện một vài chức năng nhất định. Việc tạo ra hàm mang lại các lợi ích sau:
- Hàm giúp chia dự án ra thành các nhóm nhỏ mà nhiều người có thể viết cùng lúc được và ít ảnh hưởng đến nhau.
- Hàm giúp cho một chức năng nào đó được sử dụng lại đơn giản bằng cách gọi hàm thay vì lặp lại đoạn mã của chức năng đó. Việc này cũng giúp kích thước file mã nguồn giảm đáng kể nếu hàm đó được sử dụng nhiều lần.
- Hàm giúp ẩn chi tiết đoạn mã mà người viết không cho người sử dụng lại biết thông qua một số cú pháp nhất định. Chép lại đoạn mã thì không giấu gì được nữa.
Vì lẽ đó, ngôn ngữ lập trình nào cũng có hàm và Go không ngoại lệ. Một hàm trong Go được khai báo như sau:
func <tên hàm>(<danh sách tham số>) (<danh sách trả về>) {<Khối lệnh>}
- Danh sách tham số là dãy các cặp <tên tham số> <kiểu dữ liệu> liên tiếp nhau, cách nhau dấu phẩy ,. Các tham số khai báo cạnh nhau và cùng kiểu có thể khai báo tắt theo dạng <tên tham số 1>, <tên tham số 2> <kiểu dữ liệu>. Các tham số này được sử dụng trong hàm như là các biến cục bộ hàm. Hàm chấp nhận khai báo danh sách tham số chỉ gồm kiểu dữ liệu mà không có tên. Hàm có thể không có tham số.
- Danh sách trả về khai báo y như danh sách tham số nhưng thông thường chỉ gồm các kiểu dữ liệu cách nhau dấu phẩy theo thứ tự nhất định. Hàm có thể không có giá trị trả về và lúc này có thể gọi return để kết thúc hàm hoặc không cần gọi đến khi thực hiện hết khối lệnh. Khi có 1 giá trị trả về, cặp ngoặc tròn có thể bỏ. Trong trường hợp cần rõ ràng, tên từng giá trị trả về cũng sẽ được đặt. Lúc này khi gọi return có thể không cần nêu các biến chứa các giá trị trả về vì Go sẽ tự hiểu và lấy giá trị tương ứng.
- Khối lệnh gồm các lệnh sắp theo dòng, thường mỗi lệnh một dòng. Nếu nhiều lệnh cùng dòng, các lệnh sẽ cách nhau dấu chấm phẩy ;. Khối lệnh thường bắt đầu bằng khai báo biến cục bộ và kết thúc bằng lệnh return. Lệnh return <giá trị trả về thứ 1>,.., <giá trị trả về thứ n> sẽ kết thúc thực hiện các lệnh trong hàm và trả về các giá trị tương ứng. Nếu hàm có giá trị trả về nhưng không có gọi return, Go sẽ báo lỗi.
Chúng ta cùng phân tích ví dụ hoán đổi giá trị 2 biến i, j như sau:
1 package main 2 3 import "fmt" 4 5 func main() { 6 i := 5 7 j := 10 8 fmt.Println(i, " - ", j) // 5 - 10 9 i, j = swap(i, j) 10 fmt.Println(i, " - ", j) // 10 - 5 11 } 12 13 func swap(i1, i2 int) (int, int) { 14 return i2, i1 15 }
- Dòng 1 và 3 là khai báo package và package sử dụng.
- Dòng 13 đến 15 là khai báo hàm swap có 2 tham số là i1 và i2 và 2 giá trị trả về kiểu int. Chúng ta có thể khai báo tắt do i1 và i2 liên tiếp và cùng kiểu dữ liệu. Hàm này đơn giản chỉ trả về 2 giá trị theo thứ tự là i2, i1.
+ Dòng 6 và 7 khai báo 2 biến kiểu int i và j và gán giá trị cho chúng lần lượt là 5 và 10.
+ Dòng 8 cho kết quả in giá trị i và j ra màn hình. Kết quả là 5 và 10.
+ Dòng 9 gọi hàm swap với 2 tham số vào là i và j. Lúc này khi thực thi, i1 sẽ nhận giá trị của i là 5, còn i2 nhận giá trị của j là 10. Hàm trả về cặp i2, i1 tức cặp 10, 5. Giá trị trả về này được gán lại cho i và j nên kết quả ở dòng 9 sẽ cho ta biến i có giá trị 10 và biến j có giá trị 5 như kết quả in ra ở dòng 10.
Như vậy hàm swap đã đáp ứng yêu cầu hoán đổi giá trị 2 tham số đầu vào. Với các ngôn ngữ chỉ có phép 1 giá trị trả về thì để tạo hàm swap, chúng ta phải truyền tham số là con trỏ trỏ đến i và j để đổi trực tiếp giá trị của chúng trong hàm swap.
Hàm swap ở trên chỉ dùng để ví dụ về khai báo và sử dụng hàm thôi chứ với việc hoán đổi giá trị như trên thì Go cung cấp cách nhanh chóng và tiện lợi hơn rất nhiều. Chúng ta chỉ cần gán: i, j = j, i là xong!
Hàm swap ở trên chỉ dùng để ví dụ về khai báo và sử dụng hàm thôi chứ với việc hoán đổi giá trị như trên thì Go cung cấp cách nhanh chóng và tiện lợi hơn rất nhiều. Chúng ta chỉ cần gán: i, j = j, i là xong!
Khi gọi hàm cần phải truyền đủ tham số mà hàm yêu cầu. Go không có cơ chế giá trị mặc định cho tham số như một số ngôn ngữ khác. Các tham số có phạm vi như là biến cục bộ khai báo trong hàm. Thông thường các tham số ở hàm là truyền giá trị. Nghĩa là khi gọi hàm, giá trị mà các biến làm tham số mang sẽ được sao chép vào giá trị các biến tham số của hàm nên sẽ không ảnh hưởng đến giá trị các biến làm tham số sau khi hàm thực thi xong. Tuy vậy với các biến kiểu dữ liệu chứa giá trị phức tạp, kiểu như mảng hay cấu trúc thì việc sao chép dữ liệu này tốn nhiều chi phí (thời gian, bộ nhớ,...) nên trong trường hợp này, con trỏ sẽ được sử dụng. Lúc này biến làm tham số truyền địa chỉ của nó thay vì giá trị. Việc này không chỉ giảm chi phí truyền mà còn có thể tác động lên dữ liệu mà biến làm tham số lưu nếu tham số có thay đổi giá trị trong lúc hàm thực thi. Các biến kiểu tham chiếu khác như slice, map, v.v... đều có thể bị thay đổi giá trị khi truyền chúng vào các hàm như con trỏ.
Đôi lúc chúng ta gặp hàm được khai báo mà không có khối lệnh, chúng ta phải hiểu là hàm đã được cài đặt ở ngôn ngữ khác chứ không phải Go. Và trong dự án sẽ có sử dụng thư viện đã cài đặt hàm này.
Ghi chú (Comment)
Trong ví dụ trên và nhiều ví dụ ở các bài trước, chúng ta hay thấy sau một số dòng lệnh có ký hiệu // và thông tin sau đó không theo cú pháp lập trình. Ký hiệu đó báo với Go là thông tin sau nó chỉ là ghi chú thôi, không cần phải hiểu và biên dịch.
Như các ngôn ngữ khác, Go cung cấp 2 loại ghi chú:
- Ghi chú 1 dòng: sử dụng ký hiệu 2 dấu gạch chéo kề nhau //. Các thông tin trên cùng dòng và sau dấu // sẽ được xem là ghi chú, Go không quan tâm đến.
- Ghi chú khối: sử dụng cặp ký hiệu /* ... */. Tất cả những gì nằm giữa cặp ký hiệu này, bất kể bao nhiêu dòng đều không được Go quan tâm biên dịch.
Thường các trình soạn thảo hỗ trợ lập trình sẽ đổi màu những gì được xác định là ghi chú để chúng ta dễ phân biệt.
Ghi chú có thể áp dụng ở bất kỳ đâu trong file mã nguồn chứ không nhất thiết trong hàm. Chúng ta nên dùng ghi chú để mô tả chức năng một package, thông tin file mã nguồn, ý nghĩa các biến toàn cục, các kiểu dữ liệu mới, mục đích, các tham số của hàm và ý nghĩa một khối mã lệnh.
Ngoài chú thích thì ghi chú còn được dùng để bỏ tạm thời một đoạn code nào đó chưa cần dùng tới bằng cách chuyển đoạn code đó thành đoạn ghi chú. Tôi cũng hay dùng nó để loại bỏ tạm thời một khai báo package sử dụng nhưng chưa cần sử dụng đến lúc đó.
Trong bài tới chúng ta sẽ tìm hiểu về các cấu trúc lập trình cơ bản dùng trong hàm: phân nhánh và lặp.
Tóm tắt:
- Hàm là khối lệnh cùng mục đích xử lý một tác vụ nhất định:
+ Khai báo: func <tên hàm>(<danh sách tham số>) (<danh sách trả về>) {
<Khối lệnh>
}
+ Hàm không được phép đặt trùng tên.
+ Danh sách tham số hay danh sách trả về hay khối lệnh đều có thể không có.
+ Khi có tham số, cần truyền đủ và Go không có cơ chế giá trị mặc định cho tham số.
- Ghi chú: là cách bổ sung thêm các thông tin mà không sợ Go biên dịch nó. Có 2 loại: // cho ghi chú dòng và /* */ cho ghi chú nhiều dòng.
Hi anh, em thấy truyền tham số kiểu con trỏ mảng mà thay đổi giá trị của nó trong hàm thì sẽ khá khó để kiểm soát, ví dụ như trường hợp ở bài trước. Nếu ko phân tích kỹ thì ko thể hiểu được giá trị được in ra.
ReplyDeleteCòn dùng cách truyền giá trị thì sẽ tốn thời gian cho bộ nhớ và xử lý.
Trong thực tế nên dùng cách nào cho những trường hợp nào anh nhỉ?
Em cảm ơn :)
Với các kiểu dữ liệu chiếm bộ nhớ như mảng (array) hay cấu trúc (struct) thì nên truyền theo kiểu tham chiếu (truyền con trỏ). Chuyện thay đổi dữ liệu trong hàm thì mình cần phải kiểm soát.
ReplyDeleteVâng anh :)
Delete