Tarkibga o'tish

Go’da string bilan ishlash

string matn va ixtiyoriy baytlar ketma-ketligini saqlaydigan turdir. Foydalanuvchi nomi, URL, fayl yo‘li, JSON maydoni va HTTP sarlavhasi kabi qiymatlar dasturlarda ko‘pincha string ko‘rinishida uchraydi.

String bilan ishonchli ishlash uchun uch narsani farqlash muhim: baytlar, Unicode kod nuqtalari va foydalanuvchi ko‘radigan belgilar. Ular har doim bir xil sonni anglatmaydi.

String literal va o‘zgarmaslik

Ikki tirnoq ichidagi interpreted literal \n, \t va \" kabi escape ketma-ketliklarini talqin qiladi. Backtick ichidagi raw literal esa matnni deyarli yozilganicha saqlaydi va bir nechta qatorni qamrab olishi mumkin:

main.go
package main

import "fmt"

func main() {
    line := "Birinchi qator\nIkkinchi qator"
    path := `C:\temp\main.go`

    fmt.Println(line)
    fmt.Println(path)
}

Natija:

Birinchi qator
Ikkinchi qator
C:\temp\main.go

Bitta tirnoq ichidagi 'A' string emas, rune literaldir. rune bitta Unicode kod nuqtasini ifodalaydi va int32 turining boshqa nomidir.

Go stringi o‘zgarmas (immutable). Yaratilgan string ichidagi baytni almashtirib bo‘lmaydi:

text := "Go"
// text[0] = 'N' // kompilyatsiya xatosi: string elementiga qiymat berib bo‘lmaydi

text = "No" esa mumkin. Bu eski stringni o‘zgartirmaydi, text o‘zgaruvchisiga boshqa string qiymatini beradi.

String, byte va rune

String ixtiyoriy baytlarni saqlashi mumkin. Go matn uchun ko‘pincha UTF-8dan foydalanadi, lekin string qiymatining o‘zi uning ichidagi baytlar to‘g‘ri UTF-8 ekanini kafolatlamaydi.

  • byte bitta baytni ifodalaydi va uint8 turining boshqa nomidir;
  • rune Unicode kod nuqtasini ifodalaydi va int32 turining boshqa nomidir;
  • ASCII belgilar UTF-8da bir bayt, ko‘plab boshqa belgilar esa bir necha bayt egallaydi.
main.go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    text := "Go tili — zo‘r"

    fmt.Println("Baytlar:", len(text))
    fmt.Println("Rune soni:", utf8.RuneCountInString(text))
    for index, char := range text {
        fmt.Printf("%2d: %c\n", index, char)
    }
}

Natijaning boshi:

Baytlar: 18
Rune soni: 14
 0: G
 1: o
 2: (bo‘sh joy)
 3: t

len(text) belgilarni emas, baytlarni sanaydi. utf8.RuneCountInString() Unicode kod nuqtalarini sanaydi. range har safar bitta UTF-8 kodlangan runeni beradi, indeks esa shu runening boshlanish bayt o‘rnidir.

Info

Rune soni ham foydalanuvchi ko‘radigan belgilar soniga har doim teng emas. Ayrim belgilar bir nechta Unicode kod nuqtasidan tuziladi. Go standart kutubxonasi grapheme clusterlarni alohida sanaydigan umumiy funksiya bermaydi.

Indeks orqali bayt olish

text[i] i-indeksdagi baytni byte sifatida qaytaradi:

main.go
package main

import "fmt"

func main() {
    text := "Go"
    first := text[0]

    fmt.Println(first)
    fmt.Printf("%c\n", first)
}

Natija:

71
G

71Gning UTF-8 va ASCII dagi bayt qiymati. %c shu qiymatni belgi sifatida chiqaradi. Indeks manfiy bo‘lsa yoki len(text)ga teng yoxud undan katta bo‘lsa, dastur runtime paytida panic qiladi.

Ko‘p baytli belgining faqat bir baytini olish to‘liq belgini bermaydi. Unicode matnni belgilar bo‘yicha qayta ishlash uchun range yoki []rune ishlatiladi:

main.go
package main

import "fmt"

func main() {
    text := "O‘zbek"
    runes := []rune(text)

    fmt.Printf("Birinchi rune: %c\n", runes[0])
    fmt.Printf("Uchinchi rune: %c\n", runes[2])
}

Natija:

Birinchi rune: O
Uchinchi rune: z

[]rune(text) yangi rune slice va unga mos xotira yaratadi. Matnni faqat bir marta ketma-ket o‘qish kerak bo‘lsa, range odatda kamroq xotira talab qiladi.

Stringlarni birlashtirish va taqqoslash

Kichik miqdordagi stringlarni + bilan birlashtirish mumkin. == va != baytlar ketma-ketligining tengligini tekshiradi. <, >, <= va >= esa leksikografik, ya’ni bayt qiymatlari tartibida solishtiradi.

main.go
package main

import "fmt"

func main() {
    firstName := "Ali"
    lastName := "Valiyev"
    fullName := firstName + " " + lastName

    fmt.Println(fullName)
    fmt.Println(fullName == "Ali Valiyev")
    fmt.Println("Go" < "Rust")
}

