2013-07-11 2 views
9

Мне нравится то, что Go не дает мне миллионы способов сделать простые вещи - заимствовать у Zen of Python: «Должен быть один - и желательно только один - очевидный способ для этого ».Предпочтительный способ создания экземпляров в Go

Однако я не понимаю предпочтительный/идиоматический способ создания типов экземпляров. Основные типы просты:

n := 0 
t := 1.5 
str := "Hello" 

Но как насчет структур? Являются ли следующие эквиваленты, и если да, то что является предпочтительным и почему?

var f Foo  
f := Foo{} 

А как насчет ломтиков? Я могу сделать var xs []int, xs := []int{}, или xs := make([]int), но я думаю, что первый вариант (в отличие от структур) отличается от остальных? Я предполагаю, что это применимо и к картам.

С указателями, я слышу, что следует избегать new. Это хороший совет, и если да, то что будет считаться действительным использованием new?

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

ответ

7

Когда вы объявляете переменную, где T некоторый тип:

var name T 

Go дает вам кусок неинициализированным «обнуляется» память.

С примитивами это означает, что var name int будет 0, а var name string будет "". В C it might be zeroed, or might be something unexpected. Go гарантирует, что неинициализированная переменная является нулевым эквивалентом типа.

Внутренние фрагменты, карты и каналы рассматриваются как указатели. Нулевое значение указателей равно nil, что означает, что он указывает на nil-память. Не инициализируя его, вы можете столкнуться с паникой, если попытаетесь оперировать ею.

Функция make специально предназначена для фрагмента, карты или канала. Аргументы сделать функцию, являются:

make(T type, length int[, capacity int]) // For slices. 
make(T[, capacity int]) // For a map. 
make(T[, bufferSize int]) // For a channel. How many items can you take without blocking? 

A ломтиков length, сколько предметов начинается с. Емкость - это выделенная память, прежде чем потребуется изменить размер (внутренне, новый размер * 2, затем скопируйте). Для получения дополнительной информации см. Effective Go: Allocation with make.

Структуры: new(T) равнозначно &T{}, а не T{}. *new(T) эквивалентен *&T{}.

Кусочки: make([]T,0) равнозначно []T{}.

Карты: make(map[T]T) эквивалентно map[T]T{}.

Насколько который предпочтителен метод, я задаю себе вопрос:

Вы знаете, я стоимость (ы) сейчас в функции?

Если ответ «да», то я иду с одним из вышеуказанных T{...}. Если ответ «нет», я использую make или new.

К примеру, я бы избежать чего-то вроде этого:

type Name struct { 
    FirstName string 
    LastName string 
} 

func main() { 
    name := &Name{FirstName:"John"} 
    // other code... 
    name.LastName = "Doe" 
} 

Вместо этого я бы сделать что-то вроде этого:

func main() { 
    name := new(Name) 
    name.FirstName = "John" 
    // other code... 
    name.LastName = "Doe" 
} 

Почему? Потому что, используя new(Name), я даю понять, что I intend для заполнения значений позже. Если бы я использовал , было бы непонятно, что я собирался добавить/изменить значение позже в той же функции, не читая остальную часть кода.

Исключение составляют структуры, если вы не хотите указатель. Я буду использовать T{}, но я не буду ничего вкладывать в него, если планирую добавить/изменить значения. Конечно, *new(T) также работает, но это похоже на использование *&T{}. В этом случае T{} является более чистым, хотя я стараюсь использовать указатели со структурами, чтобы избежать копирования при передаче.

Другое дело иметь в виду, что размер []*struct меньше и дешевле, чем []struct, предполагая, что структура намного больше, чем указатель, который обычно составляет 4-8 байт (8 байтов на 64 бит?).

4

Вы можете взглянуть на источники стандартной библиотеки Go, где вы можете найти много идиоматического кода Go.

Вы правы: var xs []int отличается от двух других вариантов, так как он не «инициализирует» xs, xs равен нулю. В то время как другие два действительно создают срез. xs := []int{} распространен, если вам нужен пустой кусочек с нулевой крышкой, а make дает вам больше возможностей: длина и емкость. С другой стороны, обычно начинается нулевой срез и заполняется добавлением, как в var s []int; for ... { s = append(s, num) }.

new нельзя избежать всего, поскольку это единственный способ создать указатель, например. к uint32 или другим встроенным типам. Но вы правы, пишите a := new(A) довольно необычно и написано в основном как a := &A{}, так как это можно превратить в a := &A{n: 17, whatever: "foo"}. Использование new на самом деле не обескуражено, но, учитывая способность структурных литералов, он просто выглядит как остатки Java.

4

Во время беседы Fireside с командой Go в Google IO кто-то в аудитории спросил команду Go, что они хотели бы вывести с языка.

Роб сказал, что он хотел было меньше способ объявить переменные и отметил:

Colon равно для перезаписи, названных параметров результата (https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU), переменные повторно в течение цикла является запутанным, особенно для затворов. Однако этот язык, вероятно, не сильно изменится.

+0

+1 Не отвечает на вопрос, но я точно согласен - рад узнать, что сам Пайк так сказал. Это одна слабость, которую я нахожу в GoLang: слишком много способов объявить и не понять преимущества, недостатки и уместность каждого из них - иногда дает мне некоторое «не совсем законченное» чувство. – Vector

Смежные вопросы