Go Fundamentals: Kiểu dữ liệu cơ bản
Đây là bài viết nằm trong series Go Fundamentals.
Go là ngôn ngữ static type, biến chỉ có thể chứa các giá trị thuộc kiểu mà nó được khai báo. Một kiểu dữ liệu cho biết 2 điều:
Độ lớn bộ nhớ cần cấp phát (1, 2, 4, 8,… bytes)
Định dạng được lưu trữ trong phân vùng bộ nhớ này (số nguyên, số thực, chuỗi,…).
Static type giúp cho mã của bạn trở nên minh bạch hơn, giảm thiểu các lỗi runtime và thời gian biên dịch nhanh hơn. Tiếp theo, chúng ta sẽ cùng tìm hiểu các kiểu dữ liệu cơ bản, type conversion, và các toán tử phổ biến trong Go.
Các kiểu dữ liệu cơ bản của Go bao gồm:
Kiểu luận lý:
boolKiểu số:
Số nguyên có dấu:
int8,int16,int32,int64,intSố nguyên không dấu (
>=0):uint8,uint16,uint32,uint64,uintSố thực:
float32,float64Số phức:
complex64,complex128byte(alias củauint8)rune(alias củaint32)
Kiểu chuỗi:
string
Hàm fmt.Printf
fmt.Printf là một hàm package fmt, được sử dụng để in ra chuỗi theo định dạng (formatted string). Khác với fmt.Println, hàm này cho phép bạn định dạng và hiển thị dữ liệu theo ý muốn bằng cách sử dụng các ký hiệu định dạng (“verb”).
Ví dụ:
package main
import "fmt"
func main() {
name := "Nhan"
age := 25
height := 1.735
fmt.Printf("Name: %s, Age: %d, Height: %.2f\n", name, age, height)
}Trong ví dụ trên:
"Name: %s, Age: %d, Height: %.2f\n": đây là định dạng mà bạn muốn in ra hàm hình, gồm 3 ký hiệu định dạng:%s,%d, và%.2f.name, age, height: là 3 tham số tương ứng với 3 ký hiệu định dạng ở trên. Lưu ý:Số lượng tham số phải bằng với số ký hiệu định dạng.
Tùy vào kiểu giá trị, sẽ có ký hiệu định dạng phù hợp.
%sdùng để hiển thị giá trị kiểu chuỗi.%ddùng để hiển thị giá trị kiểu số nguyên.%fdùng để hiển thị giá trị kiểu số thực.%.2fhiển thị số thực với 2 chữ số thập phân. Nếu số thực có nhiều hơn 2 chữ số thập phân thì nó sẽ tự làm tròn về 2 chữ số. Ví dụ1.735làm tròn thành1.74.\nlà ký tự xuống dòng. Chúng ta cần thêm ký tự này ở cuối vì khác vớifmt.Println,fmt.Printfkhông tự động xuống dòng sau khi in xong.
Kết quả in ra:
Name: Nhan, Age: 25, Height: 1.74Các ký tự định dạng phổ biến:
Verb Format
%d integer
%s string
%f float, default width (w), default precision(p)
%w.pf float, e.g: %9f, %.3f, %9.3f
%t bool
%v defaut format
%+v defaut format with struct fields
%T a Go-syntax representation of the type of the valueKiểu luận lý
Kiểu luận lý (bool) chỉ có 2 giá trị là true và false và cần 8 bit (1 byte) bộ nhớ.
Ví dụ:
package main
import (
"fmt"
"unsafe"
)
func main() {
x, y := true, false // short-hand declarations, type inference
fmt.Printf("x = %t, type = %T, size = %d byte\n", x, x, unsafe.Sizeof(x))
fmt.Printf("y = %t, type = %T, size = %d byte\n", y, y, unsafe.Sizeof(y))
}Ví dụ trên sử dụng thêm 1 package tên là
unsafe. Cú pháp để import nhiều package:import ( "package1" "package2" ... "packageN" )Để in ra giá trị
bool, sử dụng ký tự định dạng%t.Để in ra kiểu dữ liệu của biến, sử dụng ký tự định dạng
%T.unsafe.Sizeof(x)trả về số byte mà biếnxđang chiếm dụng. Vì kết quả là số nguyên nên ta sử dụng ký tự định dạng%d. Lưu ý: nên cẩn thận khi sử dụng packageunsafetrong dự án thực tế.
Kết quả in ra:
x = true, type = bool, size = 1 byte
y = false, type = bool, size = 1 byteKiểu số
1. Số nguyên có dấu
Bên dưới là danh sách các kiểu số nguyên có dấu kèm kích thước bộ nhớ (size) và phạm vi lưu trữ (range).
Type Size (bytes) Range
int8 1 -2^7 to 2^7 - 1
int16 2 -2^15 to 2^15 - 1
int32 4 -2^31 to 2^31 - 1
int64 8 -2^63 to 2^63 - 1
int 4 or 8 (-2^31 to 2^31 - 1) or (-2^63 to 2^63 - 1) Trong đó:
Kiểu
int8cần 8 bit (1 byte) bộ nhớ, tương tựint16cần 16 bit (2 byte),int32cần 32 bit (4 byte),int64cần 64 bit (8 byte).Kiểu
intcần 32 bit (4 byte) trên hệ thống 32 bit hoặc cần 64 bit (8 byte) trên hệ thống 64 bit.
Ví dụ:
package main
import (
"fmt"
"unsafe"
)
func main() {
var (
a int8 // zero-value: 0
b int16 = 10
c int32 = 11
d int64 = 12
e = 13 // type inference: int
)
fmt.Printf("a = %d, type = %T, size = %d\n", a, a, unsafe.Sizeof(a))
fmt.Printf("b = %d, type = %T, size = %d\n", b, b, unsafe.Sizeof(b))
fmt.Printf("c = %d, type = %T, size = %d\n", c, c, unsafe.Sizeof(c))
fmt.Printf("d = %d, type = %T, size = %d\n", d, d, unsafe.Sizeof(d))
fmt.Printf("e = %d, type = %T, size = %d\n", e, e, unsafe.Sizeof(e))
}Kết quả in ra:
a = 0, type = int8, size = 1
b = 10, type = int16, size = 2
c = 11, type = int32, size = 4
d = 12, type = int64, size = 8
e = 13, type = int, size = 8Go tự gán kiểu
int(kiểu mặc định của giá trị13) cho biếne. Đối với kiểuint, tùy vào hệ điều hành mà sẽ cósizekhác nhau: 8 byte đối với hệ thống 64 bit và 4 byte đối với hệ thống 32 bit.
2. Số nguyên không dấu
Bên dưới là danh sách các kiểu số nguyên không dấu kèm kích thước bộ nhớ (size) và phạm vi lưu trữ (range).
Type Size (bytes) Range
uint8 1 0 to 2^8 - 1
uint16 2 0 to 2^16 - 1
uint32 4 0 to 2^32 - 1
uint64 8 0 to 2^64 - 1
uint 4 or 8 (0 to 2^32 - 1) or (0 to 2^64 - 1) Tương tự như số nguyên có dấu:
Kiểu
uint8cần 8 bit (1 byte) bộ nhớ, tương tựuint16cần 16 bit (2 byte),uint32cần 32 bit (4 byte),uint64cần 64 bit (8 byte).Kiểu
uintcần 32 bit (4 byte) trên hệ thống 32 bit hoặc cần 64 bit (8 byte) trên hệ thống 64 bit.
Ví dụ:
package main
import (
"fmt"
"unsafe"
)
func main() {
var (
a uint8 // zero-value: 0
b uint16 = 10
c uint32 = 11
d uint64 = 12
e uint = 13
)
fmt.Printf("a = %d, type = %T, size = %d\n", a, a, unsafe.Sizeof(a))
fmt.Printf("b = %d, type = %T, size = %d\n", b, b, unsafe.Sizeof(b))
fmt.Printf("c = %d, type = %T, size = %d\n", c, c, unsafe.Sizeof(c))
fmt.Printf("d = %d, type = %T, size = %d\n", d, d, unsafe.Sizeof(d))
fmt.Printf("e = %d, type = %T, size = %d\n", e, e, unsafe.Sizeof(e))
}Kết quả in ra:
a = 0, type = uint8, size = 1
b = 10, type = uint16, size = 2
c = 11, type = uint32, size = 4
d = 12, type = uint64, size = 8
e = 13, type = uint, size = 8Đối với kiểu
uint, tùy vào hệ điều hành mà sẽ cósizekhác nhau: 8 byte đối với hệ thống 64 bit và 4 byte đối với hệ thống 32 bit.
3. Số thực
Bên dưới là danh sách các kiểu số thực kèm kích thước bộ nhớ (size) và phạm vi lưu trữ (range).
Type Size (bytes) Range
float32 4 -3.4e+38 to 3.4e+38
float64 8 -1.7e+308 to +1.7e+308Ví dụ:
package main
import (
"fmt"
"unsafe"
)
func main() {
var (
a float32 // zero-value: 0
b float64 = 123.456
c = 456.789 // type inference: float64
)
fmt.Printf("a = %f, type = %T, size = %d\n", a, a, unsafe.Sizeof(a))
fmt.Printf("b = %f, type = %T, size = %d\n", b, b, unsafe.Sizeof(b))
fmt.Printf("c = %.2f, type = %T, size = %d\n", c, c, unsafe.Sizeof(c))
}Kết quả in ra:
a = 0.000000, type = float32, size = 4
b = 123.456000, type = float64, size = 8
c = 456.79, type = float64, size = 8Ở ví dụ trên,
cđược khởi tạo với một số thực và Go tự động gán kiểufloat64cho biến này.
4. Số phức
Go hỗ trợ 2 kiểu số phức là complex64 và complex128. Có 2 cách để khởi tạo một số phức:
Dùng hàm
complex:func complex(r, i FloatType) ComplexTypeHàm này nhận vào 2 số thực
rđại diện cho phần thực v àiđại diện có phần ảo. Nếurvàicùng thuộc kiểufloat32thì hàmcomplexsẽ trả về kiểucomplex64. Nếu r và i thuộc kiểufloat64thì hàm này sẽ trả về kiểucomplex128.p := complex(8, 9)Khởi tạo ngắn gọn:
p := 8 + 9i
Ví dụ:
package main
import (
"fmt"
"unsafe"
)
func main() {
var (
r1 float32 = 8
i1 float32 = 9
r2 float64 = 8
i2 float64 = 9
)
a := complex(r1, i1)
b := complex(r2, i2)
c := 8 + 9i
fmt.Printf("a = %v, type = %T, size = %d\n", a, a, unsafe.Sizeof(a))
fmt.Printf("b = %v, type = %T, size = %d\n", b, b, unsafe.Sizeof(b))
fmt.Printf("c = %v, type = %T, size = %d\n", c, c, unsafe.Sizeof(c))
}Kết quả in ra:
a = (8+9i), type = complex64, size = 8
b = (8+9i), type = complex128, size = 16
c = (8+9i), type = complex128, size = 16Ví dụ trên sử dụng ký tự định đạng
%vđể in ra giá trị của số phức. Khi sử dụng%v, tùy vào kiểu dữ liệu, các trị sẽ được định dạng theo mặc định của Go.
5. Các kiểu số khác
bytelà một alias của kiểuunit8, đại diện cho 1 byte.runelà một alias của kiểuint32, được dùng để biểu diễn các unicode codepoint.
Kiểu byte và rune sẽ được trình bày kỹ hơn trong một bài viết khác về kiểu chuỗi trong Go.
Kiểu chuỗi
Chuỗi trong Go bao gồm 1 hoặc nhiều ký tự khác nhau. Mỗi ký tự được biểu diễn bằng 1 hoặc nhiều byte. Chuỗi trong Go hỗ trợ UTF-8.
Ví dụ:
package main
import (
"fmt"
"unsafe"
)
func main() {
first := "Nhân"
last := "Nguyễn ⭐️"
name := first +" "+ last
fmt.Printf("name = %s, type = %T, size = %d\n", name, name, unsafe.Sizeof(name))
}Kết quả in ra:
name = Nhân Nguyễn ⭐️, type = string, size = 16Lưu ý:
Bạn có thể nối nhiều chuỗi lại với nhau bằng toán tử
+.
Nhiều số ký tự trong các ngôn ngữ cần hơn 1 byte để biểu diễn. Vì vậy, việc đếm số lượng ký tự trong chuỗi của Go bằng cách đếm số lượng byte mà chuỗi đó chiếm dụng là không hoàn toàn chính xác.
Chuỗi là một thành phần quan trọng trong Go. Vì vậy sẽ có một bài viết khác trong series này được dành riêng để nói về nó.
Khái niệm zero-value
Nếu bạn khai báo biến mà không có giá trị khởi tạo thì mặc định Go sẽ gán cho biến đó giá trị zero-value. Tùy từng kiểu dữ liệu sẽ có zero-value tương ứng:
Type Zero-value
Bool false
Số nguyên 0
Số thực 0
Số phức 0i
Chuỗi "" (chuỗi rỗng)
Con trỏ nilToán tử trong Go
Trong Go, có 4 nhóm toán tử cơ bản: toán tử so sánh, toán tử logic, toán tử số học, và toán tử gán.
Toán tử so sánh
Dùng để so sánh giá trị giữa hai biến. Kết quả trả về là true hoặc false. Bao gồm:
So sánh bằng: ==
So sánh khác: !=
So sánh lớn hơn: >
So sánh bé hơn: <
So sánh lớn hơn hoặc bằng: >=
So sánh bé hơn hoặc bằng: <=Ví dụ:
package main
import "fmt"
func main() {
a, b := 5, 10
fmt.Println("a == b:", a == b) // false
fmt.Println("a != b:", a != b) // true
fmt.Println("a > b:", a > b) // false
fmt.Println("a < b:", a < b) // true
fmt.Println("a >= b:", a >= b) // false
fmt.Println("a <= b:", a <= b) // true
}Toán tử logic
AND:
&&(tất cả các điều kiện đều phải đúng).OR:
||(ít nhất một điều kiện đúng).NOT:
!(phủ định, đảo ngược giá trị logic)
Ví dụ:
package main
import "fmt"
func main() {
a, b := true, false
fmt.Println("a && b:", a && b) // false
fmt.Println("a || b:", a || b) // true
fmt.Println("!a:", !a) // false
}Toán tử số học
Dùng để thực hiện các phép tính số học trên các giá trị.
Toán tử:
+,-,*,/(chia),%(chia lấy dư).Các lệnh tăng giảm:
++(tăng 1),--(giảm 1).
Ví dụ:
package main
import "fmt"
func main() {
a, b := 15, 4 // int, int
fmt.Println("a + b =", a+b) // 19
fmt.Println("a - b =", a-b) // 11
fmt.Println("a * b =", a*b) // 60
fmt.Println("a / b =", a/b) // 3 (chia lấy phần nguyên)
fmt.Println("a % b =", a%b) // 3 (chia lấy dư)
fmt.Println("15.0 / 4.0 =", 15.0/4.0) // 3.75 (phần nguyên và phần thập phân)
b++
fmt.Println("b++ =", b) // 5
b--
fmt.Println("b-- =", b) // 4
// a = b++ // Error: syntax error: unexpected ++ at end of statement
}
Một số lưu ý:
a/b: Phép chia giữa hai số nguyên chỉ lấy phần nguyêna%b: Phép chia lấy phần dư15.0/4.0: Khi cả tử và mẫu là số thực, kết quả sẽ bao gồm cả phần thập phân.Trong Go, bạn không thể sử dụng toán tử tăng (
++) hoặc giảm (--) trực tiếp trong các biểu thức gán giá trị. Lý do:b++hoặcb--không trả về giá trị, mà chỉ là câu lệnh độc lập để thay đổi giá trị của biếnb.
Toán tử gán
Dùng để gán hoặc cập nhật giá trị của biến.
Toán tử:
=,+=,-=,*=,/=
Ví dụ:
package main
import "fmt"
func main() {
a := 10
a += 5 // a = a + 5
fmt.Println("a += 5:", a) // 15
a -= 3 // a = a - 3
fmt.Println("a -= 3:", a) // 12
a *= 2 // a = a * 2
fmt.Println("a *= 2:", a) // 24
a /= 4 // a = a / 4
fmt.Println("a /= 4:", a) // 6
}
Chuyển đổi kiểu dữ liệu
Chuyển đổi kiểu dữ liệu (type conversion) xảy ra khi một giá trị thuộc kiểu dữ liệu này được gán cho một biến có kiểu dữ liệu khác. Khác với Go, các ngôn ngữ khác như C/C++, Java hỗ trợ việc chuyển đổi kiểu dữ liệu một cách tự động (implicit type conversion) khi cần thiết. Go không hỗ trợ việc chuyển đổi kiểu dữ liệu một cách tự động, ngay cả đối với các kiểu dữ liệu tương thích.
Để chuyển đổi kiểu dữ liệu, lập trình viên cần chủ động làm việc này với cú pháp:
T(value)Cú pháp này chuyển đổi giá trị
valuesang kiểuT.
Ví dụ:
package main
import (
"fmt"
)
func main() {
var (
a uint8
b int = 300
c float64 = 123.456
)
fmt.Println("var a unit8 =", a)
fmt.Println("var b int =", b)
fmt.Println("var c float64 =", c)
fmt.Println() // break line
a = uint8(b)
fmt.Printf("a = uint8(b) = uint8(%d) = %d\n", b, a)
b = int(a)
fmt.Printf("b = int(a) = int(%d) = %d\n", a, b)
b = int(c)
fmt.Printf("b = int(c) = int(%.3f) = %d\n", c, b)
c = float64(b)
fmt.Printf("c = float64(b) = float64(%d) = %.1f\n", b, c)
// fmt.Println("a + b =", a + b) // Error: invalid operation: a + b (mismatched types uint8 and int)
fmt.Println("a + b =", int(a) + b) // OK
}Kết quả in ra:
var a unit8 = 0
var b int = 300
var c float64 = 123.456
a = uint8(b) = uint8(300) = 44
b = int(a) = int(44) = 44
b = int(c) = int(123.456) = 123
c = float64(b) = float64(123) = 123.0
a + b = 167a = uint8(b): Ép kiểub(int,300) sang kiểuuint8. Khi ép kiểu từ một kiểu lớn hơn sang kiểu nhỏ hơn thì giá trị bị ép kiểu có thể thay đổi. Kiểuintcó thể lưu trữ từ-2^63đến2^63 - 1(với hệ thống 64 bit). Nhưng kiểuuint8chỉ có thể lưu trữ từ0 đến 255. Giá trị hiện tại củablà300, sau khi ép kiểuanhận giá trị là44(phần dư của phép chi300cho256).b = int(a): Ép kiểua(uint8,44) sang kiểuint. Khi ép kiểu từ một kiểu nhỏ hơn sang kiểu lớn hơn thì giá trị bị ép kiểu không bị ảnh hưởng. Kết quả làb = 44.b = int(c): Ép kiểuc(float64,123.456) sangint, phần thập phân bị cắt bỏ. Kết quả làb = 123.c = float32(b): Ép kiểub(int,123) sangfloat64. Giá trị được chuyển đổi thành số thực (123.0).fmt.Println("a + b =", int(a) + b): Ngoài các phép gán ở trên, các toán hạn trong cùng một biểu thức phải có cùng kiểu dữ liệu. Nếu các toán hạn trong biểu thức khác kiểu nhau thì phải ép kiểu như trên, nếu không chương trình sẽ lỗi.
Tóm tắt
Go là một ngôn ngữ mạnh mẽ với static type, giúp mã nguồn rõ ràng, giảm lỗi runtime và tối ưu thời gian biên dịch. Qua bài viết này, bạn đã nắm được các kiểu dữ liệu cơ bản, các toán tử, type conversion và cách sử dụng fmt.Printf trong Go.
Hẹn gặp bạn ở bài viết tiếp theo của series Go Fundamentals, nơi chúng ta sẽ đi sâu hơn vào các chủ đề nâng cao trong Go!
Phòng GYM
Phòng GYM là nơi để bạn có thể ôn luyện các kiến thức được đề cập trong bài. Nghiên cứu chỉ ra rằng việc ôn tập và thực hành thường xuyên sẽ giúp học sâu và ghi nhớ kiến thức lâu hơn.
Tạo một biến cho mỗi kiểu dữ liệu cơ bản của Go mà không cần giá trị khởi tạo. In ra màn hình giá trị, kiểu dữ liệu và kích thước bộ nhớ của từng biến.
Viết chương trình tính chỉ số khối của cơ thể (BMI - Body Mass Index) dựa trên cân nặng (kg) và chiều cao (m). Nếu cân nặng là
mvà chiều cao làhthì:BMI = m/(h*h)In kết quả ra màn hình dạng (1 chữ số thập phân):
Cân nặng: [m] kg Chiều cao: [h] m Chỉ số khối của bạn là: [BMI]Yêu cầu:
Sử dụng hàm
fmt.Printf1 lần duy nhất.
Tạo biến để lưu trữ chiều cao (kiểu
float32), cân nặng (kiểufloat64), và biến chứa kết quả BMI.


