Примеры и идиомы Go [закрыто]

91

На Go не так много кода для изучения языка, и я уверен, что я не единственный, кто с ним экспериментирует. Итак, если вы узнали что-то интересное о языке, разместите здесь пример.

Я тоже ищу

  • идиоматические способы делать что-то в Go,
  • Стиль мышления C / C ++ "портирован" на Go,
  • распространенные ошибки в синтаксисе,
  • ничего интересного, правда.
Дьёрдь Андрасек
источник
Поддержка ARM, например 8-битная или 16-битная. D языка по-прежнему нет.
1
Библиотека ( golang.org/pkg ) - отличный источник информации об использовании go. Лично я считаю, что изучение того, как реализованы структуры данных, полезно для изучения языка.
tkokasih

Ответы:

35

Заявления об отсрочке

Оператор defer вызывает функцию, выполнение которой откладывается до момента возврата из окружающей функции.

DeferStmt = "отложить" Выражение.

Выражение должно быть вызовом функции или метода. Каждый раз, когда выполняется инструкция defer, параметры вызова функции оцениваются и сохраняются заново, но функция не вызывается. Отложенные вызовы функций выполняются в порядке LIFO непосредственно перед возвратом из окружающей функции, но после оценки возвращаемых значений, если таковые имеются.


lock(l);
defer unlock(l);  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
    defer fmt.Print(i);
}

Обновить:

deferтеперь также идиоматический способ обработки panicв исключениях вроде образа:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i+1)
}
оборота Дьёрдь Андрасек
источник
17
Похоже на старый добрый RAII (явный).
Конрад Рудольф
4
+1 так как я много читал о Go, но я все еще не видел этого (пока вы мне не показали)!
u0b34a0f6ae
Умно, хотя для меня было бы больше смысла, если бы операторы defer выполнялись в порядке FIFO (сверху вниз), но, возможно, это только я ...
Майк Спросс
Круто. Напоминает мне охранников с сайта
hasen
4
@Mike: если сравнить с блоками "try: .. finally:" LIFO размещается таким же образом. Для пар открытия / закрытия ресурсов и т. Д. Такое вложение - единственное, что имеет смысл (первое открытие закрывается последним).
u0b34a0f6ae
25

Объектные файлы Go фактически включают заголовок в виде открытого текста:

jurily@jurily ~/workspace/go/euler31 $ 6g euler31.go
jurily@jurily ~/workspace/go/euler31 $ cat euler31.6
amd64
  exports automatically generated from
  euler31.go in package "main"
    import

$$  // exports
  package main
    var main.coin [9]int
    func main.howmany (amount int, max int) (? int)
    func main.main ()
    var main.initdone· uint8
    func main.init ()

$$  // local types
  type main.dsigddd_1·1 struct { ? int }

$$

!
<binary segment>
оборота Дьёрдь Андрасек
источник
6
Это больше похоже на скрытую особенность, чем на идиоматический пример
hasen
22

Я видел, как несколько человек жаловались на цикл for: «Почему мы должны говорить i = 0; i < len; i++в наши дни?».

Я не согласен, мне нравится конструкция for construct. Вы можете использовать длинную версию, если хотите, но идиоматический Go

var a = []int{1, 2, 3}
for i, v := range a {
    fmt.Println(i, v)
}

for .. rangeКонструкция цикла по всем элементам , и подают два значения - индекс iи значениеv .

range также работает с картами и каналами.

Тем не менее, если вам не нравится forв какой-либо форме, вы можете определить eachи mapт. Д. В нескольких строках:

type IntArr []int

// 'each' takes a function argument.
// The function must accept two ints, the index and value,
// and will be called on each element in turn.
func (a IntArr) each(fn func(index, value int)) {
    for i, v := range a {
        fn(i, v)
    }
}

func main() {
    var a = IntArr([]int{2, 0, 0, 9}) // create int slice and cast to IntArr
    var fnPrint = func(i, v int) {
        fmt.Println(i, ":", v)
    } // create a function

    a.each(fnPrint) // call on each element
}

отпечатки

0 : 2
1 : 0
2 : 0
3 : 9

Я начинаю сильно любить го :)

jg-faustus
источник
Хотя rangeэто хорошо, только если он скомпилирован в тот же код, что и цикл for-3.
Thomas Ahle
19

Иди и получи свою репутацию stackoverflow

Это перевод этого ответа .

package main

import (
    "json"
    "fmt"
    "http"
    "os"
    "strings"
)

