[Golang cơ bản] Các loại dữ liệu cơ bản trong golang

Post on: 2023-03-29 00:12:59 | in: Golang
Các kiểu dữ liệu cơ bản trong Golang bao gồm số nguyên, số thực, chuỗi, boolean, mảng, slice, map, struct và con trỏ.

Tổng quan

Golang là một ngôn ngữ lập trình kiểu tĩnh, có nghĩa là mỗi biến có một kiểu. Golang có một vài kiểu dữ liệu tích hợp sẵn. Các kiểu dữ liệu trong Golang có thể được phân loại thành hai loại.

  1. Kiểu Cơ bản
  2. Kiểu tổng hợp
  • Kiểu Cơ bản
    • Integers
      • Signed
        • int
        • int8
        • int16 
        • int32 
        • int64
      • Unsigned
        • uint
        • uint8
        • uint16
        • uint32
        • uint64
        • uintptr
    • Floats
      • float32
      • float64
    • Complex Numbers
      • complex64
      • complex128
    • Byte
    • Rune
    • String
    • Boolean
  • Kiểu tổng hợp
    • Tập hợp/Tổng hợp hoặc Kiểu Không Tham chiếu
      • Arrays
      • Structs
    • Kiểu Tham chiếu
      • Slices
      • Maps
      • Channels
      • Pointers
      • Function/Methods
    • Interface
      • Special case of empty Interface

Kiểu cơ bản

Trong bài viết này chúng ta chỉ sẽ thảo luận về các kiểu cơ bản.

Integers 

Các số nguyên có thể được ký hiệu hoặc không ký hiệu.

Signed Integers

Số nguyên ký hiệu bao gồm 5 loại như dưới đây:

Type Size
int Phụ thuộc vào nền tảng
int8 8 bits/1 byte
int16 16 bits/2 byte
int32 32 bits/4 byte
int64 64 bits/8 byte

int

Kích thước: phụ thuộc vào nền tảng.

  • Trên máy tính 32 bit, kích thước của int sẽ là 32 bit hoặc 4 byte.
  • Trên máy tính 64 bit, kích thước của int sẽ là 64 bit hoặc 8 byte.

Phạm vi: cũng phụ thuộc vào nền tảng.

  • Trên máy tính 32 bit, phạm vi của int sẽ là từ -2^31 đến 2^31 -1.
  • Trên máy tính 64 bit, phạm vi của int sẽ là từ -2^63 đến 2^63 -1.

Khi nào sử dụng:

  • Nên sử dụng int khi sử dụng số nguyên có dấu ngoại trừ các trường hợp được đề cập dưới đây.
    • Khi máy là 32 bit và phạm vi cần thiết lớn hơn -2^31 đến 2^31 -1, thì sử dụng int64 thay vì int. Lưu ý rằng trong trường hợp này cho int64, cần sử dụng 2 địa chỉ bộ nhớ 32 bit để tạo thành một số 64 bit cùng nhau.
    • Khi phạm vi nhỏ hơn thì sử dụng loại số nguyên phù hợp.

Thuộc tính:

  • Khai báo một biến int.
var a int
  • int là kiểu mặc định cho số nguyên. Khi bạn không chỉ định kiểu, kiểu mặc định sẽ là int.
b := 2 //The default is also intfmt.Println(reflect.TypeOf(b)) => int
  • Package bits trong Golang có thể giúp bạn biết kích thước của một kiểu số nguyên (int) trên hệ thống của bạn.
//This is computed as const uintSize = 32 << (^uint(0) >> 32 & 1) // 32 or 64sizeOfIntInBits := bits.UintSizefmt.Println(sizeOfIntInBits) => 32 0r 34
  • Hàm unsafe.Sizeof() cũng có thể được sử dụng để xem kích thước của kiểu int tính bằng byte.

Mã hoạt động đầy đủ.

Dưới đây là mã nguồn đầy đủ và hoạt động của các thuộc tính đã nêu ở trên:

package main

import (
    "fmt"
    "math/bits"
    "reflect"
    "unsafe"
)

func main() {
    //This is computed as const uintSize = 32 << (^uint(0) >> 32 & 1) // 32 or 64
    sizeOfIntInBits := bits.UintSize
    fmt.Printf("%d bits\n", sizeOfIntInBits)

    var a int
    fmt.Printf("%d bytes\n", unsafe.Sizeof(a))
    fmt.Printf("a's type is %s\n", reflect.TypeOf(a))

    b := 2
    fmt.Printf("b's typs is %s\n", reflect.TypeOf(b))
}

