четверг, 24 января 2019 г.

Эффективный Go: срезы (slices)

Срезы оборачивают массивы, чтобы дать более общий, мощный и удобный интерфейс с последовательностями данных. За исключением предметов с явным измерением, такие как матрицы преобразования, большая часть программирования массивов в Go делается со срезами, а не с простыми массивами.

Срезы содержат ссылки на базовый массив, и если вы назначите один срез к другому, оба будут ссылаться на один и тот же массив. Если функция принимает аргумент срез, изменения, которые она вносит в элементы среза, будут видны вызывающей стороне, аналогично передаче указателя на базовый массив. Read функция поэтому может принимать аргумент срез, а не указатель и счет; длина внутри среза устанавливает верхний предел того, сколько данных для чтения. Вот сигнатура метода Read типа File в пакете os:

func (f *File) Read(buf []byte) (n int, err error)

Метод возвращает количество прочитанных байтов и значение ошибки, если она есть. Для считывания первых 32 байт большего буфера buf, сделаем срез буфера.

n, err := f.Read(buf[0:32])

Такая нарезка является распространенной и эффективной. Фактически, оставляя эффективность в стороне на данный момент, следующий фрагмент также будет читать первые 32 байта буфера.

var n int
var err error
for i := 0; i < 32; i++ {
    nbytes, e := f.Read(buf[i:i+1])  // Читаем один байт.
    if nbytes == 0 || e != nil {
        err = e
        break
    }
    n += nbytes
}

Длина среза может быть изменена до тех пор, пока он все еще вписывается в пределы базового массива; просто назначьте его на срез самого себя. Емкость среза, доступная встроенной функции cap, сообщает максимальную длину среза, которую можно предполагать. В следующем примере представлена функция для добавления данных в срез. Если данные превышает емкость, срез перераспределяется. Полученный срез возвращается. Функция использует тот факт, что len и cap являются законными применительно к nil и возвращает 0 в этом случае.

func Append(slice, data []byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // реаллоцируем
        // Аллоцирем в двойном размере требуемого, 
        // для будущего роста.
        newSlice := make([]byte, (l+len(data))*2)
        // copy функция предопределена 
        // и работает для любого типа среза.
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    copy(slice[l:], data)
    return slice
}

Мы должны вернуть срез позже, потому что, хотя Append может изменять элементы slice, сам срез (структура данных времени выполнения, содержащая указатель, длину и емкость) передается по значению.

Идея добавления к срезу очень полезна, и используется встроенной функцией append.


Читайте также:


Комментариев нет:

Отправить комментарий