go详解

主要特点

  • 自动垃圾回收
  • 丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 发射
  • 语言交互性

环境安装

windows

安装包地址:https://golang.org/dl/。或https://golang.google.cn/dl/。

windows上安装完后可以直接在cmd中运行go

Linux

sudo apt install golang-go
源码安装

用apt安装的版本比较低,只能手动安装

安装包下载地址为:https://golang.org/dl/。
如果打不开可以使用这个地址:https://golang.google.cn/dl/。

tar -C /usr/local -xzf go1.13.3.linux-amd64.tar.gz

vim ~/.bashrc
export PATH=$PATH:/usr/local/go/bin
source  ~/.bashrc

语言结构

  • 包声明

  • 引入包

  • 函数

  • 变量

  • 语句&表达式

  • 注释

      package main
    
      import "fmt"
    
      func main() {
         /* 这是我的第一个简单的程序 */
         fmt.Println("Hello, World!")
      }
    

注:

  • 需要注意的是 { 不能单独放在一行
  • 在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

数据类型

布尔型

布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

数字类型

uint8 uint16 uint32 uint64 int8 int16 int32 int64 float32 float32 complex64(32 位实数和虚数) complex128(64 位实数和虚数)

byte 类似 uint8

rune 类似 int32
uint 32 或 64 位
int 与 uint 一样大小
uintptr 无符号整型,用于存放一个指针

派生类型

​ (a) 指针类型(Pointer)
​ (b) 数组类型
​ (c) 结构化类型(struct)
​ (d) Channel 类型
​ (e) 函数类型
​ (f) 切片类型
​ (g) 接口类型(interface)
​ (h) Map 类型

变量

声明变量的一般形式是使用 var 关键字: var identifier type

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数

第一种,指定变量类型

​ var a *int
​ var a []int
​ var a map[string] int
​ var a chan int
​ var a func(string) int
​ var a error // error 是接口

第二种,根据值自行判定变量类型。

​ var v_name = value

省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误

格式: v_name := value

var intVal int 
intVal :=1 // 这时候会产生编译错误
intVal,intVal1 := 1,2 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句

多变量声明

​ //类型相同多个变量, 非全局变量
​ var vname1, vname2, vname3 type
​ vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误

实例

// 第一种写法
var num1 int = 1
var num2 int = 2
var num3 int = 3
或者
var num1 = 1
var num2 = 2
var num3 = 3

// 第二种写法
var num1,num2,num3 int = 1,2,3
或者
var num1, num2, num3 = 1,2,3

// 第三种写法
var (
    num1 int = 1
    num2 int = 2
    num3 int = 3
)或
var (
    num1 = 1
    num2 = 2
    num3 = 3  
)

常量

格式:const identifier [type] = value

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

运算符

      • / % ++ -- == != < > >= <=

&& || ! & | << >> = += -= *= /= %= <<= >>= &= = |=

条件语句

if

​ if 布尔表达式 {
​ /* 在布尔表达式为 true 时执行 /
​ } else {
​ /
在布尔表达式为 false 时执行 */
​ }

switch

​ switch var1 {
​ case val1:
​ ...
​ case val2:
​ ...
​ default:
​ ...
​ }

select

​ select {
​ case communication clause :
​ statement(s);
​ case communication clause :
​ statement(s);
​ /* 你可以定义任意数量的 case /
​ default : /
可选 */
​ statement(s);
​ }

  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。 否则:如果有 default 子句,则执行该语句。如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值

循环语句

for

​ for init; condition; post { }
​ for condition { }
​ for { } //死循环

  • init: 一般为赋值表达式,给控制变量赋初值;

  • condition: 关系表达式或逻辑表达式,循环控制条件;

  • post: 一般为赋值表达式,给控制变量增量或减量。

      func main() {
         var b int = 15
         var a int
         numbers := [6]int{1, 2, 3, 5}
    
         /* for 循环 */
         for a := 0; a < 10; a++ {
            fmt.Printf("a 的值为: %d\n", a)
         }
    
         for a < b {
            a++
            fmt.Printf("a 的值为: %d\n", a)
         }
    
         for i,x:= range numbers {
            fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
         }  
      }
    

权限与大小写

golang中根据首字母的大小写来确定可以访问的权限。无论是方法名、常量、变量名还是结构体的名称,如果首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用。

方法

func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {      函数体  } 
  • 指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self。由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的
  • 非指针接收器时,Go 语言会在代码运行时将接收器的值复制一份。在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。

函数

格式:

func function_name( [parameter list] ) [return_types] {
   函数体
}
  • func:函数由 func 开始声明

  • function_name:函数名称,函数名和参数列表一起构成了函数签名

  • 参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。

  • return_types:返回类型,有些功能不需要返回值,这种情况下 return_types 不是必须的。

      func swap(x, y string) (string, string) {
         return y, x
      }
    
      func main() {
         a, b := swap("Mahesh", "Kumar")
         fmt.Println(a, b)
      }
    
      /* 函数返回两个数的最大值 */
      func max(num1, num2 int) int {
         /* 定义局部变量 */
         var result int
    
         if (num1 > num2) {
            result = num1
         } else {
            result = num2
         }
         return result
      }
    

字符串双引号和反引号

在Go语言中,字符串字面量使用双引号 "" 或者反引号 ' 来创建。双引号用来创建可解析的字符串,支持转义,但不能用来引用多行;反引号用来创建原生的字符串字面量,可能由多行组成,但不支持转义,并且可以包含除了反引号外其他所有字符。

数组

格式:var variable_name [SIZE] variable_type

var balance [10]float32

初始化:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

balance[4] = 50.0

指针

格式:var var_name *var-type

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */

结构体

格式:

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}