Output:

64 bits
8 bytes
a's type is int
b's typs is int

int8

Kích thước: 8 bits hoặc 1 byte

Phạm vi: -2^7 đến 2^7 -1.

Khi nào sử dụng:

  • Nên sử dụng int8 khi biết rằng phạm vi của số nguyên nằm trong khoảng từ -2^7 đến 2^7 -1. Đối với các giá trị tạm thời như các biến loop invariants, vẫn nên sử dụng int, ngay cả khi nó chiếm nhiều không gian hơn vì nó có thể được nâng cấp lên int trong một số thao tác hoặc cuộc gọi thư viện.
  • Đối với các giá trị mảng nằm trong khoảng từ -2^7 đến 2^7 -1, việc sử dụng int8 là một trường hợp sử dụng tốt. Ví dụ, nếu bạn đang lưu trữ chỉ mục ASCII cho các chữ cái viết thường thì có thể sử dụng int8.
  • Điều đó là một ý tưởng tốt để sử dụng int8 cho các giá trị dữ liệu.

int16

Kích thước: 16 bits hoặc 2 byte
Phạm vi: -2^15 to 2^15 -1.
Khi nào sử dụng:

  • Nên sử dụng int16 khi biết rằng phạm vi của số nguyên nằm trong khoảng từ -2^15 đến 2^15 -1. Đối với các giá trị tạm thời như các biến loop invariants, vẫn nên sử dụng int, ngay cả khi nó chiếm nhiều không gian hơn vì nó có thể được nâng cấp lên int trong một số thao tác hoặc cuộc gọi thư viện.
  • Đối với các giá trị mảng nằm trong khoảng từ -2^15 đến 2^15 -1, việc sử dụng int16 là một trường hợp sử dụng tốt. Ví dụ, nếu bạn đang lưu trữ chỉ mục ASCII cho các chữ cái viết thường thì có thể sử dụng int16.

int32

Kích thước: 32 bit or 4 byte
Phạm vi: -2^31 đến 2^31 -1.

int64

Kích thước:  64 bit hoặc 8 byte
Phạm vi:-2^63 đến 2^63 -1.
Khi nào sử dụng:

  • int64 được sử dụng khi phạm vi của số nguyên là rất lớn. Ví dụ, time.Duration là kiểu int64.

UnSigned

Các số nguyên Unsigned có 5 loại như sau:

Type Size
uint Phụ thuộc vào nền tảng
uint8 8 bits/1 byte
uint16 16 bits/2 byte
uint32 32 bits/4 byte
uint64 64 bits/8 byte

uint

Kích thước: Phụ thuộc vào nền tảng.

  • Trên các máy tính 32 bit, kích thước của kiểu int là 32 bit hoặc 4 byte.
  • Trên các máy tính 64 bit, kích thước của kiểu int là 64 bit hoặc 8 byte.

Phạm vi: Phụ thuộc vào nền tảng

  • Trên các máy tính 32 bit, phạm vi của kiểu int là từ -2^31 đến 2^31 -1.
  • Trên các máy tính 64 bit, phạm vi của kiểu int là từ -2^63 đến 2^63 -1.

Khi nào nên sử dụng:

  • Nên sử dụng uint trong trường hợp sử dụng số nguyên không dấu, trừ các trường hợp được đề cập dưới đây.
    • Khi máy tính là 32 bit và phạm vi cần thiết lớn hơn -2^31 đến 2^31 -1, sau đó sử dụng int64 thay vì int. Lưu ý rằng trong trường hợp này, để tạo ra một số 64 bit, cần phải sử dụng 2 địa chỉ bộ nhớ 32 bit cùng nhau.
    • Khi phạm vi nhỏ hơn, hãy sử dụng kiểu int thích hợp.

Thuộc tính:

  • Khai báo một uint.
var a uint
  • Package bits của Golang có thể giúp bạn xác định kích thước của một uint trên hệ thống của bạn.
//This is computed as const uintSize = 32 << (^uint(0) >> 32 & 1) // 32 or 64sizeOfUintInBits := bits.UintSizefmt.Println(sizeOfIntInBits) => 32 or 64
  • Hàm unsafe.Sizeof() cũng có thể được sử dụng để xem kích thước của một uint tính bằng byte.

