# Basic Go

## **Giới thiệu về ngôn ngữ lập trình Go**

Go là một ngôn ngữ lập trình biên dịch, kiểu tĩnh được phát triển bởi công ty Google. Ngôn ngữ Go được thiết kế chủ yếu để tạo ra các dịch vụ web và ứng dụng client-server, mặc dù nó cũng có khả năng làm việc với đồ họa, tính năng cấp thấp, và nhiều thứ khác.

Công việc phát triển Go bắt đầu vào năm 2007 tại Google. Một trong những tác giả của ngôn ngữ này là Ken Thompson, người đồng sáng lập ngôn ngữ C cùng với Dennis Ritchie. Vào ngày 10 tháng 11 năm 2009, ngôn ngữ Go được công bố, và phiên bản 1.0 được phát hành vào tháng 3 năm 2012. Ngôn ngữ này vẫn tiếp tục phát triển mạnh mẽ.

Go là ngôn ngữ mã nguồn mở, có nghĩa là dự án này có mã nguồn công khai và miễn phí sử dụng. Bạn có thể tải xuống và sử dụng các công cụ cần thiết từ trang web chính thức của Go tại <https://go.dev>.

Go là ngôn ngữ đa nền tảng, cho phép tạo ra các chương trình chạy trên nhiều hệ điều hành khác nhau như Windows, Mac OS, Linux, FreeBSD, Android, v.v. Mã nguồn của Go có thể dễ dàng chuyển từ hệ điều hành này sang hệ điều hành khác qua việc biên dịch lại.

Mặc dù nhiều người gọi Go là Golang vì tên miền ban đầu là golang.org, nhưng tên chính thức của ngôn ngữ này là Go.

## **Các tính năng của Go**

Tại sao bạn nên chọn Go? Có nhiều lý do, trong đó bao gồm:

* **Go là ngôn ngữ triển vọng**, đang ngày càng được ưa chuộng.
* **Go không quá phức tạp**, có cấu trúc hợp lý và dễ hiểu.
* **Go là ngôn ngữ trẻ**, hướng đến các lập trình viên và máy tính của thế kỷ XXI.
* **Go là ngôn ngữ đa nền tảng**, cho phép tạo các chương trình cho nhiều hệ điều hành khác nhau.
* **Go là ngôn ngữ nhanh**, biên dịch các chương trình lớn thường nhanh hơn so với nhiều ngôn ngữ khác.
* **Go là dự án mã nguồn mở**, tất cả mã nguồn và trình biên dịch của nó có thể tìm thấy và sử dụng miễn phí.

Thông thường, khi bắt đầu học lập trình với một ngôn ngữ mới, người ta thường viết một chương trình đơn giản in ra câu "Hello, World!".

Dưới đây là mã chương trình sẽ thực hiện điều đó:

```go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
```

## **package**

Mỗi chương trình trong ngôn ngữ Go bao gồm các gói (package).

Chạy chương trình trong Go bắt đầu từ gói chính - `package main`:

```go
package main

import "fmt"

func main() {
    fmt.Println("Привет, Степик!")
}
```

Trong dòng đầu tiên của mã, chúng ta khai báo gói chính - `package main`. Đây là gói cần phải có ở đầu mọi chương trình Go.

## **import**

Ngoài gói chính `main`, trong ngôn ngữ Go còn có rất nhiều gói khác mà bạn có thể nhập khẩu và sử dụng trong mã để thực hiện các tác vụ khác nhau.

Một trong những gói cơ bản và phổ biến là gói `fmt`, cung cấp chức năng nhập và xuất dữ liệu.

Để nhập khẩu một gói, chúng ta sử dụng từ khóa `import`:

```go
package main

import "fmt"

func main() {
    fmt.Println("Привет, Степик!")
}
```

Bạn có thể nhập khẩu nhiều gói cùng lúc bằng cách sử dụng dấu ngoặc đơn. Ví dụ:

```go
import (
    "fmt"
    "math"
)
```

Hoặc bạn có thể viết như thế này:

```go
import ("fmt"; "math")
```

Lưu ý rằng tên của gói phải được đặt trong **dấu ngoặc kép**.

Mỗi gói mà chúng ta nhập khẩu đều có các hàm tích hợp, và bạn có thể truy cập vào những hàm này sau khi nhập khẩu gói.

Trong ngôn ngữ Go, tên của các hàm nhập khẩu từ gói bắt đầu bằng chữ cái viết hoa. Bạn có thể truy cập các hàm này bằng cách sử dụng tên gói, dấu chấm và tên hàm với dấu ngoặc, ví dụ:

```go
fmt.Println()
```

Chúng ta đang xem xét chương trình đơn giản để in ra câu "Привет, Степик!", vì vậy chúng ta sử dụng hàm `Println()` từ gói `fmt`, hàm này dùng để in ra văn bản hoặc thông tin khác.

```go
package main

import "fmt"

func main() {
    fmt.Println("Привет, Степик!")
}
```

`Println()` là tên của hàm mà chúng ta nhập khẩu từ gói, vì vậy nó bắt đầu bằng chữ cái viết hoa.

Trong dấu ngoặc, trong dấu nháy, chúng ta chỉ định dữ liệu đầu ra mà chúng ta muốn in ra.

**Quan trọng!** Nếu bạn đã quen với Go, đừng sử dụng `print()` hoặc `println()` để in ra dữ liệu trong khóa học này. Các bài tập lập trình trên Stepik được kiểm tra qua stdin → stdout, không phải qua stderr.

Giống như trong một số ngôn ngữ lập trình khác như Java hoặc C++, điểm nhập của chương trình trong Go là hàm `main()`. Đây là hàm được thực thi khi chúng ta chạy chương trình.

Dưới đây là mã chương trình của chúng ta, in ra câu "Привет, Степик!":

```go
package main

import "fmt"

func main() {
    fmt.Println("Привет, Степик!")
}
```

Chúng ta đã phân tích mã này dòng theo dòng, và bây giờ hãy tổng kết lại một số điểm chính:

* Trước tiên, chúng ta luôn khai báo gói chính `main` bằng từ khóa `package`.
* Nếu cần, chúng ta nhập khẩu các gói, trong trường hợp này là gói `fmt`, dùng để in dữ liệu.
* Sau đó, chúng ta khai báo hàm `main()`, đây là điểm nhập của chương trình.
* Sử dụng hàm `Println()` từ gói `fmt` để in ra câu "Привет, Степик!".

Các dấu ngoặc nhọn trong mã bao quanh phần thân của hàm, nhưng chúng ta sẽ nói chi tiết hơn về các hàm trong module thứ tư. Hãy kiên nhẫn, và nhớ rằng sau khi định nghĩa hàm `main()`, bạn cần mở dấu ngoặc nhọn ngay sau đó và đóng nó ở cuối chương trình.

## **Chú thích một dòng**

Chú thích là một dòng văn bản không được thực thi như một phần của chương trình. Việc thêm chú thích trong quá trình viết mã là một thói quen rất tốt. Điều này giúp người khác hiểu được bạn đã nghĩ gì khi viết mã và cũng giúp bạn nhớ lại cách bạn đã suy nghĩ khi quay lại mã sau này.

Trong Go, bạn có thể tạo chú thích một dòng bằng cách sử dụng hai dấu gạch chéo `//`.

Ví dụ:

```go
// вывод текста
fmt.Println("Привет, Степик!")
```

Trong ví dụ trên, chúng ta giải thích cho những người có thể đọc mã của chúng ta rằng đoạn mã này thực hiện việc in ra văn bản. Tất cả những gì xuất hiện sau dấu `//` là chú thích và sẽ không được biên dịch hoặc thực thi.

## **Chú thích nhiều dòng**

Trong Go, bạn cũng có thể sử dụng chú thích nhiều dòng. Để làm điều này, bạn cần đặt chú thích giữa các ký tự `/*` và `*/`.

Ví dụ:

```go
func main() {
  /*
  Это пример 
  многострочного
  комментария
  */
  fmt.Println("Привет, Степик!")
}
```

Đôi khi, việc sử dụng `/* */` có thể rất hữu ích để chú thích các khối mã mà bạn không muốn thực thi trong quá trình thử nghiệm chương trình của mình.

## **Biến (Variables)**

