Rumah  >  Artikel  >  pembangunan bahagian belakang  >  How Go Arrays Berfungsi dan Dapatkan Tricky dengan For-Julat

How Go Arrays Berfungsi dan Dapatkan Tricky dengan For-Julat

WBOY
WBOYasal
2024-08-20 18:44:00225semak imbas

How Go Arrays Work and Get Tricky with For-Range

How Go Arrays Berfungsi dan Dapatkan Tricky dengan Untuk-Julat

Ini adalah petikan siaran; siaran penuh tersedia di sini: How Go Arrays Work and Get Tricky with For-Julat.

Susun atur dan hirisan Golang klasik cukup mudah. Tatasusunan adalah saiz tetap, dan kepingan adalah dinamik. Tetapi saya perlu memberitahu anda, Go mungkin kelihatan mudah pada luaran, tetapi banyak perkara yang berlaku di bawah hud.

Seperti biasa, kami akan mulakan dengan asas dan kemudian gali sedikit lebih mendalam. Jangan risau, tatasusunan menjadi agak menarik apabila anda melihatnya dari sudut yang berbeza.

Kami akan menutup kepingan di bahagian seterusnya, saya akan meletakkannya di sini sebaik sahaja ia siap.

Apakah tatasusunan?

Tatasusunan dalam Go adalah sama seperti dalam bahasa pengaturcaraan lain. Mereka mempunyai saiz tetap dan menyimpan elemen jenis yang sama di lokasi memori bersebelahan.

Ini bermakna Go boleh mengakses setiap elemen dengan cepat kerana alamat mereka dikira berdasarkan alamat permulaan tatasusunan dan indeks elemen.

func main() {
    arr := [5]byte{0, 1, 2, 3, 4}
    println("arr", &arr)

    for i := range arr {
        println(i, &arr[i])
    }
}

// Output:
// arr 0x1400005072b
// 0 0x1400005072b
// 1 0x1400005072c
// 2 0x1400005072d
// 3 0x1400005072e
// 4 0x1400005072f

Terdapat beberapa perkara yang perlu diperhatikan di sini:

  • Alamat arr tatasusunan adalah sama dengan alamat elemen pertama.
  • Alamat setiap elemen adalah 1 bait selain antara satu sama lain kerana jenis elemen kami ialah bait.

How Go Arrays Work and Get Tricky with For-Range

Susun [5]bait{0, 1, 2, 3, 4} dalam ingatan

Lihat imej dengan teliti.

Timbunan kami berkembang ke bawah dari alamat yang lebih tinggi kepada alamat yang lebih rendah, bukan? Gambar ini menunjukkan dengan tepat bagaimana tatasusunan kelihatan dalam timbunan, daripada arr[4] kepada arr[0].

Jadi, adakah ini bermakna kita boleh mengakses mana-mana elemen tatasusunan dengan mengetahui alamat elemen pertama (atau tatasusunan) dan saiz elemen? Mari cuba ini dengan tatasusunan int dan pakej tidak selamat:

func main() {
    a := [3]int{99, 100, 101}

    p := unsafe.Pointer(&a[0])

    a1 := unsafe.Pointer(uintptr(p) + 8)
    a2 := unsafe.Pointer(uintptr(p) + 16)

    fmt.Println(*(*int)(p))
    fmt.Println(*(*int)(a1))
    fmt.Println(*(*int)(a2))
}

// Output:
// 99
// 100
// 101

Nah, kita mendapatkan penunjuk kepada elemen pertama dan kemudian mengira penunjuk kepada elemen seterusnya dengan menambah gandaan saiz int, iaitu 8 bait pada seni bina 64-bit. Kemudian kami menggunakan penunjuk ini untuk mengakses dan menukarnya kembali kepada nilai int.

How Go Arrays Work and Get Tricky with For-Range

Susun [3]int{99, 100, 101} dalam ingatan

Contohnya hanyalah bermain-main dengan pakej yang tidak selamat untuk mengakses memori secara terus untuk tujuan pendidikan. Jangan lakukan ini dalam pengeluaran tanpa memahami akibatnya.

Kini, tatasusunan jenis T bukan jenis dengan sendirinya, tetapi tatasusunan dengan saiz tertentu dan jenis T, dianggap sebagai jenis. Inilah yang saya maksudkan:

func main() {
    a := [5]byte{}
    b := [4]byte{}

    fmt.Printf("%T\n", a) // [5]uint8
    fmt.Printf("%T\n", b) // [4]uint8

    // cannot use b (variable of type [4]byte) as [5]byte value in assignment
    a = b 
}

Walaupun kedua-dua a dan b ialah tatasusunan bait, pengkompil Go melihatnya sebagai jenis yang sama sekali berbeza, format %T menjelaskan perkara ini.