Mã hoạt động đầy đủ

Dưới đây là mã đầy đủ hoạt động của các thuộc tính được đề cập ở trên.

package main

import (
    "fmt"
    "math/bits"
    "reflect"
    "unsafe"
)

func main() {
    //This is computed as const uintSize = 32 << (^uuint(0) >> 32 & 1) // 32 or 64
    sizeOfuintInBits := bits.UintSize
    fmt.Printf("%d bits\n", sizeOfuintInBits)

    var a uint
    fmt.Printf("%d bytes\n", unsafe.Sizeof(a))
    fmt.Printf("a's type is %s\n", reflect.TypeOf(a))
}

Output:

64 bits
8 bytes
a's type is uint

uintptr

Đây là một kiểu số nguyên không dấu có độ lớn đủ lớn để lưu trữ bất kỳ địa chỉ con trỏ nào. Do đó, kích thước và phạm vi của nó phụ thuộc vào nền tảng.

Kích thước: Phụ thuộc vào nền tảng

  • Trên các máy 32 bit, kích thước của một int sẽ là 32 bit hoặc 4 byte.
  • Trên các máy 64 bit, kích thước của một int sẽ là 64 bit hoặc 8 byte.

Phạm vi: Lại phụ thuộc vào nền tảng

  • Trên các máy 32 bit, phạm vi của int sẽ từ -2^31 đến 2^31-1.
  • Trên các máy 64 bit, phạm vi của int sẽ từ -2^63 đến 2^63-1.

Thuộc tính:

  • Một uintptr có thể được chuyển đổi thành unsafe.Pointer và ngược lại.
  • Có thể thực hiện phép toán số học trên uintptr.
  • uintptr, mặc dù nó chứa một địa chỉ con trỏ, chỉ là một giá trị và không tham chiếu đến bất kỳ đối tượng nào. Do đó,
    • Giá trị của nó sẽ không được cập nhật nếu đối tượng tương ứng di chuyển. Ví dụ: khi ngăn xếp goroutine thay đổi
    • Đối tượng tương ứng có thể được thu gom rác.

Khi nào nên sử dụng:

  • Mục đích của nó là được sử dụng cùng với unsafe.Pointer chủ yếu được sử dụng để truy cập bộ nhớ không an toàn.
  • Khi bạn muốn lưu trữ giá trị địa chỉ con trỏ để in nó hoặc lưu trữ nó. Vì địa chỉ chỉ được lưu trữ và không tham chiếu đến bất kỳ thứ gì, đối tượng tương ứng có thể được thu gom rác.

Mã hoạt động đầy đủ

package main

import (
    "fmt"
    "unsafe"
)

type sample struct {
    a int
    b string
}

func main() {
    s := &sample{a: 1, b: "test"}

   //Getting the address of field b in struct s
    p := unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Offsetof(s.b))

    //Typecasting it to a string pointer and printing the value of it
    fmt.Println(*(*string)(p))
}

Output

test

uint8

Kích thước: 8 bit hoặc 1 byte
Phạm vi: 0 đến 255 hoặc 0 đến 2^8 - 1.
Khi nào nên sử dụng:

  • Sử dụng uint8 khi biết rằng phạm vi int sẽ nằm trong khoảng từ 0 đến 2^8 - 1. Đối với các giá trị tạm thời như loop invariants, vẫn nên sử dụng int mặc dù nó có thể chiếm nhiều không gian hơn vì nó có thể được thăng cấp thành int trong một số thao tác hoặc cuộc gọi thư viện.
  • Đối với các giá trị mảng nằm trong khoảng từ 0 đến 28 - 1 thì uint8 là một trường hợp sử dụng tốt. Ví dụ, nếu bạn lưu trữ chỉ mục ASCII trong một mảng thì uint8 có thể được sử dụng.

uint16

Kích thước: 16 bit hoặc 2 byte
Phạm vi: 0 đến 2^16 - 1
Khi nào nên sử dụng:

  • Sử dụng int16 khi biết rằng phạm vi int sẽ nằm trong khoảng từ 0 đến 2^16 - 1. Đối với các giá trị tạm thời như loop invariants, vẫn nên sử dụng int mặc dù nó có thể chiếm nhiều không gian hơn, vì nó có thể được thăng cấp thành int trong một số thao tác hoặc cuộc gọi thư viện.
  • Đối với các giá trị mảng nằm trong khoảng từ -0 đến 2^16 - 1, thì int8 là một trường hợp sử dụng tốt.

