[Golang cơ bản] Function trong Golang - Tính năng và cách sử dụng

Post on: 2023-03-30 18:38:11 | in: Golang
Function (hàm) là một phần không thể thiếu trong bất kỳ ngôn ngữ lập trình nào, và Golang cũng không phải là một ngoại lệ.

Tổng quan

Một hàm là một nhóm các câu lệnh thực hiện một tác vụ cụ thể. Trong GO, các hàm là các biến bậc nhất. Chúng có thể được truyền đi như bất kỳ biến nào khác.

Một số điểm cần lưu ý về tên hàm

  • Tên hàm không thể bắt đầu bằng một số.
  • Tên hàm là phân biệt chữ hoa chữ thường. Do đó, sum, Sum, SUM là các hàm khác nhau.
  • Một hàm có tên bắt đầu bằng một chữ cái in hoa sẽ được xuất ra ngoài gói của nó và có thể được gọi từ các gói khác. Một hàm có tên bắt đầu bằng chữ cái thường sẽ không được xuất và chỉ hiển thị trong phạm vi của gói của nó.

Khai báo một function

func func_name(input_parameters) return_values{
  //body
}

Một hàm trong Golang

  • Được khai báo bằng từ khóa "func"
  • Có một tên
  • Các tham số đầu vào được phân tách bằng dấu phẩy, có thể không có tham số nào hoặc có nhiều hơn một tham số
  • Các giá trị trả về được phân tách bằng dấu phẩy, có thể không có giá trị trả về hoặc có nhiều hơn một giá trị trả về
  • Thân hàm
  • Có thể trả về nhiều giá trị

Một ví dụ về hàm: Hàm dưới đây

  • Có tên là "sum"
  • Nhận hai đối số kiểu int
  • Trả về một giá trị duy nhất kiểu int.
func sum(a int, b int) int {
  return a + b
}

Gọi một function

Một hàm trong Go có thể được gọi trong Go như sau

results := sum(2, 3)

Một số điểm cần lưu ý khi gọi một hàm

  • Nếu gọi hàm của một package khác thì cần phải thêm tiền tố là tên package. Ngoài ra, lưu ý rằng chỉ có thể gọi được các hàm đã được xuất (exported), nghĩa là các hàm có tên bắt đầu bằng chữ cái viết hoa, qua các package khác.
  • Trong cùng một package, hàm có thể được gọi trực tiếp bằng cách sử dụng tên của hàm được phụ đề bởi dấu ngoặc đơn ().

Tham số hàm

  • Như đã đề cập ở trên, hàm có thể có không hoặc nhiều hơn một đối số
  • Loại cho các kiểu giống nhau có thể được chỉ định chỉ một lần. Ví dụ, hàm sum ở trên có thể được viết lại như sau
func sum(a, b int)
  • Một bản sao của tất cả các đối số sẽ được tạo ra khi gọi một hàm.

Giá trị trả về

  • Như đã đề cập ở trên, một hàm có thể có một hoặc nhiều giá trị trả về. Giả sử có một hàm sum_avg trả về hai giá trị: Tổng và Trung bình. Ví dụ về nhiều giá trị trả về:
func sum_avg(a, b int) (int, int)
  • Theo quy ước, lỗi sẽ được trả về như là đối số cuối cùng trong một hàm. Ví dụ
func sum(a, b int) (int, error)
  • Thu thập nhiều giá trị trả về trong hàm gọi. Trong ví dụ dưới đây
result, err := sum(2, 3)

Giá trị trả về được đặt tên

Một hàm trong Go có thể có các giá trị trả về được đặt tên. Với các giá trị trả về được đặt tên, các giá trị trả về không cần được khởi tạo trong hàm. Các biến được đặt tên được chỉ định trong chữ ký của hàm. Nếu không có giá trị được đặt tên, chỉ có kiểu trả về được chỉ định. 

  • Xem ví dụ dưới đây: result là giá trị trả về được đặt tên.
func sum(a, b int) (result int)
  • Với giá trị trả về được đặt tên, kiểu cho các kiểu giống nhau có thể chỉ định một lần duy nhất.
func sum_avg(a, b int) (sum, avg int)

Các cách sử dụng Function

  • Sử dụng chung
  • Hàm làm kiểu dữ liệu
  • Hàm là giá trị

Sự khác biệt giữa hàm là kiểu dữ liệu và hàm là giá trị là trong kiểu dữ liệu chúng ta chỉ sử dụng chữ ký của hàm trong khi trong giá trị của hàm chữ ký cùng với thân hàm được sử dụng. Hãy xem từng phần này chi tiết để rõ hơn.

Sử dụng chung

Dưới đây là một ví dụ về cách sử dụng chung của một hàm. Chúng ta có một hàm sum trong ví dụ dưới đây, nhận vào hai số nguyên làm đối số đầu vào và trả về tổng của chúng.

package main

import "fmt"

func main() {
    res := sum(2, 3)
    fmt.Println(res)
}

func sum(a, b int) int {
    return a + b
}

Output:

5

Hàm làm kiểu dữ liệu

Trong Go, hàm cũng là một kiểu dữ liệu. Hai hàm sẽ có cùng kiểu dữ liệu nếu:

  • Chúng có cùng số đối số với mỗi đối số là cùng một kiểu dữ liệu
  • Chúng có cùng số giá trị trả về và mỗi giá trị trả về đều là cùng một kiểu dữ liệu

Kiểu hàm hữu ích trong

  • Trường hợp của các hàm bậc cao như chúng ta đã thấy ở ví dụ trên. Kiểu đối số và kiểu trả về được chỉ định bằng kiểu hàm.
  • Trong trường hợp xác định các giao diện trong Go, như trong giao diện, chỉ có kiểu hàm được chỉ định. Bất cứ thứ gì triển khai giao diện này phải định nghĩa một hàm cùng kiểu.

Hãy xem ví dụ về kiểu hàm trong giao diện. Lưu ý rằng giao diện "shape" chỉ xác định kiểu hàm.

area() int
getType() string

Code:

package main
import "fmt"
func main() {
    var shapes []shape
    s := &square{side: 2}
    shapes = append(shapes, s)
    r := &rectangle{length: 2, breath: 3}
    shapes = append(shapes, r)
    for _, shape := range shapes {
        fmt.Printf("Type: %s, Area %d\n", shape.getType(), shape.area())
    }
}
type shape interface {
    area() int
    getType() string
}
type rectangle struct {
    length int
    breath int
}
func (r *rectangle) area() int {
    return r.length * r.breath
}
func (r *rectangle) getType() string {
    return "rectangle"
}
type square struct {
    side int
}
func (s *square) area() int {
    return s.side * s.side
}
func (s *square) getType() string {
    return "square"
}

Output:

Type: square, Area 4
Type: rectangle, Area 6

Khai báo hàm dưới dạng kiểu người dùng (Function as user defined type)

Để khai báo hàm dưới dạng kiểu người dùng (Function as user defined type) trong Golang, ta sử dụng từ khóa type.

package main

import "fmt"

func main() {
    areaF := getAreaFunc()
    print(3, 4, areaF)
}

type area func(int, int) int

func print(x, y int, a area) {
    fmt.Printf("Area is: %d\n", a(x, y))
}

func getAreaFunc() area {
    return func(x, y int) int {
        return x * y
    }
}

Output:

12

Hàm là giá trị (hoặc hàm vô danh)

Trong Go, một hàm được coi là biến cấp độ đầu tiên (first-order variable) nên nó có thể được sử dụng như một giá trị. Nó còn được gọi là hàm vô danh (anonymous functions) bởi vì nó không có tên và có thể được gán vào một biến và truyền đi.

Thường thì hàm vô danh được tạo ra để sử dụng trong thời gian ngắn hoặc với chức năng giới hạn. Hãy xem ví dụ dưới đây. Trong ví dụ này, biến "max" được gán một hàm. Vì "max" được tạo ra bởi một hàm không có tên, nó là một hàm vô danh.

package main

import "fmt"

var max = func(a, b int) int {
    if a >= b {
        return a
    }
    return b
}

func main() {
    res := max(2, 3)
    fmt.Println(res)
}

Output:

3

Cách sử dụng đặc biệt của Function

Function Closures

