Ko‘rsatkichlar (pointers)
Ko‘rsatkich (pointer) boshqa qiymatning xotira manzilini saqlaydi. U bir qiymatni turli joydan o‘zgartirish, katta tuzilmani nusxalamasdan uzatish yoki qiymat mavjud emasligini nil bilan ifodalash kerak bo‘lganda ishlatiladi.
& va * operatorlari
&x—xo‘zgaruvchisining manzilini oladi.*p—pko‘rsatgan manzildagi qiymatni o‘qiydi yoki o‘zgartiradi.*int—intqiymatiga ko‘rsatkich turini bildiradi.
package main
import "fmt"
func main() {
x := 10
p := &x
fmt.Println("Qiymat:", x)
fmt.Println("Ko‘rsatkich orqali:", *p)
*p = 20
fmt.Println("Yangi qiymat:", x)
}
Natija:
pning turi *int. *p = 20 yangi ko‘rsatkich yaratmaydi; p ko‘rsatib turgan x qiymatini o‘zgartiradi. Manzilni %p bilan chiqarish mumkin, ammo uning aniq qiymati har ishga tushirishda o‘zgarishi mumkin va dastur mantig‘i unga tayanmasligi kerak.
Qiymat va ko‘rsatkichni funksiyaga uzatish
Go’da argumentlar funksiyaga qiymat bo‘yicha uzatiladi. Oddiy qiymat uzatilsa, funksiya uning nusxasini oladi:
package main
import "fmt"
func oshir(son int) {
son++
}
func main() {
son := 10
oshir(son)
fmt.Println(son)
}
Natija:
oshir() ichidagi son boshqa o‘zgaruvchi. Uning o‘zgarishi main() ichidagi qiymatga ta’sir qilmaydi.
Funksiya asl qiymatni o‘zgartirishi kerak bo‘lsa, ko‘rsatkich uzatiladi:
package main
import "fmt"
func oshir(son *int) {
*son++
}
func main() {
son := 10
oshir(&son)
fmt.Println(son)
}
Natija:
oshir(&son) o‘zgaruvchining manzilini beradi. Funksiya ichidagi *son++ yozuvi (*son)++ bilan bir xil: avval manzildagi qiymat olinadi, keyin u bittaga oshiriladi.
Ko‘rsatkich yagona yechim emas. Funksiya yangi qiymat qaytarishi ham mumkin:
Faqat bitta sodda qiymat hisoblanayotgan bo‘lsa, natijani qaytarish ko‘pincha tushunarliroq. Ko‘rsatkich funksiya bir nechta maydonni o‘zgartirishi yoki mavjud obyekt ustida ishlashi kerak bo‘lganda qulay.
Struct bilan ko‘rsatkich ishlatish
Struct funksiyaga oddiy qiymat sifatida uzatilsa, barcha maydonlari nusxalanadi. Ko‘rsatkich orqali esa shu structning o‘zi o‘zgartiriladi:
package main
import "fmt"
type User struct {
Name string
Active bool
}
func activate(user *User) {
if user == nil {
return
}
user.Active = true
}
func main() {
user := User{Name: "Ali"}
activate(&user)
fmt.Println(user.Name, user.Active)
}
Natija:
Struct maydoniga murojaat qilishda (*user).Active deb yozish mumkin. Go bu holat uchun qisqa user.Active yozuviga ruxsat beradi.
Structni ko‘rsatkich bilan yaratishning ikki keng tarqalgan ko‘rinishi bor:
Birinchi ko‘rinishda boshlang‘ich maydonlar darhol beriladi. new(User) esa barcha maydonlari nol qiymatda bo‘lgan *User qaytaradi.
Ko‘rsatkichni qaytarish
Funksiya ichida yaratilgan lokal qiymatga ko‘rsatkich qaytarish mumkin:
package main
import "fmt"
func yangiSon() *int {
son := 42
return &son
}
func main() {
p := yangiSon()
fmt.Println(*p)
}
Natija:
Funksiya tugagach son darhol yaroqsiz bo‘lib qolmaydi. Go kompilyatori qiymat qancha yashashi kerakligini aniqlaydi va xotirani boshqaradi. Dasturchi C tilidagidek xotirani qo‘lda bo‘shatmaydi.
Ko‘rsatkichlarning tengligi
Ikki ko‘rsatkich bir xil manzilni ko‘rsatsa, ular teng hisoblanadi:
package main
import "fmt"
func main() {
x := 5
p1 := &x
p2 := &x
y := 5
p3 := &y
fmt.Println(p1 == p2)
fmt.Println(p1 == p3)
}
Natija:
x va y qiymatlari teng bo‘lsa ham, ularning manzillari boshqa-boshqa. Ko‘rsatkichni faqat boshqa shu turdagi ko‘rsatkich yoki nil bilan taqqoslash mumkin.
nil ko‘rsatkich
Ko‘rsatkichning nol qiymati nil, ya’ni u hech qanday yaroqli qiymatni ko‘rsatmaydi:
package main
import "fmt"
func qiymat(p *int) int {
if p == nil {
return 0
}
return *p
}
func main() {
var p *int
x := 42
fmt.Println(qiymat(p))
fmt.Println(qiymat(&x))
}
Natija:
nil ko‘rsatkichni *p bilan ochish runtime’da panic beradi. Funksiya nil qabul qilishi mumkin bo‘lsa, uni tekshiring yoki funksiya shartida nil taqiqlanganini aniq belgilang.
new funksiyasi
new(T) T turining nol qiymati uchun joy tayyorlab, *T qaytaradi:
Natija:
Ko‘pincha oddiy lokal o‘zgaruvchi va & tushunarliroq: x := 0; p := &x. new bilan make bir xil emas. new(T) *T qaytaradi; make esa faqat slice, map va channel’ning foydalanishga tayyor qiymatini qaytaradi.
Go’da pointer arifmetikasi yo‘q
Go odatiy kodda p++ yoki p + 1 kabi pointer arifmetikasiga ruxsat bermaydi. Bu xotira bilan ishlashni xavfsizroq qiladi. Past darajadagi maxsus vazifalar uchun unsafe paketi bor, lekin u odatiy biznes kodida kerak emas.
Ko‘rsatkich ishlatish qiymat albatta heap’da saqlanadi degani ham emas. Kompilyator escape analysis orqali qiymat stack’da qolishi yoki heap’ga ko‘chishini aniqlaydi. Shuning uchun pointer faqat “tezroq” degan taxmin bilan tanlanmaydi; avval semantika va o‘lchash muhim.
Slice, map va ko‘rsatkich
Slice va map funksiyaga uzatilganda ularning sarlavha qiymati nusxalanadi, lekin nusxa ham asosiy ma’lumotga murojaat qiladi. Elementni o‘zgartirish uchun odatda slice yoki mapga ko‘rsatkich kerak emas:
func birinchiElementniAlmashtir(sonlar []int) {
if len(sonlar) > 0 {
sonlar[0] = 100
}
}
func qiymatQosh(lugat map[string]int) {
lugat["go"] = 2009
}
Ammo funksiyada append() natijasida slice sarlavhasi o‘zgarsa, yangi slice’ni qaytarish kerak:
Bu usul *[]int ishlatishdan odatda sodda. Ko‘rsatkichni faqat qiymatni o‘zgartirish zarurati aniq bo‘lganda tanlang.
Qachon ko‘rsatkich ishlatmaslik kerak?
Quyidagi holatlarda oddiy qiymat yetarli bo‘lishi mumkin:
- funksiya argumentni faqat o‘qisa va qiymat kichik bo‘lsa;
- yangi natijani qaytarish mavjud qiymatni o‘zgartirishdan tushunarliroq bo‘lsa;
nilholati dasturga ortiqcha murakkablik olib kirsa;- slice,
mapyoki channelning o‘z xususiyati kerakli murojaat semantikasini bersa.
Har bir argumentni ko‘rsatkichga aylantirish kodni avtomatik ravishda tezlashtirmaydi. Ko‘rsatkich umumiy ma’lumotni bir nechta joydan o‘zgartirish imkonini bergani uchun kod oqimini kuzatishni ham qiyinlashtirishi mumkin.
Xulosa
&manzilni oladi,*manzildagi qiymatga murojaat qiladi.nilko‘rsatkichni ochishpanicberadi.new(T)nol qiymatga ko‘rsatkich qaytaradi.- Go’da odatiy pointer arifmetikasi yo‘q.
- Pointer asl qiymatni o‘zgartirish yoki nusxalashni boshqarish zarur bo‘lganda ishlatiladi.
- Slice va
mapelementlarini o‘zgartirish uchun odatda alohida ko‘rsatkich kerak emas.
Keyingi darslarda ko‘rsatkichning funksiya argumentlari va metod receiverlari bilan ishlashi amalda ko‘riladi.