Bài 5 - Cấu trúc file mã nguồn Go


  • Trùm cuối

    Cấu trúc mã nguồn Go

    Lập trình là chuỗi những khai báo và biểu thức. Trong bài trước, chúng ta đã thấy mỗi file mã nguồn .go gồm có 3 nhóm khai báo là khai báo package thuộc về, package sử dụng và khai báo hằng, kiểu dữ liệu, biến và hàm. Ở bài này chúng ta sẽ tìm hiểu kỹ hơn về các khai báo này trước khi đi vào tìm hiểu cú pháp cũng như những đặc tính thú vị của ngôn ngữ Go.

    aa48562d-71aa-498c-b752-9becdf3a07ce-image.png

    Trước hết chúng ta cùng thống nhất vài thuật ngữ:

    • Dữ liệu: là các thông tin cần được mô tả và lưu trữ trên máy tính như chữ hay số.
    • Kiểu dữ liệu: là mô tả về các loại dữ liệu khác nhau. Go cung cấp một số kiểu dữ liệu đơn giản như số, chuỗi, luận lý (đúng, sai) và các kiểu dữ liệu tập hợp, tham chiếu, v.v... Chúng ta có thể tự định nghĩa kiểu dữ liệu riêng của mình.
    • Biến số: là các đối tượng lưu giữ những giá trị ứng với các kiểu dữ liệu đã khai báo trước đó. Chúng ta sử dụng biến số (hay gọi tắt là biến) thông qua tên của nó. Giá trị một biến có thể thay đổi trong suốt chu kỳ sử dụng.
    • Hằng số: là biến số không thay đổi giá trị một khi đã được gán giá trị.
    • Hàm: là một khối các câu lệnh được tập hợp dưới một cấu trúc nhận một số dữ liệu và thực hiện một mục đích nào đó: thay đổi dữ liệu vào, trả về một số dữ liệu khác hoặc thực hiện một tác vụ nào đó, v.v...

    Đặt tên

    Tên đối tượng là thuộc tính bắt buộc khi khai báo nên chúng ta phải đặt tên cho rất nhiều đối tượng trong Go từ tên package, tên biến, hằng số, hàm, v.v...

    Go có quy tắc đặt tên như sau:

    • Tên bắt đầu bằng chữ cái hoặc dấu gạch dưới _.
    • Sau đó thì số, chữ cái hay dấu _ đều được chấp nhận.
    • Go phân biệt chữ hoa và chữ thường trong tên nên 2 biến a và A là hoàn toàn khác nhau.
    • Go chấp nhận tên được tạo từ các ký tự Unicode nên chúng ta có thể đặt tên tiếng Việt được thay vì phải dùng tiếng Anh như đa số ngôn ngữ lập trình khác.
    • Go có 25 từ khóa đặc biệt mà chúng ta không được dùng để đặt tên. Đó là:

    break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var

    Tất cả những tên khác 25 từ khóa trên mà Go có dùng để đặt cho kiểu dữ liệu, hằng số, hay trong cú pháp ngôn ngữ đều có thể dùng để đặt tên nhưng tôi khuyên chúng ta không nên dùng để tránh rắc rối trừ những trường hợp đặc biệt sẽ nói sau.

    Có rất nhiều cách đặt tên, tùy bạn chọn nhưng Go khuyến khích sử dụng đặt tên theo kiểu lạc đà (camel case), nghĩa là sẽ viết hoa chữ cái đầu ở mỗi từ khi dùng để ghép thành tên tạo sự uốn lượn như hình dạng bên trên lưng lạc đà. Ví dụ khi muốn đặt tên hàm thực hiện chức năng tính tổng 2 số, Go khuyến khích các đặt tên TínhTổngHaiSố hoặc tínhTổngHaiSố, thay vì tínhtổnghaisố hay TÍNHTỔNGHAISỐ hay tính_tổng_hai_số. Với các ký tự viết tắt của một chuỗi thì Go khuyên nên cùng hoa cùng thường ở mọi ký tự viết tắt. Ví dụ: export2HTML hoặc export2html thay vì exportToHtml.

    Go không quy định độ dài của tên nhưng khuyến khích tên ngắn theo nguyên tắc phạm vi sử dụng càng rộng thì tên càng dài và rõ ràng. Ví dụ biến đếm trong đoạn mã có thể đơn giản là i thay vì dài lê thê nói rõ nó là gì như ngôn ngữ Objective-C.

    Package và file

    a75a650b-cfd0-4e00-9244-89dd68edfa1a-image.png

    Package (gói) trong Go phục vụ các mục đích tương tự như thư viện hoặc các module trong các ngôn ngữ khác, hỗ trợ đóng gói chức năng theo module, biên dịch riêng biệt, và tái sử dụng. Các mã nguồn của một package nằm trong một hoặc nhiều file .go, thường là trong một thư mục có cùng tên.

    Mỗi package phục vụ như là một không gian tên riêng cho các đối tượng khai báo của nó. Ví dụ, hai hàm có cùng tên thuộc hai package khác nhau sẽ đại diện cho 2 đối tượng hàm khác nhau. Để sử dụng một hàm thuộc một package khác, chúng ta phải khai báo tên package đi kèm trước tên hàm. Dấu chấm dùng để phân tách package và hàm. Như ví dụ ở bài trước, chúng ta gọi hàm Println của package fmt với khai báo: fmt.Println.

    Package cũng cho phép chúng ta che giấu thông tin bằng cách kiểm soát các đối tượng có thể được nhìn thấy và truy xuất bên ngoài package. Trong Go, một quy tắc đơn giản để nhận dạng đối tượng có thể nhìn thấy bên ngoài package hay không thông qua ký tự đầu tiên của tên đối tượng: chữ hoa thì sẽ nhìn thấy được. Như hàm Println ở trên có chữ P hoa. Nếu là p, chúng ta sẽ không sử dụng được. Quá đơn giản nếu so sánh với public, protected, private trong C++ hay java phải không các bạn.

    Tất cả các file thuộc cùng 1 package phải khai báo tên package ở ngay đầu file. Tất cả các khai báo trong cùng package thì các file cùng package đều thấy và sử dụng được. Những khai báo có chữ cái đầu là chữ hoa thì bên ngoài package cũng có thể sử dụng thông qua khai báo các package sử dụng.

    Khai báo các package sử dụng với từ khóa import. Go kiểm soát rất chặt chẽ các package mà chúng ta khai báo import, dư và thiếu đều bị báo lỗi lúc biên dịch. Lưu ý là khai báo import có tác dụng ở phạm vi file mã nguồn nên mặc dù cùng package nhưng 2 file mã nguồn cùng sử dụng fmt thì chúng ta đều phải khai báo import ở cả 2 file. Đối với package do người khác tạo để trên internet và có hỗ trợ các công cụ quản lý code như subversion, git, mercurial, hay bazaar, Go cung cấp công cụ để lấy code về và biên dịch tên là go get. Chi tiết sẽ đề cập khi sử dụng package trên internet.

    Hằng số

    Trong lập trình, đôi khi chúng ta phải xử lý những giá trị cố định, ví dụ số Pi hay số e. Nếu ở những nơi cần sử dụng tới, chúng ta phải nhập giá trị này thì đôi khi việc này khá bất tiện nhất là với số hay chuỗi dài. Như các ngôn ngữ khác, Go cung cấp hằng số. Go cho phép tạo giá trị hằng cho 3 loại dữ liệu là số, chuỗi và luận lý.

    Khai báo hằng số là khai báo một giá trị cố định và do đó khi biên dịch, Go sẽ thay thế tất cả những nơi có hằng số thành giá trị mà hằng số này mang. Do đó việc thay đổi giá trị hằng số trong lúc lập trình là không được phép.

    Cách khai báo hằng số là const <tên hằng> <kiểu dữ liệu> = <giá trị hằng> nhưng thường kiểu dữ liệu bị loại bỏ. Ví dụ:

    const pi float64 = 3.14159265359
    

    hoặc

    const pi = 3.14159265359
    

    Trường hợp có nhiều giá trị hằng cần khai báo, chúng ta có thể sử dụng cách khai báo từng áp dụng cho import như ví dụ sau:

    const ( 
       pi = 3.14159265358979323846264338328
        e = 2.718281828459045235360287471353
    )
    

    Đôi lúc chúng ta cần khai báo các hằng số mang tính chất tuần tự, liệt kê như các thứ trong tuần hay các tháng trong năm. Chúng ta có thể khai báo như cách khai báo cho pi và e nhưng Go cung cấp cách tiện hơn với từ khóa iota như ví dụ sau:

    const (
        Sunday = iota
        Monday
        Tuesday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
    )
    

    iota ở đây sẽ gán giá trị 0 ở hằng đầu tiên và tăng 1 lên qua các hằng tiếp theo. Kết quả là Sunday sẽ có giá trị 0, Monday giá trị 1 cho đến Saturday có giá trị 6.

    Chúng ta có thể tạo ra khai báo iota phức tạp hơn bằng cách tạo biểu thức cho iota. Ví dụ muốn tạo danh sách các ngày trong tuần ở trên nhưng thứ 2 có giá trị 2 và Chủ nhật có giá trị 8 thì chúng ta đơn giản chuyển lại như sau:

    const (
        Monday = 2 + iota
        Tuesday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
        Sunday
    )
    

    Để đảm bảo giá trị chính xác cho các hằng số, Go có cơ chế đặc biệt lưu trữ vượt quá khả năng của các kiểu dữ liệu do Go cung cấp. Ví dụ như số Pi ở trên, chúng ta có thể sử dụng hằng số Pi do gói math cung cấp với độ chính xác cao hơn rất nhiều giá trị tối đa mà kiểu thập phân trên Go hỗ trợ.

    Biến số

    Biến số là một đối tượng rất quan trọng trong lập trình. Nó giúp chúng ta lưu giữ các giá trị cần thiết trong quá trình lập trình. Biến được khai báo qua từ khóa var: var <tên biến> <kiểu dữ liệu> = <giá trị khởi tạo>
    Với khai báo này, Go sẽ tạo cho chúng ta một vùng nhớ ứng với kiểu dữ liệu được chỉ định, gắn với tên được đặt và giá trị khởi tạo sẽ được lưu vào vùng nhớ này. Mỗi khi sử dụng biến thông qua gọi tên biến, giá trị trong vùng nhớ này được lấy ra và sử dụng.

    Một trong hai thành phần ở khai báo trên là <kiểu dữ liệu> hoặc <giá trị khởi tạo> là không bắt buộc, có thể không cần khai báo. Lúc đó Go sẽ ứng xử như sau:

    • Nếu không có <kiểu dữ liệu>, dựa trên giá trị khởi tạo, Go sẽ khai báo ngầm giúp ta kiểu dữ liệu mà Go nghĩ biến chúng ta muốn đặt thuộc về. Ví dụ: var i = 1 thì Go sẽ gán kiểu dữ liệu int (số nguyên) cho biến i.
    • Nếu không có <giá trị khởi tạo>, Go sẽ gán giá trị zero mặc định cho biến: 0 cho các kiểu dữ liệu số, false (sai) cho kiểu dữ liệu luận lý, chuỗi rỗng "" cho kiểu chuỗi ký tự và nil cho các kiểu dữ liệu tham chiếu (sẽ tìm hiểu trong bài tới). Việc gán giá trị mặc định này là rất có ích cho chúng ta để tránh những lỗi ngớ ngẩn mà do quên khởi tạo gây ra, nhất là với người mới lập trình.

    Ngoài kiểu khai báo chuẩn như trên, Go có nhiều kiểu khai báo biến khác:

    • Khai báo hàng loạt: Chúng ta có thể khai báo cùng lúc nhiều biến với từ khóa var như sau:
    var i, j, k, l int
    

    hoặc

    var i, j, s = 1, 2.3, "Số"
    
    • Khai báo tắt: Khi khai báo biến cục bộ thuộc một hàm, Go cung cấp thêm một cách khai báo biến ngắn gọn, đó là: <tên biến> := <giá trị>. Ví dụ: i := 5.

    Giá trị trong khai báo biến không nhất thiết phải là giá trị cụ thể mà có thể là kết quả của hàm trả về. Ví dụ: f, err := os.OpenFile(<đường dẫn file>), là cách khai báo nhanh cho biến f và err, giá trị của chúng sẽ nhận được sau khi thực thi hàm os.OpenFile. Chúng ta sẽ hiểu rõ hơn ở bài về hàm.

    Phạm vi sử dụng của biến được quy định như sau:

    Biến được khai báo bên ngoài hàm sẽ có pham vi sử dụng ở toàn gói mà nó thuộc về. Thường được gọi là biến toàn cục. Nếu ký tự đầu của tên biến là chữ hoa thì có thể được sử dụng ở các gói khác khi sử dụng gói này.
    Biến được khai báo trong hàm thì có phạm vi sử dụng ở trong hàm đó. Thường được gọi là biến cục bộ. Các tham số đầu vào và tham số trả về cũng có phạm vi như biến cục bộ trong hàm.
    Biến được khai báo trong một khối xử lý thì chỉ có thể sử dụng trong khối đó. Ví dụ các biến khởi tạo trong cú pháp vòng for hay bên trong vòng for thì chỉ có tác dụng trong phạm vi vòng for. Go sẽ báo lỗi khi biên dịch nếu nó được truy xuất từ bên ngoài vòng for.
    Biến được khai báo bên ngoài khối xử lý của hàm thì trong khối xử lý đó sử dụng được.

    Do phải cấp vùng nhớ cho biến mỗi khi khai báo nên khi không còn sử dụng nữa (ngoài phạm vi sử dụng) thì cần giải phóng và thu hồi để tiết kiệm bộ nhớ và tránh thất thoát vùng nhớ. Go giúp chúng ta làm việc này tự động với bộ dọn rác thông minh của nó. Bộ dọn rác của Go hoạt động theo cơ chế khi không còn đối tượng nào tham chiếu đến vùng nhớ tạo cho biến thì vùng nhớ này được đưa vào danh sách cần thu hồi.

    Trong bài tới chúng ta sẽ cùng tìm hiểu về Các kiểu dữ liệu trong Go

    Tóm tắt

    • Đặt tên:
      ✔ Tên bắt đầu bằng chữ cái hoặc dấu _, sau đó chấp nhận thêm số.
      ✔ Go phân biệt hoa thường và chấp nhận tên Unicode.
      ✔ Không được đặt trùng tên với 25 từ khóa.
    • Khai báo:
      ✔ Package: package <tên package>
      ✔ Package sử dụng: import "<tên package>"
      ✔ Hằng: const <tên hằng> <kiểu dữ liệu> = <giá trị>
      ✔ Biến: var <tên biến> <kiểu dữ liệu> = <giá trị khởi tạo>
      ✔ Kiểu dữ liệu: type <tên kiểu dữ liệu> <mô tả kiểu dữ liệu>
      ✔ Hàm: func <tên hàm> (<danh sách tham số>) (<danh sách trả về>) {}


Có thể bạn cũng quan tâm

.
DMCA.com Protection Status