func die(message string) {
    fmt.Printf("%s.\n", message);
    os.Exit(1);
}

func main() {
    kinopiko_flair := "https://stackoverflow.com/users/flair/181548.json"
    response, _, err := http.Get(kinopiko_flair)
    if err != nil {
        die(fmt.Sprintf("Error getting %s", kinopiko_flair))
    }

    var nr int
    const buf_size = 0x1000
    buf := make([]byte, buf_size)

    nr, err = response.Body.Read(buf)
    if err != nil && error != os.EOF {
        die(fmt.Sprintf("Error reading response: %s", err.String()))
    }
    if nr >= buf_size { die ("Buffer overrun") }
    response.Body.Close()

    json_text := strings.Split(string(buf), "\000", 2)
    parsed, ok, errtok := json.StringToJson(json_text[0])
    if ! ok {
        die(fmt.Sprintf("Error parsing JSON %s at %s", json_text, errtok))
    }

    fmt.Printf("Your stackoverflow.com reputation is %s\n", parsed.Get ("reputation"))
}

Благодаря Скотту Уэльсу за помощь с .Read ().

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

181548
источник
Я не уверен, что должно было быть не так с форматированием; Я его восстановил.
5
Авторы Go рекомендуют gofmtваш код :-)
aphink
Я не могу его скомпилировать: $ ../go/src/cmd/6g/6g SO.go SO.go: 34: undefined: json.StringToJson
aphink
@Raphink: язык изменился с тех пор, как я это сделал.
Да, вы знаете, может быть, какой эквивалент наиболее близок к StringToJson? Раньше он настраивал конструктор внутри, теперь нужно предоставить свой собственный с предопределенной нативной структурой?
macbirdie 05
19

Вот хороший пример йоты из сообщения Кинопико :

type ByteSize float64
const (
    _ = iota;   // ignore first value by assigning to blank identifier
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    YB
)

// This implicitly repeats to fill in all the values (!)
Дьёрдь Андрасек
источник
5
Обратите внимание, что точки с запятой не нужны.
mk12 09
18

Вы можете поменять местами переменные путем параллельного присвоения:

x, y = y, x

// or in an array
a[j], a[i] = a[i], a[j]

простой, но эффективный.

u0b34a0f6ae
источник
18

Вот идиома со страницы Effective Go

switch {
case '0' <= c && c <= '9':
    return c - '0'
case 'a' <= c && c <= 'f':
    return c - 'a' + 10
case 'A' <= c && c <= 'F':
    return c - 'A' + 10
}
return 0

Оператор switch включается в значение true, если выражение не задано. Так что это эквивалентно

if '0' <= c && c <= '9' {
    return c - '0'
} else if 'a' <= c && c <= 'f' {
    return c - 'a' + 10
} else if 'A' <= c && c <= 'F' {
    return c - 'A' + 10
}
return 0

На данный момент версия коммутатора мне кажется немного чище.

Роб Рассел
источник
6
Ого, полностью скопировано из VB. ;-) ( Switch True…)
Конрад Рудольф
@Konrad, опереди меня! :) Я использовал эту идиому в коде VB6 раньше, и она определенно может улучшить читаемость в определенных ситуациях.
Майк Спросс
Что такое "<="? Это связано с '<-'?
aphink
@Raphink: меньше или равно.
Paul Ruane
17

Тип переключателей :

switch i := x.(type) {
case nil:
    printString("x is nil");
case int:
    printInt(i);  // i is an int
case float:
    printFloat(i);  // i is a float
case func(int) float:
    printFunction(i);  // i is a function
case bool, string:
    printString("type is bool or string");  // i is an interface{}
default:
    printString("don't know the type");
}
Дьёрдь Андрасек
источник
16

При импорте пакетов вы можете переопределить имя как угодно:

package main

import f "fmt"

func main() {
    f.Printf("Hello World\n")
}
Элвин Роу
источник
3
Я уже использовал это: stackoverflow.com/questions/1726698/…
14

Именованные параметры результата

Возвращаемые или результирующие «параметры» функции Go могут иметь имена и использоваться как обычные переменные, как и входящие параметры. При названии они инициализируются нулевыми значениями для своих типов при запуске функции; если функция выполняет оператор возврата без аргументов, текущие значения параметров результата используются в качестве возвращаемых значений.

Имена не являются обязательными, но они могут сделать код короче и понятнее: это документация. Если мы назовем результаты nextInt, станет очевидно, какой из которых был возвращен int.

