Go编程tips-2

未使用的imports

可以注释掉或者加下划线'_',否则,编译报错

package main

import (  
    - "fmt"
    "log"
    "time"
)

var _ = log.Println

func main() {  
    _ = time.Now
}

使用简式声明重复声明变量

你不能在一个单独的声明中重复声明一个变量,但在多变量声明中这是允许的,其中至少要有一个新的声明变量。

重复变量需要在相同的代码块内,否则你将得到一个隐藏变量。

Fails:

package main

func main() {  
    one := 0
    one := 1 //error
}

Compile Error:

/tmp/sandbox706333626/main.go:5: no new variables on left side of :=

Works:

package main

func main() {  
    one := 0
    one, two := 1,2

    one,two = two,one
}

用 nil 初始化slice和map

在一个“nil”的slice中添加元素是没问题的,但对一个map做同样的事将会生成一个运行时的panic。

Works:

package main

func main() {  
    var s []int
    s = append(s,1)
}

Fails:

package main

func main() {  
    var m map[string]int
    m["one"] = 1 //error

}

map 的容量

你可以在map创建时指定它的容量,但你无法在map上使用cap()函数。
package main

func main() {  
    m := make(map[string]int,99)
    cap(m) //error
}

Compile Error:

/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap

字符串不能初始化为 "nil"

这对于经常使用“nil”分配字符串变量的开发者而言是个需要注意的地方。

二维数组的创建

你可以使用纯一维数组、“独立”切片的切片,“共享数据”切片的切片来构建动态的多维数组。

如果你使用纯一维的数组,你需要处理索引、边界检查、当数组需要变大时的内存重新分配。

使用“独立”slice来创建一个动态的多维数组需要两步。首先,你需要创建一个外部的slice。然后,你需要分配每个内部的slice。内部的slice相互之间独立。你可以增加减少它们,而不会影响其他内部的slice。

package main

func main() {  
    x := 2
    y := 4

    table := make([][]int,x)
    for i:= range table {
        table[i] = make([]int,y)
    }
}

使用“共享数据”slice的slice来创建一个动态的多维数组需要三步。首先,你需要创建一个用于存放原始数据的数据“容器”。然后,你再创建外部的slice。最后,通过重新切片原始数据slice来初始化各个内部的slice。

package main

import "fmt"

func main() {  
    h, w := 2, 4

    raw := make([]int,h*w)
    for i := range raw {
        raw[i] = i
    }
    fmt.Println(raw,&raw[4])
    //prints: [0 1 2 3 4 5 6 7] <ptr_addr_x>

    table := make([][]int,h)
    for i:= range table {
        table[i] = raw[i*w:i*w + w]
    }

    fmt.Println(table,&table[1][0])
    //prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
}

判断map中节点是否存在

检测给定map中的记录是否存在的最可信的方法是,通过map的访问操作,检查第二个返回的值。

package main

import "fmt"

func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}

    if _,ok := x["two"]; !ok {
        fmt.Println("no entry")
    }
}

string的修改

如果是ASCII码,要转换成[]byte,如果是unicode,要转化成rune byte
[]byte的方法

package main

import "fmt"

func main() {  
    x := "text"
    xbytes := []byte(x)
    xbytes[0] = 'T'

    fmt.Println(string(xbytes)) //prints Text
}

string 与 rune byte 的关系

在Go当中 string底层是用byte数组存的,并且是不可以改变的。

例如 s:="Go编程" fmt.Println(len(s)) 输出结果应该是8因为中文字符是用3个字节存的。

len(string(rune('编')))的结果是3

如果想要获得我们想要的情况的话,需要先转换为rune切片再使用内置的len函数

fmt.Println(len([]rune(s)))

结果就是4了。

所以用string存储unicode的话,如果有中文,按下标是访问不到的,因为你只能得到一个byte。 要想访问中文的话,还是要用rune切片,这样就能按下表访问。

判断字符串是否为UTF8

为了知道字符串是否是UTF8,你可以使用“unicode/utf8”包中的ValidString()函数。

求字符串长度

ASCII:len();
unicode:RuneCountInString()。实际上,RuneCountInString()求的是rune的个数,不是绝对的字符个数,因为一个字符可能占多个rune