Natija:

Ali Valiyev
true
true

Katta siklda ko‘p bo‘lakni + bilan ketma-ket qo‘shish oraliq stringlar va xotira ajratishlarini ko‘paytirishi mumkin. Bunday holatda strings.Builder ishlatiladi:

main.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    var builder strings.Builder

    for i := 1; i <= 3; i++ {
        if i > 1 {
            builder.WriteString(", ")
        }
        fmt.Fprint(&builder, i)
    }

    fmt.Println(builder.String())
}

Natija:

1, 2, 3

strings.Builder qismlarni ichki bufferda yig‘adi. Tayyor natija String() bilan olinadi. Builderni nusxalab ishlatmaslik kerak.

Qism string olish

text[start:end] start indeksidan boshlab end indeksigacha, ammo endning o‘zini olmasdan baytlar oralig‘ini qaytaradi:

main.go
package main

import "fmt"

func main() {
    text := "Salom, dunyo!"

    fmt.Println(text[:5])
    fmt.Println(text[7:12])
    fmt.Println(text[7:])
}

Natija:

Salom
dunyo
dunyo!

Chegaralar bayt indekslaridir. 0 <= start <= end <= len(text) bo‘lmasa, dastur panic qiladi. UTF-8 belgining o‘rtasidan kesish noto‘g‘ri kodlangan string hosil qilishi mumkin.

Unicode kod nuqtalari bo‘yicha kesish kerak bo‘lsa, avval []runega o‘tkaziladi:

runes := []rune("Go‘zal")
part := string(runes[:3]) // "Go‘"

Qism string odatda asl string ma’lumotining bir qismini ulashishi mumkin. Juda katta stringdan kichik qismni uzoq saqlash katta asosiy ma’lumotning xotirada qolishiga sabab bo‘lishi mumkin. Mustaqil nusxa zarur bo‘lsa, strings.Clone(part) ishlatiladi. Buni faqat xotira profili yoki aniq ownership talabi asoslaganda qo‘llash kerak.

strings paketi

Standart kutubxonadagi strings paketi qidirish, ajratish, birlashtirish, almashtirish va registrni o‘zgartirish funksiyalarini beradi.

Qidirish va tekshirish

main.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "Go dasturlash tili"

    fmt.Println(strings.Contains(text, "Go"))
    fmt.Println(strings.Count(text, "a"))
    fmt.Println(strings.HasPrefix(text, "Go"))
    fmt.Println(strings.HasSuffix(text, "tili"))
    fmt.Println(strings.Index(text, "dastur"))
    fmt.Println(strings.Index(text, "Python"))
}

Natija:

true
3
true
true
3
-1

Contains() qism mavjudligini, Count() bir-birini qoplamaydigan uchrashuvlar sonini tekshiradi. Index() birinchi uchrashuvning bayt indeksini, topilmasa -1ni qaytaradi. Natijani indeks sifatida ishlatishdan oldin -1 emasligini tekshirish kerak.

strings.ContainsAny(text, "abc") ikkinchi argumentdagi runelardan kamida bittasi borligini tekshiradi. Butun qism stringni qidirish uchun esa Contains() kerak.

Ajratish va birlashtirish

main.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    line := " go, backend ,api "
    parts := strings.Split(line, ",")

    for i := range parts {
        parts[i] = strings.TrimSpace(parts[i])
    }

    fmt.Println(parts)
    fmt.Println(strings.Join(parts, " | "))
    fmt.Println(strings.Fields("  Go\tbackend\nAPI  "))
}

Natija:

[go backend api]
go | backend | api
[Go backend API]

Split() aynan berilgan ajratgich bo‘yicha bo‘ladi va bo‘sh elementlarni ham saqlashi mumkin. Fields() esa ketma-ket Unicode bo‘sh joylarni ajratgich deb oladi va bo‘sh elementlarni tashlaydi. Join() slice elementlarini berilgan ajratgich bilan birlashtiradi.

Almashtirish va takrorlash

main.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "Go tez, Go sodda"

    fmt.Println(strings.Replace(text, "Go", "Golang", 1))
    fmt.Println(strings.ReplaceAll(text, "Go", "Golang"))
    fmt.Printf("%q\n", strings.Repeat("Go! ", 3))
}

Natija:

Golang tez, Go sodda
Golang tez, Golang sodda
"Go! Go! Go! "

Replace()ning oxirgi argumenti nechta uchrashuv almashtirilishini belgilaydi. -1 barcha uchrashuv degani; shu niyat uchun ReplaceAll() aniqroq o‘qiladi. Repeat() manfiy takrorlash sonida yoki natija juda katta bo‘lsa panic qilishi mumkin. Tashqi qiymatni oldindan tekshirish kerak.

Bosh va oxirni tozalash

main.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    value := "\t Go dasturlash tili \n"
    url := "https://go-lang.uz/"

    fmt.Printf("%q\n", strings.TrimSpace(value))
    fmt.Println(strings.TrimPrefix(url, "https://"))
    fmt.Println(strings.TrimSuffix(url, "/"))
    fmt.Println(strings.Trim("...Go...", "."))
}