type Books struct {
   title string
   author string
   subject string
   book_id int
}
func main() {
    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}

枚举

在golang中并没有枚举类型,但可根据iota特性进行枚举类型的定义

详见iota使用

切片(Slice)

可以声明一个未指定大小的数组来定义切片:

var identifier []type

或使用make()函数来创建切片:

var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)

空(nil)切片:一个切片在未初始化之前默认为 nil,长度为 0

实例:

//只要在声明时不做任何初始化,就会创建一个 nil 切片
// 创建 nil 整型切片
var myNum []int

//// 使用 make 创建空的整型切片
myNum := make([]int, 0)

// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := make([]int, 5)

// 创建一个整型切片,分别指定长度和容量
// 其长度为 3 个元素,容量为 5 个元素,不允许创建容量小于长度的切片
slice := make([]int, 3, 5)

// 创建字符串切片
// 其长度和容量都是 3 个元素
myStr := []string{"Jack", "Mark", "Nick"}

//用索引方式创建长度和容量都是100个元素的切片
// 使用空字符串初始化第 100 个元素
myStr := []string{99: ""}

slice[i:]  // 从 i 切到最尾部
slice[:j]  // 从最开头切到 j(不包含 j)
slice[:]   // 从头切到尾,等价于复制整个 slice


//切片扩容
myNum := []int{10, 20, 30, 40, 50}
// 创建新的切片,其长度为 2 个元素,容量为 4 个元素
newNum := myNum[1:3]
// 使用原有的容量来分配一个新元素
// 将新元素赋值为 60
newNum = append(newNum, 60)


//将一个切片追加到另一个切片
// 创建两个切片,并分别用两个整数进行初始化
num1 := []int{1, 2}
num2 := []int{3, 4}
// 将两个切片追加在一起,并显示结果
fmt.Printf("%v\n", append(num1, num2...))


//遍历切片
myNum := []int{10, 20, 30, 40, 50}
// 迭代每一个元素,并显示其值
for index, value := range myNum {
    fmt.Printf("index: %d value: %d\n", index, value)
}


//切片拷贝
num1 := []int{10, 20, 30}
num2 := make([]int, 5)
count := copy(num2, num1)
fmt.Println(count)
fmt.Println(num2)
输出:
3
[10 20 30 0 0]

列表(list)

初始化列表

1.New 方法初始化 list

变量名 := list.New()
2.通过声明初始化list

var 变量名 list.List

实例

package main

import "container/list"

func main() {
    l := list.New()
    // 尾部添加
    l.PushBack("canon")
    // 头部添加
    l.PushFront(67)
    // 尾部添加后保存元素句柄
    element := l.PushBack("fist")
    // 在fist之后添加high
    l.InsertAfter("high", element)
    // 在fist之前添加noon
    l.InsertBefore("noon", element)
    // 移除 element 变量对应的元素
    l.Remove(element)
}

字典map

map[keyType][valueType]

并发安全

  • golang中的map并不是并发安全的,开很多线程同时写map就会崩溃concurrent map writes,可以加锁,但影响性能。
  • Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
    package main
    import (
          "fmt"
          "sync"
    )
    func main() {
        var scene sync.Map
        // 将键值对保存到sync.Map
        scene.Store("greece", 97)
        scene.Store("london", 100)
        scene.Store("egypt", 200)
        // 从sync.Map中根据键取值
        fmt.Println(scene.Load("london"))
        // 根据键删除对应的键值对
        scene.Delete("london")
        // 遍历所有sync.Map中的键值对
        scene.Range(func(k, v interface{}) bool {
            fmt.Println("iterate:", k, v)
            return true
        })
    }

go字典和python字典异同

  • 相同: 键值对, 无序集合, 每个键都是唯一的, 对一个键多次赋值会更新当前键的值;
  • 不同: go语言的字典里面的类型是定好的, 不可变更, python可以随意写类型.

初始化

func test1() {
    map1 := make(map[string]string, 5)
    map2 := make(map[string]string)
    map3 := map[string]string{}
    map4 := map[string]string{"a": "1", "b": "2", "c": "3"}
}
用4种方式分别创建数组,其中第一种和第二种的区别在于,有没有指定初始容量,不过使用的时候则无需在意这些,因为map的本质决定了,一旦容量不够,它会自动扩容

必须给map分配内存空间之后,才可以往map中添加元素

var m map[int]int // 使用var语法声明一个map,不会分配内存
m[1] = 1 // 报错:assignment to entry in nil map

m := make(map[int]int) // make语法创建map
m[1] = 1 // ok

遍历

func test2() {
    map1 := make(map[string]string)
    map1["a"] = "1"
    map1["b"] = "2"
    map1["c"] = "3"
    for key, value := range map1 {
        fmt.Printf("%s->%-10s", key, value)
    }
}

查找 修改 删除

func test3() {
    map4 := map[string]string{"a": "1", "b": "2", "c": "3"}
    val, exist := map4["a"]//exist 存在返回true否则false
    val2, exist2 := map4["d"]
    fmt.Printf("%v,%v\n", exist, val)
    fmt.Printf("%v,%v\n", exist2, val2)
    map4["a"] = "8" // 修改字典和添加字典没什么区别
    fmt.Printf("%v\n", map4)
    fmt.Println("删除b:")
    delete(map4, "b")
    fmt.Printf("%v", map4)
}

可变参数

声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“...”,这表示该函数会接收任意数量的该类型参数。

func sum(vals ...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为[] int的切片。sum可以接收任意数量的int型参数:

fmt.Println(sum())           // "0"
fmt.Println(sum(3))          // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"

标准库

https://studygolang.com/pkgdoc

包引用

同一个包里面,不同文件之间,不需要 import,直接用就好。不同包的话,需要引用包,只能使用大写字母开头的方法 ,变量 等等,小写子母开头的只能包内使用。大写字母开头的变量,方法暴露给其他包用的,包内的话可以随便引用。

直接引用

import "fmt"

import "os"

将包一块引入

 import (
      "fmt"
      "os"
    )

相对路径

import   "./model"  // 当前文件同一目录的 model 目录

绝对路径

import   "shorturl/model"  // 加载 GOPATH/src/shorturl/model 模块

点操作

import( 
    . "fmt" 
) 

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名 fmt.Println( "我爱北京天安门" )可以省略的写成 Println( "我爱北京天安门" )。

别名操作

import( 
    f "fmt" 
) 
f.Println( "我爱北京天安门" )

下划线操作

import ( 
    “database/sql” 
    _ “github.com/ziutek/mymysql/godrv” 
) 

下滑线 操作其实只是引入该包。当导入一个包时,它所有的 init() 函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的 init() 函数被执行而已。这个时候就可以使用下滑线操作引用该包了。即使用下滑线操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其 init() 函数。

make

内建函数 make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上)

slice

mySlice = make([]int, 5)//创建了长度为5初值为0的切片
mySlice = make([]int, 5, 10)//创建了长度为5,容量为10的切片

map

var m_ map[string]int = make(map[string]int)
m_["one"] = 1
fmt.Println(m_)

var m map[string]int = map[string]int{"1":1}
m["2"] = 2
fmt.Println(m)

结果:
map[one:1]
map[1:1 2:2]

chan

通道实例 := make(chan 数据类型)

new()和make()区别

  • new 的作用是初始化一个指向类型的指针(*T),make 的作用是为 slice,map 或 chan 初始化并返回引用(T)。
  • func new(Type) *Type
  • func make(Type, size IntegerType) Type

下划线

忽略返回值

v1, v2, _ := function(...)
某个函数返回三个参数,但是我们只需要其中的两个,另外一个参数可以忽略

下划线在import中

让导入的包做初始化,而不使用包中其他功能

匿名函数

定义

 func(参数列表) 返回值列表 {
     函数体...
 }

实例

//无参数直接加括号
func() int {
	fmt.Printf("func 1\n")
	return 1
}()

//有参数,在括号里加参数
func(arge int)  {
	fmt.Printf("func %d\n",arge)
}(2)

range

返回的第一个索引是连续的,可以看到第二个值都是整型数字

func rangeString() {
    datas := "aAbB"

    for k, d := range datas {
        fmt.Printf("k_addr:%p, k_value:%v\nd_addr:%p, d_value:%v\n----\n", &k, k, &d, d)
    }
}

k_addr:0xc420014148, k_value:0
d_addr:0xc420014150, d_value:97
k_addr:0xc420014148, k_value:1
d_addr:0xc420014150, d_value:65
k_addr:0xc420014148, k_value:2
d_addr:0xc420014150, d_value:98
k_addr:0xc420014148, k_value:3
d_addr:0xc420014150, d_value:66

type

定义别名

例如:type name string // name类型与string等价,就是将一个string类型起一个别名叫做name

defer

  • defer语句被用于预定对一个函数的调用。我们把这类被defer语句调用的函数称为延迟函数。
  • defer语句只能出现在函数或方法的内部。
  • 把这个函数放入到一个栈上, 当外部的包含方法return之前,返回参数到调用方法之前调用,也可以说是运行到最外层方法体的"}"时调用。我们经常用他来做一些资源的释放,比如关闭io操作。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×