【问题标题】:Go examples and idioms [closed]Go示例和习语[关闭]
【发布时间】:2010-12-15 18:23:53
【问题描述】:

没有很多 Go 代码可以用来学习这门语言,而且我确信我不是唯一一个尝试使用它的人。因此,如果您发现了有关该语言的有趣内容,请在此处发布示例。

我也在找

  • 在 Go 中做事的惯用方式,
  • C/C++ 的思维方式“移植”到 Go,
  • 关于语法的常见缺陷,
  • 任何有趣的东西,真的。

【问题讨论】:

  • ARM 支持,例如 8 位或 16 位。 D语言还是不行。
  • 库 (golang.org/pkg) 是学习如何使用 go 的绝佳资源。就个人而言,我发现学习数据结构的实现方式有助于学习语言。

标签: go


【解决方案1】:

延迟语句

“defer”语句调用一个函数,该函数的执行被推迟到周围函数返回的那一刻。

DeferStmt = "defer" 表达式。

表达式必须是函数或方法调用。每次执行“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 现在也是以exception-like 方式处理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)
}

【讨论】:

  • 看起来不错的旧 RAII(明确表示)。
  • +1 因为我读了很多关于 Go 的文章,但我仍然没有看到这个(直到你给我看)!
  • 聪明,虽然如果 defer 语句按 FIFO 顺序执行(从上到下)对我来说更有意义,但也许这只是我......
  • 酷。让我想起了来自 D digitalmars.com/d/2.0/exception-safe.html 的农作物守卫
  • @Mike:如果您与“try:..finally:”的块进行比较,LIFO 嵌套的方式相同。对于资源打开/关闭对等,像这样的嵌套是唯一有意义的事情(首先打开将最后关闭)。
【解决方案2】:

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>

【讨论】:

  • 这更像是一个隐藏的功能而不是一个惯用的例子
【解决方案3】:

我看到一些人抱怨 for 循环,大意是“在这个时代,为什么我们必须说 i = 0; i &lt; len; i++?”。

我不同意,我喜欢 for 构造。如果您愿意,可以使用长版本,但惯用的 Go 是

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

for .. range 构造循环遍历所有元素并提供两个值 - 索引 i 和值 v

range 也适用于地图和频道。

不过,如果您不喜欢任何形式的for,您可以在几行中定义eachmap 等:

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

我开始非常喜欢 Go :)

【讨论】:

  • 虽然range 很好,但如果它被编译成与 for-3 循环相同的代码。
【解决方案4】:

去获取你的 stackoverflow 声誉

这是this answer的翻译。

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"))
}

感谢Scott Wales for help with .Read ().

这看起来仍然相当笨重,有两个字符串和两个缓冲区,所以如果有任何 Go 专家有建议,请告诉我。

【讨论】:

  • 我不确定格式应该有什么问题;我已经恢复了。
  • Go 作者推荐gofmt你的代码:-)
  • 我无法编译它:$ ../go/src/cmd/6g/6g SO.go SO.go:34: undefined: json.StringToJson
  • @Raphink:自从我做这个之后,语言发生了变化。
  • 是的,你知道什么是最接近 StringToJson 的吗?以前是在内部设置构建器,现在必须自己提供预定义的原生结构?
【解决方案5】:

这是来自Kinopiko's post的iota的一个很好的例子:

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 (!)

【讨论】:

  • 请注意,分号是不必要的。
【解决方案6】:

您可以通过并行赋值交换变量:

x, y = y, x

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

简单但有效。