Để lưu trữ dữ liệu trong chương trình, chúng ta sử dụng các biến. Để khai báo một biến, ta dùng từ khóa `var`, sau đó là **tên biến** và **loại dữ liệu** của nó:

```go
var x int
```

Trong mã trên, chúng ta khai báo một biến tên là `x` với kiểu dữ liệu `int`. `int` là viết tắt của "integer" (số nguyên) và là kiểu dữ liệu cho phép lưu trữ các số nguyên.

Chúng ta có thể gán giá trị cho biến và in nó ra:

```go
var x int = 12
fmt.Println(x)
```

Tên biến có thể bao gồm các ký tự **chữ cái**, **chữ số** và **dấu gạch dưới**. Tuy nhiên, **ký tự đầu tiên** **phải** là **một chữ cái** hoặc **dấu gạch dưới**. Các từ khóa sau không thể được sử dụng làm tên biến: `break`, `case`, `chan`, `const`, `continue`, `default`, `defer`, `else`, `fallthrough`, `for`, `func`, `go`, `goto`, `if`, `import`, `interface`, `map`, `package`, `range`, `return`, `select`, `struct`, `switch`, `type`, `var`.

### **Khai báo nhiều biến và gán giá trị**

Bạn cũng có thể khai báo nhiều biến trong một dòng và ngay lập tức gán giá trị cho chúng:

```go
var x, y int = 5, 11
```

Nếu bạn gán giá trị cho biến, bạn có thể bỏ qua phần khai báo kiểu dữ liệu vì Go sẽ tự động xác định kiểu của giá trị đã gán:

```go
var x, y = 5, 11
```

Quá trình gán giá trị ban đầu cho các biến được gọi là **khởi tạo (initialization)**.

### **Khai báo biến ngắn gọn với toán tử `:=`**

Go hỗ trợ cách khai báo biến ngắn gọn bằng cách sử dụng toán tử `:=`.

Ví dụ:

```go
x := 8
```

Cách này cũng có thể được sử dụng để khai báo và khởi tạo nhiều biến trong một dòng:

```go
x, y := 5, 11
```

Toán tử `:=` sẽ tự động xác định kiểu dữ liệu và khởi tạo các biến với giá trị được chỉ định.

## **Kiểu dữ liệu**

Go là một ngôn ngữ lập trình có kiểu tĩnh, nghĩa là các biến luôn có kiểu dữ liệu xác định và kiểu này không thể thay đổi.

Cũng giống như trong nhiều ngôn ngữ lập trình khác, Go có nhiều kiểu dữ liệu khác nhau, trong đó các kiểu dữ liệu được sử dụng phổ biến nhất là:

* **Số nguyên (integer)**: Dùng để lưu trữ các số nguyên.
* **Số thực (float)**: Dùng để lưu trữ các số có phần thập phân.
* **Chuỗi (string)**: Dùng để lưu trữ các dãy ký tự.
* **Kiểu logic (bool)**: Dùng để lưu trữ giá trị đúng (true) hoặc sai (false).

### **Số nguyên (Integer)**

Trong bài học trước, chúng ta đã sử dụng kiểu `int` để khai báo các giá trị số nguyên:

```go
var i int = 3
```

`int` là một kiểu số nguyên đặc biệt. Nó đặc biệt vì trên các máy tính với bộ xử lý 32-bit, kiểu `int` đại diện cho một số nguyên có dấu 32-bit (tương đương với `int32`), còn trên các máy tính với bộ xử lý 64-bit, kiểu `int` đại diện cho một số nguyên có dấu 64-bit (tương đương với `int64`). Đây là kiểu dữ liệu số nguyên phổ biến nhất.

Ngoài kiểu `int`, Go còn có các kiểu số nguyên khác:

<table><thead><tr><th width="120">Tên kiểu</th><th>Phạm vi giá trị có thể có</th></tr></thead><tbody><tr><td><code>int8</code></td><td>Từ –128 đến 127</td></tr><tr><td><code>int16</code></td><td>Từ –32,768 đến 32,767</td></tr><tr><td><code>int32</code></td><td>Từ –2,147,483,648 đến 2,147,483,647</td></tr><tr><td><code>int64</code></td><td>Từ –9,223,372,036,854,775,808 đến 9,223,372,036,854,775,807</td></tr><tr><td><code>uint8</code></td><td>Từ 0 đến 255</td></tr><tr><td><code>uint16</code></td><td>Từ 0 đến 65,535</td></tr><tr><td><code>uint32</code></td><td>Từ 0 đến 4,294,967,295</td></tr><tr><td><code>uint64</code></td><td>Từ 0 đến 18,446,744,073,709,551,615</td></tr></tbody></table>

Đừng cố gắng nhớ hết các phạm vi này. Bạn có thể sử dụng `int` trong hầu hết các tình huống, vì nó sẽ đủ cho các nhu cầu thông thường.

Ví dụ:

```go
var a int = 11
var b uint8 = 132
var c int64 = 345326
```

### **Số thực (Floating-point numbers)**

Số thực là các số có phần thập phân. Trong Go, có hai kiểu dữ liệu cho số thực:

* `float32`: Số thực với độ chính xác đơn.
* `float64`: Số thực với độ chính xác kép.

Sự khác biệt giữa chúng là độ chính xác. `float64` chính xác hơn, nhưng chiếm nhiều bộ nhớ hơn. Trong hầu hết các trường hợp, bạn có thể sử dụng `float32` vì nó đủ chính xác.

Ví dụ:

```go
var x float32 = 3.14
var y float64 = 3.1415926
```

### **Kiểu logic (Boolean)**

Để biểu diễn giá trị logic, Go sử dụng kiểu `bool`. Các biến có kiểu `bool` có thể nhận một trong hai giá trị: `true` (đúng) hoặc `false` (sai).

Ví dụ:

```go
var t bool = true
var f bool = false
```

### **Chuỗi (Strings)**

Trong Go, chuỗi là một dãy byte không thay đổi, mặc dù chúng ta thường biểu diễn chuỗi dưới dạng văn bản trong dấu ngoặc kép `"Привет, Степик!"` hoặc trong dấu nháy ngược `` `Привет, Степик!` ``. Điều này tạo ra sự phức tạp khi làm việc với chuỗi trong Go, và vì lý do đó, trong khóa học này, chúng ta sẽ không đi sâu vào chi tiết về kiểu dữ liệu này.

Ví dụ:

```go
var s = "Привет, Степик!"
fmt.Println(s)          // sẽ in ra: Привет, Степик!
fmt.Println(len(s))     // sẽ in ra: 27
fmt.Println(s[0], s[1]) // sẽ in ra: 208 159
```

Hãy xem xét đoạn mã trên. Nếu chúng ta thử in chuỗi ra, nó sẽ được hiển thị mà không gặp vấn đề gì.

Tuy nhiên, nếu chúng ta thử lấy độ dài của chuỗi bằng cách sử dụng `len(s)`, kết quả là 27. Nguyên nhân là vì chuỗi trong Go là một dãy byte, trong đó mỗi ký tự có mã số riêng.

Hơn nữa, nếu chúng ta thử in ra phần tử đầu tiên và thứ hai của chuỗi (lưu ý rằng chỉ số trong chuỗi và mảng trong Go bắt đầu từ 0), kết quả sẽ là 208 và 159. Đây là cách biểu diễn chữ cái đầu tiên "П" trong bảng mã Unicode, được mã hóa bằng hai byte: 208 và 159.

### **Đặc điểm quan trọng của Go: Giá trị mặc định**

Trong Go, khi khai báo biến mà không gán giá trị (không khởi tạo), chúng sẽ nhận giá trị mặc định của kiểu dữ liệu tương ứng:

* `0` cho số nguyên và số thực,
* `false` cho kiểu logic (boolean),
* `""` (chuỗi rỗng) cho chuỗi.

Ví dụ:

```go
var i int
var f float32
var b bool
var s string

fmt.Println(i)  // sẽ in ra 0
fmt.Println(f)  // sẽ in ra 0
fmt.Println(b)  // sẽ in ra false
fmt.Println(s)  // sẽ in ra chuỗi rỗng
```

Điều này giúp tránh lỗi khi sử dụng biến mà không cần khởi tạo giá trị ban đầu.