func nextInt(b []byte, pos int) (value, nextPos int) {

Поскольку именованные результаты инициализируются и привязаны к неукрашенному возврату, они могут как упрощать, так и уточнять. Вот версия io.ReadFull, которая их хорошо использует:

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
    for len(buf) > 0 && err == nil {
        var nr int;
        nr, err = r.Read(buf);
        n += nr;
        buf = buf[nr:len(buf)];
    }
    return;
}
Дьёрдь Андрасек
источник
1
Мне любопытно - есть ли это на каком-нибудь другом языке?
u0b34a0f6ae
1
В Matlab есть нечто подобное.
Дэн Лоренк,
Паскаль использует аналогичный синтаксис для возврата одного значения.
nes1983
1
@ nes1983 Для тех, кто не знает, в Паскале вы классически присваиваете возвращаемое значение имени функции.
fuz
FORTRAN в значительной степени имеет это.
Hut8
14

Из ответа Джеймса Антилла :

foo := <-ch     // This blocks.
foo, ok := <-ch // This returns immediately.

Также потенциальная ловушка: тонкая разница между операторами приема и отправки:

a <- ch // sends ch to channel a
<-ch    // reads from channel ch
Дьердь Андрасек
источник
3
Принимающий оператор сам по себе является теперь блокирующая операция, от Go 1.0.3. Спецификация была изменена: golang.org/ref/spec#Receive_operator . Пожалуйста, попробуйте поведение блокировки (тупик) здесь: play.golang.org/p/0yurtWW4Q3
Deleplace
13
/* 
 * How many different ways can £2 be made using any number of coins?
 * Now with 100% less semicolons!
 */

package main
import "fmt"


/* This line took me over 10 minutes to figure out.
 *  "[...]" means "figure out the size yourself"
 * If you only specify "[]", it will try to create a slice, which is a reference to an existing array.
 * Also, ":=" doesn't work here.
 */
var coin = [...]int{0, 1, 2, 5, 10, 20, 50, 100, 200}

func howmany(amount int, max int) int {
    if amount == 0 { return 1 }
    if amount < 0 { return 0 }
    if max <= 0 && amount >= 1 { return 0 }

    // recursion works as expected
    return howmany(amount, max-1) + howmany(amount-coin[max], max)
}


func main() {
    fmt.Println(howmany(200, len(coin)-1))
}
оборота Дьёрдь Андрасек
источник
4
Я бы посоветовал удалить название сайта по решению проблем, а также идентификационный номер. Может перефразировать вопрос. Чтобы не испортить проблему кому-то, кто споткнется о ней. Или пытаетесь обмануть, ища проблему в сети, если на то пошло.
Мизипзор
1
Для справки: это алгоритм из algorithmist.com/index.php/Coin_Change. Это первый результат Google для "размена монеты".
Дьёрдь Андрасек
13

Мне нравится, что вы можете переопределять типы, включая примитивы, такие как int, сколько угодно раз и присоединять разные методы. Как определение типа RomanNumeral:

package main

import (
    "fmt"
    "strings"
)

var numText = "zero one two three four five six seven eight nine ten"
var numRoman = "- I II III IV V VI VII IX X"
var aText = strings.Split(numText, " ")
var aRoman = strings.Split(numRoman, " ")

type TextNumber int
type RomanNumber int

func (n TextNumber) String() string {
    return aText[n]
}

func (n RomanNumber) String() string {
    return aRoman[n]
}

func main() {
    var i = 5
    fmt.Println("Number: ", i, TextNumber(i), RomanNumber(i))
}

Что распечатывает

Number:  5 five V

RomanNumber()Вызов по существу литая, он переопределяет Int типа в качестве более конкретного типа междунар. И Println()звонки String()за кадром.

jg-faustus
источник
12

Возврат канала

Это настоящая идиома, которая очень важна: как передать данные в канал и затем закрыть его. С его помощью вы можете создавать простые итераторы (поскольку диапазон будет принимать канал) или фильтры.

