Go语言的学习与应用(偏后端)

golang的中文文档网址

http://studygolang.com/pkgdoc

go语言的入门

01 认识它

1
2
go build ***.go//进行编译,生存程序exe再运行
go run ***.go//直接进行编译运行

02 数据类型

准备工作

  1. 必须引入main包
  2. import导入“头文件”
1
2
package main
import "fmt"

赋值

  1. 注意:变量声明了,就一定要用不然会报错
  2. =是赋值,:=是声明自动推导变量
1
2
3
4
5
6
7
8
9
10
11
12
var a int//没有初始化默认为0
var a,b int

var c=10

d:=30//类型推导,很常用,只能对同一个变量名使用一次
d=20

var{
a int
b float64
}
Println和Printf
  1. Println是换行

  2. Printf可以格式化

    1
    2
    3
    a,b:= 20,30
    fmt.Println("a=",a,"b=",b)
    fmt.Printf("a=%d,b=%d",a,b)
多重赋值
1
2
3
//交换两个数值
i,j:=1,2
i,j=j,i//是的居然可以这样,所以次序很重要
匿名变量

丢弃数据不处理,一般配合返回值使用

1
2
3
4
5
6
func test()(a,b,c int){
return 1,2,3
}
var a,b,c int
a,b,c=test()//a=1,b=2,c=3
_,b,_=test()//三个返回值只想要中间的,就启用匿名变量
常量
1
2
3
4
5
6
7
const a int=10
const b=10;//都是定义了就不能改变的值,注意常量不能使用:=声明定义,直接=

const{
c =10
d =3.14//自动推导
}
浮点
1
2
3
var a float64
var b float32
//64比32更精度准确
字符类型
1
2
var a byte
//跟c++不一样,输出用%c但是声明用byte
fmt格式化输出
  1. 其他都和c语言差不多,**%v是万能格式**,写了自动推导输出
  2. %T,输出的是变量类型
输入
1
2
3
4
var a int
//阻塞等待用户输入
fmt.Scan(&a)
fmt.Scanf("%d",&a)
类型别名
1
2
3
4
5
6
7
8
9
type bigint int64
bigint a

type{
long int64
char byte
}//一起给他们起别名
var b long
var c char
range
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
str:="abc"
//常规打印每个字符
for i:=0;i<len(str);i++{
fmt.Printf("str[%d]=%c\n",i,str[i])
}
//用range,默认两个返回值一个是序数,一个是数据
for i,data:=range str{
fmt.Printf("str[%d]=%c\n",i,data)
}
//用range,默认返回一个
for i:=range str{
fmt.Printf("str[%d]=%c\n",i,str[i])
}
//range,用匿名丢弃
for i,_:=range str{
fmt.Printf("str[%d]=%c\n",i,str[i])
}

03函数

格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//无参函数无return
func Myfunc(){
fmt.Println("hhhh");
}
//有参无返回
func Myfunc01(a,b int){
a=111
fmt.Println(a)
}
//不定 指定类型 的个数,...type
func Myfunc02(args ...int){
for i:=0;i<len(args);i++{
fmt.Printf("args[%d]=%d\n",i,args[i])
}
}
func main(){
Myfunc02(1,2,3)
Myfunc02(1,2)
}
//无参有返回,推荐写法1
func Myfunc03()(res int){
res=55
return
}
//无参有返回,推荐写法2
/*func Myfunc03()(res int){
return 55
}*/
//多个返回值
func Myfunc03()(int,int,int){
return 1,2,3
}
//多个返回值,推荐写法
func Myfunc03()(a int,b int,c int){
a,b,c=1,2,3
return
}
函数类型(多态性)

函数也是可以当做类型赋值,甚至还有多态性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func Add(a,b int) int{
return a+b
}
func Minus(a,b int) int{
return a-b
}
type FuncType func(int,int) int
func main(){
var res int
var functest FuncType
functest=Add
res=functest(1,2)//3

funtest=Minus
res=functest(10,7)//3
}
回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type FuncType func(int,int) int//用到函数类型时一定有的东西

func Add(a,b int) int{
return a+b
}
func Minus(a,b int) int{
return a-b
}

func Calc(a,b int,fTest FuncType)(res int){
fmt.Println("回调函数")
res=fTest(a,b)
return
}

func main(){
var res int
res=Calc(1,2,Add)
res=Calc(4,3,Minus)
}
匿名函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
f1:=func(){
fmt.Println("哈哈哈哈哈")
}
f1();
//或者
type FuncType func()

var f2 FuncType
f2=f1
f2()