## **Hằng số**

Biến có thể thay đổi giá trị trong quá trình thực thi chương trình.

Ví dụ:

```go
var x = 10 // giá trị của biến x là 10
x = 15     // biến x giờ có giá trị là 15
```

Đôi khi bạn cần những giá trị không thay đổi trong suốt quá trình chạy chương trình. Những giá trị này gọi là *hằng số* và giá trị khởi tạo của chúng không thể thay đổi.

Hằng số được khai báo tương tự như biến, nhưng bắt buộc phải sử dụng từ khóa `const`. Khi khai báo, hằng số cần được gán giá trị ngay lập tức:

```go
const pi = 3.14
```

Trong ví dụ trên, `pi` là hằng số với giá trị cố định không thể thay đổi.

Lưu ý: hằng số không thể khai báo bằng cú pháp ngắn `:=`.

## **Các toán tử số học**

Go hỗ trợ tất cả các toán tử số học cơ bản.

Dưới đây là ví dụ về cách hoạt động của các toán tử này:

```go
var a int = 31
var b int = 14
var result int

// Phép cộng
result = a + b
fmt.Println(result)  // xuất ra 45

// Phép trừ
result = a - b
fmt.Println(result)  // xuất ra 17

// Phép nhân
result = a * b
fmt.Println(result)  // xuất ra 434

// Phép chia
result = a / b
fmt.Println(result)  // xuất ra 2

// Phép chia lấy dư
result = a % b
fmt.Println(result)  // xuất ra 3
```

Trong ví dụ trên, các phép tính toán học cơ bản được thực hiện bằng các toán tử: cộng (`+`), trừ (`-`), nhân (`*`), chia (`/`), và chia lấy dư (`%`).

### **Phép chia**

Trong Go, cần chú ý đặc biệt đến phép chia. Khi phép chia được thực hiện với hai số nguyên, kết quả sẽ được làm tròn xuống thành số nguyên bằng cách bỏ phần thập phân, tức là phép chia nguyên:

```go
var x int = 35 / 11
fmt.Println(x)       // kết quả: 3
```

Để nhận kết quả là một số thập phân, ít nhất một trong các toán hạng phải là số thập phân, và kết quả cũng phải được lưu trong biến có kiểu số thực:

```go
var x float32 = 13.0 / 4
// hoặc
var x float32 = 13 / 4.0
fmt.Println(x)       // kết quả: 3.25
```

Trong ví dụ này, phép chia giữa số nguyên và số thực sẽ cho kết quả thập phân, lưu vào biến kiểu `float32`.

### **Nối chuỗi**

Toán tử `+` trong Go có thể được sử dụng để ghép chuỗi.

Ví dụ:

```go
a := "Привет, "
b := "Степик!"
fmt.Println(a + b) // kết quả: Привет, Степик!
```

Cách ghép chuỗi này gọi là "nối chuỗi" hay "concatenation".

### **Toán tử gán**

Ngôn ngữ Go hỗ trợ các toán tử gán ngắn gọn, giúp đơn giản hóa các biểu thức như `a = a + b` thành `a += b`.

Ví dụ:

```go
var a int = 31
var b int = 14

a += b
fmt.Println(a)  // kết quả: 45

a -= b
fmt.Println(a)  // kết quả: 17

a *= b
fmt.Println(a)  // kết quả: 434

a /= b
fmt.Println(a)  // kết quả: 2

a %= b
fmt.Println(a)  // kết quả: 3
```

Các toán tử này cho phép viết mã ngắn gọn và dễ đọc hơn khi thực hiện các phép tính trên chính biến đó.

### **Tăng giảm giá trị**

Trong Go, ta có thể dùng cú pháp hậu tố `x++` để tăng giá trị biến lên 1 đơn vị:

```go
var a int = 2
a++            // tương đương với biểu thức a = a + 1
fmt.Println(a) // kết quả: 3
```

Và cú pháp hậu tố `x--` để giảm giá trị biến đi 1 đơn vị:

```go
var a int = 2
a--            // tương đương với biểu thức a = a - 1
fmt.Println(a) // kết quả: 1
```

Toán tử tăng (`++`) và giảm (`--`) thường được dùng để thay đổi giá trị của biến đếm trong vòng lặp.

## **Toán tử so sánh**

Toán tử so sánh được sử dụng để so sánh hai giá trị và trả về kết quả kiểu `bool`: `true` nếu điều kiện đúng và `false` nếu sai.

Ví dụ:

```go
var a int = 31
var b int = 14

// bằng nhau
fmt.Println(a == b) // kết quả: false

// khác nhau
fmt.Println(a != b) // kết quả: true

// lớn hơn
fmt.Println(a > b)  // kết quả: true

// lớn hơn hoặc bằng
fmt.Println(a >= b) // kết quả: true

// nhỏ hơn
fmt.Println(a < b)  // kết quả: false

// nhỏ hơn hoặc bằng
fmt.Println(a <= b) // kết quả: false
```

Những toán tử này rất hữu ích khi kiểm tra điều kiện, đặc biệt trong các cấu trúc điều khiển như câu lệnh `if`.

## **Toán tử logic**

Toán tử logic được sử dụng để kết hợp hai hoặc nhiều điều kiện lại với nhau.

* Toán tử `&&` (VÀ) trả về `true` chỉ khi cả hai điều kiện đều là `true`.
* Toán tử `||` (HOẶC) trả về `true` khi một trong hai (hoặc cả hai) điều kiện là `true`.
* Toán tử `!` (PHỦ ĐỊNH) trả về `true` nếu điều kiện là sai (nghĩa là nó đảo ngược kết quả của điều kiện).

Ví dụ:

```go
var a int = 31
var b int = 14

// toán tử VÀ
fmt.Println(a != b && a >= b)  // kết quả: true

// toán tử HOẶC
fmt.Println(a == b || a > b)   // kết quả: true

// toán tử PHỦ ĐỊNH
fmt.Println(!(a < b))          // kết quả: true
```

Khi có nhiều toán tử logic trong biểu thức, chúng được đánh giá theo thứ tự ưu tiên:

1. `!` (PHỦ ĐỊNH)
2. `&&` (VÀ)
3. `||` (HOẶC)

Khi sử dụng nhiều điều kiện, có thể sử dụng dấu ngoặc `()` để thay đổi thứ tự tính toán, tương tự như trong toán học:

```go
(b > 0 && b < 10) || b != 5
```

## Input (đầu vào)

Gói `fmt` trong Go cho phép chương trình nhận dữ liệu đầu vào từ người dùng. Để nhận đầu vào, có thể sử dụng hàm `Scan()` và truyền vào biến để lưu giá trị người dùng nhập vào:

```go
var number int
fmt.Scan(&number)
```

Giờ đây, biến `number` sẽ chứa giá trị mà người dùng nhập vào khi chạy chương trình. Trong ví dụ trên, ta khai báo biến kiểu `int`, nghĩa là chỉ có thể nhập số nguyên. Tùy theo kiểu dữ liệu mong muốn, ta cần chọn đúng kiểu cho biến lưu trữ.

Chú ý dấu `&` trước tên biến, nó trả về địa chỉ của biến. Khi dùng hàm `Scan()`, đừng quên thêm dấu `&` trước tên biến.

Trong Go, bạn có thể sử dụng hàm `Scan()` để nhận dữ liệu nhập từ người dùng. Ví dụ, nếu bạn muốn nhận ba giá trị và lưu chúng vào ba biến, bạn có thể làm như sau:

```go
var a, b, c int
fmt.Scan(&a, &b, &c)
```

Trong ví dụ trên, nếu người dùng nhập ba số cách nhau bằng dấu cách, chúng sẽ được lưu lần lượt vào các biến `a`, `b`, và `c`. Bạn có thể gọi `Scan()` nhiều lần để nhận nhiều dữ liệu nhập từ người dùng trong suốt chương trình.

### **Scan() và Scanln()**

Cả hai hàm `Scan()` và `Scanln()` trong gói `fmt` đều dùng để đọc dữ liệu từ người dùng, nhưng có sự khác biệt nhỏ về cách hoạt động:

* **Scan()**: Hàm này đọc dữ liệu liên tục và phân tách các đối số dựa trên khoảng trắng và dấu chuyển dòng (newline). Điều này có nghĩa là nó có thể đọc nhiều dòng dữ liệu và phân chia chúng thành các đối số khi gặp khoảng trắng hoặc chuyển dòng.

  Ví dụ:

  ```go
  package main  
  import "fmt" 

  func main() { 
      var a, b, c, d int
      fmt.Scan(&a, &b, &c, &d)
      fmt.Print(a, b, c, d)
  }
  ```

  **Đầu vào**:

  ```
  4 5
  6
  7
  ```

  **Đầu ra**: `4 5 6 7`

  Tất cả các giá trị đều được đọc thành công, vì `Scan()` sẽ phân tách chúng bằng khoảng trắng và chuyển dòng.
* **Scanln()**: Hàm này chỉ đọc dữ liệu trong một dòng duy nhất, phân tách các đối số bằng khoảng trắng, và dừng lại khi gặp dấu chuyển dòng. Nếu có nhiều dòng, chỉ dòng đầu tiên sẽ được đọc.

  Ví dụ:

  ```go
  package main  
  import "fmt" 

  func main() { 
      var a, b, c, d int
      fmt.Scanln(&a, &b, &c, &d)
      fmt.Print(a, b, c, d)
  }
  ```

  **Đầu vào**:

  ```
  4 5
  6
  7
  ```

  **Đầu ra**: `4 5 0 0`

  Chỉ dòng đầu tiên được đọc vào các biến `a` và `b`, còn `c` và `d` không được đọc do `Scanln()` chỉ hoạt động trên một dòng.

Tóm lại, `Scan()` có thể đọc dữ liệu từ nhiều dòng, còn `Scanln()` chỉ đọc dữ liệu từ một dòng duy nhất.

## Output

Trong Go, chúng ta sử dụng các hàm từ gói `fmt`: `Print()` và `Println()` để in dữ liệu ra màn hình. Hai hàm này có một số điểm khác biệt như sau:

* **Print()**: In các đối tượng ra màn hình, ngăn cách giữa chúng bằng khoảng trắng nếu không phải là chuỗi và không thêm ký tự xuống dòng.

  Ví dụ:

  ```go
  a := 5
  b := 11
  fmt.Print(a, b)  // in ra: 5 11

  fmt.Print("Chào,", "Stepic")  // in ra: Chào,Stepic
  ```
* **Println()**: In các đối tượng ra màn hình với khoảng trắng giữa chúng và thêm một ký tự xuống dòng sau khi in xong.

  Ví dụ:

  ```go
  a := 5
  b := 11
  fmt.Println(a, b)  // in ra: 5 11

  fmt.Println("Chào,", "Stepic")  // in ra: Chào, Stepic
  ```

**Lưu ý quan trọng**: Đối với các bài tập trên nền tảng Stepic, chỉ nên sử dụng các hàm `Print()` và `Println()` từ gói `fmt` vì các hàm `print` và `println` trong Go sẽ in dữ liệu vào luồng `stderr`, không phù hợp với yêu cầu `stdin -> stdout` trong các bài kiểm tra.

## if

Cấu trúc điều kiện `if` cho phép điều khiển thứ tự thực thi của mã nguồn. Trong Go, cú pháp của câu lệnh `if` như sau:

```go
if điều_kiện {
    // mã sẽ được thực thi nếu điều kiện là true
}
```

Nếu điều kiện là `true`, mã trong dấu ngoặc nhọn `{}` sẽ được thực thi; nếu điều kiện là `false`, mã sẽ không được thực thi.

**Ví dụ:**

```go
pass = 7055
if pass == 7055 {
    fmt.Println("Mật khẩu đúng")
}
// Điều kiện là true nên sẽ in ra: Mật khẩu đúng

pass = 7055
if pass == 3142 {
    fmt.Println("Mật khẩu sai")
}
// Điều kiện là false nên mã không được thực thi và không có gì được in ra
```

**Lưu ý:** Trong Go, dấu ngoặc nhọn mở phải được đặt ngay sau câu lệnh `if` trên cùng một dòng.

### else

Toán tử `else` được sử dụng khi điều kiện của `if` là `false`, cho phép thực thi một đoạn mã thay thế.

**Ví dụ:**

```go
iq := 70

if iq > 120 {
    fmt.Println("Bạn là thiên tài!")
} else {
    fmt.Println("Rất tiếc, bạn không phải là thiên tài")
}
```

Trong ví dụ này, điều kiện `if` là `false`, nên mã trong khối `else` sẽ được thực thi.

**Giả mã:**

```plaintext
nếu iq > 120 thì {
    fmt.Println("Bạn là thiên tài!")
} ngược lại {
    fmt.Println("Rất tiếc, bạn không phải là thiên tài")
}
```

**Lưu ý:** Sau `else` không cần điều kiện. Khối `else` sẽ được thực thi khi các điều kiện trước đó là `false`.

### **else if**&#x20;

Cấu trúc điều kiện `else if` mở rộng chức năng của `if`, giúp thực hiện các hành động khác nhau khi điều kiện của câu lệnh `if` đầu tiên là `false`. Tuy nhiên, khác với `else`, phần mã sau `else if` chỉ thực thi khi điều kiện của nó là `true`.

Ví dụ, đoạn mã dưới đây nhập vào hai số và in ra một trong ba thông báo: "a lớn hơn b", "a bằng b", hoặc "a nhỏ hơn b":

```go
var a, b int
fmt.Scan(&a)
fmt.Scan(&b)
if a > b {
    fmt.Println("a lớn hơn b")
} else if a == b {
    fmt.Println("a bằng b")
} else {
    fmt.Println("a nhỏ hơn b")
}
```

Một câu lệnh `if` có thể chứa nhiều `else if`. Dưới đây là dạng tổng quát của cấu trúc `if...else if...else`:

```go
if điều_kiện {
    // Mã thực hiện nếu điều kiện là true
} else if điều_kiện_2 {
    // Mã thực hiện nếu điều kiện_2 là true
} else {
    // Mã thực hiện nếu cả hai điều kiện đều là false
}
```

Cấu trúc điều kiện sẽ dừng lại ngay khi gặp điều kiện đầu tiên là `true`. Thứ tự cần phải theo nguyên tắc: bắt đầu bằng `if`, sau đó là một hoặc nhiều `else if`, và có thể kết thúc với `else`. Không nên đặt điều kiện trong `else`, vì phần này thực thi khi tất cả các điều kiện trước đó là `false`.

Đôi khi, bạn có thể chỉ cần một biến cho câu lệnh điều kiện.

Trong trường hợp này, câu lệnh `if` trong Go có thể bắt đầu bằng việc khai báo biến ngay trước điều kiện:

```go
if iq := 125; iq > 120 {
   fmt.Println("Thiên tài!")
} else {
   fmt.Println("Rất tiếc :(")
}
```

Lưu ý dấu chấm phẩy `;` sau khi khai báo biến - nó được dùng để phân cách các biểu thức.

Các biến được khai báo trong câu lệnh `if` chỉ có thể truy cập trong các khối `if` hoặc `else`.

Trong điều kiện, chúng ta có thể sử dụng các toán tử logic để tạo điều kiện phức hợp, ví dụ:

```go
height := 177
if height <= 165 {
     fmt.Println("Người có chiều cao thấp")
} else if height > 165 && height <= 185 {
     fmt.Println("Người có chiều cao trung bình")
} else {
     fmt.Println("Người cao")
}
// Kết quả: Người có chiều cao trung bình
```

Toán tử logic `AND (&&)` trong ví dụ trên được dùng để kết hợp hai điều kiện và tạo một khoảng kiểm tra. Nếu chiều cao nằm trong khoảng từ 165 đến 185, kết quả sẽ là "Người có chiều cao trung bình." Bằng cách này, có thể kết hợp nhiều toán tử logic:

```go
if x >= 1 && x <= 100 && x % 2 == 0
```

Điều kiện phức hợp này kiểm tra xem giá trị của `x` có nằm trong khoảng từ 1 đến 100 và là số chẵn hay không.

## switch

**Cấu trúc điều kiện switch** là cách viết gọn hơn cho các chuỗi điều kiện if/else.

Giả sử bạn có đoạn mã sau, sử dụng cấu trúc if/else:

```go
x := 2

if x == 1 {
    fmt.Println("Một")
} else if x == 2 {
    fmt.Println("Hai")
} else if x == 3 {
    fmt.Println("Ba")
} else {
    fmt.Println(x)
}
```

Đoạn mã kiểm tra giá trị của biến `x` và hiển thị văn bản tương ứng.

Chúng ta có thể đạt được kết quả tương tự bằng cách sử dụng cấu trúc switch:

```go
x := 2

switch x {
    case 1:
        fmt.Println("Một")
    case 2:
        fmt.Println("Hai")
    case 3:
        fmt.Println("Ba")
    default:
        fmt.Println(x)
}
```

Cấu trúc switch được xem là giúp mã dễ đọc hơn. Đánh giá điều này là tùy thuộc vào bạn.

Mỗi câu lệnh `case` bao gồm một giá trị để so sánh với biến được chỉ định ở đầu và mã sẽ thực hiện sau dấu hai chấm. Mã thực thi từ trên xuống và dừng lại khi có sự trùng khớp giữa biến và giá trị của một `case`.

Cuối cùng, có thể sử dụng `default`, phần mã sau nó sẽ được thực hiện nếu không có `case` nào khớp. Đây là tương đương với `else` trong cấu trúc if/else.

Một cách sử dụng khác của cấu trúc `switch` là dùng các điều kiện trong mỗi `case` mà không cần chỉ định biến sau từ khóa `switch`:

```go
month := "Январь"

switch {
    case month == "Декабрь" || month == "Январь" || month == "Февраль":
        fmt.Println("Mùa đông")
    case month == "Март" || month == "Апрель" || month == "Май":
        fmt.Println("Mùa xuân")
}
```

Mã trong `case` sẽ được thực thi nếu điều kiện trong `case` là `true`.

Lưu ý rằng có hai cách sử dụng cấu trúc `switch`:

1. Khi chỉ định một biến và không sử dụng điều kiện trong `case`, mà chỉ đưa ra các giá trị chính xác để so sánh với giá trị của biến;
2. Khi không chỉ định biến và sử dụng điều kiện trong `case`. Mã trong `case` sẽ được thực hiện nếu điều kiện trong đó là `true`.

Không thể kết hợp hai cách này trong cùng một cấu trúc `switch`.

### **Fallthrough**

Trong Go, mã sau `case` sẽ tự động thực thi mà không cần dùng từ khóa `break` như trong một số ngôn ngữ lập trình khác. Tuy nhiên, Go có từ khóa `fallthrough` trong cấu trúc `switch`. Khi thêm `fallthrough` vào cuối một khối `case`, mã trong khối `case` tiếp theo sẽ được thực thi, bất kể điều kiện của `case` tiếp theo có đúng hay không.

Ví dụ:

```go
day := 3

switch day {
    case 1:
        fmt.Println("Thứ Hai")
        fallthrough
    case 2:
        fmt.Println("Thứ Ba")
        fallthrough
    case 3:
        fmt.Println("Thứ Tư")
        fallthrough
    case 4:
        fmt.Println("Thứ Năm")
        fallthrough
    case 5:
        fmt.Println("Thứ Sáu")
    default:
        fmt.Println("Ngày không hợp lệ")
}

/* Kết quả:
Thứ Tư
Thứ Năm
Thứ Sáu
*/
```

Đoạn mã này giúp hiển thị số ngày làm việc còn lại trong tuần tính từ ngày hiện tại. Với `fallthrough`, mã thực thi từ `case 3`, tiếp tục sang `case 4`, rồi `case 5`, và dừng lại vì không có `fallthrough` tiếp theo.

## for

Nếu chúng ta cần lặp lại một hành động nhiều lần, ta có thể sao chép mã nhiều lần hoặc sử dụng vòng lặp. Trong Go, khác với nhiều ngôn ngữ lập trình khác, chỉ có một cấu trúc vòng lặp duy nhất là `for`, bao gồm ba thành phần: khởi tạo biến đếm, điều kiện, và thay đổi biến đếm:

```go
for [khởi tạo biến đếm]; [điều kiện]; [thay đổi biến đếm] {
    // đây là phần thân của vòng lặp,
    // nơi mã lệnh được thực hiện trong quá trình vòng lặp chạy
}
```

Ví dụ về vòng lặp:

```go
for i := 0; i <= 10; i++ {
    fmt.Println(i)
}
```

Mã trên sẽ in ra các số từ 0 đến 10. Ban đầu, biến đếm `i` được khởi tạo với giá trị 0 (`i := 0`) và tăng lên một đơn vị ở cuối mỗi lần lặp (`i++`) cho đến khi điều kiện (`i <= 10`) không còn đúng nữa. Khi điều kiện trở thành `false`, vòng lặp sẽ dừng.

Lưu ý rằng, không giống như một số ngôn ngữ lập trình khác, không thể đặt dấu ngoặc nhọn mở của phần thân vòng lặp xuống dòng tiếp theo. Ví dụ cách viết không đúng:

```go
for i := 0; i <= 10; i++ 
{   // không được đặt ngoặc nhọn ở dòng tiếp theo
    fmt.Println(i)
}
```

Không nhất thiết phải chỉ rõ tất cả các thành phần của cấu trúc vòng lặp. Ví dụ, có thể khai báo biến đếm bên ngoài vòng lặp:

```go
var i = 0
for ; i < 10; i++ {
    fmt.Println(i)
}
```

Cũng có thể chuyển thao tác thay đổi biến đếm vào phần thân của vòng lặp:

```go
var i = 0
for ; i < 10; {
    fmt.Println(i)
    i++
}
```

Hai cách này hoạt động tương tự nhau. Chỉ cần lưu ý rằng phải đặt dấu chấm phẩy để tách các thành phần, ngay cả khi bạn bỏ qua một trong ba thành phần, nếu không sẽ gặp lỗi cú pháp.

Thông thường, người ta sử dụng dạng vòng lặp tiêu chuẩn, nơi cả biến đếm, điều kiện, và thay đổi biến đếm đều được khai báo trong vòng lặp (như trong ví dụ đầu tiên).

Nếu chỉ cần điều kiện, vòng lặp có thể viết gọn hơn (bỏ dấu chấm phẩy):

```go
var i = 0
for i < 10 {
    fmt.Println(i)
    i++
}
```

Dạng này tương tự với vòng lặp `while` trong các ngôn ngữ lập trình khác.

### continue

Có những tình huống cần kết thúc sớm vòng lặp hiện tại mà không thực thi toàn bộ mã lệnh trong vòng lặp và chuyển ngay sang lần lặp tiếp theo. Để làm điều này, có thể sử dụng toán tử `continue`. Ví dụ, để tính tổng các số chẵn trong khoảng từ 1 đến 10, ta có thể bỏ qua các số lẻ và chuyển sang lần lặp kế tiếp với `continue`:

```go
sum_even := 0

for i := 1; i <= 10; i++ {
    if i % 2 != 0 {
        continue           // nếu số là lẻ, bỏ qua lần lặp này
    }
    sum_even += i
}
fmt.Println(sum_even)      // xuất ra: 30
```

Với đoạn mã trên, kết quả sẽ là 30, do vòng lặp chỉ tính tổng các số chẵn.

### break

Trong một số trường hợp, cần thoát khỏi vòng lặp khi điều kiện nào đó được thỏa mãn. Để làm điều này, có thể sử dụng toán tử `break`:

```go
for i := 1; i <= 9; i++ {
    if i % 5 == 0 {
        break          // nếu số chia hết cho 5, thoát khỏi vòng lặp
    }
    fmt.Println(i)
}
/* Kết quả:
1
2
3
4
*/
```

Lưu ý rằng nếu có nhiều vòng lặp lồng nhau và `break` được gọi trong vòng lặp bên trong, nó chỉ thoát khỏi vòng lặp đó chứ không thoát khỏi tất cả các vòng lặp.

Toán tử `break` cho phép thoát khỏi các vòng lặp và cũng có thể sử dụng trong cấu trúc `switch`.

## Function

Hàm là một khối mã lệnh có thể được sử dụng nhiều lần trong chương trình. Mỗi hàm cần có một tên duy nhất và có thể được gọi ở nhiều vị trí khác nhau trong chương trình. Các hàm giúp cho mã nguồn trở nên ngắn gọn và có cấu trúc rõ ràng hơn.