// return a channel that doubles the values in the input channel
func DoublingIterator(input chan int) chan int {
    outch := make(chan int);
    // start a goroutine to feed the channel (asynchronously)
    go func() {
        for x := range input {
            outch <- 2*x;    
        }
        // close the channel we created and control
        close(outch);
    }();
    return outch;
}
u0b34a0f6ae
источник
+1. Кроме того, вы можете передавать каналы через каналы.
Дьёрдь Андрасек
5
Но будьте осторожны, чтобы не вырваться из цикла for x: = range chan {}, это приведет к утечке горутины и всей памяти, на которую она ссылается.
Джефф Аллен,
3
@JeffAllen, как насчет defer close(outch);первого утверждения горутины?
1
Defer ставит в очередь оператор для выполнения, когда функция возвращается, независимо от того, какая точка возврата выбрана. Но если вход канала никогда не закрывается, то анонимная функция в этом примере никогда не выйдет из цикла for.
Джефф Аллен
11

Тайм-аут для канала читает:

ticker := time.NewTicker(ns);
select {
    case v := <- chan_target:
        do_something_with_v;
    case <- ticker.C:
        handle_timeout;
}

Украдено у Дэвиса Лю .

Дьёрдь Андрасек
источник
11
for {
    v := <-ch
    if closed(ch) {
        break
    }
    fmt.Println(v)
}

Поскольку диапазон автоматически проверяет наличие закрытого канала, мы можем сократить до этого:

for v := range ch {
    fmt.Println(v)
}
мбархау
источник
9

Настроена система make, которую можно использовать в $ GOROOT / src

Настройте свой make-файл с помощью

TARG=foobar           # Name of package to compile
GOFILES=foo.go bar.go # Go sources
CGOFILES=bang.cgo     # Sources to run cgo on
OFILES=a_c_file.$O    # Sources compiled with $Oc
                      # $O is the arch number (6 for x86_64)

include $(GOROOT)/src/Make.$(GOARCH)
include $(GOROOT)/src/Make.pkg

Затем вы можете использовать инструменты автоматического тестирования, запустив make test, или добавить пакет и общие объекты из cgo в свой $ GOROOT с помощью make install.

Скотт Уэльс
источник
7

Еще одна интересная вещь в Go является то , что godoc. Вы можете запустить его как веб-сервер на своем компьютере, используя

godoc -http=:8080

где 8080 - номер порта, а весь веб-сайт golang.org будет доступен по адресу localhost:8080.

user181548
источник
Это обычная программа или демон?
Дьёрдь Андрасек
Это обычная программа.
Джереми
7

Это реализация стека. Он иллюстрирует добавление методов к типу.

Я хотел превратить стек в слайс и использовать его свойства, но, хотя у меня это работало без type, я не видел синтаксиса для определения слайса с помощью type.

package main

import "fmt"
import "os"

const stack_max = 100

type Stack2 struct {
    stack [stack_max]string
    size  int
}

func (s *Stack2) push(pushed_string string) {
    n := s.size
    if n >= stack_max-1 {
        fmt.Print("Oh noes\n")
        os.Exit(1)
    }
    s.size++
    s.stack[n] = pushed_string
}

func (s *Stack2) pop() string {
    n := s.size
    if n == 0 {
        fmt.Print("Underflow\n")
        os.Exit(1)
    }
    top := s.stack[n-1]
    s.size--
    return top
}

func (s *Stack2) print_all() {
    n := s.size
    fmt.Printf("Stack size is %d\n", n)
    for i := 0; i < n; i++ {
        fmt.Printf("%d:\t%s\n", i, s.stack[i])
    }
}

func main() {
    stack := new(Stack2)
    stack.print_all()
    stack.push("boo")
    stack.print_all()
    popped := stack.pop()
    fmt.Printf("Stack top is %s\n", popped)
    stack.print_all()
    stack.push("moo")
    stack.push("zoo")
    stack.print_all()
    popped2 := stack.pop()
    fmt.Printf("Stack top is %s\n", popped2)
    stack.print_all()
}
user181548
источник
10
Вместо того, чтобы использовать fmt.Printf(...); os.Exit();, вы можете использовать panic(...).
notnoop
1
Это дает трассировку стека, которая мне не нужна.
3
Почему ограничено? Go - управляемый язык gc'd. Ваш стек может быть сколь угодно большим. Используйте новую встроенную функцию append (), которая будет делать что-то вроде перераспределения C, когда это необходимо.
Джефф Аллен,
«Go не нуждается в дженериках», - сказали они.
cubuspl42
4

Вызов кода c с ходу

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

Функции C имеют вид

void package·function(...)

(обратите внимание, что разделитель точек - это символ Юникода), где аргументы могут быть базовыми типами go, фрагментами, строками и т. д. Чтобы вернуть вызов значения

FLUSH(&ret)

(вы можете вернуть более одного значения)