uint32

Kích thước: 32 bit hoặc 4 byte
Phạm vi: 0 đến 2^32 - 1

uint64

Kích thước: 64 bit hoặc 8 byte
Phạm vi: 0 đến 2^64 - 1
Khi nào nên sử dụng:

  • uint64 được sử dụng khi phạm vi là cao hơn.

Floats

Float là số có thập phân. Nó có hai loại.

Type Size
float32 32 bit hoặc 4 bytes
float64 64 bit hoặc 8 bytes

float64 là loại số thực mặc định. Khi bạn khởi tạo một biến với giá trị thập phân và không chỉ định kiểu float, kiểu mặc định được suy ra sẽ là float64.

float32

float32 sử dụng định dạng số thực độ chính xác đơn để lưu trữ giá trị. Tổng thể, đây là tập hợp của tất cả các số thực 32-bit theo chuẩn IEEE-754. 32 bit được chia thành - 1 bit dấu, 8 bit số mũ và 23 bit phần mũ. float32 chỉ chiếm một nửa kích thước của float64 và có tốc độ nhanh hơn trên một số kiến ​​trúc máy tính.

Kích thước: 32 bit hoặc 4 byte

Phạm vi: 1.2E-38 đến 3.4E+38

Giá trị mặc định: 0.0

Khi nào nên sử dụng:

Nếu trong hệ thống của bạn bộ nhớ là một điểm hạn chế và phạm vi nhỏ hơn, thì có thể sử dụng float32.

Ví dụ:

Mã ví dụ bên dưới minh họa cho các điểm sau đây

  • Khai báo một float32
  • In kích thước của float32 theo byte

Code:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    //Declare a float32
    var a float32 = 2

    //Size of float32 in bytes
    fmt.Printf("%d bytes\n", unsafe.Sizeof(a))
    fmt.Printf("a's type is %s\n", reflect.TypeOf(a))
}

Output:

4 bytes
a's type is float32

float64

float64 là kiểu dữ liệu số thực dùng để lưu trữ giá trị với độ chính xác gấp đôi so với kiểu dữ liệu float32. Thông thường, nó được sử dụng để đại diện cho tất cả các số thực 64-bit theo chuẩn IEEE-754, bao gồm 1 bit dấu, 11 bit chỉ số mũ và 52 bit số đằng sau dấu phẩy.

Kích thước: 64-bit hoặc 8 bytes

Phạm vi: 2.2E-308 đến 1.8E+308

Giá trị mặc định: 0.0

Khi nào nên sử dụng:

Sử dụng kiểu float64 khi cần độ chính xác cao hơn so với kiểu float32.

Ví dụ:

Đoạn mã dưới đây minh họa cho việc khai báo và sử dụng kiểu float64:

  • Khai báo một biến kiểu float64
  • In ra kích thước của float64 theo byte
  • Mặc định sẽ là kiểu float64 nếu không chỉ định kiểu dữ liệu

Code:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    //Declare a float64
    var a float64 = 2

    //Size of float64 in bytes
    fmt.Printf("%d bytes\n", unsafe.Sizeof(a))
    fmt.Printf("a's type is %s\n", reflect.TypeOf(a))
  
    //Default is float64 when you don't specify a type
    b := 2.3
    fmt.Printf("b's type is %s\n", reflect.TypeOf(b))
}

Output:

8 bytes
a's type is float64
b's type is float64

Complex Numbers

Số phức có hai loại

Type Property
complex64 Cả phần thực và phần ảo của số phức đều là kiểu float32.
complex128 Cả phần thực và phần ảo đều là float64.

Dạng số phức mặc định là complex128

Khởi tạo

Số phức có thể được khởi tạo bằng hai cách:

  • Sử dụng hàm complex. Hàm này có chữ ký như sau. Chú ý rằng cả a và b phải cùng kiểu dữ liệu, nghĩa là cả hai đều phải là float32 hoặc cả hai phải là float64.
complext(a, b)
  • Sử dụng cú pháp rút gọn. Được sử dụng khi tạo số phức với các số trực tiếp. Loại số phức được tạo bằng cách sử dụng phương thức dưới đây sẽ là kiểu complex128 nếu kiểu không được chỉ định.
a := 5 + 6i

complex64

Đối với số phức 64 bit thì cả phần thực và ảo đều là float32

Kích thước: Cả phần thực và ảo đều có kích thước giống như float32. Kích thước là 32 bit hoặc 4 byte.

Phạm vi: Cả phần thực và ảo đều có phạm vi giống như float32, tức là từ 1.2E-38 đến 3.4E+38.

Ví dụ:

Dưới đây là một đoạn mã mẫu cho thấy

  • Cách tạo một số phức 64 bằng hai phương pháp trên
  • In kích thước của một số phức 64. Kích thước sẽ là 8 byte (4 +4) tương đương với hai số float32
  • In loại của một số phức 64
  • Phép toán + trên số phức

Code:

package main
import (
    "fmt"
    "reflect"
    "unsafe"
)
func main() {
    var a float32 = 3
    var b float32 = 5

    //Initialize-1
    c := complex(a, b)

    //Initialize-2
    var d complex64
    d = 4 + 5i

    //Print Size
    fmt.Printf("c's size is %d bytes\n", unsafe.Sizeof(c))
    fmt.Printf("d's size is %d bytes\n", unsafe.Sizeof(d))

    //Print type
    fmt.Printf("c's type is %s\n", reflect.TypeOf(c))
    fmt.Printf("d's type is %s\n", reflect.TypeOf(d))

    //Operations on complex number
    fmt.Println(c+d, c-d, c*d, c/d)
}

Output:

c's size is 8 bytes
d's size is 8 bytes
c's type is complex64
d's type is complex64
(7+10i) (-1+0i) (-13+35i) (0.902439+0.12195122i)

complex128

Đối với complex128, cả phần thực và phần ảo đều là float64

Kích thước: Cả phần thực và phần ảo có cùng kích thước là float64. Kích thước là 64 bit hoặc 8 byte

Phạm vi: Cả phần thực và phần ảo đều có phạm vi như là float64, tức là từ -1.7E+308 đến +1.7E+308

Ví dụ:

Dưới đây là một đoạn mã mẫu thể hiện

  • Cách tạo số complex128 bằng hai phương pháp trên. Nó cũng cho thấy khi không chỉ định kiểu, kiểu mặc định sẽ là complex128
  • In kích thước của một số phức complex128. Kích thước sẽ là 16 byte (8 + 8) tương đương với hai số float64
  • In kiểu của một số phức complex128
  • Các phép toán khác nhau trên số phức.

Code:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    var a float64 = 3
    var b float64 = 5

    //Initialize-1
    c := complex(a, b)

    //Initialize-2. When don't specify a type , the default type will be complex128
    d := 4 + 5i

    //Print Size
    fmt.Printf("c's size is %d bytes\n", unsafe.Sizeof(c))
    fmt.Printf("d's size is %d bytes\n", unsafe.Sizeof(d))

    //Print type
    fmt.Printf("c's type is %s\n", reflect.TypeOf(c))
    fmt.Printf("d's type is %s\n", reflect.TypeOf(d))

    //Operations on complex number
    fmt.Println(c+d, c-d, c*d, c/d)
}

Output:

c's size is 16 bytes
d's size is 16 bytes
c's type is complex128
d's type is complex128
(7+10i) (-1+0i) (-13+35i) (0.902439024390244+0.12195121951219513i)

Byte

Trong Go, byte là một bí danh cho uint8, có nghĩa là nó là một giá trị số nguyên. Giá trị số nguyên này là 8 bit và nó đại diện cho một byte, tức là số từ 0-255. Một byte đơn có thể đại diện cho các ký tự ASCII. Trong Go, không có kiểu dữ liệu 'char'. Do đó,

  • byte được sử dụng để biểu thị ký tự ASCII
  • rune được sử dụng để biểu thị tất cả các ký tự UNICODE bao gồm tất cả các ký tự tồn tại. Chúng ta sẽ học về rune sau trong bài học này.

Định nghĩa Byte

var rbyte byte := 'a'

Trong khi khai báo byte, chúng ta phải chỉ định kiểu, giống như trong chương trình ở trên. Nếu chúng ta không chỉ định kiểu, thì kiểu mặc định được coi là một rune.

Ví dụ

Trong ví dụ code bên dưới:

  • Chúng ta khai báo một biến byte.
  • In kiểu byte.
  • In kích thước của byte.