【讨论】:

    【解决方案7】:

    这是来自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
    

    目前,switch 版本对我来说看起来更干净一些。

    【讨论】:

    • 哇,完全从 VB 中删除。 ;-) (Switch True …)
    • @Konrad,打败我! :) 我以前在 VB6 代码中使用过这个习惯用法,它在某些情况下肯定有助于提高可读性。
    • 什么是'
    • @Raphink:小于或等于。
    【解决方案8】:

    Type switches:

    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");
    }
    

    【讨论】:

      【解决方案9】:

      在导入包的时候,你可以将名字重新定义为你想要的任何东西:

      package main
      
      import f "fmt"
      
      func main() {
          f.Printf("Hello World\n")
      }
      

      【讨论】:

      【解决方案10】:

      命名结果参数

      a 的返回或结果“参数” 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;
      }
      

      【讨论】:

      • 我很好奇——还有其他语言有这个吗?
      • Matlab 也有类似的东西。
      • pascal 使用类似的语法返回一个值。
      • @nes1983 对于那些不知道的人,在 Pascal 中,您通常会将返回值分配给函数名称。
      • FORTRAN 差不多有这个。
      【解决方案11】:

      来自James Antill's answer

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

      还有一个潜在的陷阱:接收和发送运算符之间的subtle difference

      a <- ch // sends ch to channel a
      <-ch    // reads from channel ch
      

      【讨论】:

      【解决方案12】:
      /* 
       * 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))
      }
      

      【讨论】:

      • 我建议删除问题解决站点的名称以及 ID 号。也许改写这个问题。为了不将问题破坏给绊倒它的人。或者试图通过在网上搜索问题来作弊。
      • 备案:这是来自algorithmist.com/index.php/Coin_Change的算法 这是“硬币找零”的第一个谷歌结果。
      【解决方案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 类型重新定义为更具体的 int 类型。而Println() 在幕后调用String()

      【讨论】:

        【解决方案14】:

        返回频道

        这是一个非常重要的真正成语:如何将数据输入通道并在之后关闭它。有了它,您可以制作简单的迭代器(因为 range 将接受一个通道)或过滤器。

        // 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;
        }
        

        【讨论】:

        • +1。此外,您还可以通过频道传递频道。
        • 但请注意不要跳出 for x := range chan {} 循环,否则会泄漏 goroutine 及其引用的所有内存。
        • @JeffAllen defer close(outch); 作为 goroutine 的第一条语句怎么样?
        • Defer 在函数返回时将一条语句排队等待执行,无论采用哪个返回点。但是如果通道输入永远不会关闭,那么本例中的匿名函数将永远不会离开 for 循环。
        【解决方案15】:

        通道读取超时:

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

        Davies Liu窃取。

        【讨论】:

          【解决方案16】:
          for {
              v := <-ch
              if closed(ch) {
                  break
              }
              fmt.Println(v)
          }
          

          由于 range 自动检查关闭的通道,我们可以缩短为:

          for v := range ch {
              fmt.Println(v)
          }
          

          【讨论】:

            【解决方案17】:

            在 $GOROOT/src 中有一个可以使用的 make 系统设置

            设置你的makefile
            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 来使用自动化测试工具,或者使用 make install 将包和共享对象从 cgo 添加到您的 $GOROOT。

            【讨论】:

              【解决方案18】:

              Go 中另一个有趣的事情是godoc。您可以使用

              在您的计算机上将其作为网络服务器运行
              godoc -http=:8080
              

              其中 8080 是端口号,然后可以通过 localhost:8080 访问 golang.org 上的整个网站。

              【讨论】:

              • 这是常规程序还是守护进程?
              • 这是一个常规程序。
              【解决方案19】:

              这是一个堆栈的实现。它说明了向类型添加方法。

              我想将堆栈的一部分变成一个切片并使用切片的属性,但是尽管我可以在没有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()
              }
              

              【讨论】:

              • 不使用fmt.Printf(...); os.Exit();,可以使用panic(...)
              • 这给出了我不想要的堆栈跟踪。
              • 为什么会受到限制? Go 是一种托管的、经过 gc 处理的语言。您的堆栈可以任意深。使用新的 append() 内置函数,它会在需要时执行类似于 C 的 realloc 的操作。
              • “Go 不需要泛型”,他们说。
              【解决方案20】:

              从 go 调用 c 代码

              使用 c 运行时可以访问较低级别的 go。

              C函数的形式

              void package·function(...)
              

              (注意点分隔符是一个 unicode 字符)其中参数可能是基本的 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 以了解运行时中使用的示例。

              另请参阅this answer 以将 c++ 代码与 go 链接。

              【讨论】:

              • 真的使用“飞点”吗?我不敢编辑,但这似乎有点出人意料和激进。
              • 是的,你需要用6c(或8c等)编译。我不认为 gcc 处理 unicode 标识符。
              • 我认为 AltGr+period 类型相同·但我不确定使用 unicode。在我阅读的源代码中看到这一点感到非常惊讶。为什么不使用 :: 之类的东西?
              • 字符是中点 U+00B7。解析器可能已被伪造,因此它将其视为一个字符,以便生成有效的 c 标识符,我认为这会排除 ::。
              • “·”只是一个临时的 hack,rob 甚至对它仍然存在感到惊讶,他说它将被一些不那么特殊的东西所取代。
              【解决方案21】:

              这是一个使用 sqlite3 包的 go 示例。

              http://github.com/bikal/gosqlite-example

              【讨论】:

                【解决方案22】:

                你看this talk了吗?它展示了你可以做的很多很酷的事情(谈话结束)

                【讨论】:

                • 是的,我做到了。它归结为“那里还有很多,让我们继续下一个主题。”
                • 是的,显然有很多话要说,时间不多
                【解决方案23】:

                基于其他答案的堆栈,但使用切片附加没有大小限制。

                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()
                }
                

                【讨论】:

                  【解决方案24】:
                  const ever = true
                  
                  for ever {
                      // infinite loop
                  }
                  

                  【讨论】:

                  • 咳咳。 for { /* infinite loop */ } 就够了。
                  • 当然。这正是这里发生的事情。我只是喜欢forever 关键字。甚至 Qt 也有一个宏。
                  • 但 Go 不需要宏或可爱的 true 别名来执行此操作。
                  • @kaizer.se:Jurily 的观点是,for ever(在声明变量之后)是一件很可爱的事情,如果你愿意,你可以在 Go 中做。它看起来像英语(以空格为模)。
                  • 你也可以在 C 中做一些可爱的事情.. :-) #define ever (;;)
                  【解决方案25】:

                  主目录test里面有很多小程序。例子:

                  • peano.go 打印阶乘。
                  • hilbert.go 有一些矩阵乘法。
                  • iota.go 有一些奇怪的例子。

                  【讨论】:

                    猜你喜欢
                    • 2010-10-17
                    • 2015-08-02
                    • 2012-03-04
                    • 2018-02-18
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-01-31
                    • 2012-09-19
                    • 2011-09-08
                    相关资源
                    最近更新 更多