Go语言学习
Go语言学习
Go语言学习
1 安装
1.1 下载
可以访问Go语言官网下载go语言安装包.如下图选择Download Go:
进入下载页面后根据自己的操作系统选择对应的go语言安装包即可下载:
1.2 安装和配置
我的操作系统是linux的,所以我选择下载了linux版本的安装包.将下载好的go语言安装包复制到安装目录然后解压,解压好后配置环境变量:
GOROOT=/home/deepin/dev/env/go/go
export PATH=$PATH:$JAVA_HOME/bin:$CLASS_PATH:$MAVEN_HOME/bin:$NODE_HOME/bin:$GOROOT/bin/
配置好环境变量后,在控制台输入如下命令,出现类似输出则表示配置成功:
$ go version
go version go1.12.5 linux/amd64
注意:配置好环境变量后可能要使配置文件被重新加载source 配置文件
或者注销当前账户重新登录!!!
2 入门Hello World
2.1 新建一个文件main.go
新建一个文件`main.go`,并在文件中键入以下内容:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
依次对上面的代码解释:
- package main 包,表示当前程序在哪个包下,入口程序一定要在
main
包下,入口程序也即程序最开始运行的地方.main
方法就是程序的入口. - import “fmt” 导入外部依赖(库),这里导入了一个
fmt
库(format),这个库提供了多种数据格式化的方法. - func main() 定义
main
函数,main
函数就是程序执行和入口函数,main
函数一定要在main
包下. - fmt.Println(“Hello World”) 程序向控制台输出Hello World。
3 基础
3.1 包
包程序由包构成,程序从main程序开始执行,mian程序在mian包下。
3.1.1 主程序
新建一个index.go
文件,并键入以下代码:
package main
func main() {
println("hello world!")
}
主程序必须是func main
并且在main
包下。
3.1.2 导包
包中可以提供方法供其他程序使用,其他程序要使用包就需要导包,导包通过import
关键字。导包有要注意的地方是导包的路径是从GOPATH
和GOROOT
开始查找包的,其中GOROOT
是在安装GO环境是配置的,GOPATH
默认是用户家目录/go
。我们更改默认的GOPATH
配置:
GOPATH=//home/deepin/Documents/workspace/go
export PATH=$PATH:$JAVA_HOME/bin:$CLASS_PATH:$MAVEN_HOME/bin:$NODE_HOME/bin:$GOROOT/bin/:$GOPATH
配置好后,新建测试程序:
- 新建项目(文件夹demo)并在项目目录下新建两个文件夹
main
和util
- 在
main
包下新建一个main.go
文件并键入以下代码:
package main
import "../util"
func main() {
util.PrintHello()
- 在
util
文件夹下新建一个hello.go
文件,并键入以下内容:
package util
func PrintHello() {
hello()
}
func hello() {
println("hello")
}
运行go run main/main.go
。
注意:
相对路径导包:
import "../util"
如果写成绝对路径导包的话要走GOPATH
,也就是从$GOPATH/src/
开始查找包,应该是import 项目名/util
(项目在src下)。项目如果不在src
/下的话,就要把util
包发到src下
。
注:import
导包的时候写的是目录名
使用的时候使用的是包名
。
3.1.3 导包可以同时导入多个,例如:
import (
"demo00/utils"
"fmt"
)
3.1.4 给包起别名
import (
hello "demo00/utils"
"fmt"
)
// 或
import hello "demo00/utils"
3.1.5 导出约定
要导出的内容(方法/变量等)必须以大小字母开头。
3.2 函数
使用func
关键字定义函数,格式为
func 函数名(参数名1 参数类型, 参数名2 参数类型, ....) 返回值类型 {
}
例如定义一个两数相加返回结果的方法:
func add(x int, y int) int {
return x + y
}
3.2.1 参数类型缩写
如上add
函数,x
和y
是同一个类型的,所以参数列表也可以用下缩写写法:
func add(x, y int) int {
return x + y
}
3.2.2 多值返回
函数不能返回多值,比如下面这个方法计算计算两个数的和和差并分别返回
:
func calc(x, y int) (int, int) {
return x + y, x - y;
}
3.2.3 命令返回值
可以为返回值定义变量名,在函数末尾使用return
就直接返回:
func calc(x, y int) (add, minus int) {
add = x + y
minux = x - y
return
}
3.3 变量
3.3.1 变量声明及初始化
// 声明变量
var a, b int
// 声明变量并初始化
var x, y int = 2, 3
3.3.2 变量声明及初始化简写
简写变量的形式只能在函数内使用:
// 声明变量初始化简写,类型自动推导
a, b := 3, 4
3.3.3 定义多组变量
就像导入多组包一样,可以定义多级变量:
var (
a,b int = 1, 2
c, d bool
)
3.3.4 默认值
在定义了变量且没有显示赋值时,变量会有一个默认值:
数值类型为 0,
布尔类型为 false,
字符串为 ""(空字符串)。
3.3.5 类型转换
go中类型必须显示转换:
var a int = 1
// 显示转换
var b float64 = float64(a)
println(b)
// 显示转换
var c int = int(b)
println(c)
3.3.6 常量
go中常量通过const
关键字定义,常量不允许被修改:
const Pi = 3.14
println(Pi)
// 试图修改一个常量,编译不通过
Pi = 3.15
println(Pi)
3.4 流程控制语句
3.4.1 for
在go中只有一种循环语句,就是for
循环,如下:
for 前置语句; 条件语句; 后置语句 {
}
前置语句最先执行且执行一次,每次执行循环前执行条件语句,每次循环结束时执行后置语句。{}
不能省略。
- 死循环
for {
// 代码块...
}
- 只判断条件
for ; i < 10; {
// 代码块...
}
3.4.2 if
if
语句不需要小括号,必须要大括号,如下
if 条件 {
// do something...
}
- 可以在if中执行一条语句
类似for
语句一样,在执行if
语句的时候也可以执行一条语句:
func main() {
var i int = 1
if a := 2; i < a {
i = a + i
}
println(i)
}
如果在if
中定义了一个变量,这个变量在整个if
*享:
func main() {
var i int = 1
if a := 2; i < 0 {
i = a + i
} else if i < 2 {
i = a + 2
}
println(i)
}
3.4.3 switch
下面例子是所有switch
语句相关的用法:
package main
func main() {
var c string = "abc"
// switch 也可以执行一条语句
switch i := "bc"; c {
case "a" + i:
println("a")
// 默认有break语句的作用 不会继续向下执行
case "ab":
println("ab")
// break语句作用无效 也就是会继续向下执行
fallthrough
// switch 的case 可以是一个函数
case fun():
println("abc")
case "d":
println("d")
default: // 条件都不匹配时执行
println("default")
}
}
func fun() string {
println("fun执行...")
return "abc"
}
如果switch
语句没有条件,相当于swith true
:
func main() {
switch {
case 1 < 10:
println("true")
case 1 > 10:
println("false")
}
}
4.4 延迟执行defer
defer
后面跟着一个函数,defer
关键字会使该函数延迟执行,也即在defer
所在函数执行完后(return后)执行该函数。
func main() {
j := test()
println(j) // 4
defer println("main", j) // 5
}
func test() int{
defer println("test exec ...") // 3
i := 1
println("test", i) // 1
defer println("test defer", i) // 2
return 1
}
执行结果是:
test 1
test defer 1
test exec ...
1
main 1
defer 栈:推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
3.5 更多类型
指针指向(保存)了值的内存地址。
3.5.1 指针
a 定义指针
通过var 变量名 *类型
可以定义一个指向对应类型的指针变量,如下:
var p *int
变量都有默认值,这里p的默认值是nil
。
b 取址
通过&变量
就可以取到对应变量的地址了,如下:
i := 10
// 取变量 i的地址
var p *int = &i
c 取值
通过*指针变量
就可以取到指针变量对应的地址的值:
var p *int
i := 3
p = &i
j := *p
// j 的值就是3
下面两种是不同的操作(地址就是内存地址):
func main() {
var p *int
i := 3
p = &i
// 取p中的值,p保存的是i的地址,所以是地址值
println(p)
// 取p中的地址中的值, 也就是对就的i的值
println(*p)
}
看下面 这个程序:
package main
func main() {
// 定义一个变量 值是21 根据类型推导 i的类型是int型
i := 21
// 定义一个变量 值是&i 根据类型推导 p的类型是 *int型
p := &i
// *p 的操作就是对 p中保存的地址上的内存的操作 *p = 42 就是改变了内存地址上的值为42
*p = 42
// 所以在这里打印i 就是42
println(i)
}
3.5.2 结构体
a 定义结构体
定义一个结构体通过type 结构体名 struct
:
type Student struct {
}
b 为结构体添加字段
结构体可以看做是一个对象,字段可以看做是这个对象的属性:
type Student struct {
X int
Y int
}
c 定义一个结构体
// X = 1 , Y = 3
var s Student = Student{1, 3}
// X = 1, Y = 0
var s1 = Student{X: 1}
// X = 1 , Y = 2
var s2 = Student{X: 1, Y: 2}
// X = 0, Y = 0
var s3 = Student{}
d 获取结构体字段值
var s Student = Student{1, 3}
// 获取s中的X的值保存在变量i中
var i int = s.X
e 结构体指针
可以通过(*指针).字段
通过指针访问属性值,也可以直接访问指针.字段
。
package main
import "fmt"
func main() {
var s Student = Student{1, 3}
p := &s
fmt.Println((*p).X)
fmt.Println(p.X)
}
type Student struct {
X int
Y int
}
3.5.3 数组
a 定义数组
定义一个10个长度的数组i,数组中每个元数都被会初始化(这里每个元素的类型是int,所以被始化为0)
var i [10]int
如果用:
形式定义数组就必需显示初始化数组:
i := [4]int{1, 3, 4, 5}
3.5.4 切片
切片类似于数组,数组长度固定,切片长度是动态的。
a 定义一个切片
定义一个切片可以看做是定义一个没有长度的数组,如下定义了一个接收int类型的切片:
var i []int
b 从数组中初始化
从一个数组中初始化切片,如下j[1:3]
即取j
中下角标1和2中的数据(包含1不包含3):
var i []int
// 定义一个数组
j := [4]int{1, 2, 3, 4}
// 初始化切片
i = j[1:3]
注:j[1:3]
其左右两边的数值都可以省略,如果左边省略表示从0
开始,右边省略表示到数组最后的下角标
.
c 切片中不保存数据
切下中不保存任何数据,只是描述了数组中的一段,如下从数组中扩展的切片改变数组上对应的数据也会发生变化:
j := [4]int{1, 2, 3, 4}
i := j[1:3]
i[1] = 2 // j[2]也变成了2
c 直接初始化切片
可以不从数组来初始化切片:
s := []int{1, 2, 3, 4, 3}
d 切片长度
可以通过leng(切片)
获取切片长度,cap(切片)
来获取切片的容量:
s := []int{1, 2, 3, 4, 3}
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 5
*长度和容量:长度可以理解为当前切片中元素的个数,容量可以理解为当前切片可以保存元素的个数,容量可以扩充,当前当前元素个数>容量时,容量扩充一倍。
e 重新切片
重新切片,改变切片容量:
s := []int{1, 2, 3, 4, 3} // [1 2 3 4 3] len = 5 cap = 5
s = s[3:] // [4 3] len = 2 , cap = 2
d 通过make创建切片
如下,创建了一个长度为0,容量为5的切片:
s := make([]int, 0, 5) // []
如下,创建一个长度为2(切片中有两个元素,默认值为0),容量为3的切片:
s := make([]int, 2, 3) // [0 0]
e 向切片中添加元素
可以通过append(切片, 元素1, 元素2, ..., 元素n)
向切片中添加元素,当元素长度超过了容量时,容量扩容一倍:
s := make([]int, 0, 2) // [] len = 0 cap = 2
s = append(s, 1) // [1] len = 1 cap = 2
s = append(s, 2) // [1 2] len = 2 cap = 2
s = append(s, 3, 4) // [1 2 3 4] len = 4 cap = 4
f 遍历切片
通过for
和range
可以遍历切片:
func main() {
s := []int{1, 2, 3, 4, 5}
// i 是下角标 v是对应下角标的元素
for i, v := range s {
fmt.Print("i = ", i, " ")
fmt.Println("v = ", v)
}
}
可以用_
代替i
或v
中的其中一个,也就是说如果只要获取下角标或元素可以像下面这样写:
for i, _ := range s {
fmt.Print("i = ", i, " ")
}
// 或
for _, v := range s {
fmt.Print("i = ", i, " ")
}
如果只要下角标也可以这样写:
for i := range s {
// do something...,
}
3.5.5 映射 map
a 定义映射
新定义 的映射是nil
,不能向其添加数据:
var m map[string]string
b make初始化
映射通过make初始化后可以向其中添加数据:
var m map[string]string
m = make(map[string]string])
c 初始化数据
在创建时初始化数据:
var m = map[string]string {
"a": "xiaoming",
"b": "xiaohong",
}
fmt.Println(m) // map[a:xiaoming b:xiaohong]
d 向映射中添加或删除数据
// 如果没有添加数据,如果存在就修改数据
m["c"] = "xiaogang"
e 获取元素
直接通过key来获取元素:
func main() {
var m = map[string]string {
"a": "xiaoming",
"d": "xiaohong",
}
fmt.Println(m["a"])
}
f 删除元素
可以通过delete(映射, key)
删除元素:
func main() {
var m = map[string]string {
"a": "xiaoming",
"d": "xiaohong",
}
// 删除元素
delete(m, "a")
fmt.Println(m)
}
```下单接口json
##### g 检测值是否存在
通过下面语法检测值是否存在,如果存在 `elem`就是对应的值,`ok`为`true`,如果不存在,`elem`是对应类型的默认值,`ok`为`false`:
```go
elem, ok := m["c"]
3.5.6 函数作为值传递
下面这个例子,函数作为另一个函数的参数和返回值传递:
func main() {
i := func(a, b int) int {
return a + b
}
fn := test(i)
r := fn(1, 2)
fmt.Println(r)
}
func test(fn func(a, b int) int ) func(a, b int) int {
return fn
}
3.5.7 函数的闭包
函数的闭包也就是函数的返回值是一个函数,如果函数中有变量,那么返回的函数在执行时会共享同一个变量:
func main() {
f1 := test()
fmt.Println(f1(1)) // 1
fmt.Println(f1(1)) // 2
f2 := test()
fmt.Println(f2(1)) // 1
fmt.Println(f2(1)) // 2
}
func test() func(b int) int {
sum := 0 // 这个就是共享变量
return func(b int) int{
sum += b
return sum
}
}
4 方法和接口
4.1 方法
go中没有类。不过你可以为结构体定义方法。方法也就是带接收者的函数,用func
定义方法,func
后是接收者类似:func 接收者 方法名(参数) 返回值
.
4.1.1 定义方法
为结构体定义一个方法:
func main() {
s := Student{"Tom", 18}
s.say("hello")
}
type Student struct {
name string
age int
}
// 为结构体定义方法
func (s Student) say(word string) {
fmt.Println(s.name, ":", word);Scale
}
4.1.2 扩展类型添加方法
我们可以扩展一个类型,type 类型名 类型
,如type MyInt int
,也可以为其添加方法,如下:
package main
import "fmt"
func main() {
var m MyInt = MyInt(1)
sum := m.add(11)
fmt.Println(sum)
}
type MyInt int
func (m MyInt) add(param int) (sum int){
sum = int(m) + param
return
}
4.1.3 方法的接收者
方法的接收者可以是一个‘类型’,也可以是一个指针,如果是一个类型的话,接收者只是这个‘类型’的副本,如果是指针,接收者指向对应的‘类型’,也就是说如果修改值,一个会修改副本一个修改值本身:
package main
import "fmt"
func main() {
var m My = My{1, 2}
m.print() // x 1 y 2
m.change(2) // x 2 y 4
m.print() // x 1 y 2
m.changeP(3)
m.print() // x 3 y 6
}
type My struct {
X int
Y int
}
// 打印结果
func (m My) print() {
fmt.Println("m.X -> ", m.X)
fmt.Println("m.Y -> ", m.Y)
}
// 改变值
func (m My) change(param int) {
m.X = param
m.Y = 2 * param
println("---------------------")
m.print()
println("---------------------")
}
// 使用指针改变值
func (m *My) changeP(param int) {
m.X = param
m.Y = 2 * param
}
如果方法的接收者是值本身,如果使用指针
调用这个方法,会被解释为(*指针).方法()
,如果方法的接收者是值本身,使用值本身
调用方法时,会被解释为(&值本身).方法名()
。
4.2 接口
接口是定义了一组方法签名的集合,如下就是一个接口:
type MyInterface interface {
say(word string)
eat(food string)
}
4.1 实现接口
实现接口只需要实现接口中定义的方法即可!
a 使用指针作用接收者
// 结构体
type Student struct {
name string
age int
}
// 为Student实现接口MyInterface中的say方法
func (s *Student) say(word string) {
fmt.Println(s.name, " say -> ", word)
}
// 为Student实现接口MyInterface中的eat方法
func (s *Student) eat(food string) {
fmt.Println(s.name, " eat -> ", food)
}
使用指针接收的方法,接口接收的类型也应该是指针类型:
func main() {
var mi MyInterface
s := Student{"Tom", 18}
// 这里是指针类型
mi = &s
mi.say("hello")
mi.eat("orange")
}
b 不使用指针接收
// 结构体
type Student struct {
name string
age int
}
// 为Student实现接口MyInterface中的say方法
func (s Student) say(word string) {
fmt.Println(s.name, " say -> ", word)
}
// 为Student实现接口MyInterface中的eat方法
func (s Student) eat(food string) {
fmt.Println(s.name, " eat -> ", food)
}
不使用指针接收方法,接口接收的类型可以是指针
或不是指针
:
func main() {
var mi MyInterface
s := Student{"Tom", 18}
mi = &s
mi = s
mi.say("hello")
mi.eat("orange")
}
4.2 接口作为值传递
下面是接口作为值传递的例子:
package main
import "fmt"
func main() {
s := MyStruct1{"Tom"}
var ms MyStruct1 = s
exec(ms, "hello")
}
type MyInterface interface {
show(param string)
}
type MyStruct1 struct {
name string
}
func (m MyStruct1) show(param string) {
fmt.Println(param)
}
// 传递接口
func exec(m MyStruct1, param string) {
m.show(param)
}
4.3 空接口
没有任何方法定义的接口就是空接口,空接口可以接收任何参数类型interface{}
:
func main() {
print(Student{"Tom"})
}
type Student struct {
name string
}
func print(i interface{}) {
fmt.Println(i)
}
4.4 空接口断言
变量1 , 变量二 : 空接口.(类型)
,变量1保存了空接口中保存的变量值,变量保存了空接口中是否是保存的对应变量的布尔值,具体看下面这个例子:
package main
import "fmt"
func main() {
var i interface{} = Student{"Tom"}
// 判断i保存的变量 是不是int类型的
// 如果是就返回 该变量 并保存到 s1中,且ok为true,
// 如果不是s1为int类型默认值 ok为false
s1, ok := i.(int)
fmt.Println(s1, ok)
s2, ok := i.(Student)
fmt.Println(s2, ok)
// 如果只有一个接收的变量,这个值就是口中保存的值
s3 := i.(Student)
}
type Student struct {
name string
}
4.5 类型选择
可以使用switch i.(type)
作类型选择,i.(type)
会返回i
中保存的变量,并且只能用于switch
:
package main
import "fmt"
func main() {
var i interface{} = Student{"Tom"}
switch v := i.(type) {
case string:
fmt.Println("string")
case int:
fmt.Println("int")
case Student:
fmt.Println("student", v)
}
}
type Student struct {
name string
}
4.6 Stringer
fmt 包中定义的 Stringer 是最普遍的接口之一。
type Stringer interface {
String() string
}
该接口的方法有点类似于toString()
方法,可以返回用fmt.Sprintf()
格式化好的字符串,在调用fmt.Print()
等方法时会被输出:
package main
import "fmt"
func main() {
s := Student{"Tom", 18}
fmt.Println(s)
}
type Student struct {
name string
age int
}
func (s Student) String() string {
return fmt.Sprintf("Student[name = %v, age = %v]", s.name, s.age)
}
4.7错误
在go中有一个内置接口error
,定义如下:
type error interface {
Error() string
}
自定义异常时,只需要实现这个接口即可 :
type MyError struct {
Name string
Desc string
}
func (m *MyError) Error() string {
return fmt.Sprintf("错误:(%v),%v", m.Name, m.Desc)
}
使用自定义异常时,在要抛出异常的地方返回该异常:
func testError(i int) error {
if i == 0 {
return &MyError{"零值", "接收的参数不能为零"}
}
fmt.Println("接收的参数是:", i)
return nil
}
要捕获异常时,判断返回的异常的值:
func main() {
err := testError(0)
if err != nil {
fmt.Println(err)
}
}
4.8 Reader
package main
import (
"fmt"
"strings"
"io"
)
func main() {
// 被读取的数据
s := strings.NewReader("hello world!")
// 切片,容量长度为8
b := make([]byte, 8)
for {
n, err := s.Read(b)
// n是读取了多少 长度 err在读取到文件末尾时返回eof错误
fmt.Printf("%q", b[:n])
println()
if err == io.EOF {
break
}
}
}
5 并发
5.1 goroutine
使用go关键字,方法会启用新的线程来执行:
package main
import (
"fmt"
"time"
)
func main() {
// 使用了go关键字 开启新的线程运行
go say("hello")
// 当前线程执行下面的方法
say("hi")
}
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
time.Sleep(100 * time.Millisecond)
}
}
5.2 信道
5.2.1 创建信道
package main
import (
"fmt"
"time"
)
func main() {
// 创建信道
c := make(chan int)
go in(1, c)
// <- 从信道中拿出数据 是阻塞的操作,会等待数据写入
fmt.Println(<- c)
}
func in(i int, c chan int) {
time.Sleep(1000 * time.Millisecond)
c <- i
}
5.2.2 写入和取出数据
通过<-
进行写入和读取数据,如上所示c <- i
就是把i
写入信道c
中,<- c
就是把信道中的值从信道中读出。
5.2.3 带缓冲的信道
在创建信道是指定缓冲大小,缓冲区满了就无法再写入数据。
package main
import (
"fmt"
)
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
// c <- 3
// fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
}
5.2.3 range和close
range
可以从信道中读取值,直到遇到close
:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 2)
go in(c)
for i := range c {
fmt.Println(i)
}
}
func in(c chan int) {
for i := 0; i <= 10; i++ {
time.Sleep(1000 * time.Millisecond)
c <- i
}
// 不写入数据后close
close(c)
}
5.2.4 select
从信道中读取数据时,如果信道中没有数据且没关闭,会出现死锁的情况,如果使用select
就不会出现类似情况:
package main
import (
"fmt"
"time"
)
func main() {
i := make(chan int, 10)
s := make(chan string, 5)
for a := 0; a < 10; a++ {
i <- a
}
for a := 0; a < 5; a++ {
s <- "h"
}
for {
select {
// 从 i 中读取数据,如果读取到了就执行
case v := <- i:
fmt.Printf("从i中读取的数据:%d \n", v)
time.Sleep(100 * time.Millisecond)
// 从s中读取数据,如果读取到了就执行
case <- s:
fmt.Println("从s中读取了数据")
time.Sleep(100 * time.Millisecond)
default:
fmt.Println("默认...")
return;
}
}
}