Natija:

"Go dasturlash tili"
go-lang.uz/
https://go-lang.uz
Go

TrimSpace() bosh va oxirdagi Unicode bo‘sh joylarni olib tashlaydi. Trim(value, ".") ikkinchi argumentni qism string emas, olib tashlanadigan runelar to‘plami sifatida talqin qiladi. Aniq prefiks yoki suffiksni bir marta olib tashlash uchun TrimPrefix() va TrimSuffix() ishlatiladi. Mos qism bo‘lmasa, ular asl stringni qaytaradi.

Katta-kichik harf va registrsiz taqqoslash

main.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    value := "Go Dasturlash"

    fmt.Println(strings.ToLower(value))
    fmt.Println(strings.ToUpper(value))
    fmt.Println(strings.EqualFold("GO", "go"))
}

Natija:

go dasturlash
GO DASTURLASH
true

Faqat registrni e’tiborsiz qoldirib tenglikni tekshirish uchun ikkala tomonni ToLower() qilishdan ko‘ra EqualFold() maqsadni aniqroq ifodalaydi va Unicode case folding qoidalaridan foydalanadi.

strings.ToTitle() har bir runeni title case ko‘rinishiga o‘tkazadi; u gap yoki ismning faqat birinchi harfini katta qiladigan funksiya emas. Tabiiy tildagi sarlavha qoidalari tilga bog‘liq, shuning uchun bunday formatlash alohida talab asosida bajariladi.

strings.Map() bilan runelarni o‘zgartirish

strings.Map() har bir runega funksiya qo‘llab, yangi string qaytaradi. Funksiya manfiy qiymat qaytarsa, shu rune natijadan olib tashlanadi:

main.go
package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    value := "Go 1.22"

    onlyLetters := strings.Map(func(char rune) rune {
        if unicode.IsLetter(char) {
            return char
        }
        return -1
    }, value)

    fmt.Println(onlyLetters)
}

Natija:

Go

Bu usul matnni normalizatsiya qilish yoki ruxsat etilgan belgilarni qoldirishda foydali. Biroq real validatsiyada olib tashlangan belgilarni jim yashirish o‘rniga foydalanuvchiga xato qaytarish talab qilinishi mumkin.

UTF-8 to‘g‘riligini tekshirish

Fayl, tarmoq yoki boshqa tashqi manbadan olingan string noto‘g‘ri UTF-8 baytlarini saqlashi mumkin:

main.go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    value := string([]byte{0xff, 'G', 'o'})

    fmt.Println(utf8.ValidString(value))
    fmt.Printf("%q\n", value)
}

Natija:

false
"\xffGo"

utf8.ValidString() string to‘g‘ri UTF-8 ekanini tekshiradi. Noto‘g‘ri ketma-ketlik bo‘ylab range qilinganda Go uning o‘rniga utf8.RuneError, ya’ni runesini beradi. Protokol yoki fayl formati UTF-8 talab qilsa, qiymatni qayta ishlashdan oldin tekshirish kerak.

Keng tarqalgan xatolar

len()ni belgilar soni deb qabul qilish

len() baytlarni sanaydi. ASCII matnda bu ko‘pincha belgilar soniga teng ko‘rinadi, Unicode matnda esa farq qiladi. Kod nuqtalari uchun utf8.RuneCountInString(), foydalanuvchi ko‘radigan belgilar uchun esa talabga mos grapheme yechimi kerak.

Stringni bayt chegarasidan noto‘g‘ri kesish

text[a:b] chegaralari bayt indekslari. Unicode runening o‘rtasidan kesish noto‘g‘ri UTF-8 hosil qiladi. Rune bo‘yicha kesish zarur bo‘lsa, []runega o‘tkazing va qo‘shimcha xotira xarajatini hisobga oling.

strings.Trim()ni qism string olib tashlaydi deb o‘ylash

strings.Trim("abba", "ab") bosh va oxirdagi barcha a hamda b runelarini olib, bo‘sh string qaytaradi. Aniq "ab" qismi uchun TrimPrefix() yoki TrimSuffix() kerak.

Registrni o‘zgartirishni barcha matn qoidalariga yechim deb bilish

Unicode registri va tabiiy til qoidalari murakkab. Registrsiz texnik tenglik uchun EqualFold() mos. Foydalanuvchi ismi yoki lokal tilga xos saralash va formatlash uchun mahsulot talabi va maxsus kutubxona kerak bo‘lishi mumkin.

Xulosa

Go stringi o‘zgarmas baytlar ketma-ketligidir. len() baytlarni, range esa UTF-8 runelarni ko‘radi. Indekslash va kesish bayt chegaralari bilan ishlaydi. strings paketi qidirish, ajratish, birlashtirish, tozalash va almashtirish amallarini beradi. Unicode matnda byte, rune va foydalanuvchi ko‘radigan belgi farqini hisobga olish ishonchli kod yozish uchun muhim.

Keyingi darslarda stringlarni array va slice qiymatlari bilan birga qayta ishlashni ko‘ramiz.