Nếu bạn thấy một đoạn mã lặp lại ở nhiều nơi trong chương trình, đó là dấu hiệu nên xem xét việc sử dụng hàm để thay thế.

Trong các phần trước của khóa học, chúng ta đã gặp một số hàm có sẵn, những hàm này do các nhà phát triển đã viết sẵn để ta sử dụng. Ví dụ, hàm `Println()` in ra văn bản:

```go
fmt.Println("Привет, Степик!")
```

`Println` là tên của hàm, còn `"Привет, Степик!"` là đối số truyền vào hàm thông qua cặp dấu ngoặc tròn.

Để gọi một hàm trong mã nguồn, bạn cần viết tên hàm kèm theo dấu ngoặc tròn ngay sau tên hàm.

### **Hàm do người dùng tạo**

Cú pháp tổng quát của một hàm:

```go
func helloStepik() {
    fmt.Println("Привет, Степик!") // thân hàm
}
```

Khai báo một hàm bắt đầu bằng từ khóa `func`. Sau đó là tên hàm, trong ví dụ trên là `helloStepik`, tiếp theo là dấu ngoặc tròn `()`. Sau dấu ngoặc tròn là dấu ngoặc nhọn `{ }`, bên trong chứa mã lệnh của hàm (thân hàm), đây là đoạn mã sẽ được thực thi khi hàm được gọi.

Hàm không tự thực thi ngay khi chương trình chạy mà chỉ thực hiện khi được gọi. Để gọi hàm, bạn viết tên hàm kèm dấu ngoặc tròn:

```go
helloStepik()
```

Bạn có thể gọi hàm trong chương trình, bên trong hàm `main` chính:

```go
package main

import "fmt"

func helloStepik() {
    fmt.Println("Привет, Степик!")
}

func main() {
    helloStepik() // sẽ in ra: Привет, Степик!
}
```

Lưu ý rằng hàm của bạn được viết ngoài hàm chính `main()`. Các hàm thường không nằm lồng nhau, dù vẫn có thể làm vậy bằng cách dùng kỹ thuật closure.

Lưu ý: dấu ngoặc nhọn mở của thân hàm không nên được đặt ở dòng tiếp theo mà phải nằm ngay sau dấu ngoặc tròn:

// không đúng:

```go
func helloStepik() 
{
    fmt.Println("Привет, Степик!")
}
```

// đúng:

```go
func helloStepik() {
    fmt.Println("Привет, Степик!")
}
```

Điều này là do Go tự động chèn các ký tự phân tách ở cuối dòng. Nếu bạn xuống dòng cho dấu ngoặc nhọn, trình biên dịch sẽ chèn ký tự phân tách ở đó, khiến hàm bị tách và gây lỗi.

### Đối số

Nếu cần truyền thông tin vào hàm, bạn có thể làm điều này qua các đối số, thường là các biến. Đối số được chỉ định khi gọi hàm, sau tên hàm và nằm trong dấu ngoặc tròn:

```go
func argFunc(thamSo) {
    // mã lệnh
}

argFunc(doiSo)
```

Khi khai báo hàm, tham số nhận giá trị từ đối số. Lưu ý rằng khi khai báo hàm, bạn phải chỉ định tên tham số và kiểu dữ liệu của nó:

```go
func argFunc(x int) {
    // mã lệnh
}
```

Trong ví dụ trên, `x` là tham số kiểu `int` mà hàm `argFunc` sẽ nhận.

### Nhiều đối số

Hàm có thể nhận một hoặc nhiều đối số. Nếu có nhiều đối số, chúng được ngăn cách bằng dấu phẩy:

```go
func argFunc(a int, b int, c int) {
    // mã lệnh
}
```

Hoặc nếu tất cả có cùng kiểu dữ liệu, bạn có thể viết gọn như sau:

```go
func argFunc(a, b, c int) {
    // mã lệnh
}
```

Khi gọi hàm, bạn cần truyền vào đúng số lượng đối số như số tham số của hàm:

```go
func argFunc(a int, b int, c int) {
    // mã lệnh
}

argFunc(num1, num2, num3)
```

Lưu ý rằng khi gọi hàm, bạn sử dụng các tên biến bên ngoài hàm, còn trong khai báo và thân hàm, bạn có thể dùng tên khác. Điều này xảy ra như sau: khi hàm được gọi, giá trị của các biến bên ngoài như `num1`, `num2`, `num3` (gọi là đối số) sẽ được truyền vào các biến cục bộ `a`, `b`, `c` (gọi là tham số) trong hàm. Sau đó, mọi thao tác trong hàm đều thực hiện với các biến cục bộ này.

### Return

Toán tử `return` được sử dụng để trả kết quả từ hàm về lại thân chính của chương trình. Để làm điều này, ta đặt `return` trong phần thân của hàm, sau đó là biến hoặc biểu thức cần trả về.

Ví dụ:

```go
package main

import "fmt"

func square(num int) int {
    return num * num
}

func main() {
    res := square(4)
    fmt.Println(res) // sẽ in ra: 16
}
```

Chú ý rằng sau dấu ngoặc tròn chứa tham số của hàm, ta cần chỉ rõ kiểu dữ liệu mà hàm sẽ trả về. Nếu không, trình biên dịch sẽ báo lỗi vì không có nơi nào để trả về giá trị.

Toán tử `return` kết thúc hàm và trả kết quả về cho đoạn mã đã gọi hàm đó.

### Trả về nhiều giá trị

Một tính năng hữu ích của Go là các hàm có thể trả về nhiều giá trị.

Ví dụ:

```go
func plusMinus(x, y int) (int, int) {
    return x + y, x - y
}
```

Hàm `plusMinus()` nhận hai tham số kiểu số nguyên và trả về hai giá trị: tổng và hiệu của chúng. Lưu ý rằng cần chỉ rõ kiểu dữ liệu của từng giá trị trả về. Trong trường hợp này là hai số nguyên - `(int, int)`. Nếu có nhiều giá trị trả về, chúng phải được đặt trong ngoặc.

Giờ đây, ta có thể gọi hàm và gán kết quả cho các biến:

```go
a, b := plusMinus(15, 5)
```

Trả về nhiều giá trị thường được sử dụng để trả về cả kết quả và thông báo lỗi từ hàm.

### defer

Toán tử `defer` đảm bảo rằng một hàm sẽ chỉ được gọi sau khi hàm bao quanh nó hoàn tất.

Ví dụ:

```go
package main

import "fmt"

func welcome() {
    fmt.Println("Добро пожаловать")
}

func main() {
    defer welcome()
    fmt.Println("Привет!")
}
```

Trong mã trên, `Привет!` sẽ được in ra trước, sau đó mới đến kết quả của hàm `welcome()`. Điều này xảy ra vì lệnh gọi `welcome()` bị trì hoãn, đợi hàm `main()` kết thúc rồi mới thực thi.

`defer` thường được dùng để dọn dẹp, ví dụ như giải phóng tài nguyên mà mã đang sử dụng, như tệp hoặc kết nối.

### **Nhiều lệnh `defer`**

Khi nhiều lệnh gọi hàm được hoãn lại bằng `defer`, chúng sẽ được thực thi theo thứ tự ngược lại, tuân theo nguyên tắc "vào sau ra trước" (LIFO - Last In, First Out).

Ví dụ:

```go
package main

import "fmt"

func main() {
    fmt.Println("начало")

    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("конец")
}
```

Trong mã trên, các lệnh `defer` sẽ được thực thi ngược lại, in ra các giá trị từ 4 đến 0 sau khi `конец` được in.

### **Phạm vi (Scope)**

Phạm vi của một biến là nơi mà biến có thể được sử dụng. Trong Go, có hai phạm vi chính: phạm vi cục bộ và phạm vi toàn cục.

Biến được định nghĩa trong một hàm gọi là biến cục bộ. Phạm vi của biến này chỉ giới hạn trong thân hàm, nghĩa là nó chỉ tồn tại trong hàm và không thể được truy cập từ ngoài hàm đó.

Ví dụ:

```go
func scope() {
    var s = "локальная переменная"
}
```

Biến `s` trong ví dụ trên là biến cục bộ và chỉ có thể truy cập trong hàm `scope()`.

