Skip to content

Commit

Permalink
added: Data block with first 3 items - new, constructors and composit…
Browse files Browse the repository at this point in the history
…e literals and make
  • Loading branch information
0x0FACED committed Sep 4, 2024
1 parent 54db567 commit 885e523
Showing 1 changed file with 111 additions and 0 deletions.
111 changes: 111 additions & 0 deletions effective_go_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
- [Множественные возвращаемые значения](#множественные-возвращаемые-значения)
- [Именованные параметры результата](#именованные-параметры-результата)
- [Отложенные вызовы (defer)](#отложенные-вызовы-defer)
- [Данные](#данные)
- [Выделение (аллокация) памяти с помощью `new`](#выделение-аллокация-памяти-с-помощью-new)
- [Конструкторы и составные литералы](#конструкторы-и-составные-литералы)
- [Выделение (аллокация) памяти с помощью `make`](#выделение-аллокация-памяти-с-помощью-make)

## Форматирование

Expand Down Expand Up @@ -716,3 +720,110 @@ leaving: b
```

Для программистов, привыкших к управлению ресурсами на уровне блоков кода в других языках, `defer` может показаться странным, **но его самые интересные и мощные применения связаны с тем, что он работает на уровне функции, а не блока кода**. В разделе о `panic` и `recover` будет показан ещё один пример его возможностей.

## Данные

### Выделение (аллокация) памяти с помощью `new`

В Go есть два примитива выделения памяти: **встроенные функции `new` и `make`.** Они выполняют разные задачи и применяются к различным типам, что может быть немного запутанным, но правила просты. Сначала поговорим о `new`. Это встроенная функция, **которая выделяет память, но, в отличие от аналогичных функций в некоторых других языках, она *не инициализирует* память, а лишь *обнуляет* её**. То есть `new(T)` выделяет память для нового объекта типа `T`, обнуляет её и возвращает его адрес, значение типа `*T`. В терминологии Go, она возвращает указатель на только что выделенное нулевое значение типа `T`.

Поскольку память, возвращаемая `new`, обнулена, полезно при проектировании ваших структур данных предусмотреть, чтобы нулевое значение каждого типа можно было использовать без дальнейшей инициализации. Это означает, что пользователь структуры данных может создать её с помощью `new` и сразу начать работу. Например, в документации для `bytes.Buffer` говорится, что "**нулевое значение для `Buffer` — это пустой буфер, готовый к использованию**". Аналогично, `sync.Mutex` не имеет явного конструктора или метода `Init`. **Вместо этого нулевое значение для `sync.Mutex` определяется как разблокированный мьютекс.**

Свойство "**нулевое значение полезно**" работает транзитивно. Рассмотрим это объявление типа.

```go
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
```

Значения типа `SyncedBuffer` также готовы к использованию сразу после выделения или просто объявления. В следующем фрагменте кода и `p`, и `v` будут работать корректно без дальнейшей настройки.

```go
p := new(SyncedBuffer) // тип *SyncedBuffer
var v SyncedBuffer // тип SyncedBuffer
```

*Транзитивность означает, что при объявлении через `new` или как `var` **поля структуры будут обнулены, то есть нулевыми, то есть у каждого поля в зависимости от типа будет нулевое значение.***

### Конструкторы и составные литералы

Иногда нулевое значение недостаточно, и необходим конструктор для инициализации, как в следующем примере из пакета `os`.

```go
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File)
f.fd = fd
f.name = name
f.dirinfo = nil
f.nepipe = 0
return f
}
```

В этом примере много шаблонного кода (так называемый `boilerplate code`). Мы можем упростить его, **используя составной литерал, который является выражением, создающим новый экземпляр при каждом его выполнении.**

```go
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := File{fd, name, nil, 0}
return &f
}
```

Обратите внимание, что в отличие от `C`, **возвращать адрес локальной переменной в Go вполне допустимо**; **память**, связанная с переменной, **сохраняется после завершения функции**. На самом деле, **взятие адреса составного литерала выделяет новый экземпляр при каждом выполнении, поэтому мы можем объединить последние две строки:**

```go
return &File{fd, name, nil, 0}
```

**Поля составного литерала располагаются в порядке и должны быть все указаны**. Однако, указывая элементы явно в виде пар "**поле: значение"**, инициализаторы могут быть в любом порядке, а пропущенные будут иметь нулевые значения. Так мы можем написать:

```go
return &File{fd: fd, name: name}
```

В качестве крайнего случая, если составной литерал не содержит полей, **он создаст нулевое значение для типа**. Выражения `new(File)` и `&File{}` ***эквивалентны***.

Составные литералы также могут быть созданы для массивов, срезов и карт, где метки полей будут индексами или ключами карты. В этих примерах инициализация работает независимо от значений `Enone`, `Eio` и `Einval`, если они уникальны.

```go
const Enone = 0
const Eio = 1
const Einval = 2
a := [...]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
```

### Выделение (аллокация) памяти с помощью `make`

Вернемся к выделению памяти. Встроенная функция `make(T, args)` имеет другую цель по сравнению с `new(T)`. Она создает **только срезы, карты и каналы и возвращает *инициализированное* (*а не обнуленное*) значение типа `T` (а не `*T`)**. Причина различия в том, что **эти три типа представляют собой ссылки на структуры данных, которые должны быть инициализированы перед использованием**. Например, *срез — это трехкомпонентный дескриптор, содержащий указатель на данные (внутри массива), длину и емкость, и пока эти элементы не инициализированы, срез равен `nil`*. Для срезов, карт и каналов функция `make` инициализирует внутреннюю структуру данных и подготавливает значение к использованию. Например,

```go
make([]int, 10, 100)
```

**выделяет массив из 100 целых чисел и затем создает структуру среза с длиной 10 и емкостью 100, указывающую на первые 10 элементов массива**. (При создании среза емкость можно опустить; см. раздел о срезах для получения дополнительной информации.) В отличие от этого, `new([]int`) возвращает указатель на только что выделенную, обнуленную структуру среза, то есть указатель на значение `nil` среза.

Эти примеры иллюстрируют разницу между new и make.

```go
var p *[]int = new([]int) // выделяет структуру среза; *p == nil; редко полезно
var v []int = make([]int, 100) // срез v теперь ссылается на новый массив из 100 целых чисел

// Неоправданно сложно (оно не надо, не делайте так):
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// Идиоматично:
v := make([]int, 100)
```

Помните, что `make` **применяется только к картам, срезам и каналам и не возвращает указатель**. Чтобы получить явный указатель, используйте `new` или **явно возьмите адрес переменной**.

0 comments on commit 885e523

Please sign in to comment.