12/08/2018, 16:19

Cơ bản về Golang language

Rất vui được gặp lại các bạn trong chủ đề giới thiệu ngày hôm nay của mình. Như tiêu đề mình đã nói, hôm nay mình xin được phép giới thiệu với các bạn một ngôn ngữ lập trình có tên là Golang(gọi tắt kaf Go). Về cơ bản thì ngôn ngữ này lấy cảm hứng dựa trên một ngôn ngữ cơ sở đó là ngôn ngữ lập ...

Rất vui được gặp lại các bạn trong chủ đề giới thiệu ngày hôm nay của mình. Như tiêu đề mình đã nói, hôm nay mình xin được phép giới thiệu với các bạn một ngôn ngữ lập trình có tên là Golang(gọi tắt kaf Go). Về cơ bản thì ngôn ngữ này lấy cảm hứng dựa trên một ngôn ngữ cơ sở đó là ngôn ngữ lập trình C, và cao hơn một chút đó là C++.

Ngôn ngữ lập trình Go là ngôn ngữ nguồn mở của Google, giúp bạn dễ dàng tạo được phần mềm đơn giản, ổn định và hiệu quả. Go là một phần trong dòng ngôn ngữ lập trình của nhóm Communicationg Sequential Processes của Tony Hoare đưa ra, ngoài Go còn có Occam, Erlang, Newsqueak và Limbo. Go có một tính nặng hữu dụng hơn hầu hết những ngôn ngữ lập trình cơ bản đó là concurrency. Sau đây mình xin giới thiệu về một số tính năng của Go

1: Slice

Mình có đoạn code như sau:

package main
import "fmt"
func fib() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    f := fib()
    fmt.Println(f(), f(), f(), f(), f())
}

Ngôn ngữ Go triển khai ý tưởng về mảng (array) với slice. Một slice trỏ tới một mảng giá trị và có một độ dài nhất định. []T là một slice với các yếu tố của loại T. Trong ví dụ ở trên, mình đã sử dụng để tính dãy Fibonacci. trong đó import là bản mở rộng của include trong C và C++, cú pháp := để khởi tạo một biến, trình biên dịch sẽ cho ta một kiểu dữ liệu khi có thể.

2: Map

Khai báo Go map gán khoá key đến giá trị. Với slice, bạn có thể tạo một map bằng make, không phải new. Trong Go, chúng ta khai báo một map như sau:

var x map[string]int

Chúng ta dùng từ khóa map, sau đó là kiểu dữ liệu của khóa trong cặp dấu ngoặc vuông [], rồi đến kiểu dữ liệu của giá trị. Trong dòng code trên mỗi phần tử trong map x có khóa kiểu string mang giá trị kiểu int. Hoặc chúng ta có thể tạo một map bằng cách dùng hàm make:

x := make(map[string]int)

Chúng ta sẽ dùng khóa để truy xuất giá trị trong map:

package main

import "fmt"

func main() {
    x := make(map[string]int)
    x["key"] = 10
    fmt.Println(x["key"])
}

Đoạn code trên sẽ in số 10 ra màn hình. Chúng ta có thể xóa một phần tử trong map bằng hàm delete():

delete(x, "key")

3: Struct

Một struct là một kiểu dữ liệu đặc biệt, kiểu này chứa biến thuộc các kiểu dữ liệu khác, các biến ở đây thường được gọi là các trường hoặc các thuộc tính… Ví dụ chúng ta định nghĩa struct có tên Circle (hình tròn) và Rectangle (hình chữ nhật) như sau:

type Circle struct {
    x float64
    y float64
    z float64
}

type Rectangle struct {
    x1 float64
    y1 float64
    x2 float64
    y2 float64
}

Để định nghĩa một struct thì chúng ta dùng từ khóa type(tương tự type def trong C), từ khóa này báo cho Go biết là chúng ta đang định nghĩa một kiểu dữ liệu mới, theo sau là tên kiểu dữ liệu do chúng ta tự đặt, tiếp theo là từ khóa struct để báo cho Go biết là chúng ta đang định nghĩa một struct, cuối cùng là danh sách các trường của struct này. Mỗi trường chúng ta khai báo gồm tên trường và tên kiểu dữ liệu. Ngoài ra chúng ta có thể khai báo ngắn gọn lại như sau:

type Circle struct {
    x, y, r float64 
}

type Rectangle struct {
    x1, y1, x2, y2 float64
}

Chúng ta khai báo biến kiểu struct giống như khai báo một biến bình thường:

var c Circle
var r Rectangle

hoặc dùng new:

c := new(Circle)
r := new(Rectangle)

Nếu muốn khởi tạo và gán giá trị cho các trường luôn thì chúng ta làm như sau:

c := Circle{x: 0, y : 0, r : 5}
r := Rectangle{x1 : 0, y1 : 10, x2 : 0, y2 : 10}

Hoặc chúng ta không cần ghi tên trường ra nhưng phải truyền kiểu dữ liệu theo đúng thứ tự đã định nghĩa:

c := Circle{0, 0, 5}
r := Rectangle{0, 0, 10, 10}

Để thao tác các trường thì chúng ta dùng dấu chấm “.” như sau:

fmt.Println(c.x, c.y, c.z)
c.x = 10
c.y = 5

4: Interface

Trong các ví dụ trên, chúng ta đã định nghĩa 2 struct là Rectangle (hình chữ nhật) và Circle (hình tròn), cả 2 struct này đều một phương thức tính diện tích có tên giống nhau là area(). Chúng ta có thể “gộp chung” 2 phương thức đó vào một kiểu dữ liệu khác có tên là Interface:

type Shape interface {
    area() float64
}

Trong ví dụ trên chúng ta định nghĩa một Interface có tên Shape, interface này có một phương thức là area(). Để định nghĩa một interface thì cũng giống như định nghĩa một struct, chúng ta dùng từ khóa type, tiếp đến là tên interface rồi đến từ khóa interface, sau đó là danh sách các phương thức trong cặp dấu ngoặc nhọn {}. Interface thực ra cũng không hẳn là một kiểu dữ liệu như struct vì interface chỉ chứa các phương thức chứ không chứa các trường, interface cũng không có phần định nghĩa phương thức ở ngoài như các struct, chúng chỉ chứa tên phương thức là hết. Vậy thì việc sử dụng interface có gì hay? Câu trả lời là chúng ta có thể dùng interface để thực hiện tính toán trên nhiều kiểu struct khác nhau mà không quan tâm các struct đó là gì. Ví dụ:

package main 

import (
    "fmt"
    "math"
)

type Shape interface {
    area() float64
}

type Circle struct {
    x, y, r float64
}

type Rectangle struct {
    x1, y1, x2, y2 float64
}

func distance(x1, y1, x2, y2 float64) float64 {
    a := x2 - x1
    b := y2 - y1
    return math.Sqrt(a*a + b*b)
}

func (c *Circle) area() float64 {
    return math.Pi * c.r*c.r
}

func (r *Rectangle) area() float64 {
    l := distance(r.x1, r.y1, r.x1, r.y2)
    w := distance(r.x1, r.y1, r.x2, r.y1)
    return l * w
}

func totalArea(shapes ...Shape) float64 {
    var area float64
    for _, s := range shapes {
    area += s.area()
    }
    return area
}

func main() {
    c := Circle{0, 0, 5}
    r := Rectangle{0, 0, 10, 10}

    fmt.Println(totalArea(&c, &r))
}

Trong ví dụ trên, chúng ta định nghĩa hàm totalArea() có chức năng tính tổng diện tích của bất cứ hình nào, hàm này nhận vào tham số là kiểu Shape, nhưng chúng ta có thể truyền vào kiểu Circle hoặc kiểu Rectangle đều được, nếu chúng ta truyền vào kiểu Circle, khi gọi phương thức area() thì Go sẽ gọi phương thức area() của struct Circle, và ngược lại khi truyền vào Rectangle thì gọi phương thức area() của Rectangle.

5: Hàm

Bài toán tính giá trị trung bình của một dãy số là rất phổ biến, do đó chúng ta nên định nghĩa hàm riêng để thực hiện công việc này. Ví dụ:

package main

import "fmt"

func average(input []float64) float64 {
    total := 0.0
    for _, v := range input {
        total += v
    }
    return total / float64(len(input))
}

func main() {
    xs := []float64{98, 93, 77, 82, 83}
    fmt.Println(average(xs))
}

Để khai báo một hàm thì chúng ta dùng từ khóa func, tiếp theo là tên hàm, rồi danh sách các tham số đầu vào trong cặp dấu ngoặc tròn (), rồi đến kiểu dữ liệu trả về, cuối cùng là phần thân hàm nằm trong cặp dấu ngoặc nhọn {}. Tham số đầu vào và kiểu dữ liệu trả về có thể không có cũng được.

Trong đoạn code trên chúng ta định nghĩa hàm average() có kiểu trả về là float64, hàm này nhận một tham số đầu vào là biến xs. Trong phần thân hàm chúng ta thay câu lệnh fmt.Println() thành câu lệnh return, câu lệnh return có chức năng kết thúc hàm và “trả về” một giá trị cho hàm đã gọi nó, để gọi một hàm thì chúng ta chỉ đơn giản là ghi tên hàm đó ra rồi đưa tham số vào, ở đây chúng ta gọi hàm average() trong câu lệnh fmt.Println(average(xs)) trong hàm main(). Cả 2 đoạn code trên đều cho ra kết quả giống nhau. Lưu ý:

  • Một hàm không thể đọc một biến được định nghĩa trong hàm khác(không truyền biến đó vào thì hàm kia không nhận được)
  • Hàm được gọi chồng lên nhau(hàm A gọi hàm B, Trong hàm B lại gọi hàm C)

Hàm trả về nhiều giá trị

func f() (int, int) {
    return 5, 6
}

func main() {
   x, y := f()
}

Tùy biến số lượng tham số

package main

import "fmt"

func add(args ...int) int {
    total := 0
    for _, v := range args {
        total += v
    } 
    return total
}

func main() {
    fmt.Println(add(1, 2, 3))
}

Chúng ta thêm 3 dấu chấm "..." vào trước tên kiểu dữ liệu của tham số cuối cùng, Go sẽ hiểu rằng tham số này có thể có 0 hoặc nhiều giá trị được truyền vào, khi gọi hàm chúng ta có thể truyền vào 0 hoặc nhiều giá trị, ngăn cách nhau bởi dấu phẩy, đặc tính này cho phép hàm nhận tham số một cách linh hoạt hơn.

Kết luận

Trong ngày hôm nay mình xin được phép giới thiệu cơ bản đến đây, mình sẽ cố gắng giới thiệu nốt phần còn lại trong bài sắp tới. Rất mong các bạn có thể đóng góp ý kiến để cho bài mình có thể hoàn thiện hơn. Mình xin chân thành cảm ơn

0