//匿名有参函数必须有定义返回值
x,y:=func(a,b int)(max,min int){
if a<b{
max=b
min=a
}else{
max=a
min=b
}
return
}(10,20)//匿名函数后面写括号传入形参
//x=20,y=10
闭包

我觉得这一点跟C有区别一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func Test() func() int{
var x int=0
return func() int{
//这个{}是一个独立空间x在这里还没有历经内存释放,所以x每一轮的变动都有保存
x++
return x*x
}
}
func main(){
f:=Test()
fmt.Println(f())//1
fmt.Println(f())//4
fmt.Println(f())//9
fmt.Println(f())//16
}
defer

延迟调用,只用在大括号里

1
2
3
4
5
defer fmt.Println("hhhhh")
fmt.Println("jjjjj")
//jjjjj
//hhhhh
//因为第一行被defer延迟调用了

多个defer的调用顺序:和栈一样:先写的后调用,后写的先调用

注意:

  1. 如果defer的语句有错误,依旧能继续调用下方的代码
  2. 有defer关键词的正常代码会按照栈的顺序执行
  3. 有defer关键词的错误代码会在正常defer语句执行
defer和匿名函数

如果有传参的话,是按顺序该传就传,只不过被传参的函数是defer延时调用的

获取命令行参数(os.Args)
  1. os.Args是用户输入的以空格相隔的字符串切片(string[])
  2. 使用方式:cmd,终端
    1. 第一种:go build生成可运行.exe,再运行.exe输入以空格为间隔的多个字符串,注意输入的第一个字符在切片中的下标为1,(因为下标为0的是该.exe的名字)
    2. 第二种:go run直接接空格输入字符串组,同样输入的第一个字符串下标为1

工程管理

  1. 建一个工程,将源码放入src文件管理
  2. 同级目录引入的包应该相同package main,且调用同级不同文件的函数可以直接调用
  3. 不同级目录调用函数
    1. 引入包名import
    2. 调用:包名.函数名
    3. 函数一定是大写字母开头不然调用不了

04复杂类型

数组

注意:数组做函数参数是值传递(值拷贝):即函数内部改变数组元素不会影响原来的数组

所以如果想要用函数改变数组元素要用数组指针,地址传递

1
2
3
4
5
6
7
8
9
10
func f(a [7]int){
a[2]=999//外部不会发生改变
}

func f1(p *[7]int){
(*p)[2]=999//外部会发生改变
}
a:=[7]int{1,2}
f(a)//[1,2,0,0,0,0,0]
f1(&a)//[1,999,0,0,0,0,0]
1
2
3
var a [50]int//元素个数必须是常量
b:=[5]int{1,2,3}//简洁初始化
//[1,2,3,0,0]
切片

本质上不是数组

长度可以不固定

1
2
3
4
5
6
7
//初始化
//自动推导
a:=[]int{1,2,3,4,5}//切片,只要不固定长度
//借助make函数
//make(类型,len,cap).不指定容量:默认cap=len
s:=make([]int,5,10)//len=5,cap=10
s:=make([]int,5)//len=5,cap=5
切片与底层数组
  1. 切片截取的片段元素改动,那么被截取的原数组(切片)也会改变
  2. 另外在上述切片上再定义一个新切片,也是建立在原数组的基础上的:即取值范围可以超过上述切片,不要超过原数组就行
1
2
3
4
5
6
a:=[]int{1,2,3,4,5,6,7,8}//切片,只要不固定长度
slice:=a[0:4]//low,high,max可省略
slice[0]=100//slice:[100,2,3,4],a:[100,2,3,4,5,6,7,8]

newslice:=slice[2:7]//[3,4,5,6,7]
newslice[2]=1000//[3,4,1000,5,6,7],a:[100,2,3,4,1000,5,6,7]
1
2
3
4
5
6
7
8
a:=[]int{1,2,3,4,5}//切片,只要不固定长度
slice:=a[0:4:5]//low,high,max
/*
左闭右开
len=high-low
cap=max-low
*/
//slice:[1,2,3,4],len=4,cap=5

append()的扩容速度:在超过原来的通量后以2倍的速度扩容

1
slice=append(slice,100)//在切片末位加一个成员
切片做函数参数

和之前讲过的数组不一样,(感觉更接近于C中的数组,不用特意用地址传递,也能在函数内部影响外部)

1
2
3
4
5
6
func Test(s []int){
s[0]=222
}
s:=[]int{1,2,3,4}
Test(s)//s:[222,2,3,4]
//函数内部影响外部
随机数
1
2
3
//随机数种子,要以当前系统时间作为种子保证随机数不一样
rand.Seed(time.Now().UnixNano())
rand.Intn(100)//100以内的随机数
map

