суббота, 26 января 2019 г.

Эффективный Go: методы - указатели и значения

Указатели и значения

Как мы видели в ByteSize, методы могут быть определены для любого именованного типа (кроме указателя или интерфейса); получатель не должен быть структурой.

В обсуждении срезов ранее мы написали Append функцию. Вместо этого мы можем определить ее как метод на срезах. Для того чтобы сделать это мы сначала объявляем именованный тип, к которому мы можем привязать метод, и затем делаем получатель для метода значением этого типа.

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
    // Тело точно такое же как в Append функции, 
    // определенной ранее.

    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
}

Здесь все еще требуется метод возврата обновленного среза. Мы можем устранить эту неуклюжесть путем переопределения метода, чтобы взять указатель на ByteSlice в качестве получателя, таким образом метод сможет перезаписывать срез вызывающего.

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // Тело как ранее, без return.
    *p = slice
}

На самом деле, мы можем сделать еще лучше. Если мы изменим нашу функцию, чтобы она выглядела как стандартный метод Write, как следующий:

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // Снова как ранее.
    *p = slice
    return len(data), nil
}

тогда тип *ByteSlice удовлетворяет стандартному интерфейсу io.Writer, что удобно. Например, мы можем распечатать в одном выражении.

var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)

Мы передаем адрес ByteSlice, потому что только *ByteSlice удовлетворяет io.Writer. Правило об указателях и значениях для получателей заключается в том, что методы-значения можно вызывать для указателей и значений, но методы указателей могут быть вызываны только на указателях.

Это правило возникает потому, что методы указателя могут модифицировать получателя; вызов их на значении приведет к тому, что метод получит копию значения, поэтому любые изменения будут отклонены. Поэтому язык не допускает этой ошибки. Однако есть удобное исключение. Когда значение адресуемое, язык заботится о частом случае вызова метода указателя на значение, вставляя адрес оператора автоматически. В нашем примере переменная b является адресуемой, поэтому мы можем вызвать его метод Write только с b.Write. Компилятор перепишет это в (&b).Write для нас.

Кстати, идея использования Write на срезе байтов занимает центральное место в реализации bytes.Buffer.


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


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

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