欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Go语言学习

程序员文章站 2022-05-12 15:30:09
...

Go语言学习

Go语言学习

1 安装

1.1 下载

可以访问Go语言官网下载go语言安装包.如下图选择Download Go:
Go语言学习
进入下载页面后根据自己的操作系统选择对应的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关键字。导包有要注意的地方是导包的路径是从GOPATHGOROOT开始查找包的,其中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)并在项目目录下新建两个文件夹mainutil
  • 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函数,xy是同一个类型的,所以参数列表也可以用下缩写写法:

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 遍历切片

通过forrange可以遍历切片:

func main() {
	s := []int{1, 2, 3, 4, 5}
	// i 是下角标 v是对应下角标的元素
	for i, v := range s {
		fmt.Print("i = ", i, "  ")
		fmt.Println("v = ", v)
	}
}

可以用_代替iv中的其中一个,也就是说如果只要获取下角标或元素可以像下面这样写:

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

   }

}
相关标签: GO语言