Указатели и значения
Как мы видели в 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
.
Читайте также:
Комментариев нет:
Отправить комментарий