Например, чтобы создать функцию

package foo
bar( a int32, b string )(c float32 ){
    c = 1.3 + float32(a - int32(len(b))
}

в C вы используете

#include "runtime.h"
void foo·bar(int32 a, String b, float32 c){
    c = 1.3 + a - b.len;
    FLUSH(&c);
}

Обратите внимание, что вы все равно должны объявить функцию в файле go и вам придется позаботиться о памяти самостоятельно. Я не уверен, можно ли с помощью этого вызывать внешние библиотеки, возможно, лучше использовать cgo.

Посмотрите на $ GOROOT / src / pkg / runtime примеры, используемые во время выполнения.

См. Также этот ответ для связывания кода C ++ с go.

Скотт Уэльс
источник
3
Он действительно использует "летающую точку"? Не берусь редактировать, но это кажется несколько неожиданным и радикальным.
расслабьтесь
Да, вам нужно компилировать с 6c (или 8c и т. Д.). Я не думаю, что gcc обрабатывает идентификаторы Unicode.
Скотт Уэльс,
1
Я думаю, что AltGr + периоды одинаковы, но с юникодом я не уверен. Был очень удивлен, увидев, что в источнике я прочитал ... почему бы не использовать что-то вроде ::?
u0b34a0f6ae
Это СРЕДНЯЯ ТОЧКА U + 00B7. Синтаксический анализатор, возможно, был обманут, так что он увидел в нем символ для создания действительного идентификатора c, что, как я полагаю, предотвратило бы ::.
Scott Wales
4
«·» - это всего лишь временный взлом, Роб был даже удивлен, что он все еще там, он сказал, что его собираются заменить чем-то менее идиосинкразическим.
uriel
3

Вы смотрели этот разговор ? Он показывает много интересных вещей, которые вы можете сделать (конец разговора)

user180100
источник
2
Да. Это сводится к следующему: «Там намного больше, давайте перейдем к следующей теме».
Дьёрдь Андрасек,
Да, очевидно, есть, что сказать в
3

Стек основан на другом ответе, но с использованием добавления среза не имеет ограничения по размеру.

package main

import "fmt"
import "os"

type Stack2 struct {
        // initial storage space for the stack
        stack [10]string
        cur   []string
}

func (s *Stack2) push(pushed_string string) {
        s.cur = append(s.cur, pushed_string)
}

func (s *Stack2) pop() (popped string) {
        if len(s.cur) == 0 {
                fmt.Print("Underflow\n")
                os.Exit(1)
        }
        popped = s.cur[len(s.cur)-1]
        s.cur = s.cur[0 : len(s.cur)-1]
        return
}

func (s *Stack2) print_all() {
        fmt.Printf("Stack size is %d\n", len(s.cur))
        for i, s := range s.cur {
                fmt.Printf("%d:\t%s\n", i, s)
        }
}

func NewStack() (stack *Stack2) {
        stack = new(Stack2)
        // init the slice to an empty slice of the underlying storage
        stack.cur = stack.stack[0:0]
        return
}

func main() {
        stack := NewStack()
        stack.print_all()
        stack.push("boo")
        stack.print_all()
        popped := stack.pop()
        fmt.Printf("Stack top is %s\n", popped)
        stack.print_all()
        stack.push("moo")
        stack.push("zoo")
        stack.print_all()
        popped2 := stack.pop()
        fmt.Printf("Stack top is %s\n", popped2)
        stack.print_all()
}
Джефф Аллен
источник
3
const ever = true

for ever {
    // infinite loop
}
Дьёрдь Андрасек
источник
25
кхм. for { /* infinite loop */ }достаточно.
u0b34a0f6ae
2
Конечно. Именно это и происходит здесь. Мне просто нравится foreverключевое слово. Даже в Qt есть макрос для этого.
Дьёрдь Андрасек,
6
но для этого Go не нужен макрос или симпатичный псевдоним true.
u0b34a0f6ae
@ kaizer.se: Юрили считает, что for ever(после объявления переменной) это что-то забавное, что вы можете сделать в Go, если хотите. Похоже на английский (по модулю пробела).
Фрэнк
8
это кое-что милое, что вы можете сделать и на C .. :-)#define ever (;;)
u0b34a0f6ae
2

В testглавном каталоге много маленьких программ . Примеры:

  • peano.go печатает факториалы.
  • hilbert.go имеет некоторое матричное умножение.
  • iota.go есть примеры странной йоты.
user181548
источник