Nếu cố gắng truy cập vào biến này ngoài hàm, sẽ gặp lỗi:

```go
package main

import "fmt"

func scope() {
    var s = "локальная переменная"
}

func main() {
    fmt.Println(s)
}

// Sẽ xuất hiện lỗi:
// undefined: s
```

Các tham số của hàm cũng là biến cục bộ và không thể truy cập từ bên ngoài hàm. Tóm lại, tất cả các biến trong hàm đều mặc định là cục bộ và chỉ có thể sử dụng trong phạm vi của hàm đó.

Phạm vi của biến cục bộ có thể được hiểu là giới hạn trong các dấu ngoặc nhọn của hàm.

### **Biến toàn cục**

Biến được định nghĩa ngoài phạm vi cục bộ là biến toàn cục. Biến toàn cục có thể được sử dụng ở bất kỳ đâu trong chương trình.

Ví dụ:

```go
package main

import "fmt"

var s = "глобальная переменная"

func scope() {
    fmt.Println(s)
}

func main() {
    fmt.Println(s)
    scope()
}
```

Trong ví dụ trên, biến `s` được khai báo ngoài các hàm, khiến nó trở thành biến toàn cục. Biến này có thể được truy cập ở bất kỳ đâu trong chương trình, và mã trên sẽ chạy mà không gặp lỗi.

Tuy nhiên, việc sử dụng quá nhiều biến toàn cục mà không có lý do chính đáng được coi là một thói quen xấu trong lập trình.

## **Con trỏ (Pointers)**

Mỗi giá trị mà chúng ta định nghĩa trong chương trình đều được lưu trữ trong bộ nhớ của máy tính và có một địa chỉ duy nhất.

Con trỏ là những biến đặc biệt dùng để lưu trữ địa chỉ của các giá trị trong bộ nhớ (chỉ vào địa chỉ của biến trong bộ nhớ của máy tính).

Trong Go, con trỏ được khai báo bằng cách sử dụng toán tử `*` (dấu sao), sau đó là kiểu của giá trị mà con trỏ sẽ chỉ đến:

```go
var p *int
```

Trong ví dụ trên, `p` là con trỏ đến một giá trị kiểu số nguyên.

Nhờ vào việc sử dụng con trỏ, chúng ta có thể, ví dụ, truyền vào hàm không phải giá trị của biến mà chính bản thân biến, và sau đó thay đổi giá trị của nó trong hàm.

### **Gán địa chỉ cho con trỏ**

Chúng ta có thể gán địa chỉ của biến cho con trỏ bằng toán tử `&`, toán tử này sẽ trả về địa chỉ của biến.

Ví dụ:

```go
a := 23
p := &a
```

Giờ đây, `p` là con trỏ lưu trữ địa chỉ của biến `a`. Khi in ra `p`, chúng ta sẽ thấy một giá trị tương tự như `0xc000100010`, là địa chỉ trong bộ nhớ.

Để truy cập giá trị mà con trỏ chỉ đến, ta sử dụng toán tử `*`:

```go
fmt.Println(*p) // sẽ in ra 23
```

Toán tử `*` cũng có thể dùng để thay đổi giá trị của biến thông qua địa chỉ mà con trỏ đang lưu:

```go
a := 23
p := &a

*p = 11
fmt.Println(*p) // sẽ in ra 11
fmt.Println(a)  // sẽ in ra 11
```

Ở đây, chúng ta đã thay đổi giá trị của `a` bằng con trỏ `p`.

Toán tử `*` còn được gọi là toán tử "giải tham chiếu".

### **Sử dụng con trỏ**

Mặc dù có thể bạn không nhận ra, nhưng chúng ta đã sử dụng con trỏ trong các bài học trước khi lấy đầu vào từ người dùng:

```go
var input string
fmt.Scanln(&input)
```

Ở đây, chúng ta truyền địa chỉ bộ nhớ của biến `input` cho hàm `Scanln()`, hàm này sẽ sử dụng địa chỉ đó để lưu giá trị được nhập vào.

### **Truyền con trỏ làm tham số của hàm**

Chúng ta có thể truyền con trỏ làm tham số của hàm.

Ví dụ:

```go
package main

import "fmt"

func change(x int) {
    x = 22
}

func changePointer(pointer *int) {
    *pointer = 22
}

func main() {
    num := 14

    change(num)
    fmt.Println(num) // sẽ in ra 14

    changePointer(&num)
    fmt.Println(num) // sẽ in ra 22
}
```

Trong ví dụ này, chúng ta có hai hàm. Hàm `change()` nhận tham số là một số nguyên và thay đổi giá trị của nó. Hàm `changePointer()` làm điều tương tự nhưng sử dụng con trỏ.

Khi chạy mã, bạn sẽ thấy hàm `change()` không thay đổi giá trị của `num`, vì tham số trong hàm này chỉ là bản sao của tham số được truyền vào. Tuy nhiên, hàm `changePointer()` lại thay đổi giá trị thực của `num`, vì chúng ta truyền vào địa chỉ của `num` bằng toán tử `&`:

```go
changePointer(&num)
```

## Structure

Cấu trúc (Structure) là một kiểu dữ liệu chứa các trường có tên, cho phép nhóm nhiều loại dữ liệu khác nhau lại với nhau.

Ví dụ, để lưu trữ dữ liệu liên hệ (Contacts), ta có thể tạo cấu trúc như sau:

```go
type Contact struct {
    name string
    age  int
}
```

Từ khóa `type` được sử dụng để định nghĩa một cấu trúc, sau đó là tên và kiểu của cấu trúc, ở đây là `struct`. Cấu trúc `Contact` này có hai trường: chuỗi `name` và số nguyên `age`.

Có thể tạo một thể hiện của cấu trúc `Contact` bằng cú pháp sau:

```go
x := Contact{"Andrey", 33}
```

`x` bây giờ là một cấu trúc, được khởi tạo với các dữ liệu trong dấu ngoặc nhọn.

Ngoài ra, ta có thể chỉ định tên trường khi tạo một thể hiện mới của cấu trúc:

```go
x := Contact{name: "Andrey", age: 33}
```

Cách này giúp mã dễ đọc hơn.

Chúng ta có thể truy cập các trường của cấu trúc bằng cách sử dụng tên của cấu trúc và dấu chấm:

```go
x := Contact{"Andrey", 33}

x.age = 27
fmt.Println(x.age)  // sẽ hiển thị 27
fmt.Println(x.name) // sẽ hiển thị "Andrey"
```

Lưu ý rằng chúng ta có thể thay đổi giá trị của các trường bằng cách gán giá trị mới cho chúng.

### Con trỏ structure

Giống như với các con trỏ đơn giản, chúng ta cũng có thể tạo con trỏ đến cấu trúc bằng toán tử `&`:

```go
x := Contact{"Andrey", 33}
p := &x
```

Con trỏ đến cấu trúc sẽ tự động được giải tham chiếu, có nghĩa là chúng ta có thể truy cập các trường của cấu trúc chỉ bằng cách sử dụng dấu chấm:

```go
x := Contact{"Andrey", 33}
p := &x
fmt.Println(p.age)
```

Chúng ta có thể sử dụng `(*p).age` để truy cập trường `age` trong cấu trúc, nhưng cách này trông phức tạp và khó đọc. Go cho phép rút gọn cú pháp này và chỉ cần sử dụng `p.age` thay thế.

Chúng ta cũng có thể sử dụng con trỏ khi tạo một cấu trúc mới:

```go
p := &Contact{"Andrey", 22}
```

Bây giờ `p` là một con trỏ đến thể hiện mới tạo của cấu trúc `Contact`.

Con trỏ đến cấu trúc rất hữu ích vì chúng cho phép truyền cấu trúc vào các hàm như một tham số.

## Phương thức

Chúng ta có thể thêm hàm cho các cấu trúc thông qua các phương thức!\
Phương thức thực chất là các hàm với một đối số đặc biệt gọi là "nhận" (receiver).

Ví dụ:

```go
func (x Contact) welcome() {
    fmt.Println(x.name)
    fmt.Println(x.age)
}
```

Đối tượng nhận được chỉ định giữa từ khóa `func` và tên phương thức.\
Trong ví dụ trên, đối tượng nhận là cấu trúc `Contact`.