Berikut ialah cara pengkompil Go melihatnya secara dalaman (src/cmd/compile/internal/types2/array.go):

// An Array represents an array type.
type Array struct {
    len  int64
    elem Type
}

// NewArray returns a new array type for the given element type and length.
// A negative length indicates an unknown length.
func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} }

Panjang tatasusunan "dikodkan" dalam jenis itu sendiri, jadi pengkompil mengetahui panjang tatasusunan daripada jenisnya. Cuba untuk menetapkan tatasusunan satu saiz kepada saiz yang lain, atau membandingkannya, akan menghasilkan ralat jenis yang tidak sepadan.

Tatasusunan literal

Terdapat banyak cara untuk memulakan tatasusunan dalam Go, dan sesetengah daripadanya mungkin jarang digunakan dalam projek sebenar:

var arr1 [10]int // [0 0 0 0 0 0 0 0 0 0]

// With value, infer-length
arr2 := [...]int{1, 2, 3, 4, 5} // [1 2 3 4 5]

// With index, infer-length
arr3 := [...]int{11: 3} // [0 0 0 0 0 0 0 0 0 0 0 3]

// Combined index and value
arr4 := [5]int{1, 4: 5} // [1 0 0 0 5]
arr5 := [5]int{2: 3, 4, 4: 5} // [0 0 3 4 5]

Apa yang kami lakukan di atas (kecuali yang pertama) ialah mentakrifkan dan memulakan nilainya, yang dipanggil "harfiah komposit." Istilah ini juga digunakan untuk kepingan, peta dan struktur.

Sekarang, berikut ialah perkara yang menarik: apabila kita mencipta tatasusunan dengan kurang daripada 4 elemen, Go menjana arahan untuk meletakkan nilai ke dalam tatasusunan satu demi satu.

Jadi apabila kita buat arr := [3]int{1, 2, 3, 4}, apa yang sebenarnya berlaku ialah:

arr := [4]int{}
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4

Strategi ini dipanggil permulaan kod tempatan. Ini bermakna bahawa kod permulaan dijana dan dilaksanakan dalam skop fungsi tertentu, dan bukannya menjadi sebahagian daripada kod permulaan global atau statik.

Ia akan menjadi lebih jelas apabila anda membaca strategi pemulaan lain di bawah, di mana nilai tidak diletakkan ke dalam tatasusunan satu demi satu seperti itu.

"Bagaimana pula dengan tatasusunan dengan lebih daripada 4 elemen?"

Pengkompil mencipta perwakilan statik tatasusunan dalam binari, yang dikenali sebagai strategi 'pemulaan statik'.

This means the values of the array elements are stored in a read-only section of the binary. This static data is created at compile time, so the values are directly embedded into the binary. If you're curious how [5]int{1,2,3,4,5} looks like in Go assembly:

main..stmp_1 SRODATA static size=40
    0x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00  ................
    0x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00  ................
    0x0020 05 00 00 00 00 00 00 00                          ........

It's not easy to see the value of the array, we can still get some key info from this.

Our data is stored in stmp_1, which is read-only static data with a size of 40 bytes (8 bytes for each element), and the address of this data is hardcoded in the binary.

The compiler generates code to reference this static data. When our application runs, it can directly use this pre-initialized data without needing additional code to set up the array.

const readonly = [5]int{1, 2, 3, 4, 5}

arr := readonly

"What about an array with 5 elements but only 3 of them initialized?"

Good question, this literal [5]int{1,2,3} falls into the first category, where Go puts the value into the array one by one.

While talking about defining and initializing arrays, we should mention that not every array is allocated on the stack. If it's too big, it gets moved to the heap.

But how big is "too big," you might ask.

As of Go 1.23, if the size of the variable, not just array, exceeds a constant value MaxStackVarSize, which is currently 10 MB, it will be considered too large for stack allocation and will escape to the heap.

func main() {
    a := [10 * 1024 * 1024]byte{}
    println(&a)

    b := [10*1024*1024 + 1]byte{}
    println(&b)
}

In this scenario, b will move to the heap while a won't.

Array operations

The length of the array is encoded in the type itself. Even though arrays don't have a cap property, we can still get it:

func main() {
    a := [5]int{1, 2, 3}
    println(len(a)) // 5
    println(cap(a)) // 5
}

The capacity equals the length, no doubt, but the most important thing is that we know this at compile time, right?

So len(a) doesn't make sense to the compiler because it's not a runtime property, Go compiler knows the value at compile time.

...

This is an excerpt of the post; the full post is available here: How Go Arrays Work and Get Tricky with For-Range.

Atas ialah kandungan terperinci How Go Arrays Berfungsi dan Dapatkan Tricky dengan For-Julat. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn