go 学习文档

1、Go入门指南
2、Go语言学习(四)常用类型介绍

本地启动 GO文档:

$ godoc -http=:8080

然后浏览器访问:http://localhost:8080

一、构建并运行Go程序

$ go run main.go // 直接运行,不进行编译
$ go build  ./main/main.go  //  编译并安装自身包和依赖包
$ go install    //安装自身包和依赖包

二、格式化源代码

  • 1、gofmt –w program.go 会格式化该源文件的代码然后将格式化后的代码覆盖原始内容(如果不加参数 -w则只会打印格式化后的结果而不重写文件);

  • 2、gofmt -w *.go 会格式化并重写所有 Go 源文件;

  • 3、gofmt map1 会格式化并重写map1 目录及其子目录下的所有 Go 源文件。

  • 4、gofmt 也可以通过在参数 -r 后面加入用双引号括起来的替换规则实现代码的简单重构,规则的格式:<原始内容> -> <替换内容>。

实例:

gofmt -r '(a) -> a' –w *.go

上面的代码会将源文件中没有意义的括号去掉。

gofmt -r 'a[n:len(a)] -> a[n:]' –w *.go

上面的代码会将源文件中多余的 len(a) 去掉。( 译者注:了解切片(slice)之后就明白这为什么是多余的了 )

gofmt –r 'A.Func1(a,b) -> A.Func2(b,a)' –w *.go

上面的代码会将源文件中符合条件的函数的参数调换位置。

三、关键字

1、下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for

2、36 个预定义标识符:

int uint byte len uintptr close
int8 uint8 new cap real
int16 uint16 complex bool nil append imag
int32 float32 uint32 complex64 true string make panic print
int64 float64 uint64 complex128 false iota copy recover println

四、常量、变量

1、常量:使用关键字 const 定义,用于存储不会改变的数据

存储在常量中的数据类型只可以是布尔型数字型(整数型、浮点型和复数)字符串型

常量的定义格式:const identifier [type] = value

在 Go 语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"

2、iota

const (
    a = iota
    b
    c
)
#  a=0  b=1 c=2

iota 默认值是0,每当在新的一行使用时,值会自动加1;
简单地讲,每遇到一次 const 关键字,iota 就重置为 0

3、变量:使用 var 关键字

var identifier type

变量初始值:
int = 0;float = 0.0;bool = false;string = '';指针 = nil;


4、声明和赋值可同时进行

var identifier [type] = value
var a int = 15
var i = 5
var b bool = false
var str string = "Go says hello to the world!"

1)、go语言可以在编译期根据变量的值来自动判断类型。所以可以这样赋值:

var a = 15
var b = false
var str = "Go says hello to the world!"
或: 分解因式式声明:
var (
    a = 15
    b = false
    str = "Go says hello to the world!"
    numShips = 50
    city string
)

这种因式分解关键字的写法一般用于声明包级别的全局变量。

2)、对于特殊的类型还是需要手动指定

var n int64 = 2;
# 如果不显示指定,go不会指定 类型为:int64 

3)、变量类型可以在运行时自动判断

go 会在运行时,根据os.Getenv()返回值的类型动态指定 各变量的类型

var (
    HOME = os.Getenv("HOME")
    USER = os.Getenv("USER")
    GOROOT = os.Getenv("GOROOT")
)

5、值类型和引用类型

1)、值类型
  • 1、int、float、bool、string、数组、结构 都属于基本类型
  • 2、值类型的变量存在栈中。
整数:
int8(-128 -> 127)
int16(-32768 -> 32767)
int32(-2,147,483,648 -> 2,147,483,647)
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)

无符号整数:
uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)

浮点型(IEEE-754 标准):
float32(+- 1e-45 -> +- 3.4 * 1e38)-->  精确到小数点后 7 位
float64(+- 5 * 1e-324 -> 107 * 1e308)--> 精确到小数点后 15 位
2)、引用类型
  • 1、指针、slices、maps、channel 都属于引用类型
  • 2、引用类型的变量存在堆中,便于垃圾回收。

6、简短形式 := 赋值操作符

在代码块内部,可以使用:= 方式声明变量

func main() {
   a := "abc"
   fmt.Println("hello, world--", a)    # hello, world--abc
}

注意:
1、该方式不可声明全局变量
2、该方式声明的变量必须被使用
3、不可在该方式声明的变量之前使用该变量。

并行赋值