在多行的Slice、Array和Map语句中遗漏逗号

Fails:

package main

func main() {  
    x := []int{
    1,
    2 //error
    }
    _ = x
}

Works:

package main

func main() {  
    x := []int{
    1,
    2,
    }
    x = x

    y := []int{3,4,} //no error
    y = y
}

当你把声明折叠到单行时,如果你没加末尾的逗号,你将不会得到编译错误

log.Fatal和log.Panic不仅仅是Log

Logging库一般提供不同的log等级。与这些logging库不同,Go中log包在你调用它的Fatal()和Panic()函数时,可以做的不仅仅是log。当你的应用调用这些函数时,Go也将会终止应用 🙂

Go面向对象

本来想写下相关的内容,找材料时,发现了这篇不错的内容,强烈推荐,关于面向对象的基本用法都讲到了。

原文:Go面向对象
原英文:Go Object Oriented Design

正文:

Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。

Go没有使用classes,但提供很多相似的功能:

·通过嵌入实现的自动消息委托

·通过接口实现多台

·通过exports实现的命名空间

Go语言中没有继承。忘记is-a的关系,而是就组合而言的面向对象设计。

“使用经典的继承始终是可选的;每个问题都可以通过其他方法得到解决” - Sandi Metz

通过例子说明组合

最近阅读了一篇Ruby的面向对象编程实践, 我决定使用Go语言翻译这个例子。

Chapter 6说明了一个简单的问题。维修工需要知道自行车出行需要带上的备件,决定于哪一辆自行车已经被租出去。问题可以通过经典的继承来解决,山地车和公路自行车是自行车基类的一个特殊化例子。Chapter 8使用组合改写了同一个例子。我很高兴这个例子翻译成Go。让我们看看。

Packages(包)

  1. package main
  2. import "fmt"

包提供了命名空间概念. main() 函数是这个包的入口函数. fmt包提供格式化功能

Types(类型)

  1. type Part struct {
  2.     Name        string
  3.     Description string
  4.     NeedsSpare  bool
  5. }

我们定义了一个新的类型名为Part, 非常像c的结构体

  1. type Parts []Part

Parts类型是包含Part类型的数组切片, Slice可以理解为动态增长的数组, 在Go中是很常见的.

我们可以在任何类型上声明方法,  所以我们不需要要再去封装 []Part, 这意味着 Parts 会拥有slice的所有行为, 再加上我们自己定义的行为方法.

方法

  1. func (parts Parts) Spares() (spares Parts) {
  2.     for _, part := range parts {
  3.         if part.NeedsSpare {
  4.             spares = append(spares, part)
  5.         }
  6.     }
  7.     return spares
  8. }

Go中定义方法就像一个函数,除了它有一个显式的接收者,紧接着func之后定义。这个函数利用命名返回变量,并为我们初始化备件。

方法的主体十分简单。我们重复parts,忽略索引的位置(_),过滤parts后返回。append builtin 需要分配和返回一个大的切片,因为我们并没有预先分配好它的容量。

这段代码没有ruby代码来得优雅。在Go语言中有过滤函数,但它并非是builtin.

内嵌

  1. type Bicycle struct {
  2.     Size string
  3.     Parts
  4. }

自行车由Size和Parts组成。没有给Parts指定一个名称,我们是要保证实现 内嵌。这样可以提供自动的委托,不需特殊的声明,例如bike.Spares()和bike.Parts.Spares()是等同的。

如果我们向Bicycle增加一个Spares()方法,它会得到优先权,但是我们仍然引用嵌入的Parts.Spares()。这跟继承十分相似,但是内嵌并不提供多态。Parts的方法的接收者通常是Parts类型,甚至是通过Bicycle委托的。

与继承一起使用的模式,就像模板方法模式,并不适合于内嵌。就组合和委托而言去考虑会更好,就如我们这个例子一样。

Composite Literals(复合语义)

  1. var (
  2.     RoadBikeParts = Parts{
  3.         {"chain""10-speed"true},
  4.         {"tire_size""23"true},
  5.         {"tape_color""red"true},
  6.     }
  7.     MountainBikeParts = Parts{
  8.         {"chain""10-speed"true},
  9.         {"tire_size""2.1"true},
  10.         {"front_shock""Manitou"false},
  11.         {"rear_shock""Fox"true},
  12.     }
  13.     RecumbentBikeParts = Parts{
  14.         {"chain""9-speed"true},
  15.         {"tire_size""28"true},
  16.         {"flag""tall and orange"true},
  17.     }
  18. )