package main
import (
    "fmt"
    "reflect"
    "unsafe"
)
func main() {
    var r byte = 'a'

    //Print Size
    fmt.Printf("Size: %d\n", unsafe.Sizeof(r))

    //Print Type
    fmt.Printf("Type: %s\n", reflect.TypeOf(r))

    //Print Character
    fmt.Printf("Character: %c\n", r)
    s := "abc"

    //This will the decimal value of byte
    fmt.Println([]byte(s))
}

Output:

Size: 1
Type: uint8
Character: a
[97 98 99]

Rune

Trong Go, rune là một đồng nghĩa với int32, nghĩa là nó là một giá trị số nguyên. Giá trị số nguyên này được dùng để biểu diễn một Unicode Code Point. Để hiểu về rune, bạn phải hiểu về Unicode trước. Dưới đây là mô tả ngắn gọn về Unicode, tuy nhiên bạn có thể tham khảo bài đăng phổ biến về chủ đề này - "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)".

UniCode là gì

Unicode là một tập hợp con của các ký tự ASCII, mỗi ký tự được gán một số duy nhất được gọi là Unicode Code Point.

Ví dụ

  • Chữ số 0 được biểu thị bằng điểm mã Unicode U+0030 (Giá trị thập phân - 48)
  • Ký tự thường b là U+0062 (Giá trị thập phân - 98)
  • Ký tự đồng bảng Anh là U+00A3 (Giá trị thập phân - 163)

Bạn có thể truy cập vào trang Wikipedia "List of Unicode characters" để biết mã Unicode Point của các ký tự khác.

UTF-8

UTF-8 lưu trữ mỗi Unicode Point bằng 1, 2, 3 hoặc 4 byte. Các điểm ASCII được lưu trữ bằng 1 byte. Đó là lý do tại sao rune là đồng nghĩa với int32 vì một Unicode Point có thể có tối đa 4 byte trong Go vì trong Go, mỗi chuỗi được mã hóa bằng utf-8.

Mỗi rune được dùng để tham chiếu tới một Unicode Point. Ví dụ, nếu bạn in một chuỗi sau khi ép kiểu nó thành một mảng rune, thì nó sẽ in ra Unicode Point của từng ký tự. Ví dụ như chuỗi "0b£" sẽ in ra - [U+0030 U+0062 U+00A3].

fmt.Printf("%U\n", []rune("0b£"))

Khai báo Rune

Một rune được khai báo bằng cách sử dụng một ký tự nằm trong dấu nháy đơn như ví dụ dưới đây để khai báo một biến có tên là 'rPound'

rPound := '£'

Sau khi khai báo Rune, bạn có thể thực hiện các thao tác sau đây:

  • Print Type – Kết quả sẽ là int32
fmt.Printf("Type: %s\n", reflect.TypeOf(rPound))
  • Print Unicode Code Point – Kết quả sẽ là  U+00A3
fmt.Printf("Unicode CodePoint: %U\n", rPound)
  • Print Character – Kết quả sẽ là £
fmt.Printf("Character: %c\n", r)

Khi nào sử dụng:

Bạn nên sử dụng rune khi bạn muốn lưu trữ Unicode Code Point trong giá trị. Một mảng rune nên được sử dụng khi tất cả các giá trị trong mảng đều là một Unicode Code Point.

Code:

Dưới đây là mã code minh họa cho mỗi điểm chúng ta đã thảo luận

package main
import (
    "fmt"
    "reflect"
    "unsafe"
)
func main() {
    r := 'a'

    //Print Size
    fmt.Printf("Size: %d\n", unsafe.Sizeof(r))

    //Print Type
    fmt.Printf("Type: %s\n", reflect.TypeOf(r))

    //Print Code Point
    fmt.Printf("Unicode CodePoint: %U\n", r)

    //Print Character
    fmt.Printf("Character: %c\n", r)
    s := "0b£"

    //This will print the Unicode Points
    fmt.Printf("%U\n", []rune(s))

    //This will the decimal value of Unicode Code Point
    fmt.Println([]rune(s))
}

Output:

Size: 4
Type: int32
Unicode CodePoint: U+0061
Character: a
[U+0030 U+0062 U+00A3]
[48 98 163]

String

Trong Golang, string là một slice chỉ đọc của các byte. Có hai cách để khởi tạo một string:

  • Sử dụng dấu ngoặc kép "" ví dụ như "this".