# _ 空白标识符,表示付给标识符的值不需要使用
_, a = 5, 6   # 这里 值 5 就被废弃了
# 并行赋值还可用于 承接:多返回值的函数
val, err = Func1(prams)  # 变量val, err 分别承接Func1 返回val和err。

7、init 函数

  • 每个源文件都只能包含一个 init 函数。
  • 每个包初始化完成以后自动执行。
  • 初始化总是以单线程执行,并且按照包的依赖关系顺序执行
  • 不可人为调用

8、格式化输出

const a string = "abc";
fmt.Printf("this is a value : %s", a)

输出格式符:

%s:输出一个字符串(string)
%c: 用于表示字符(char)
%U : 输出格式为 U+hhhh 的字符串
%t:输出一个bool;
%v:按照其自身类型输出;
%d: 格式化整数;
%x 、%X :输出16进制数字
%g:格式化浮点型
%f:输出浮点数
%e:输出科学计数表示法
%0d:用于规定输出定长的整数
%n.mg:用于n位的占位表示数字并精确到小数点后 m 位(四舍五入),除了使用 g(到整数) 之外,还可以使用 e(科学计数法) 或者 f(以普通浮点数方式展示),例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00
%p:格式化输出指针
%T:输出变量的原始类型

# 关于 %n.mg 中 n 的作用:
fmt.Printf("c")
fmt.Printf("%10.2e\n", 12.355678)
fmt.Printf("%10.2f\n", 12.345678)
fmt.Printf("%10.2g\n", 12.645678)
//c
//  1.24e+01
//     12.35
//        13

9、随机数

package main
import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    for i := 0; i < 10; i++ {
        a := rand.Int()
        fmt.Printf("%d / ", a)
    }
    for i := 0; i < 5; i++ {
        r := rand.Intn(8)
        fmt.Printf("%d / ", r)
    }
    fmt.Println()
    timens := int64(time.Now().Nanosecond())
    rand.Seed(timens)
    for i := 0; i < 10; i++ {
        fmt.Printf("%2.2f / ", 100*rand.Float32())
    }
}

函数 rand.Float32rand.Float64 返回介于 [0.0, 1.0)之间的伪随机数,其中包括 0.0 但不包括 1.0。
函数rand.Intn 返回介于[0, n) 之间的伪随机数。

你可以使用 Seed(value) 函数来提供伪随机数的生成种子,一般情况下都会使用当前时间的纳秒级数字

10、运算符优先级

优先级     运算符
 7      ^ !
 6      * / % << >> & &^
 5      + - | ^
 4      == != < <= >= >
 3      <-
 2      &&
 1      ||

11、类型别名

package main
import "fmt"

type TZ int

func main() {
    var a, b TZ = 3, 4
    c := a + b
    fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}

类型别名得到的新类型没有原始类型的附带方法,但可以自定义新方法。

12、字符串

  • 解释字符串:
该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\\:反斜杠自身
  • 非解释字符串:
该类字符串使用反引号括起来,支持换行,例如:

  `This is a raw string \n` 中的 \n 会被原样输出。

unicode包 包含了一些针对测试字符的非常有用的函数

  • 判断是否为字母:unicode.IsLetter(ch)
  • 判断是否为数字:unicode.IsDigit(ch)
  • 判断是否为空白符号:unicode.IsSpace(ch)

13、指针

  • 声明时:
    *:表示声明一个 指针变量

    声明一个指针变量:如果不赋值,默认为nil

  • 取值时:
    &:用于取 变量的内存地址;
    *:用于取指针所指的值(反引用)。
    它俩刚好是相反的。
// 声明一个指针变量:如果不赋值,默认为`nil`
var stringP *string  
fmt.Printf("stringP:%p\n", stringP)  // 0x0
// 取值时:
fmt.Printf("stringP's value is :%s\n", *stringP)  // 会报空指针错误
s := 'i'm a string'
stringP = &s  // 将 string s 的指针赋给 stringP,stringP此时拿着s的内存地址
var ss = *stringP  // 将 指针变量stringP 指向的值赋给 ss

你不能得到一个文字常量的地址,例如:

const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10

N、可见性原则及导入自定义包

标识符声明时用大写字母开头,则外包可见。
标识符声明时用小写字母开头,仅包内可见。

项目目录结构如下图:


目录结构

注意:go语言以为基本单元,实际也就是一个文件夹