Go提供优美的语法,来初始化对象,叫做 composite literals。使用像数组初始化一样的语法,来初始化一个结构,使得我们不再需要ruby例子中的Parts工厂。

  1. func main() {
  2.     roadBike := Bicycle{Size: "L", Parts: RoadBikeParts}
  3.     mountainBike := Bicycle{Size: "L", Parts: MountainBikeParts}
  4.     recumbentBike := Bicycle{Size: "L", Parts: RecumbentBikeParts}

Composite literals(复合语义)同样可以用于字段:值的语法,所有的字段都是可选的。

简短的定义操作符(:=)通过Bicycle类型,使用类型推论来初始化roadBike,和其他。

输出

  1. fmt.Println(roadBike.Spares())
  2. fmt.Println(mountainBike.Spares())
  3. fmt.Println(recumbentBike.Spares())

我们将以默认格式打印 Spares 的调用结果:

  1. [{chain 10-speed true} {tire_size 23 true} {tape_color red true}]
  2. [{chain 10-speed true} {tire_size 2.1 true} {rear_shock Fox true}]
  3. [{chain 9-speed true} {tire_size 28 true} {flag tall and orange true}]

组合 Parts

  1.     comboParts :Parts{}
  2.     comboParts = append(comboParts, mountainBike.Parts...)
  3.     comboParts = append(comboParts, roadBike.Parts...)
  4.     comboParts = append(comboParts, recumbentBike.Parts...)
  5.     fmt.Println(len(comboParts), comboParts[9:])
  6.     fmt.Println(comboParts.Spares())
  7. }

Parts 的行为类似于 slice。按照长度获取切片,或者将数个切片结合。Ruby 中的类似解决方案就数组的子类,但是当两个 Parts 连接在一起时,Ruby 将会“错置” spares 方法。

“……在一个完美的面向对象的语言,这种解决方案是完全正确的。不幸的是,Ruby语言并没有完美的实现……”
—— Sandi Metz

在 Ruby 中有一个那看的解决方法,使用 Enumerable、forwardable,以及 def_delegators。 Go有没有这样的缺陷。 []Part 正是我们所需要的,且更为简洁(更新:Ruby 的 SimpleDelegator 看上去好了一点)。

接口 Interfaces

Go的多态性由接口提供。不像JAVA和C#,它们是隐含实现的,所以接口可以为不属于我们的代码定义。

和动态类型比较,接口是在它们声明过程中静态检查和说明的,而不是通过写一系列响应(respond_to)测试完成的。

“不可能不知不觉的或者偶然的创建一个抽象;在静态类型语言中定义的接口总是有倾向性的。” - Sandi Metz

给个简单的例子,假设我们不需要打印Part的NeedsSpare标记。我们可以写这样的字符串方法:

  1. func (part Part) String() string {
  2.     return fmt.Sprintf("%s: %s", part.Name, part.Description)
  3. }

然后对上述Print的调用将会输出这样的替代结果:

  1. [chain: 10-speed tire_size: 23 tape_color: red]
  2. [chain: 10-speed tire_size: 2.1 rear_shock: Fox]
  3. [chain: 9-speed tire_size: 28 flag: tall and orange]

这个机理是因为我们实现了fmt包会用到的Stringer接口。它是这么定义的:

  1. type Stringer interface {
  2.     String() string
  3. }

接口类型在同一个地方可以用作其它类型。变量与参数可以携带一个Stringer,可以是任何实现String() string方法签名的接口。

Exports 导出

Go 使用包来管理命名空间, 要使某个符号对其他包(package )可见(即可以访问),需要将该符号定义为以大写字母开头,  当然,如果以小写字母开关,那就是私有的.包外不可见.

  1. type Part struct {
  2.     name        string
  3.     description string
  4.     needsSpare  bool
  5. }

为了对Part类型应用统一的访问原则(uniform access principle), 我们可以改变Part类型的定义并提供setter/getter 方法,就像这样:

  1. func (part Part) Name() string {
  2.     return part.name
  3. }
  4. func (part *Part) SetName(name string) {
  5.     part.name = name
  6. }

