Kiểu dữ liệu
Giống như bất kỳ ngôn ngữ lập trình nào khác, chúng ta sẽ bắt đầu với các loại dữ liệu.
Kiểu số
Các loại này bao gồm các kiểu số nguyên, trong Go thì phân ra nhiều loại hơn hầu hết các ngôn ngữ khác, trong đó có số nguyên (int, int8, int16, int32, int64), các kiểu số thực (float32, float64), số phức (complex64, complex128)
int
Kiểu int là kiểu số nguyên mặc định trong Go. Đây là kiểu số nguyên có dấu có kích thước ít nhất là 32 bit hoặc 64 bit, tùy thuộc vào kiến trúc cơ bản của máy mà code chạy trên đó.
// int
a := 10
b := -5
c := a + b // 5
“Bạn có thể giải thích thêm về kích thước của kiểu int theo kiến trúc là như nào không?”
Trên máy tính có kiến trúc 32 bit, kiểu int có kích thước là 32 bit (4 byte), trong khi trên máy tính có kiến trúc 64 bit, kiểu int có kích thước là 64 bit (8 byte).
int8, int16, int32, int64
Nếu bạn cần chỉ định kích thước cố định cho một kiểu số nguyên nhằm mục đích tối ưu peformance, bạn có thể sử dụng một trong các kiểu số nguyên có kích thước cố định được cung cấp bởi Go: int8, int16, int32 hoặc int64.
// Kiểu int8 là kiểu số nguyên có dấu 8 bit. Nó có thể chứa giá trị từ -128 đến 127.
d := int8(127)
e := int8(-128)
f := d + e // -1 // Kiểu int16 là kiểu số nguyên có dấu 16 bit. Nó có thể chứa giá trị từ -32768 đến 32767.
g := int16(32767)
h := int16(-32768)
i := g + h // -1 // Kiểu int32 là kiểu số nguyên có dấu 32 bit. Nó có thể chứa giá trị từ -2147483648 đến 2147483647.
j := int32(2147483647)
k := int32(-2147483648)
l := j + k // -1 // Kiểu int64 là kiểu số nguyên có dấu 64 bit. Nó có thể chứa giá trị từ -9223372036854775808 đến 9223372036854775807.
m := int64(9223372036854775807)
n := int64(-9223372036854775808)
o := m + n // -1
Những loại này được định nghĩa có kích thước cố định bất kể kiến trúc của máy, do đó bạn có thể sử dụng chúng để lưu trữ số nguyên với kích thước cụ thể nếu cần thiết.
float32, float64
Các kiểu dữ liệu float32 và float64 đại diện cho số dấu phẩy động đơn và đôi (floating-point number) tương ứng. Những kiểu dữ liệu này được sử dụng để biểu diễn các số thực, tức là các số có phần thập phân.
// Kiểu dữ liệu float32 là kiểu dấu phẩy động 32-bit.
p := float32(3.14159)
q := float32(2.71828)
r := p * q // r là 8.5397405 // Kiểu dữ liệu float64 là kiểu dấu phẩy động 64-bit. Nó được sử dụng để biểu diễn các giá trị có độ chính xác thập phân cao hơn.
s := float64(3.14159)
t := float64(2.71828)
u := s * t // u là 8.539728707423581
float32 hay float64 nên sử dụng loại nào?
float32 là kiểu dữ liệu đại diện cho số dấu phẩy động đơn. Đây là những số có phần thập phân, chẳng hạn như 3.14 hoặc 2.71828. Các số dấu phẩy động đơn được lưu trữ trong 4 byte (32 bit) của bộ nhớ và có thể biểu diễn một loạt các giá trị với độ chính xác tốt (khoảng 6-9 chữ số có ý nghĩa). Sử dụng float32 thường nhanh hơn khi làm việc với các giá trị float64, nhưng có thể không chính xác bằng.
Ngoài ra, các kiểu float32 và float64 có các quy tắc cụ thể để xử lý các giá trị đặc biệt như dương vô cực (+Inf) và âm cô cực (-Inf) hay không phải là một số (NaN).
complex64, complex128
Đây là kiểu số phức, một kiểu mà mình chưa bao giờ đụng tới trong khi làm việc ở backend side nên mình chỉ nói sơ qua phần này nhé:
func main() { x := complex(2, 3) // 2 + 3i y := complex(4, 5) // 4 + 5i // Perform arithmetic operations fmt.Println("Addition:", x+y) // (6+8i) fmt.Println("Subtraction:", x-y) // (-2-2i) fmt.Println("Multiplication:", x*y) // (-7+22i) fmt.Println("Division:", x/y) // (0.5609756097560976+0.0487804878048781i) // Extract real and imaginary parts fmt.Println("Real part of x:", real(x)) // 2 fmt.Println("Imaginary part of x:", imag(x)) // 3
}
Kiểu Logic (Boolean)
Kiểu boolean đại diện cho giá trị logic đúng (true) hoặc sai (false), kiểu này quá đơn giản kể cả với các bạn mới lập trình.
a := true b := false if a { fmt.Println("a là đúng") } else { fmt.Println("a là sai") } // a là đúng
Boolean thường được sử dụng trong các câu lệnh điều khiển như if và switch để thực hiện quyết định trong mã của bạn dựa trên tính đúng hay sai của một điều kiện.
Kiểu chuỗi (String)
Chuỗi là một chuỗi các ký tự.
a := "hello"
b := "world"
c := a + " " + b // "hello world" fmt.Println(a) // "hello"
fmt.Println(b) // "world"
fmt.Println(c) // "hello world"
fmt.Println(len(a)) // 5 ('hello' is 5 characters)
fmt.Println(a[0]) // "h" (the first character of 'hello')
fmt.Println(a[1:3]) // "el"
Trong ví dụ này, chúng ta định nghĩa hai chuỗi a và b, và sau đó nối chúng với nhau bằng toán tử +. Chúng ta cũng sử dụng hàm len() để lấy độ dài của chuỗi a và toán tử index [] để truy cập vào từng ký tự riêng lẻ trong chuỗi a.
Chuỗi trong Go là bất biến (immutable), có nghĩa là sau khi bạn tạo một chuỗi, bạn không thể thay đổi các ký tự trong chuỗi đó. Tuy nhiên, bạn có thể tạo ra một chuỗi mới là phiên bản đã chỉnh sửa của chuỗi hiện có thông qua việc sử dụng thư viện "strings".
e := strings.ToUpper(a) // "HELLO"
f := strings.Repeat(a, 3) // "hellohellohello"
Mảng (Array)
Trong Go, một mảng là một tập các giá trị cùng kiểu, có kích thước cố định. Mỗi giá trị trong mảng được gọi là một phần tử, và mỗi phần tử có chỉ số riêng, đó là vị trí của nó trong mảng. Bạn có thể truy cập vào một phần tử cụ thể trong mảng bằng cách sử dụng chỉ số của nó trong dấu ngoặc vuông, như sau:
var a [3]int // a is an array of 3 int
a[0] = 1 // set the first element of a to 1
a[1] = 2 // second element is 2
a[2] = 3 // third element is 3 fmt.Println(a[0]) // 1
fmt.Println(a[1]) // 2
fmt.Println(a[2]) // 3
fmt.Println(len(a)) // 3 (a is an array of 3 int) // short declaration syntax to create and initialize an array
b := [3]int{1, 2, 3}
Kích thước của mảng (số lượng phần tử mà nó có thể chứa) là một phần của mảng, do đó bạn không thể thay đổi kích thước của một mảng sau khi bạn đã tạo nó. Bạn có thể sử dụng hàm len() để lấy độ dài của một mảng tại thời điểm chạy.
Slice (Mảng cắt)
Khác với mảng có kích thước cố định, slice (mảng cắt) là một mảng động có thể mở rộng hoặc co lại theo nhu cầu.
Slice là một giải pháp linh hoạt và hiệu quả hơn so với mảng khi bạn cần lưu trữ và xử lý một số lượng biến đổi của các giá trị cùng loại.
a := make([]int, 3) // a is a slice of 3 integers
a[0] = 1 // first element is 1
a[1] = 2 // second element is 2
a[2] = 3 // third element is 3 // we can append more int to a, after this line, a is: [1, 2, 3, 4]
a = append(a, 4) fmt.Println(a[3]) // 4, fourth element is 4
fmt.Println(len(a)) // 4, a have 4 ints // short declaration syntax to create and initialize a slice
b := []int{1, 2, 3}
Còn nhiều điều để nói về mảng cắt, nhưng không nằm trong phạm vi của bài này.
Struct (Cấu trúc)
Struct hữu ích khi bạn cần biểu diễn các cấu trúc dữ liệu phức tạp có nhiều thuộc tính.
Trong Go, struct là một kiểu dữ liệu tùy chỉnh có thể chứa một kết hợp của nhiều kiểu khác nhau:
type Person struct { Name string Age int Address string
} // create a person struct instance is simple
p := Person{ Name: "Jane Smith", Age: 25, Address: "456 Maple Ave."
} fmt.Println(p.Name) // "John Smith" // struct also has its own method (or action)
func (p *Person) Greet() string { return "Hello, my name is " + p.Name
} fmt.Println(p.Greet()) // "Hello, my name is John Smith"
Pointers (Con trỏ)
Con trỏ hữu ích khi bạn muốn chuyển địa chỉ bộ nhớ của một giá trị cho một hàm hoặc khi bạn muốn sửa đổi giá trị của một biến một cách gián tiếp.
Nếu bạn mới làm quen với khái niệm con trỏ, có thể sẽ khó tưởng tượng, trong Go, một con trỏ là một biến lưu trữ địa chỉ bộ nhớ của một giá trị.
Hãy xem ví dụ bên dưới rồi đọc giải thích của mình nhé.
a := 1
b := &a // b is point to a's memory fmt.Println(a) // 1
fmt.Println(*b) // 1, the * operator point to the value that memory holding *b = 2 // set the value of a through the pointer b fmt.Println(a) // 2
fmt.Println(*b) // 2
Trong ví dụ này, chúng ta khai báo một biến số nguyên a và gán giá trị là 1. Sau đó, chúng ta tạo một con trỏ b trỏ tới a bằng toán tử &.
Toán tử * được sử dụng để giải tham chiếu (deference) con trỏ b và truy cập giá trị của a. Sau đó, chúng ta sử dụng toán tử * để thiết lập giá trị của a thành 2 thông qua con trỏ b.
Function
function là một khối mã thực hiện một nhiệm vụ cụ thể và mang tính sử dụng lại (re-usable).
Function cũng có thể được coi như giá trị và có thể được gán cho các biến hoặc được truyền như tham số cho các functions khác:
func Add(x int, y int) int { return x + y
} func Multiply(x, y int) int { return x * y
} // `operation` now work like an `Add` function
operation := Add fmt.Println(operation(1, 2)) // 3 // `operation` now work like an `Multiply` function
operation = Multiply
fmt.Println(operation(3, 4)) // 12 // what type of operation?
fmt.Println(reflect.TypeOf(operation)) // func(int, int) int
Như bạn có thể thấy, type của operation
là func(int, int) int, đây được gọi là chữ ký (signature) của hàm (function).
Biến và hằng số (Constants)
Variables
Trong Go, biến và hằng số được sử dụng để lưu trữ các giá trị.
Biến được sử dụng để lưu trữ các giá trị có thể thay đổi trong quá trình thực thi của chương trình. Để tạo một biến trong Go, bạn sử dụng từ khóa var, tiếp theo là tên biến và kiểu giá trị mà nó sẽ lưu trữ. Dưới đây là một ví dụ:
var x int
Code này tạo một biến x có kiểu int, có thể lưu trữ các giá trị số nguyên. Bạn cũng có thể chỉ định giá trị khởi tạo ban đầu cho biến khi bạn khai báo nó:
var x int = 5
Go cũng cung cấp cú pháp ngắn hơn để khai báo và khởi tạo biến, gọi là "short declaration" Bạn có thể sử dụng syntax này bằng cách thay thế từ khóa var bằng toán tử :=
:
x := 5
Constants
Hằng số được sử dụng để lưu trữ các giá trị không thể thay đổi trong quá trình thực thi của chương trình. Để tạo một hằng số, bạn sử dụng từ khóa const, tiếp theo là tên hằng số và kiểu giá trị mà nó sẽ lưu trữ. Dưới đây là một ví dụ:
const pi float64 = 3.14
Code này tạo một hằng số pi có kiểu float64, có thể lưu trữ các giá trị thập phân. Tương tự như biến, bạn cũng có thể chỉ định giá trị khởi tạo ban đầu cho hằng số khi bạn khai báo nó.
Việc sử dụng hằng số khi bạn cần lưu trữ các giá trị không thay đổi, nó giúp làm cho code của bạn dễ dàng dự đoán và dễ hiểu hơn. Sử dụng hằng số cũng giúp bạn tránh những lỗi lập trình phổ biến và cải thiện code performance.
Toán tử và biểu thức
Toán tử là những ký tự đặc biệt thực hiện các phép toán cụ thể trên một hoặc nhiều giá trị (toán hạng) và tạo ra một kết quả.
Toán tử số học
Go hỗ trợ các toán tử số học sau:
- +: Toán tử cộng. Cộng hai toán hạng và tạo ra tổng.
- -: Toán tử trừ. Trừ toán hạng thứ hai từ toán hạng đầu tiên và tạo ra hiệu.
- *: Toán tử nhân. Nhân hai toán hạng và tạo ra tích.
- /: Toán tử chia. Chia toán hạng đầu tiên cho toán hạng thứ hai và tạo ra thương.
- %: Toán tử chia lấy phần dư. Chia toán hạng đầu tiên cho toán hạng thứ hai và tạo ra phần dư.
Dưới đây là một số ví dụ về cách sử dụng những toán tử này trong Go:
x := 10
y := 3 // Addition
z := x + y // 13
// Subtraction
z = x - y // 7
// Multiplication
z = x * y // 30
// Division
z = x / y // 3
// Modulus
z = x % y // 1
Toán tử nhân và chia có độ ưu tiên cao hơn so với toán tử cộng và trừ, vì vậy chúng được thực hiện trước các phép toán này.
Bạn có thể sử dụng dấu ngoặc đơn để ghi đè độ ưu tiên mặc định và chỉ định thứ tự thực hiện các phép toán.
Toán tử so sánh
Các toán tử so sánh so sánh hai giá trị và trả về một giá trị boolean chỉ ra liệu phép so sánh đó đúng hay sai.
- ==: Toán tử bằng. Trả về true nếu hai toán hạng bằng nhau, ngược lại trả về false.
- !=: Toán tử khác. Trả về true nếu hai toán hạng không bằng nhau, ngược lại trả về false.
- >: Toán tử lớn hơn. Trả về true nếu toán hạng đầu tiên lớn hơn toán hạng thứ hai, ngược lại trả về false.
- <: Toán tử nhỏ hơn. Trả về true nếu toán hạng đầu tiên nhỏ hơn toán hạng thứ hai, ngược lại trả về false.
- >=: Toán tử lớn hơn hoặc bằng. Trả về true nếu toán hạng đầu tiên lớn hơn hoặc bằng toán hạng thứ hai, ngược lại trả về false.
- <=: Toán tử nhỏ hơn hoặc bằng. Trả về true nếu toán hạng đầu tiên nhỏ hơn hoặc bằng toán hạng thứ hai, ngược lại trả về false.
Hãy xem ví dụ dưới đây:
x := 10
y := 3 // Equal to
z := x == y // false // Not equal to
z = x != y // true // Greater than
z = x > y // true // Less than
z = x < y // false // Greater than or equal to
z = x >= y // true // Less than or equal to
z = x <= y // false
Các toán tử so sánh chỉ hoạt động với các giá trị cùng loại. Nếu so sánh các giá trị khác loại sẽ dẫn đến lỗi biên dịch.
Toán tử logic
Các toán tử logic thực hiện logic boolean và trả về một giá trị boolean.
- &&: Toán tử VÀ logic. Trả về true nếu cả hai toán hạng đều đúng, false nếu không.
- ||: Toán tử HOẶC logic. Trả về true nếu một trong hai toán hạng đúng, false nếu cả hai đều sai.
- !: Toán tử KHÔNG logic. Trả về true nếu toán hạng là sai, false nếu toán hạng là đúng.
x := true
y := false // Logical AND
z := x && y // z = false // Logical OR
z = x || y // z = true // Logical NOT
z = !x // z = false
Cần lưu ý rằng toán tử VÀ và HOẶC logic có độ ưu tiên thấp hơn so với các toán tử so sánh, vì vậy chúng sẽ được thực hiện sau các phép so sánh.
Toán tử gán
Bạn có thể đoán kết quả của toán tử gán dựa trên toán tử số học tương ứng.
- =: Toán tử gán đơn giản. Gán giá trị của toán hạng bên phải cho toán hạng bên trái.
- +=, -=, /=, *=, %=: Toán tử gán. Tính giá trị của toán hạng bên phải và gán kết quả cho toán hạng bên trái.
x := 10
y := 3 // Simple assignment
x = y // 3 // Addition assignment
x += y // 6, x = x + y // Subtraction assignment
x -= y // 3, x = x - y // Multiplication assignment
x *= y // 9, x = x * y // Division assignment
x /= y // 3, x = x / y // Modulus assignment
x %= y // x = 0, x = x % y
Toán tử bitwise
Các toán tử bitwise trong Go cho phép bạn làm việc với các bit riêng lẻ trong một giá trị để xây dựng các cấu trúc phức tạp hơn.
Bạn có thể sử dụng các toán tử bitwise để thao tác các bit trong một giá trị như set giá trị bit, xóa các bit cụ thể, hoặc kiểm tra xem một bit cụ thể là 0 hay 1 (on hay off).
- &: Toán tử AND bitwise, toán tử này so sánh từng bit của toán hạng đầu tiên với bit tương ứng của toán hạng thứ hai, và nếu cả hai bit đều là 1, bit kết quả tương ứng sẽ được set là 1. Ngược lại, bit kết quả sẽ được set là 0.
- |: Toán tử OR bitwise, nếu một trong hai bit là 1, bit kết quả tương ứng sẽ được set là 1. Ngược lại, bit kết quả sẽ được set là 0.
- ^: Toán tử XOR bitwise, nếu hai bit khác nhau, bit kết quả tương ứng được set là 1.
Dưới đây là ví dụ:
x := 10 // 1010 in binary
y := 3 // 0011 in binary z := x & y // 2, 0010 in binary
z = x | y // 11, 1011 in binary
z = x ^ y // 9, 1001 in binary
Kết
Bài viết được dịch từ: https://medium.com/@func25/a-comprehensive-golang-guide-for-beginners-go-fundamentals-p1-2b4109ce2a7c