如图:test01.goversion.go 实际上都属于pkg1 这个包。
代码如下:

# pkg1/test01.go

package pkg1
import (
    "fmt"
)
/**
注释: 函数首字母大写,表示它将被导出,即:它在其他包中可见。
*/
func Test01() {
    fmt.Printf("这是Test01.go")
}

# pkg1/version.go
import (
    "fmt"
    "runtime"
)
func Version() {
    fmt.Printf("%s\n", runtime.Version())
}

#hello_world.go
package main
import (
    "fmt"
    "./versions"  // 引入versions 这个包。
  // V "./versions"  // 也可以给versions这个包起一个别名:V
)
func main()  {
    fmt.Print("hello world\n")
    versions.Version() // 调用versions这个包中的versions.go中导出的方法。
    versions.Test01()   // 调用versions这个包中的test01.go中导出的方法。
//        V.Version()  // 使用包的别名来调用
//        V.Test01()  // 使用包的别名来调用
}

# bash
$ go build ./main/hello_world.go

五、控制结构

条件结构和分支结构:

  • if-else 结构
  • switch 结构
  • select 结构,用于 channel 的选择(第 14.4 节)

迭代或循环结构:

  • for (range) 结构
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index, value := range slice {
    myMap[index] = &value
}

for-rang 拿到的value 是值的拷贝!这个一定要注意

一些如 breakcontinue 这样的关键字可以用于中途改变循环的状态。

此外,你还可以使用 return 来结束某个函数的执行,或使用 goto标签来调整程序的执行位置。

1、if-else 结构

if-else结构,可以直接定义变量并赋初始值。
该变量只能在该 if-else结构中可见

if val := 10; val > max {
    // do something
} else {
  // do something
}

2、switch 结构

var num1 int = 100
#TYPE 1    (指定判断值)
  switch num1 {
    case 98, 99:
        fmt.Println("It's equal to 98")
    case 100: 
        fmt.Println("It's equal to 100")
    default:
        fmt.Println("It's not equal to 98 or 100")
    }
#TYPE 2 (不跟任何判断值)
  switch {
    case condition1:
        ...
    case condition2:
        ...
    default:
        ...
}

六、for 结构

1、基础for循环

条件初始化;条件判断;条件变更

for i := 0; i < 5; i++ {
    fmt.Printf("This is the %d iteration\n", i)
}

2、只有判断条件的for循环

for 结构中只有判断条件,条件初始化在for结构之外进行;条件变更在for结构内进行。

var i int = 5

for i >= 0 {
    i = i - 1
    fmt.Printf("The variable i is now: %d\n", i)
}

3、无限循环

没有任何判断条件,通过在for循环中进行条件判断并结合bradkreturn来结束循环。
注意:break只会终止当前循环;return会终止当前函数。

ii := 1
for {
    if ii++; ii < 10 {
        fmt.Println(ii)
    } else {
        return
    }
}

4、基于goto实现的循环

标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母

# 案例一、
i := 0
START:
    fmt.Printf("The counter is at %d\n", i)
    i++
    if i < 15 {
        goto START
    }
# 案例二、
LABEL1:
    for i := 0; i <= 5; i++ {
        for j := 0; j <= 5; j++ {
            if j == 4 {
                continue LABEL1
            }
            fmt.Printf("i is: %d, and j is: %d\n", i, j)
        }
    }

}

5、for-range循环

它可以迭代任何一个集合(包括数组和 map)