只有len没有cap,也可以说len即cap,也是随着创建会自动扩容的

键值是唯一的

map是无序的,与创建顺序无关

1
2
3
4
5
6
7
8
9
10
11
12
13
var m1 map[int]string
//可以用make创建,可以指定长度
m2:=make(map[int]string)
m3:=make(map[int]string,10)

//判断map的key值是否存在
//默认的第一个是key对应的value,第二个是key是否存在
value,ok:=m2[1]
if ok==true{
fmt.Println("m2[1]=",value)
}else{
fmt.Println("key不存在")
}
删除
1
delete(m,1)//map名,key值
map做函数参数

也是和切片一样是引用传递:即内部影响外部

05结构体

创建结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Student struct{
num int
name string
sex byte
age int
addr string
}

func main(){
//如果每一个属性都要写,就顺序创建
var s Student=Student{1,"czy",'f',20,"翻斗花园"}
//指定初始化
s1:= Student{name:"orange",addr:"化成花园"}//其余无论类型赋值自动为0
}
new和结构体指针

跟C差不多,不过没有->符号(怪难受的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Student struct{
num int
name string
sex byte
age int
addr string
}

var p1 *Student//甚至可以不写这一步
p1=new(Student)
p1.num=1;
p1.name="mingming"
//现在是指针操作,把->简化为.操作了
(*p1).name="czy"//等同于p1.name
//输出p1:是&{...}

//或者
var s Student
var p2 *Student
p2:=&s
p2.name="guangguang"

如果想在不同的包里使用结构,并且创建其属性,那么在创建结构体的时候一定要首字母大写

06方法

带接收者的函数叫方法

但接收者类型本身不能是pointer

1
2
3
4
5
6
7
8
9
10
func (p *Person)Setinfo(n string,s byte,a int){
p.name=n;
p.sex=s;
p.a=age;
}
//该方法跟着Person是能够被别的结构体继承过去的
func main(){
var p Person
(&p).Setinfo("czy",'f',20)//***
}

07接口

  1. 知道怎么写就好啦,只有方法声明,没有实现也没有数据字段
  2. 习惯使用”er”结尾来命名接口
  3. 有点涉及到Java的向上转型,多态性
  4. 继承接口的结构体都是直接把接口写在属性列表里
  5. 上述结构体只能进行向上转型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Humaner interface{
SayHi()
}

type Human struct{
Humaner//就像写SayHi()一样
Sing(lyrics string)
}
//实现Human的方法
func (h *Human)SayHi(){
...
}
func (h *Human)Sing(lyrics string){
...
}
空接口

弹幕:“类似于Java中的Object类”,我觉得有道理

异常处理(我终于搞懂了)

error接口的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "errors"
func MyDiv(a, b int)(res int,err error){
err=nil;
if b==0{
err=errors.New("分母不能为0")
}else{
res=a/b;
}
return
}

func main(){
res,err:=MyDiv(10,2)
if(err!=nil){
fmt.Println("err=",err)//***
}else{
fmt.Println("res=",res)
}
}
panic函数(崩程序
显式调用panic函数
1
panic("this is a panic test")//会使得程序中断
数组越界导致panic

反正就是数组如果写了越界了,panic函数自己就跳出来报错中断程序了

recover(解决程序崩

解决panic中断程序,必须和defer一起使用

1
2
3
4
5
6
7
8
9
10
11
12
func test (x int){
//不让程序崩
defer func(){
recover()
if err:=recover();err!=nil{
fmt.Println(err)
}
}()//该括号调用匿名函数
var a [10]int
a[x]=10;//x大于9的时候数组越界产生一个panic,程序崩溃

}

08文本文件的处理

1
2
import "strings"
import "strconv"

字符串操作

Contains是否包含字符串

1
func Contains(s,substr string) bool

Join连接字符串

1
strings.Join(s,",")

Index查找位置

1
func Index(s,sep string) int//找不到返回-1	

字符串转换

Append系列是各种类型追加到字符串(有需要查看文档吧)

Format系列都是把别的类型转为字符串(有需要查看文档吧)

1
str:=strconv.Itoa(6666)//整型转字符串

·字符串转其他类型:Parase系列

1
2
3
4
5
6
flag,err:=strconv.ParaseBool("true")
if err!=nil{//判断很重要
fmt.Println("err=",err)
}else{
fmt.Println("flag=",flag)
}

字符串转整型

1
a,_:=strconv.Atoi("567")