Function closures là các hàm vô danh có thể truy cập vào các biến được khai báo bên ngoài hàm và giữ giá trị hiện tại giữa các lần gọi hàm khác nhau. Hãy xem một ví dụ. Trong trường hợp dưới đây, giá trị count được giữ giá trị giữa các lần gọi hàm khác nhau của hàm modulus.

package main

import (
    "fmt"
)

func main() {
    modulus := getModulus()
    modulus(-1)
    modulus(2)
    modulus(-5)
}

func getModulus() func(int) int {
    count := 0
    return func(x int) int {
        count = count + 1
        fmt.Printf("modulus function called %d times\n", count)
        if x < 0 {
            x = x * -1
        }
        return x
    }
}

Output:

modulus function called 1 times
modulus function called 2 times
modulus function called 3 times

Higher Order Function

Higher-order functions là các hàm chấp nhận một hàm như một kiểu hoặc trả về một hàm. Vì hàm là biến bậc một trong Golang nên chúng có thể được chuyển đi và trả về từ một số hàm và được gán vào một biến. Trong ví dụ dưới đây:

  • Hàm print nhận một hàm kiểu func(int, int) int làm đối số.
  • Hàm getAreaFunc trả về một hàm kiểu func(int, int) int.
package main

import "fmt"

func main() {
    areaF := getAreaFunc()
    print(3, 4, areaF)
}

func print(x, y int, area func(int, int) int) {
    fmt.Printf("Area is: %d\n", area(x, y))
}

func getAreaFunc() func(int, int) int {
    return func(x, y int) int {
        return x * y
    }
}

Output:

12

IIF or Immediately Invoked Function

IIF hoặc Immediately Invoked Function là những hàm có thể được định nghĩa và thực thi cùng một lúc.

package main

import "fmt"
​​​​​​​
func main() {
    squareOf2 := func() int {
        return 2 * 2
    }()
    fmt.Println(squareOf2)
}

Output:

4

Use Cases of IIF functions

  • Khi bạn không muốn tiết lộ logic của hàm bên trong hoặc bên ngoài gói. Ví dụ, giả sử có một hàm đang thiết lập một giá trị nào đó. Bạn có thể đóng gói tất cả logic của hàm này vào một hàm IIF. Hàm này sẽ không được phép gọi bên trong hoặc bên ngoài gói.

Variadic Function

Trong Go, một hàm có thể chấp nhận một số lượng đối số động được gọi là hàm Variadic. Dưới đây là cú pháp cho hàm Variadic. Ba dấu chấm được sử dụng như một tiền tố trước kiểu dữ liệu.

func add(numbers ...int)

Code:

package main

import "fmt"

func main() {
    fmt.Println(add(1, 2))
    fmt.Println(add(1, 2, 3))
    fmt.Println(add(1, 2, 3, 4))
}
​​​​​​​
func add(numbers ...int) int {
    sum := 0
    for _, num := range numbers {
        sum += num
    }
    return sum
}

Output:

3
6
10

Methods

Một method có một đối số receiver. Khi bạn gán một hàm cho một kiểu dữ liệu nào đó, thì hàm đó trở thành một method cho kiểu đó. Receiver có thể là một struct hoặc bất kỳ kiểu dữ liệu nào khác. Method sẽ có quyền truy cập vào các thuộc tính của receiver và có thể gọi các method khác của receiver.

Function:

func some_func_name(arguments) return_values

Method:

func (receiver receiver_type) some_func_name(arguments) return_values

Đây là sự khác biệt duy nhất giữa hàm và phương thức, nhưng vì điều đó mà chúng khác nhau về các chức năng mà chúng cung cấp.

  • Một hàm có thể được sử dụng như các đối tượng first-order và có thể được truyền đi trong khi phương thức thì không được.
  • Phương thức có thể được sử dụng để liên kết trên receiver trong khi hàm thì không thể được sử dụng cho việc này.
  • Có thể tồn tại các phương thức khác nhau cùng tên với các receiver khác nhau, nhưng không thể tồn tại hai hàm khác nhau cùng tên trong cùng một package.

Tổng kết

Đó là tất cả về hàm trong Golang. Hy vọng bạn đã thích bài viết này. Vui lòng chia sẻ phản hồi hoặc sửa đổi/cải thiện trong các ý kiến ​​bên dưới.

Tag: golang cơ bản go