Trong string bằng dấu ngoặc kép, các ký tự escape sẽ được xử lý. Ví dụ, nếu chuỗi chứa "\n", khi in ra màn hình sẽ có một dòng mới.

  • Sử dụng dấu nháy ngược ` ví dụ như  \` this`.

String trong dấu nháy ngược chỉ là một chuỗi thô, nó không xử lý bất kỳ escape sequences nào.

Mỗi ký tự trong một string sẽ chiếm một số byte tùy thuộc vào encoding được sử dụng. Ví dụ, trong utf-8 encoded string, mỗi ký tự sẽ chiếm từ 1-4 byte. Bạn có thể đọc về utf-8 trong bài viết nổi tiếng và cực kỳ quan trọng này: "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)". Trong utf-8, các ký tự "a" hoặc "b" được mã hóa bằng một byte, trong khi ký tự dấu phẩy đô la "£" được mã hóa bằng hai byte. Do đó, chuỗi "ab£" sẽ đưa ra 4 byte khi bạn chuyển đổi chuỗi thành mảng byte và in nó ra màn hình.

s := "ab£"
fmt.Println([]byte(s))

Output

[48 98 194 163]

Cũng chú ý rằng khi bạn cố gắng in độ dài của chuỗi trên bằng cách sử dụng len("ab£"), nó sẽ in ra 4 chứ không phải 3 vì nó chứa 4 byte.

Hãy lưu ý rằng range lặp qua các chuỗi byte tạo thành mỗi ký tự, do đó với vòng lặp range dưới đây.

for _, c := range s {
   fmt.Println(string(c))
}

Kết quả sẽ là

a
b
£

Có nhiều hoạt động có thể thực hiện trên một chuỗi. Một trong số đó là ghép nối hai chuỗi. Ký hiệu ‘+’ được sử dụng để ghép nối. Hãy xem mã hoạt động đầy đủ cho tất cả những điều chúng ta đã thảo luận

Code:

package main
import (
    "fmt"
)
func main() {
    //String in double quotes
    x := "this\nthat"
    fmt.Printf("x is: %s\n", x)

    //String in back quotes
    y := `this\nthat`
    fmt.Printf("y is: %s\n", y)
    s := "ab£"

    //This will print the byte sequence.
    //Since character a and b occupies 1 byte each and £ character occupies 2 bytes.
    //The final output will 4 bytes
    fmt.Println([]byte(s))

    //The output will be 4 for same reason as above
    fmt.Println(len(s))

    //range loops over sequences of byte which form each character
    for _, c := range s {
        fmt.Println(string(c))
    }

    //Concatenation
    fmt.Println("c" + "d")
}

Output:

x is: this
that
y is: this\nthat
[97 98 194 163]
4
a
b
£
cd

Booleans

Kiểu dữ liệu là bool và có hai giá trị có thể có là true hoặc false.

Giá trị mặc định: false

Các phép toán:

  • AND – &&
  • OR  – ||
  • Negation – !

Ví dụ

Ví dụ code dưới đây cho thấy:

  • Nếu không khởi tạo, giá trị mặc định là false
  • Tất cả các phép toán trên bool

Code

package main

import "fmt"

func main() {
    //Default value will be false it not initialized
    var a bool
    fmt.Printf("a's value is %t\n", a)

    //And operation on one true and other false
    andOperation := 1 < 2 && 1 > 3
    fmt.Printf("Ouput of AND operation on one true and other false %t\n", andOperation)

    //OR operation on one true and other false
    orOperation := 1 < 2 || 1 > 3
    fmt.Printf("Ouput of OR operation on one true and other false: %t\n", orOperation)

    //Negation Operation on a false value
    negationOperation := !(1 > 2)
    fmt.Printf("Ouput of NEGATION operation on false value: %t\n", negationOperation)
}

Output:

a's value is false
Ouput of AND operation on one true and other false false
Ouput of OR operation on one true and other false: true
Ouput of NEGATION operation on false value: true

Kết luận

Đây là tất cả về các kiểu cơ bản trong Golang. Hy vọng bạn đã thích bài viết này. Vui lòng chia sẻ phản hồi hoặc cải tiến hoặc lỗi trong phần bình luận.

 
 
 
 
 
 
 
 
 
 
Tag: golang cơ bản go