Làm các bài tập sau:
https://laptrinhonline.club/problem/0001dayc
https://laptrinhonline.club/problem/0014dayc
https://laptrinhonline.club/problem/0021dayc
https://laptrinhonline.club/problem/basic4
Tài liệu Golang từ A đến Z
Mục lục
-
Giới thiệu về Go
-
Cài đặt Go
-
Cấu trúc chương trình Go
-
Các kiểu dữ liệu cơ bản
-
Biến và hằng số
-
Toán tử
-
Cấu trúc điều khiển
-
Hàm
-
Con trỏ
-
Cấu trúc (Struct)
-
Interface
-
Mảng và Slice
-
Map
-
Xử lý chuỗi
-
Xử lý lỗi
-
Goroutines và đồng thời
-
Channels
-
Context
-
Làm việc với file
-
Xử lý JSON
-
Làm việc với database
-
Testing
-
Quản lý package với Go Modules
-
Viết RESTful API
-
Mẫu thiết kế trong Go
-
Các công cụ và thư viện phổ biến
Giới thiệu về Go
Go (hay còn gọi là Golang) là một ngôn ngữ lập trình mã nguồn mở được phát triển bởi Google vào năm 2007 và ra mắt công chúng vào năm 2009. Go được thiết kế bởi Robert Griesemer, Rob Pike và Ken Thompson.
Đặc điểm của Go:
-
Đơn giản và dễ học: Cú pháp gọn gàng, dễ đọc và dễ hiểu.
-
Biên dịch nhanh: Thời gian biên dịch nhanh hơn so với các ngôn ngữ truyền thống như C/C++.
-
Hiệu suất cao: Gần với C/C++ nhưng có quản lý bộ nhớ tự động.
-
Tích hợp hỗ trợ đồng thời: Goroutines và channels hỗ trợ lập trình đồng thời dễ dàng.
-
Garbage Collection: Tự động quản lý bộ nhớ.
-
Tĩnh và mạnh mẽ: Là ngôn ngữ kiểu tĩnh (statically typed).
-
Thư viện chuẩn phong phú: Hỗ trợ nhiều tính năng mạnh mẽ sẵn có.
Các trường hợp sử dụng Go:
-
Lập trình hệ thống
-
Phát triển web backend
-
Ứng dụng mạng và phân tán
-
Công cụ dòng lệnh
-
Cloud computing và microservices
Cài đặt Go
Windows:
-
Tải trình cài đặt từ golang.org
-
Chạy trình cài đặt và làm theo hướng dẫn
-
Kiểm tra cài đặt:
go version
macOS:
-
Sử dụng Homebrew:
brew install go -
Hoặc tải trình cài đặt từ golang.org
-
Kiểm tra cài đặt:
go version
Linux:
-
Tải và giải nén:
wget https://golang.org/dl/go1.21.0.linux-amd64.tar.gzsudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz -
Thêm vào PATH:
echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.profilesource ~/.profile -
Kiểm tra cài đặt:
go version
Cấu hình GOPATH
GOPATH là thư mục làm việc cho các dự án Go. Mặc định là $HOME/go trên Unix/Linux và %USERPROFILE%\go trên Windows.
Cấu trúc chương trình Go
Hello World
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Giải thích:
-
package main: Khai báo package,mainlà package đặc biệt cho điểm bắt đầu chương trình -
import "fmt": Import package fmt để sử dụng hàm Println -
func main(): Hàm khởi đầu của chương trình -
fmt.Println(): In chuỗi ra màn hình
Cách biên dịch và chạy:
# Biên dịch và chạy trong một bước
go run hello.go
# Biên dịch thành tệp thực thi
go build hello.go
# Chạy tệp thực thi
./hello # trên Unix/Linux/macOS
hello.exe # trên Windows
Các kiểu dữ liệu cơ bản
Go có các kiểu dữ liệu cơ bản sau:
Kiểu số nguyên:
-
int,int8,int16,int32,int64 -
uint,uint8,uint16,uint32,uint64 -
byte(alias chouint8) -
rune(alias choint32, biểu diễn một Unicode code point) -
uintptr(số nguyên không dấu đủ lớn để lưu con trỏ)
Kiểu số thực:
-
float32,float64 -
complex64,complex128
Kiểu boolean:
bool(true hoặc false)
Kiểu chuỗi:
string
Ví dụ:
package main
import "fmt"
func main() {
// Số nguyên
var a int = 10
var b int8 = 127
// Số thực
var c float64 = 3.14
// Boolean
var d bool = true
// Chuỗi
var e string = "Hello, Go!"
// In các giá trị
fmt.Println(a, b, c, d, e)
// Kiểu dữ liệu phức tạp
var f complex128 = 1 + 2i
fmt.Println("Complex number:", f)
}
Biến và hằng số
Khai báo biến:
// Khai báo chuỗi
s1 := "Hello, Go!"
// Độ dài chuỗi (số byte)
fmt.Println(len(s1)) // 10
// Độ dài chuỗi Unicode (số ký tự)
fmt.Println(utf8.RuneCountInString(s1))
// Truy cập ký tự (byte)
fmt.Println(s1[0]) // 72 (mã ASCII của 'H')
// Duyệt qua các ký tự Unicode
for i, r := range s1 {
fmt.Printf("%d: %c\n", i, r)
}
}
Package strings:
func main() {
s := "Hello, Go!"
// Tìm kiếm
fmt.Println(strings.Contains(s, "Go")) // true
fmt.Println(strings.HasPrefix(s, "Hello")) // true
fmt.Println(strings.HasSuffix(s, "!")) // true
fmt.Println(strings.Index(s, "Go")) // 7
// Chuyển đổi
fmt.Println(strings.ToUpper(s)) // HELLO, GO!
fmt.Println(strings.ToLower(s)) // hello, go!
fmt.Println(strings.Title(s)) // Hello, Go!
// Cắt và nối
parts := strings.Split(s, ", ") // ["Hello", "Go!"]
fmt.Println(strings.Join(parts, " - ")) // Hello - Go!
fmt.Println(strings.Trim(" test ", " ")) // "test"
// Thay thế
fmt.Println(strings.Replace(s, "Go", "Golang", 1)) // Hello, Golang!
}
Rune:
func main() {
// Xử lý các ký tự Unicode
s := "Xin chào"
runes := []rune(s)
// Đếm số ký tự thực
fmt.Println(len(runes)) // 8
// Truy cập ký tự Unicode
fmt.Printf("%c\n", runes[4]) // c
// Chuyển đổi rune sang string
fmt.Println(string(runes[4:])) // chào
}
String Builder:
import "strings"
func main() {
var builder strings.Builder
// Thêm chuỗi
builder.WriteString("Hello")
builder.WriteString(", ")
builder.WriteString("Go!")
// Kết quả
fmt.Println(builder.String()) // Hello, Go!
// Độ dài hiện tại
fmt.Println(builder.Len()) // 10
// Reset builder
builder.Reset()
}
Xử lý lỗi
Go sử dụng một kiểu trả về đặc biệt error để xử lý lỗi thay vì throw/catch exceptions.
Cơ bản về error:
package main
import (
"errors"
"fmt"
)
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("không thể chia cho 0")
}
return a / b, nil
}
Tạo error tùy chỉnh:
// Tạo error với fmt.Errorf
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("không thể chia %d cho %d", a, b)
}
return a / b, nil
}
// Tạo error custom
type MyError struct {
Operation string
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("%s error: %s", e.Operation, e.Message)
}
func process() error {
return &MyError{
Operation: "math",
Message: "lỗi xử lý",
}
}
Kiểm tra kiểu của error:
func main() {
err := process()
// Kiểm tra kiểu của error
if myErr, ok := err.(*MyError); ok {
fmt.Println("Operation:", myErr.Operation)
fmt.Println("Message:", myErr.Message)
}
}
Wrapping errors:
import "fmt"
func readConfig() error {
// Đọc file cấu hình
err := readFile()
if err != nil {
return fmt.Errorf("không thể đọc file cấu hình: %w", err)
}
return nil
}
// Unwrap errors
import "errors"
func main() {
err := readConfig()
// Kiểm tra error gốc
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("File không tồn tại")
}
}
Panic và recover:
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
dangerousOperation()
fmt.Println("Done") // Dòng này không được thực thi
}
func dangerousOperation() {
panic("something went wrong")
}
Goroutines và đồng thời
Goroutine là một hàm hoặc phương thức chạy đồng thời với các goroutine khác trong cùng một không gian địa chỉ.
Tạo goroutine:
package main
import (
"fmt"
"time"
)
func main() {
// Tạo goroutine
go sayHello("World")
// Goroutine khác
sayHello("Go")
// Chờ để goroutine đầu tiên hoàn thành
time.Sleep(time.Second)
}
func sayHello(name string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Printf("Hello, %s! (%d)\n", name, i)
}
}
Đồng bộ với WaitGroup:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
// Thêm 2 goroutine cần đợi
wg.Add(2)
go func() {
defer wg.Done() // Báo hiệu goroutine hoàn thành
sayHello("World")
}()
go func() {
defer wg.Done()
sayHello("Go")
}()
// Chờ tất cả goroutine hoàn thành
wg.Wait()
fmt.Println("All goroutines completed")
}
Mutex để tránh race condition:
package main
import (
"fmt"
"sync"
)
func main() {
var counter int
var mutex sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// Khóa để truy cập an toàn vào counter
mutex.Lock()
counter++
mutex.Unlock()
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // 1000
}
RWMutex:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var rwMutex sync.RWMutex
var data = make(map[string]string)
var wg sync.WaitGroup
// Writers
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
// Khóa để ghi
rwMutex.Lock()
data[fmt.Sprintf("key%d", n)] = fmt.Sprintf("value%d", n)
time.Sleep(10 * time.Millisecond)
rwMutex.Unlock()
}(i)
}
// Readers
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
// Khóa để đọc (nhiều goroutine có thể đọc cùng lúc)
rwMutex.RLock()
fmt.Println("Length:", len(data))
time.Sleep(1 * time.Millisecond)
rwMutex.RUnlock()
}
}()
}
wg.Wait()
fmt.Println("Final map:", data)
}
Once:
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// Hàm này chỉ được gọi một lần duy nhất
once.Do(func() {
fmt.Println("Initialized!")
})
fmt.Println("Worker done")
}()
}
wg.Wait()
}
Channels
Channel là một cơ chế để goroutine giao tiếp và đồng bộ hóa với nhau.
Cơ bản về channel:
package main
import "fmt"
func main() {
// Tạo channel
ch := make(chan int)
// Gửi dữ liệu vào channel (trong goroutine khác)
go func() {
ch <- 42 // Gửi giá trị 42 vào channel
}()
// Nhận dữ liệu từ channel
value := <-ch
fmt.Println(value) // 42
}
Buffered channel:
func main() {
// Tạo buffered channel với capacity là 3
ch := make(chan int, 3)
// Có thể gửi 3 giá trị mà không cần goroutine nào nhận
ch <- 1
ch <- 2
ch <- 3
// Nhận giá trị
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}
Đóng channel:
func main() {
ch := make(chan int, 3)
// Gửi dữ liệu
ch <- 1
ch <- 2
ch <- 3
// Đóng channel
close(ch)
// Kiểm tra channel đã đóng
v, ok := <-ch
fmt.Println(v, ok) // 1, true
v, ok = <-ch
fmt.Println(v, ok) // 2, true
v, ok = <-ch
fmt.Println(v, ok) // 3, true
v, ok = <-ch
fmt.Println(v, ok) // 0, false (channel đã đóng)
}
Lặp qua channel:
func main() {
ch := make(chan int, 5)
// Gửi dữ liệu
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
// Lặp qua tất cả các giá trị trong channel
for v := range ch {
fmt.Println(v)
}
}
Select:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
// Đợi 2 channel
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
return
}
}
}
Channel chỉ gửi hoặc chỉ nhận:
// Channel chỉ gửi
func send(ch chan<- int) {
ch <- 42
}
// Channel chỉ nhận
func receive(ch <-chan int) {
val := <-ch
fmt.Println(val)
}
func main() {
ch := make(chan int)
go send(ch)
receive(ch)
}
Context
Context giúp quản lý goroutine, truyền tín hiệu hủy bỏ và deadline.
Sử dụng context cơ bản:
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Tạo context với cancel
ctx, cancel := context.WithCancel(context.Background())
// Đảm bảo gọi cancel để tránh rò rỉ tài nguyên
defer cancel()
// Khởi chạy goroutine với context
go doSomething(ctx)
// Chờ 2 giây
time.Sleep(2 * time.Second)
// Hủy bỏ context
cancel()
// Chờ cho goroutine kết thúc
time.Sleep(100 * time.Millisecond)
fmt.Println("Main: exiting")
}
func doSomething(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("doSomething: cancelled")
return
default:
fmt.Println("doSomething: working...")
time.Sleep(500 * time.Millisecond)
}
}
}
Context với deadline:
func main() {
// Tạo context với timeout 3 giây
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go doSomething(ctx)
// Chờ cho đến khi context done
<-ctx.Done()
fmt.Println("Main: context done with error:", ctx.Err())
}
Context với value:
func main() {
// Tạo context gốc
ctx := context.Background()
// Thêm giá trị vào context
ctx = context.WithValue(ctx, "userID", 123)
ctx = context.WithValue(ctx, "authToken", "abc123")
// Khởi chạy goroutine với context
go processRequest(ctx)
time.Sleep(time.Second)
}
func processRequest(ctx context.Context) {
// Lấy giá trị từ context
userID := ctx.Value("userID")
authToken := ctx.Value("authToken")
fmt.Printf("Processing request for user %v with auth token %v\n", userID, authToken)
}
Context trong HTTP request:
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
// Lấy context từ request
ctx := r.Context()
// Tạo context với timeout
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
// Khởi chạy xử lý trong goroutine
result := make(chan string, 1)
go func() {
// Mô phỏng xử lý tốn thời gian
time.Sleep(3 * time.Second)
result <- "Processing completed"
}()
// Đợi kết quả hoặc context timeout
select {
case <-ctx.Done():
// Context đã hết hạn
fmt.Fprint(w, "Request timed out")
return
case res := <-result:
// Nhận được kết quả
fmt.Fprint(w, res)
}
}
Làm việc với file
Đọc file:
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
// Đọc toàn bộ file
data, err := ioutil.ReadFile("file.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println("File content:", string(data))
// Đọc file với os.Open
file, err := os.Open("file.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // Đảm bảo đóng file
// Đọc một buffer nhỏ
buffer := make([]byte, 100)
n, err := file.Read(buffer)
if err != nil {
fmt.Println("Error reading:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
}
Ghi file:
func main() {
// Ghi toàn bộ dữ liệu
data := []byte("Hello, Go file I/O!")
err := ioutil.WriteFile("output.txt", data, 0644)
if err != nil {
fmt.Println("Error writing file:", err)
return
}
// Ghi với os.Create
file, err := os.Create("output2.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
n, err := file.Write([]byte("Writing data to a file in Go.\n"))
if err != nil {
fmt.Println("Error writing:", err)
return
}
fmt.Printf("Wrote %d bytes\n", n)
// Ghi chuỗi
n, err = file.WriteString("Writing string to a file.\n")
if err != nil {
fmt.Println("Error writing string:", err)
return
}
fmt.Printf("Wrote %d more bytes\n", n)
}
Đọc và ghi theo dòng:
import (
"bufio"
"fmt"
"os"
)
func main() {
// Đọc theo dòng
file, err := os.Open("file.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 1
for scanner.Scan() {
fmt.Printf("Line %d: %s\n", lineNum, scanner.Text())
lineNum++
}
if err := scanner.Err(); err != nil {
fmt.Println("Error scanning:", err)
}
// Ghi theo dòng
outFile, err := os.Create("lines.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer outFile.Close()
writer := bufio.NewWriter(outFile)
for i := 1; i <= 5; i++ {
_, err := writer.WriteString(fmt.Sprintf("Line %d\n", i))
if err != nil {
fmt.Println("Error writing:", err)
return
}
}
// Flush đảm bảo dữ liệu được ghi xuống file
writer.Flush()
}
Làm việc với thư mục:
func main() {
// Tạo thư mục
err := os.Mkdir("testdir", 0755)
if err != nil && !os.IsExist(err) {
fmt.Println("Error creating directory:", err)
return
}
// Tạo thư mục nested
err = os.MkdirAll("path/to/nested/dir", 0755)
if err != nil {
fmt.Println("Error creating nested directory:", err)
return
}
// Đọc thư mục
dir, err := os.Open(".")
if err != nil {
fmt.Println("Error opening directory:", err)
return
}
defer dir.Close()
files, err := dir.Readdir(-1) // -1 để đọc tất cả các mục
if err != nil {
fmt.Println("Error reading directory:", err)
return
}
for _, file := range files {
fileType := "File"
if file.IsDir() {
fileType = "Directory"
}
fmt.Printf("%s: %s, Size: %d bytes\n", fileType, file.Name(), file.Size())
}
}
Xử lý JSON
Marshal (Object to JSON):
package main
import (
"encoding/json"
"fmt"
)
// Struct với JSON tags
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Age int `json:"age"`
Email string `json:"email,omitempty"` // Bỏ qua nếu trống
Address *Address `json:"address,omitempty"`
}
type Address struct {
Street string `json:"street"`
City string `json:"city"`
Country string `json:"country"`
}
func main() {
// Khởi tạo struct
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
Email: "john.doe@example.com",
Address: &Address{
Street: "123 Main St",
City: "New York",
Country: "USA",
},
}
// Chuyển đổi struct thành JSON
jsonData, err := json.Marshal(person)
if err != nil {
fmt.Println("Error marshaling JSON:", err)
return
}
fmt.Println(string(jsonData))
// JSON đẹp hơn (pretty print)
jsonPretty, err := json.MarshalIndent(person, "", " ")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(jsonPretty))
}
Unmarshal (JSON to Object):
func main() {
// JSON string
jsonStr := `{
"first_name": "Jane",
"last_name": "Smith",
"age": 25,
"email": "jane.smith@example.com",
"address": {
"street": "456 Elm St",
"city": "Boston",
"country": "USA"
}
}`
// Khai báo biến để lưu kết quả
var person Person
// Chuyển đổi JSON thành struct
err := json.Unmarshal([]byte(jsonStr), &person)
if err != nil {
fmt.Println("Error unmarshaling JSON:", err)
return
}
// In kết quả
fmt.Printf("Person: %+v\n", person)
fmt.Printf("Address: %+v\n", *person.Address)
}
Encoder và Decoder:
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
func main() {
// JSON Encoder
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
person := Person{FirstName: "Alice", LastName: "Johnson", Age: 28}
encoder.Encode(person)
fmt.Println("Encoded JSON:")
fmt.Println(buf.String())
// JSON Decoder
jsonReader := strings.NewReader(`{"first_name":"Bob","last_name":"Brown","age":35}`)
decoder := json.NewDecoder(jsonReader)
var newPerson Person
decoder.Decode(&newPerson)
fmt.Printf("Decoded Person: %+v\n", newPerson)
}
Xử lý JSON động:
func main() {
// JSON với cấu trúc không xác định
jsonStr := `{
"name": "Product",
"price": 19.99,
"in_stock": true,
"attributes": {
"color": "red",
"size": "medium"
}
}`
// Sử dụng map[string]interface{} cho JSON objects
var result map[string]interface{}
err := json.Unmarshal([]byte(jsonStr), &result)
if err != nil {
fmt.Println("Error:", err)
return
}
// Truy cập các trường
fmt.Println("Name:", result["name"])
fmt.Println("Price:", result["price"])
// Truy cập nested objects
attrs := result["attributes"].(map[string]interface{})
fmt.Println("Color:", attrs["color"])
fmt.Println("Size:", attrs["size"])
// Xử lý JSON array
jsonArray := `[
{"name": "Apple", "price": 1.99},
{"name": "Orange", "price": 2.49},
{"name": "Banana", "price": 0.99}
]`
var fruits []map[string]interface{}
json.Unmarshal([]byte(jsonArray), &fruits)
for i, fruit := range fruits {
fmt.Printf("Fruit %d: %s, Price: $%.2f\n", i+1, fruit["name"], fruit["price"])
}
}
Làm việc với database
SQL Database (sử dụng package database/sql):
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // Driver cho MySQL
)
func main() {
// Kết nối đến database
// Format: username:password@tcp(host:port)/dbname
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/testdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Kiểm tra kết nối
err = db.Ping()
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to database!")
// Tạo bảng
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`)
if err != nil {
log.Fatal(err)
}
// Insert dữ liệu
result, err := db.Exec("INSERT INTO users (username, email) VALUES (?, ?)", "johndoe", "john@example.com")
if err != nil {
log.Fatal(err)
}
// Lấy ID của bản ghi vừa tạo
userID, err := result.LastInsertId()
if err != nil {
log.Fatal(err)
}
fmt.Printf("User created with ID: %d\n", userID)
// Query một bản ghi
var (
id int
username string
email string
createdAt string
)
row := db.QueryRow("SELECT * FROM users WHERE id = ?", userID)
err = row.Scan(&id, &username, &email, &createdAt)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %d, %s, %s, %s\n", id, username, email, createdAt)
// Query nhiều bản ghi
rows, err := db.Query("SELECT id, username FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
fmt.Println("Users:")
for rows.Next() {
var id int
var username string
if err := rows đầy đủ
var name string = "Go"
// Khai báo không chỉ định kiểu dữ liệu
var age = 30
// Khai báo ngắn gọn (chỉ sử dụng trong function)
shortName := "Go Language"
Khai báo nhiều biến:
var (
name string = "Go"
age int = 30
isAwesome bool = true
)
// Hoặc
var a, b, c int = 1, 2, 3
Hằng số:
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
// iota là bộ đếm tự động
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
Toán tử
Toán tử số học:
-
+: Cộng -
-: Trừ -
*: Nhân -
/: Chia -
%: Chia lấy phần dư -
++: Tăng 1 -
--: Giảm 1
Toán tử so sánh:
-
==: Bằng -
!=: Khác -
<: Nhỏ hơn -
>: Lớn hơn -
<=: Nhỏ hơn hoặc bằng -
>=: Lớn hơn hoặc bằng
Toán tử logic:
-
&&: Và -
||: Hoặc -
!: Phủ định
Toán tử gán:
=,+=,-=,*=,/=,%=
Ví dụ:
package main
import "fmt"
func main() {
a, b := 10, 3
// Toán tử số học
fmt.Println("a + b =", a+b)
fmt.Println("a - b =", a-b)
fmt.Println("a * b =", a*b)
fmt.Println("a / b =", a/b)
fmt.Println("a % b =", a%b)
// Toán tử so sánh
fmt.Println("a == b:", a == b)
fmt.Println("a != b:", a != b)
fmt.Println("a > b:", a > b)
// Toán tử logic
c, d := true, false
fmt.Println("c && d:", c && d)
fmt.Println("c || d:", c || d)
fmt.Println("!c:", !c)
}
Cấu trúc điều khiển
If-else:
if condition {
// code
} else if anotherCondition {
// code
} else {
// code
}
// If với statement ngắn
if x := calculateValue(); x > 10 {
fmt.Println("x lớn hơn 10")
} else {
fmt.Println("x không lớn hơn 10")
}
Switch:
switch value {
case 1:
// code
case 2, 3:
// code cho cả 2 và 3
default:
// code
}
// Switch không cần biến so sánh
switch {
case age < 18:
fmt.Println("Chưa đủ tuổi")
case age >= 18 && age < 60:
fmt.Println("Người trưởng thành")
default:
fmt.Println("Người cao tuổi")
}
For:
// Cú pháp cơ bản
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// Giống while trong các ngôn ngữ khác
for condition {
// code
}
// Vòng lặp vô hạn
for {
// code
if shouldBreak {
break
}
}
// Vòng lặp với range
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
// Nếu không cần index
for _, value := range numbers {
fmt.Println(value)
}
Break và Continue:
for i := 0; i < 10; i++ {
if i == 5 {
continue // Bỏ qua phần còn lại của vòng lặp
}
if i == 8 {
break // Thoát khỏi vòng lặp
}
fmt.Println(i)
}
Goto (ít khi được sử dụng):
func main() {
goto myLabel
fmt.Println("Đoạn này bị bỏ qua")
myLabel:
fmt.Println("Nhảy đến đây")
}
Hàm
Khai báo hàm cơ bản:
func functionName(param1 type1, param2 type2) returnType {
// code
return returnValue
}
// Ví dụ
func add(a, b int) int {
return a + b
}
Trả về nhiều giá trị:
func getNameAndAge() (string, int) {
return "Gopher", 10
}
// Sử dụng
name, age := getNameAndAge()
Trả về giá trị được đặt tên:
func divide(a, b float64) (quotient float64, remainder float64) {
quotient = a / b
remainder = math.Mod(a, b)
return // Trả về quotient và remainder
}
Variadic functions (hàm với số lượng tham số không cố định):
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
// Sử dụng
result := sum(1, 2, 3, 4, 5)
Hàm như kiểu dữ liệu:
func mathOperation(a, b int, op func(int, int) int) int {
return op(a, b)
}
// Định nghĩa hàm
func multiply(x, y int) int {
return x * y
}
// Sử dụng
result := mathOperation(5, 3, multiply) // 15
Hàm ẩn danh (anonymous functions):
func main() {
// Hàm ẩn danh
add := func(a, b int) int {
return a + b
}
fmt.Println(add(3, 4)) // 7
// Hàm ẩn danh được gọi ngay lập tức
result := func(a, b int) int {
return a * b
}(4, 5)
fmt.Println(result) // 20
}
Closure:
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
}
Con trỏ
Con trỏ lưu địa chỉ của một biến.
Khai báo và sử dụng con trỏ:
func main() {
var x int = 10
var p *int = &x // p lưu địa chỉ của x
fmt.Println("Giá trị của x:", x) // 10
fmt.Println("Địa chỉ của x:", &x) // Địa chỉ của x (0xc000000000 chẳng hạn)
fmt.Println("Giá trị của p:", p) // Cũng là địa chỉ của x
fmt.Println("Giá trị tại địa chỉ p trỏ tới:", *p) // 10
// Thay đổi giá trị của x thông qua con trỏ
*p = 20
fmt.Println("Giá trị mới của x:", x) // 20
}
Tạo biến con trỏ mới:
func main() {
ptr := new(int) // Tạo con trỏ đến một giá trị int mới
*ptr = 42
fmt.Println(*ptr) // 42
}
Truyền con trỏ vào hàm:
func increment(n *int) {
*n++ // Tăng giá trị mà con trỏ trỏ tới
}
func main() {
x := 10
increment(&x)
fmt.Println(x) // 11
}
Lưu ý về con trỏ:
-
Go không cho phép con trỏ trỏ đến con trỏ (pointer arithmetic) như trong C.
-
Con trỏ mặc định có giá trị nil khi chưa được khởi tạo.
-
Khi truyền con trỏ vào hàm, giá trị thực của biến có thể bị thay đổi.
Cấu trúc (Struct)
Struct là một kiểu dữ liệu tổng hợp, chứa nhiều trường với các kiểu dữ liệu khác nhau.
Khai báo và sử dụng struct:
// Khai báo struct
type Person struct {
Name string
Age int
City string
}
func main() {
// Khởi tạo struct
var p1 Person
p1.Name = "Alice"
p1.Age = 30
p1.City = "New York"
// Khởi tạo nhanh
p2 := Person{
Name: "Bob",
Age: 25,
City: "San Francisco",
}
// Khởi tạo theo thứ tự các trường (không nên dùng)
p3 := Person{"Charlie", 35, "Boston"}
fmt.Println(p1)
fmt.Println(p2)
fmt.Println(p3)
}
Struct lồng nhau:
type Address struct {
Street string
City string
Country string
}
type Employee struct {
Name string
Age int
Address Address // Struct lồng nhau
}
func main() {
emp := Employee{
Name: "John",
Age: 40,
Address: Address{
Street: "123 Main St",
City: "Chicago",
Country: "USA",
},
}
fmt.Println(emp.Name)
fmt.Println(emp.Address.City)
}
Con trỏ đến struct:
func main() {
p := &Person{
Name: "David",
Age: 28,
City: "Miami",
}
// Hai cách truy cập đều hợp lệ
fmt.Println((*p).Name) // David
fmt.Println(p.Name) // David (Go tự động dereference)
}
Phương thức cho struct:
// Phương thức cho struct Person
func (p Person) Greeting() string {
return fmt.Sprintf("Hello, my name is %s and I'm %d years old", p.Name, p.Age)
}
// Phương thức với receiver là con trỏ (có thể thay đổi giá trị của struct)
func (p *Person) IncreaseAge() {
p.Age++
}
func main() {
person := Person{Name: "Eva", Age: 30}
fmt.Println(person.Greeting())
person.IncreaseAge()
fmt.Println(person.Age) // 31
}
Struct tag:
type User struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"-"` // Trường password không được export khi chuyển sang JSON
}
Interface
Interface định nghĩa một tập hợp các phương thức mà một kiểu dữ liệu cần phải thực hiện.
Khai báo và sử dụng interface:
// Khai báo interface
type Shape interface {
Area() float64
Perimeter() float64
}
// Struct Rectangle
type Rectangle struct {
Width float64
Height float64
}
// Struct Circle
type Circle struct {
Radius float64
}
// Rectangle thực hiện interface Shape
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Circle thực hiện interface Shape
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Hàm nhận các kiểu dữ liệu thực hiện interface Shape
func PrintShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 5, Height: 3}
circle := Circle{Radius: 2}
PrintShapeInfo(rect)
PrintShapeInfo(circle)
}
Interface rỗng:
// Interface rỗng có thể chứa bất kỳ giá trị nào
func PrintInfo(i interface{}) {
fmt.Printf("Giá trị: %v, Kiểu: %T\n", i, i)
}
func main() {
PrintInfo(42) // int
PrintInfo("hello") // string
PrintInfo(true) // bool
PrintInfo([]int{1,2,3}) // slice
}
Type assertion:
func main() {
var i interface{} = "hello"
// Type assertion
s, ok := i.(string)
if ok {
fmt.Println(s) // hello
}
// Type assertion thất bại
n, ok := i.(int)
if !ok {
fmt.Println("i không phải kiểu int")
}
// Type assertion không kiểm tra (có thể gây panic)
// s2 := i.(string) // OK
// n2 := i.(int) // Panic
}
Type switch:
func describeType(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
case bool:
fmt.Printf("Boolean value is %v\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
func main() {
describeType(42)
describeType("hello")
describeType(true)
describeType(3.14)
}
Mảng và Slice
Mảng:
Mảng trong Go có kích thước cố định và không thể thay đổi.
// Khai báo mảng
var arr1 [5]int // Mảng 5 phần tử kiểu int
arr2 := [3]string{"a", "b", "c"} // Khai báo và khởi tạo
arr3 := [...]int{1, 2, 3, 4, 5} // Compiler tự đếm số phần tử
// Truy cập phần tử
fmt.Println(arr2[1]) // b
// Gán giá trị
arr1[0] = 10
arr1[1] = 20
// Độ dài mảng
fmt.Println(len(arr1)) // 5
Slice:
Slice là một cấu trúc dữ liệu động, giống như mảng nhưng có thể thay đổi kích thước.
// Tạo slice từ mảng
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:4] // Slice có phần tử 2, 3, 4
// Tạo slice mới
slice2 := []int{1, 2, 3} // Slice với 3 phần tử
slice3 := make([]int, 5) // Slice với 5 phần tử, giá trị mặc định 0
slice4 := make([]int, 3, 5) // Slice với 3 phần tử, capacity 5
// Thêm phần tử vào slice
slice2 = append(slice2, 4, 5) // Slice: [1, 2, 3, 4, 5]
// Nối hai slice
slice5 := []int{6, 7}
slice2 = append(slice2, slice5...) // Slice: [1, 2, 3, 4, 5, 6, 7]
// Chiều dài và dung lượng
fmt.Println(len(slice2)) // 7
fmt.Println(cap(slice2)) // Capacity (dung lượng)
Các thao tác với slice:
// Cắt slice
slice := []int{1, 2, 3, 4, 5}
slice1 := slice[1:3] // [2, 3]
slice2 := slice[:2] // [1, 2]
slice3 := slice[2:] // [3, 4, 5]
slice4 := slice[:] // [1, 2, 3, 4, 5]
// Sao chép slice
dest := make([]int, len(slice))
copy(dest, slice)
// Xóa phần tử (không có hàm xóa built-in)
// Ví dụ: Xóa phần tử tại vị trí 2
slice = append(slice[:2], slice[3:]...) // [1, 2, 4, 5]
Lưu ý:
-
Slice là tham chiếu đến mảng, không chứa dữ liệu thực sự
-
Khi bạn truyền slice vào hàm, thay đổi nội dung slice sẽ ảnh hưởng đến mảng gốc
-
Slice có thể bị thay đổi kích thước khi sử dụng append
Map
Map là kiểu dữ liệu dùng để lưu trữ cặp key-value.
Khai báo và khởi tạo map:
// Khai báo map
var m1 map[string]int // Nil map (chưa được khởi tạo)
// Khởi tạo map
m2 := make(map[string]int) // Map rỗng
m3 := map[string]int{ // Map với giá trị khởi tạo
"apple": 5,
"banana": 8,
"orange": 3,
}
Các thao tác với map:
// Thêm hoặc cập nhật phần tử
m2["apple"] = 5
m2["banana"] = 8
// Truy cập phần tử
fmt.Println(m2["apple"]) // 5
// Kiểm tra key tồn tại
value, exists := m3["grape"]
if exists {
fmt.Println("grape:", value)
} else {
fmt.Println("grape không tồn tại trong map")
}
// Xóa phần tử
delete(m3, "orange")
// Số lượng phần tử
fmt.Println(len(m3)) // 2
Lặp qua map:
for key, value := range m3 {
fmt.Printf("%s: %d\n", key, value)
}
// Chỉ lặp qua keys
for key := range m3 {
fmt.Println(key)
}
Lưu ý:
-
Thứ tự lặp qua map không được đảm bảo
-
Map trong Go không phải là thread-safe, đồng thời truy cập và sửa đổi map cần được đồng bộ
-
Map là kiểu tham chiếu, khi bạn truyền map vào hàm, thay đổi trong hàm sẽ ảnh hưởng đến map gốc