Lưu ý rằng trong phương thức, chúng ta có thể truy cập các trường của cấu trúc mà phương thức nhận.

Sau khi định nghĩa phương thức, chúng ta có thể gọi phương thức đó trên cấu trúc của mình bằng cú pháp dấu chấm:

```go
package main

import "fmt"

type Contact struct {
    name string
    age int
}

func (x Contact) welcome() {
    fmt.Println(x.name)
    fmt.Println(x.age)
}

func main() {
    x := Contact{"Andrey", 33}
    x.welcome()
}
```

Vì phương thức chỉ là một hàm với đối số nhận, chúng ta có thể đạt được cùng chức năng bằng cách sử dụng một hàm thông thường nhận cấu trúc làm tham số:

```go
func welcome(x Contact) {
    fmt.Println(x.name)
    fmt.Println(x.age)
}

func main() {
    x := Contact{"Andrey", 33}
    welcome(x)
}
```

Trong mã trên, `welcome()` là một hàm nhận cấu trúc làm tham số.

Nếu chúng ta cần thay đổi dữ liệu của cấu trúc trong phương thức, chúng ta có thể sử dụng con trỏ làm đối tượng nhận phương thức.

Ví dụ:

```go
func (ptr *Contact) increase(val int) {
    ptr.age += val
}
```

Bây giờ phương thức `increase()` sử dụng con trỏ làm đối tượng nhận và có thể thay đổi trường `age` trong cấu trúc mà nó được gọi:

```go
x := Contact{"Andrey", 33}
x.increase(5)
fmt.Println(x.age) // sẽ hiển thị 38
```

Go tự động giải tham chiếu các con trỏ, vì vậy chúng ta chỉ cần gọi phương thức với tên cấu trúc như khi sử dụng đối tượng nhận giá trị thông thường.

Vì phương thức thường cần thay đổi đối tượng nhận của nó, con trỏ thường được sử dụng làm đối tượng nhận nhiều hơn so với đối tượng nhận giá trị.

## Mảng

Mảng (Array) là một dãy các phần tử có cùng kiểu dữ liệu.

Mảng được khai báo bằng cách sử dụng dấu ngoặc vuông, trong đó số lượng phần tử trong mảng được xác định bằng số trong dấu ngoặc. Sau dấu ngoặc vuông, ta chỉ định kiểu dữ liệu của các phần tử trong mảng.

Ví dụ:

```go
var a [5]int
```

Bây giờ `a` là một mảng gồm 5 số nguyên.

Bạn cũng có thể khai báo và khởi tạo giá trị cho mảng ngay lập tức bằng cú pháp sau:

```go
a := [5]int{0, 2, 4, 6, 8}
```

Như bạn thấy, khi khai báo mảng, chúng ta phải chỉ định kích thước của nó. Điều này có nghĩa là mảng có kích thước cố định.

Sau khi khai báo mảng, chúng ta có thể truy cập các phần tử của mảng bằng cách sử dụng dấu ngoặc vuông và chỉ số của chúng.

Ví dụ:

```go
var a [5]int

a[0] = 8
a[1] = 42

fmt.Println(a[1]) // sẽ hiển thị 42
```

Mỗi phần tử trong mảng có một chỉ số duy nhất, bắt đầu từ 0. Phần tử đầu tiên có chỉ số 0, phần tử thứ hai có chỉ số 1, và cứ thế.

Bạn có thể gán giá trị cho các phần tử trong mảng cũng như truy xuất giá trị của chúng.

Theo mặc định, các phần tử trong mảng sẽ được khởi tạo với giá trị mặc định của kiểu dữ liệu đó (ví dụ: 0 cho số nguyên).

### Cắt mảng

Mảng có kích thước cố định, nghĩa là sau khi khai báo, bạn không thể thay đổi số lượng phần tử trong nó.

Để giải quyết vấn đề này, Go cung cấp **cắt mảng** (slices), là một cách đại diện cho các phần tử của mảng có thể thay đổi kích thước động.

Cắt mảng dựa trên mảng và được xác định bằng cách chỉ định hai chỉ số, giới hạn dưới và giới hạn trên, được phân cách bằng dấu hai chấm:

```go
a := [5]int{0, 2, 4, 6, 8}
var s []int = a[1:3]
```

Đoạn mã này lấy từ mảng `a` các phần tử có chỉ số từ 1 đến 3, bao gồm chỉ số đầu tiên nhưng không bao gồm chỉ số cuối cùng.

Do đó, cắt mảng `s` sẽ chứa các giá trị `[2, 4]`:

```go
fmt.Println(s) // sẽ hiển thị [2 4]
```

Bạn có thể bỏ qua giới hạn dưới hoặc giới hạn trên.\
Nếu bỏ qua giới hạn dưới, giá trị mặc định là 0, và nếu bỏ qua giới hạn trên, giá trị mặc định là chiều dài của mảng.\
Ví dụ: `a[:3]` sẽ lấy 3 phần tử đầu tiên của mảng.

Bạn có thể truy cập giá trị của cắt mảng (slice) bằng cú pháp tương tự như khi làm việc với mảng:

```go
a := [5]int{0, 2, 4, 6, 8}
var s []int = a[1:3]

fmt.Println(s[1]) // sẽ hiển thị 4
```

Cắt mảng không lưu trữ dữ liệu, nó chỉ mô tả một phần của mảng ban đầu. Việc thay đổi các phần tử trong cắt mảng sẽ thay đổi các phần tử tương ứng trong mảng gốc.

Ví dụ:

```go
package main

import "fmt"

func main() {
  a := [5]int{0, 2, 4, 6, 8}
  var s []int = a[1:3]

  s[0] = 8
  fmt.Println(a) // sẽ hiển thị [0 8 4 6 8]
}
```

Bạn có thể có nhiều cắt mảng từ cùng một mảng. Việc thay đổi trong bất kỳ cắt mảng nào cũng sẽ ảnh hưởng đến tất cả các cắt mảng khác, vì chúng đều liên kết với mảng gốc.

Go cung cấp hàm `make()` để tạo cắt mảng (slice). Điều này giúp tạo ra các mảng có kích thước động.

Ví dụ:

```go
a := make([]int, 5)
```

Hàm `make()` tạo ra một mảng có kiểu và kích thước đã chỉ định và trả về một cắt mảng, trỏ tới mảng đó.

Sau khi tạo cắt mảng, chúng ta có thể thêm các phần tử mới vào nó bằng cách sử dụng hàm `append()`:

```go
a := make([]int, 5)
a = append(a, 8)
fmt.Println(a) // sẽ hiển thị [0 0 0 0 0 8]
```

Hàm `append()` nhận cắt mảng làm đối số đầu tiên và các phần tử cần thêm vào cuối cắt mảng làm đối số tiếp theo. Sau đó, nó trả về một cắt mảng mới, bao gồm cắt mảng cũ và các phần tử mới đã thêm vào.

Bạn cũng có thể thêm nhiều giá trị cùng lúc bằng cách phân tách chúng bằng dấu phẩy, ví dụ: `append(s, 1, 2, 3)`.

## Range

Bây giờ, khi chúng ta biết cách tạo mảng và cắt mảng (slice), hãy học cách duyệt qua các phần tử của chúng bằng vòng lặp!

Cú pháp `range` trong vòng lặp `for` cho phép chúng ta duyệt qua các phần tử của cắt mảng (slice):

```go
package main

import "fmt"

func main() {
  a := make([]int, 5)
  a[1] = 2
  a[2] = 3

  for i, v := range a {
    fmt.Println(i, v)
  }
}
```

Kết quả in ra sẽ là:

```
0 0
1 2
2 3
3 0
4 0
```

Trong mỗi lần lặp, vòng lặp trả về hai giá trị: chỉ số của phần tử và giá trị của nó.

Nếu bạn chỉ cần giá trị mà không cần chỉ số, bạn có thể bỏ qua chỉ số bằng dấu gạch dưới `_`:

```go
for _, v := range a {
  fmt.Println(v)
}
```

Cú pháp `range` có thể được sử dụng cho cả cắt mảng và mảng.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://viettaliii.gitbook.io/home/education/window-pe-.net/go-language/basic-go.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