这样可以很容易的确定哪些是public API, 哪些是私有的属性和方法, 只要通过字母的大小写.(例如(part.Name()vs.part.name)

注意 我们不必要对 getters 加前Get, (例如.GetName),Getter不是必需,特别是对于字符串,当我们有需要时,我们可以使用满足Stringer 类型接口的自定义的类型去改变Name 字段。

找到一些私有性

私有命名(小写字母)可以从同一个包的任何地方访问到,即使是包含了跨越多个文件的多个结构。如果你觉得这令人不安,包也可以像你希望的那么小。

可能的情况下用(更稳固的)公共API是一个好的实践,即使是来自经典语言的同样的类中。这需要一些约定,当然这些约定可以应用在GO中。

最大的好处

组合,内嵌和接口提供了Go语言中面向对象设计的强大工具。继承概念的思想真的不起什么作用。相信我,我尝试了

习惯Go需要思维的改变,当触及到Go对象模型的力量时,我非常高兴的吃惊于Go代码的简单和简洁。

Go编程tips-1

结构体的初始化

示例代码中,前者是创建普通对象,后者是创建指针

package main

import (
    "fmt"
)

type Rect struct {
    x, y          float64
    width, height float64
}

func main() {
    // common object
    rect2 := Rect{}
    rect3 := Rect{0, 0, 100, 200}
    rect4 := Rect{width: 100, height: 200}
    fmt.Println(rect2, rect3, rect4)

    /*
        // pointer objects
        rect1 := new(Rect)
        rect2 := &Rect{}
        rect3 := &Rect{0, 0, 100, 200}
        rect4 := &Rect{width: 100, height: 200}
        fmt.Println(rect1, rect2, rect3, rect4)
    */
}

变参

go支持变参,变参中所有参数的类别必须是同一种,且必须是最后一个形参。使用方法如下:“…type”表示具有不定个type类型的参数,不定参数实质上是一个slice类型,故可以使用range对其参数进行取值。如下例子。

func myfunc(args ...int) {
    for _, arg := range args {
        fmt.Println(arg)
    }
}

defer 与return执行顺序

当函数执行到最后时,先执行defer语句,然后才执行return语句.所以可以利用这个来进行资源安全关闭,解加锁,记录执行情况等。defer是采用先进后出的模式的,这种情形与栈的情况一致。注意:定义的defer延迟操作,如有提供参数会发生值的拷贝,尽管这些函数在退出时才执行,但所使用的参数是在定义时就进行拷贝,拷贝的原则和变量的赋值原则一个,slice,map,channel是指针拷贝.如下例子:

package main

import (
    "fmt"
)

func main() {
    x := 1
    defer func(a int) { //直接将x=1赋值给a,虽然他在后面才执行.
        fmt.Println("a=", a)
    }(x)
    defer func() {
        fmt.Println("x=", x) //经过x++后,x值变为2
    }()
    x++
}

运行结果:
x= 2
a= 1

临时变量的作用域

go语言中对于堆和栈的内存分配没有严格区分,在go中返回一个局部变量的地址是绝对没有问题的,变量关联的存储在函数返回后依然存在.(注:尤其对由C/C++转过来的程序员,开始肯定不是很适应,但是go这种内存分配方式解放了程序员,使得程序员能够专注做事情,而不用花费太多的时间在堆和栈的内存分配上).更直接的说,在go语言中,如果一个局部变量在函数返回后仍然被使用,那么这个变量会在堆heap,而不是栈stack中进行内存分配.详情参考How do I know whether a variable is allocated on the heap or the stack?

Mac下零基础学习go语言-2-开发环境的搭建

环境介绍

  • 系统:OS X EI Capitan 10.11.1
  • go version:1.5.1

安装

下载最新的安装包 https://golang.org/dl/

mac下安装

直接点击,按照引导就可以完成安装。

linux下安装

linux下实际就是手动设置文件路径了,大概的动作如下:

#!/bin/bash
tar -C /usr/local -xzf go*.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile

最后重启,或者:

source /etc/profile

验证版本

终端验证,输入

go version
go version go1.11.2 linux/amd64

配置环境变量

设置环境变量,go默认安装在/usr/local/go 下面,参见:Mac OS X 配置环境变量

所以我们修改 .bash_profile:

export GOPATH=/Users/alex/dev/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

注意:

  • 默认情况下GOROOT已经在安装时指定为安装目录了,是不需要设置的
  • GOPATH 设置成你的本地开发路径,而不是安装路径

设置完成后可以用 go env 命令检测

测试

在 GOPATH下 的src目录下创建项目demo。注意这个将是默认的bin档名。 然后在demo目录下创建main.go

package main

import (
  "fmt"
)

func main() {
  fmt.Println("hello world");
}

然后运行 go build demo,如果在demo项目目录下,直接 go build,完成后,在项目目录下生成 demo

直接运行 ./demo,就会输出 :hello world

DEBUG

初学者建议在LiteIDE下面debug,因为IDE直接UI支持,熟悉了以后也可以在命令行下面直接debug。第一次试用时要解决GDB的证书签名问题,可以参看网上同学给出的操作流程。

下面终于可以开始coding了!

Mac下零基础学习go语言-1-开篇

缘由

一直以来都在慢慢悠悠地关注各种go的相关主题,因为本职还是做c/c++相关的工作,所以一致以来都没有系统的对go进行系统的学习,仅局限于对相关的知识点进行进行简单的了解和学习。最近由于工作变动原因,稍息赋闲,打算对go做一个系统的学习,所以开此主题,希望能详细记录下学习的细节,能对后来的新人有个引路的作用

为什么学习go

这个应该是最先要回答的问题,也是群里经常争论的问题,为什么要选go,而不是php,java。
首先说下go的几个特点

  • 静态语言
  • 有高级语法,又不是非常复杂晦涩
  • 高并发,非常小的协程开销,非常适合服务器场景
  • 其他的网友自行google,这里不详细展开

我自己的观点是,如果你是c,c++的背景,要提高后台开发的效率,go是不二的选择,go的语法复杂度,介于c和c++之间,功能上长于后端开发,也有完备的库,开发效率上会有很大提高。
如果你原来就是php,java,node的背景,你所做的工作都是基于接口的数据开发,也建议尝试go。go天生适合做后台接口,开发效率高,性能也不错。
但如果你想要做一个web系统,其实go就目前的情况看,并不比php,java,node更合适,不必盲目跟风。
最后,建议初学者读一下这篇
Donovan/Kernighan大神们关于go语言的问答
其中关于go的特点,潜力,为什要选go等都有涉及。
其他的问题参见go的FAQ

环境

mac air
go version go1.5.1 darwin/amd64

主要学习材料

  1. google go官网教程
  2. 谢大的书籍:
    go web编程
    go实践开发
  3. 无闻的<<go编程基础>>视频教程
  4. 雨痕的笔记
  5. 其他:go学习资料链接汇总

最后,由于本人在go方面也是小白,难免水平有限,如果有大牛路过,一定不要怜惜您的键盘,狠狠的拍吧

go基础笔记

本文不讲解详细的语法,详细的知识请参见 tour.go-zh.org,只是纪录本人学习中觉得需要注意或者有意思的点

  • switch 可以不带条件,用来简化冗长的 if-else if-else
  • 结构体初始化参数可变,缺省有零值。支持 Name:特定字段赋值
  • slice 区间表示时左闭右开,可以省略上标或下标
  • _ 用来在 slice 值对中忽略序号和值
  • make 用来创建slice map channel ,其他的用new
  • map 可以同时获取值和行为结果(true/falses)
  • 值可以隐式表示函数
  • 闭包的概念

MAC下go code安装问题

在安装go lang时,已经设置过GOPATH,go 也运行正常,但是安装 gocode时提示:

Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
package github.com/nsf/gocode: exit status 69

当我们使用sudo 时,提示:

package github.com/nsf/gocode: cannot download, $GOPATH not set. For more details see: go help gopath

虽然之前已经设置,但是当切换权限时,也切换了环境变量,所以当用sudo管理员权限时提示没有设置环境变量。

解决:

sudo env GOPATH=/Users/alex/dev/go 
go get -u github.com/nsf/gocode

在命令行里临时指定env变量,完成