pos:循环的下标;rune:每个下标的值。
注意:rune对应的是每个索引的值的拷贝,所以修改rune时,原值是不会改变的;
但是:如果rune是一个指针的话,则原值是可以改变的(#这个不知道怎么验证#

str := "Go is a beautiful language!"
for pos, rune := range str {
    fmt.Printf("Character on position %d is: %c \n", pos, char)
}

七、函数

1、函数类型

Go 里面有三种类型的函数:

  • 普通的带有名字的函数
  • 匿名函数或者lambda函数
  • 方法

2、函数不允许重载

func foo (入参1 类型1, 入参2 类型2...)(返回值1 类型2,返回值2 类型2...) { ... }
如果各入参类型相同,则可只在最后一个入参后面加类型;返回值亦然。

func Test02(p1, p2 int) (r1 int, r2 string) {
    fmt.Printf("接收到了入参:%d和%d", p1, p2);
    return (p1 + p2), "Test02的第二个返回值,哈哈"   //  这里是返回值,返回了两个。
}

3、值传递和引用传递

Go 默认使用按值传递来传递参数,也就是传递参数的副本

  • 值传递:调用函数时,传递的实参是值的副本,所以函数内改变实参的值,对应的变量值不会改变。
  • 引用传递:调用函数时,传递的是值的引用(指针),改变指针值的时候,对应的变量值会发生改变。
# 值传递
fun(arg1)
# 引用传递
func Multiply(a, b int, reply *int) {
    *reply = a * b
}

func main() {
    n := 0
    reply := &n
    Multiply(10, 5, reply)  // 传递 变量 n 的指针。
    fmt.Println("Multiply:", *reply) // Multiply: 50
}

4、返回值类型

  • 未命名返回值

return 后面需要跟上需要返回的值

func getX2AndX3(input int) (int, int) {
    return 2 * input, 3 * input
}
  • 命名返回值(尽量使用该种返回值方式

一个空的 return即可 ----即使只有一个命名返回值,也需要使用 () 括起来

func getX2AndX3_2(input int) (x2 int, x3 int) {
    x2 = 2 * input
    x3 = 3 * input
    // return x2, x3
    return
}

5、空白符_

空白符用来匹配一些不需要的值,然后丢弃掉

package main

import "fmt"

func main() {
    var i1 int
    var f1 float32
    i1, _, f1 = ThreeValues()
    fmt.Printf("The int: %d, the float: %f \n", i1, f1)
}

func ThreeValues() (int, int, float32) {
    return 5, 6, 7.5
}

6、变长参数的函数

参数个数不确定,以:func fun(prarm ...int) {...} 的形式来定义。
传递 slice 类型的变量时可以这样传递:fun(slice...)
函数内部接收到的参数param的类型是一个 slice | 切片

package main

import "fmt"

func main() {
    x := min(1, 3, 2, 0)
    fmt.Printf("The minimum is: %d\n", x)
    slice := []int{7,9,3,5,1}
    x = min(slice...)
    fmt.Printf("The minimum in the slice is: %d", x)
}

func min(s ...int) int {
    if len(s)==0 {
        return 0
    }
    min := s[0]
    for _, v := range s {
        if v < min {
            min = v
        }
    }
    return min
}

7、defer 和追踪

defer 允许我们推迟函数 在 return之后主函数执行完毕之后 再执行。
有点类似于其他语言的finally
注意:
1、再循环中使用defer 的时候,遵循栈的后进先出规则。及最后定义的defer最先执行。
2、defer后面的函数值和参数会被求值但是实际函数调用却要等到最后

****** 一定要注意:变量的引用问题!!,如下例******

//  例1:
func func2() {
    i := 0
    defer ff(i)
    defer func() {
        fmt.Println("打印--3:", i)
    }()
    defer fmt.Println("打印--2:", i)
    i++
    fmt.Println("打印--1:", i)
}
func ff(i int) {
    fmt.Println("打印--4:", i)
}
func2() 
// 打印--1: 1
// 打印--2: 0
// 打印--3: 1
// 打印--4: 0


// 例2:
func defer06(funcName string) func(){
    start := time.Now()
    fmt.Printf("function %s enter\n",funcName)
    return func(){
            fmt.Printf("function %s exit (elapsed %s)\n",funcName,time.Since(start))
    }
}

func defer07(funcName string){
    defer defer06(funcName)()
    time.Sleep(2 * time.Second)
}
func main(){
    defer07("func01")
    defer07("func02")
}
输出:
// function func01 enter
// function func01 exit (elapsed 2.000297349s)
// function func02 enter
// function func02 exit (elapsed 2.001124425s)

上面例2中,我们可以看到,defer 会先将函数defer06(funcName)执行,
然后等待time.Sleep正常执行完成后再执行defer06的返回值一个匿名函数

详见:https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/06.4.md

8、内置函数

名 称 说明
close 用于管道通信
len、cap len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
new、make new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针(详见第 10.1 节)。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作(详见第 7.2.3/4 节、第 8.1.1 节和第 14.2.1 节)new() 是一个函数,不要忘记它的括号
copy、append 用于复制和连接切片
panic、recover 两者均用于错误处理机制
print、println 底层打印函数(详见第 4.2 节),在部署环境中建议使用 fmt 包
complex、real imag 用于创建和操作复数(详见第 4.5.2.2 节)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容