В контексте gRPC (Google Remote Procedure Call) unary и stream — это два разных типа взаимодействия между клиентом и сервером.
это стандартный запрос-ответ:
Клиент отправляет одно сообщение → сервер отвечает одним сообщением.
service UserService {
rpc GetUserInfo(UserRequest) returns (UserResponse);
}
Go-реализация Unary RPC
func (s *server) GetUserInfo(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
user := &pb.UserResponse{
Id: req.Id,
Name: "John Doe",
Email: "john@example.com",
}
return user, nil // Обычный ответ
}
В streaming RPC передача данных идёт потоком. gRPC поддерживает три вида стримов:
Клиент отправляет один запрос → сервер возвращает поток ответов.
service UserService {
rpc GetUserActivity(UserRequest) returns (stream ActivityResponse);
}
Go-реализация Server Streaming
func (s *server) GetUserActivity(req *pb.UserRequest, stream pb.UserService_GetUserActivityServer) error {
activities := []string{"Login", "Upload File", "Logout"}
for _, activity := range activities {
err := stream.Send(&pb.ActivityResponse{Message: activity})
if err != nil {
return err
}
time.Sleep(time.Second) // Имитация задержки
}
return nil
}
Клиент отправляет поток данных → сервер отвечает одним ответом.
service UploadService {
rpc UploadFile(stream FileChunk) returns (UploadResponse);
}
Go-реализация Client Streaming
func (s *server) UploadFile(stream pb.UploadService_UploadFileServer) error {
var totalSize int64
for {
chunk, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.UploadResponse{Size: totalSize})
}
if err != nil {
return err
}
totalSize += chunk.Size
}
}
Клиент и сервер обмениваются данными в потоке одновременно.
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
Go-реализация Bi-directional Streaming
func (s *server) Chat(stream pb.ChatService_ChatServer) error {
for {
msg, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
response := &pb.ChatMessage{Text: "Echo: " + msg.Text}
if err := stream.Send(response); err != nil {
return err
}
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Внимание: .count возвращает количество графемных кластеров, а не байтов или Unicode-кодов. Например, emoji или диакритические символы могут быть представлены несколькими кодовыми точками, но отображаются как один символ.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Карты (maps) предоставляют несколько основных функций и операций для работы с ними. Эти функции позволяют добавлять, удалять, получать значения и проверять наличие ключей в карте.
Для этого используется ключевое слово
map
, после которого указываются типы ключей и значений.var myMap map[string]int
Это можно сделать с помощью функции
make
или литерала карты.// Инициализация с помощью make
myMap = make(map[string]int)
// Инициализация с помощью литерала карты
myMap = map[string]int{
"Alice": 25,
"Bob": 30,
}
Для этого используется синтаксис индексирования.
myMap["Charlie"] = 35
myMap["Alice"] = 26 // обновление значения по ключу "Alice"
Для этого используется синтаксис индексирования.
age := myMap["Alice"]
fmt.Println(age) // 26
Чтобы проверить это можно использовать двойное присваивание.
age, exists := myMap["David"]
if exists {
fmt.Println("Age of David:", age)
} else {
fmt.Println("David not found")
}
Для этого используется встроенная функция
delete
.delete(myMap, "Bob")
Для этого используется цикл
for range
.for key, value := range myMap {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
Пример
package main
import (
"fmt"
)
func main() {
// Инициализация карты с помощью литерала
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
// Добавление нового элемента
myMap["Charlie"] = 35
// Обновление существующего элемента
myMap["Alice"] = 26
// Извлечение значения по ключу
age := myMap["Alice"]
fmt.Println("Age of Alice:", age) // 26
// Проверка существования ключа
age, exists := myMap["David"]
if exists {
fmt.Println("Age of David:", age)
} else {
fmt.Println("David not found")
}
// Удаление элемента
delete(myMap, "Bob")
// Итерация по карте
for key, value := range myMap {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В Go пустой интерфейс
interface{}
является особым типом, который может содержать значение любого типа. Это связано с тем, что в Go любой тип реализует пустой интерфейс, поскольку в нем нет методов, которые нужно реализовать. Поскольку пустой интерфейс не требует реализации каких-либо методов, любой тип в Go автоматически реализует этот интерфейс. Это делает пустой интерфейс универсальным контейнером для значений любых типов.
type interface{} interface {}
Типа конкретного значения
Самого значения
Когда значение присваивается переменной типа интерфейс, Go сохраняет информацию о типе и значении этого значения. Для пустого интерфейса эта информация может быть любого типа.
Когда значение из пустого интерфейса приводится к конкретному типу, происходит проверка типа во время выполнения. Если значение внутри интерфейса действительно является указанным типом, приведение успешно. В противном случае приведение не удается, и возвращается значение
nil
или происходит паника, если приведение выполнено без проверки.Присваивание значений пустому интерфейсу
package main
import "fmt"
func main() {
var i interface{}
i = 42
fmt.Println(i) // 42
i = "hello"
fmt.Println(i) // hello
}
Утверждение типа (Type Assertion)
package main
import "fmt"
func main() {
var i interface{} = "hello"
// Утверждение типа с проверкой
s, ok := i.(string)
if ok {
fmt.Println("String:", s)
} else {
fmt.Println("Not a string")
}
// Утверждение типа без проверки
// Это вызовет панику, если тип не соответствует
s = i.(string)
fmt.Println("String:", s)
}
Использование switch для проверки типа
package main
import "fmt"
func printType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Integer:", v)
case bool:
fmt.Println("Boolean:", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
func main() {
printType("hello")
printType(42)
printType(true)
printType(3.14)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Денормализация нужна, когда система испытывает проблемы с производительностью при чтении данных. Особенно полезна, если запросы часто требуют объединения нескольких таблиц. Это снижает нагрузку на базу, уменьшает количество операций соединения и ускоряет выборки — за счёт хранения дублирующих данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это гибкий и мощный инструмент для работы с последовательностями элементов. Они предоставляют более высокоуровневый интерфейс для работы с массивами. Рассмотрим, как с ними работать, почему они нужны и какие операции можно выполнять.
Это динамическая последовательность элементов одного типа, которая предоставляет доступ к части или всем элементам массива без копирования данных. Он содержит три компонента:
Указатель на массив.
Длина (количество элементов в слайсе).
Ёмкость (максимальное количество элементов, которые могут быть включены в слайс без перераспределения памяти).
Слайсы позволяют работать с массивами более гибко:
В отличие от массивов, длина слайса может изменяться.
Слайсы можно передавать в функции и возвращать из них, не копируя данные.
Предоставляют множество встроенных функций для работы с последовательностями данных.
Из массива
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // слайс содержит элементы {2, 3, 4}
Используя make
slice := make([]int, 5) // создаёт слайс длиной и ёмкостью 5, заполненный нулями
Литерал слайса
slice := []int{1, 2, 3, 4, 5}
Доступ к элементам
fmt.Println(slice[0]) // выводит первый элемент слайса
Изменение элементов
slice[1] = 10 // изменяет второй элемент слайса
Добавление элементов
slice = append(slice, 6, 7) // добавляет элементы 6 и 7 к слайсу
Срезка (slicing)
newSlice := slice[1:3] // создаёт новый слайс с элементами с 1-го по 3-й
Рассмотрим пример функции, которая добавляет элемент в слайс и возвращает новый слайс
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
nums = append(nums, 4) // добавление элемента
fmt.Println(nums) // выводит [1, 2, 3, 4]
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это структура данных, которая используется для хранения и поиска пар "ключ-значение". Обеспечивают быстрый доступ к данным по ключу, обычно с константным временем доступа в среднем случае. Основой работы хэш-таблицы является хеш-функция, которая преобразует ключ в индекс, по которому хранится значение.
Функция, которая принимает ключ и преобразует его в индекс массива, называемого "хэш-таблицей". Хорошая хеш-функция распределяет ключи равномерно по хэш-таблице, минимизируя количество коллизий.
Массив фиксированного размера, где каждый элемент называется "корзиной" (bucket). Корзина может содержать одно или несколько значений.
Ситуация, когда два разных ключа хешируются в один и тот же индекс. Коллизии решаются с помощью различных методов, таких как цепочки (chaining) или открытая адресация (open addressing).
Хеш-функция вычисляет индекс для данного ключа. Значение помещается в соответствующую корзину по этому индексу. Если возникает коллизия, используется метод разрешения коллизий.
Хеш-функция вычисляет индекс для ключа. Корзина по этому индексу проверяется на наличие значения. Если значение найдено, оно возвращается; если нет, возвращается индикатор отсутствия значения.
Хеш-функция вычисляет индекс для ключа. Значение удаляется из соответствующей корзины. При необходимости корректируются ссылки или структура данных для разрешения коллизий.
Среднее время доступа к элементу составляет O(1).
Обеспечивает простой интерфейс для вставки, поиска и удаления данных.
Требуют дополнительных механизмов для разрешения, что может усложнить реализацию.
Эффективность хэш-таблицы зависит от качества хеш-функции.
При увеличении количества элементов может потребоваться перераспределение и увеличение размера таблицы, что временно снижает производительность.
package main
import "fmt"
func main() {
// Создание карты
myMap := make(map[string]int)
// Вставка значений
myMap["Alice"] = 25
myMap["Bob"] = 30
// Поиск значений
value, exists := myMap["Alice"]
if exists {
fmt.Println("Alice:", value) // Alice: 25
} else {
fmt.Println("Alice not found")
}
// Удаление значений
delete(myMap, "Alice")
_, exists = myMap["Alice"]
if !exists {
fmt.Println("Alice has been deleted") // Alice has been deleted
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Пустой интерфейс нужен для универсальности: он позволяет писать код, не привязанный к конкретным типам. Примеры использования включают универсальные контейнеры (например, массивы и словари) и функции, работающие с любыми типами. Например, в логировании часто используют interface{} для передачи любых данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В многопоточных (параллельных) программах горутины (goroutines) могут одновременно изменять одни и те же данные. Если не синхронизировать доступ, это приведёт к гонке данных (data race), когда несколько потоков читают/пишут одно и то же значение одновременно.
Используется для блокировки критической секции кода, чтобы в один момент только одна горутина могла изменять данные.
package main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем доступ
counter++ // Изменяем данные
mutex.Unlock() // Разблокируем доступ
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Итоговый счетчик:", counter) // 1000
}
Позволяет нескольким горутинам читать данные одновременно, но блокирует запись.
var (
data int
mutex sync.RWMutex
)
func readData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.RLock() // Разрешаем чтение
fmt.Println("Читаем данные:", data)
mutex.RUnlock()
}
func writeData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем на запись
data++
mutex.Unlock()
}
Позволяет дождаться завершения всех горутин без блокировки данных.
var wg sync.WaitGroup
wg.Add(2) // Ожидаем 2 горутины
go func() {
defer wg.Done()
fmt.Println("Горутина 1 завершилась")
}()
go func() {
defer wg.Done()
fmt.Println("Горутина 2 завершилась")
}()
wg.Wait() // Ждём завершения всех горутин
fmt.Println("Все горутины завершены")
Атомарные операции быстрее мьютексов и гарантируют безопасное обновление переменных без гонок данных.
import "sync/atomic"
var counter int64
func incrementAtomic() {
atomic.AddInt64(&counter, 1) // Атомарное увеличение
}
В Go рекомендуется избегать блокировок и использовать каналы для передачи данных между горутинами.
package main
import "fmt"
func main() {
ch := make(chan int) // Канал для передачи данных
go func() {
ch <- 42 // Отправляем данные
}()
data := <-ch // Получаем данные
fmt.Println("Получено:", data)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Карты (maps) представляют собой ассоциативные массивы, которые связывают ключи с соответствующими значениями. Работа с картами включает получение и запись значений, и Go предоставляет удобный синтаксис для этих операций. Рассмотрим особенности синтаксиса получения и записи значений в карты.
Для этого используется синтаксис индексирования
value := myMap[key]
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
value := myMap["Alice"]
fmt.Println("Alice:", value) // Alice: 25
}
Для этого используется синтаксис двойного присваивания
value, exists := myMap[key]
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
value, exists := myMap["Charlie"]
if exists {
fmt.Println("Charlie:", value)
} else {
fmt.Println("Charlie not found")
}
}
Для этого используется синтаксис индексирования:
myMap[key] = value
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{}
// Добавление значений
myMap["Alice"] = 25
myMap["Bob"] = 30
fmt.Println("Map:", myMap) // Map: map[Alice:25 Bob:30]
// Обновление значения
myMap["Alice"] = 26
fmt.Println("Updated Map:", myMap) // Updated Map: map[Alice:26 Bob:30]
}
Для этого используется встроенная функция
delete
:delete(myMap, key)
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
// Удаление значения по ключу
delete(myMap, "Alice")
fmt.Println("Map after deletion:", myMap) // Map after deletion: map[Bob:30]
}
Если ключ отсутствует в карте, при попытке получения значения будет возвращено нулевое значение типа значения карты. Например, для карты
map[string]int
это будет 0
.package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
}
value := myMap["Bob"] // Ключ "Bob" отсутствует
fmt.Println("Value:", value) // Value: 0
}
Нельзя записывать значения в
nil
карту. Это приведет к панике времени выполнения.package main
func main() {
var myMap map[string]int // nil карта
myMap["Alice"] = 25 // Вызовет панику: runtime error: assignment to entry in nil map
}
Порядок итерации по карте не определен и может различаться между разными запусками программы.
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
"Carol": 35,
}
for key, value := range myMap {
fmt.Printf("%s: %d\n", key, value)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Work stealing — это стратегия параллельной обработки задач, при которой неактивный поток берёт задачу у перегруженного потока.
Это помогает равномерно распределять нагрузку и повышает эффективность многопоточности. Используется в современных планировщиках, в том числе в языках с горутинами и акторной моделью.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Может быть как безопасным, так и небезопасным, в зависимости от контекста и конкретного использования.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := make([]byte, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
buffer[i] = byte(i) // Несколько горутин одновременно модифицируют буфер
}(i)
}
wg.Wait()
fmt.Println("Buffer:", buffer)
}
Если несколько горутин только читают из буфера, это безопасно. Пример безопасного использования:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")
for i := 0; i < len(buffer); i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("buffer[%d]: %c\n", i, buffer[i])
}(i)
}
wg.Wait()
}
Для синхронизации доступа к буферу используйте мьютексы (
sync.Mutex
). Это гарантирует, что только одна горутина в данный момент модифицирует буфер.package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
buffer := make([]byte, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
mu.Lock()
buffer[i] = byte(i)
mu.Unlock()
}(i)
}
wg.Wait()
fmt.Println("Buffer:", buffer)
}
Если каждая горутина должна работать с независимой копией буфера, создавайте копии для каждой горутины.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
localBuffer := make([]byte, len(buffer))
copy(localBuffer, buffer)
localBuffer[i] = byte(i)
fmt.Printf("Goroutine %d: %s\n", i, localBuffer)
}(i)
}
wg.Wait()
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Всё зависит от типа числа:
- int, uint — обычно 8 байт на 64-битных системах.
- int32, uint32 — 4 байта.
- int64, uint64 — 8 байт.
- float32 — 4 байта, float64 — 8 байт.
Go явно различает размеры, что позволяет эффективно управлять памятью.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Каналы — это мощные инструменты для обмена данными между горутинами, обеспечивающие синхронизацию и безопасную коммуникацию. Основное различие между буферизированными и небуферизированными каналами заключается в их поведении при отправке и получении данных.
Не имеют внутренней емкости, т.е. они не могут хранить значения. Эти каналы требуют, чтобы отправитель и получатель были готовы обмениваться данными одновременно. Если одна сторона не готова, другая будет заблокирована:
Отправка данных в него блокирует отправителя до тех пор, пока получатель не прочитает данные из канала.
Получение данных из него блокирует получателя до тех пор, пока другая горутина не отправит данные в канал.
ch := make(chan int) // Создание небуферизированного канала
go func() {
val := <-ch // Блокируется, ожидая данные
fmt.Println("Received:", val)
}()
ch <- 3 // Блокируется, пока данные не будут получены
Имеют внутреннюю емкость, что позволяет хранить одно или несколько элементов без непосредственного получателя данных. Отправка или получение данных работает следующим образом:
Отправка блокируется, только если буфер заполнен. До этого момента данные могут быть отправлены без блокировки, даже если получатель не готов их принять.
Получение из буферизированного канала блокируется, только если канал пуст. Если в канале есть данные, получение происходит без блокировки.
ch := make(chan int, 2) // Создание буферизированного канала с емкостью 2
ch <- 1 // Отправка данных без блокировки
ch <- 2 // Отправка данных без блокировки
go func() {
val := <-ch // Получение данных без блокировки
fmt.Println("Received:", val)
}()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Да, JOIN можно использовать со вложенными (subquery) запросами. Варианты:
- JOIN с подзапросом, возвращающим таблицу (SELECT ... FROM (SELECT ...) AS subquery JOIN ...).
- JOIN с подзапросом в ON (SELECT ... FROM table1 JOIN (SELECT ...) AS subquery ON ...).
- Использование подзапроса в WHERE или IN, но это менее эффективно, чем JOIN.
Вложенные запросы могут снижать производительность, поэтому лучше использовать индексы и анализировать EXPLAIN.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это функции, которые принимают набор значений и возвращают одно агрегированное значение. В языке Go нет встроенных агрегатных функций, как в SQL, но их можно реализовать самостоятельно.
суммирует все элементы.
вычисляет среднее значение.
находит минимальный элемент.
находит максимальный элемент.
считает количество элементов.
Функция суммы (
SUM
) func Sum(nums []int) int {
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
Функция среднего (
AVG
) func Average(nums []int) float64 {
if len(nums) == 0 {
return 0
}
return float64(Sum(nums)) / float64(len(nums))
}
Функция минимума (
MIN
) func Min(nums []int) int {
if len(nums) == 0 {
panic("empty slice")
}
min := nums[0]
for _, num := range nums {
if num < min {
min = num
}
}
return min
}
Функция максимума (
MAX
) func Max(nums []int) int {
if len(nums) == 0 {
panic("empty slice")
}
max := nums[0]
for _, num := range nums {
if num > max {
max = num
}
}
return max
}
Функция подсчёта (
COUNT
) func Count(nums []int) int {
return len(nums)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Компилятору не нужно сообщать это явно – соответствие интерфейсу проверяется автоматически. Однако для явной декларации можно использовать конструкцию вида var _ InterfaceName = (*StructName)(nil).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это целочисленные значения, которые используются для доступа к элементам упорядоченных структур данных. В контексте Go индексы чаще всего применяются для работы со строками, массивами, срезами, а также картами (косвенно, через ключи).
Индексы позволяют обращаться к конкретным элементам массива, строки или среза. Например, если у нас есть массив чисел, индекс указывает, какой именно элемент извлечь.
С помощью индексов можно перебирать элементы массива, строки или среза, например, используя циклы.
В изменяемых структурах данных, таких как срезы или массивы, индекс позволяет присвоить новое значение конкретному элементу.
Индексы упрощают и ускоряют доступ к данным, потому что доступ осуществляется за O(1) (константное время) в массивах или срезах.
В строках индексы используются для доступа к конкретным байтам.
package main
import "fmt"
func main() {
str := "Привет"
fmt.Println(str[0]) // 208 (байт, не символ!)
fmt.Printf("%c\n", str[0]) // П (символ, представленный первым байтом UTF-8)
}
В массивах и срезах индексы используются для извлечения и изменения значений
package main
import "fmt"
func main() {
arr := [5]int{10, 20, 30, 40, 50}
fmt.Println(arr[2]) // 30
// Изменение значения по индексу
arr[2] = 100
fmt.Println(arr) // [10 20 100 40 50]
}
Обычно индексы используются для итерации по элементам коллекции с помощью цикла
for
.package main
import "fmt"
func main() {
nums := []int{10, 20, 30, 40, 50}
for i, v := range nums {
fmt.Printf("Индекс: %d, Значение: %d\n", i, v)
}
}
Индексы полезны для извлечения подстрок с использованием срезов:
package main
import "fmt"
func main() {
str := "Привет, Мир!"
fmt.Println(str[8:12]) // Мир
}
Если попытаться обратиться к элементу по индексу, который выходит за пределы коллекции, Go выдаст runtime panic:
package main
func main() {
nums := []int{1, 2, 3}
fmt.Println(nums[5]) // panic: runtime error: index out of range
}
Если неверно учитывать байтовое представление символов UTF-8, можно получить некорректный результат.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM