Go 简明教程

8,344 阅读9分钟

最近有需求做后端,技术选型选择主力开发语言,于是乎从Python,Go,Java,PHP中做选择,经过测试和调研

  • Go,天生高并发支持,语法简洁,C系列有语言基础
  • 占用内存和CPU资源小,创业公司机器紧张哈
  • 效率超然

通过wrk测试,递归测试,字符串拼接测试

Go完美胜出,无论是内存占用,还是处理速度,都位列第一。编译型的语言就是强 233

IDE选择

强烈推荐IntelliJ IDEA,下载之后安装Go语言插件(直接在IDEA的插件里面在线安装)

让我们从0开始

先记住几个点

  • Go没有char,所有的字符都用rune(跟int是一个)代替,输出需要手动转string

  • Go语言的大括号必须放在一句话的后面

    /*这是一个Hello World,不经意间你就学会了*/
    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	fmt.Println("Hello World");
    }
    

    Go语言只有一个入口,main包下的main函数

  • Go语言函数内不能声明不用的变量和包名,编译器会报错,但是在函数外面是可以的

    package main
    
    import (
    	"fmt"
    	"net/http" //Error,因为下面没用到
    )
    
    const a = 3 //虽然没用但是可以
    var b = 4 //同上
    
    func main() {
    	var str string //Error,因为下面没用到
    	fmt.Println("Hello World")
    }
    
  • Go语言不用写分号除非你想在一行里面写多条语句

  • Go不支持静态变量,并没有static

  • Go语言的变量和函数的公有私有靠首字母大小写区分,首字母大写是共有的,小写是私有的

    fmt.Println(str); //这是一个公有的函数,因为P大写了,变量也是一样的
    
  • Go语言的定义变量,变量类型写变量后面

    package main
    
    import (
    	"fmt"
    )
    func main() {
    	var str string = "Hello World"
    	fmt.Println(str);
    }
    
  • Go没有try/catch,finally的功能靠defer关键字实现 (稍后解惑)

  • Go函数可以有多个返回值 (稍后解惑)

变量定义

const IP = "127.0.0.1" //常量用const
var str1 string = "small"
str2 := "solo" //这里go会自动解析出类型
var str3 = "smallSohoSolo" //同上

这些方式都是等价的

四种方式定义,其中第三种

str2 := "solo"

只能在函数内部定义,不能在函数外部

package main

import (
	"fmt"
)

global := "small" //这个不行!!!!!!!

func main() {
	fmt.Println("Hello World")
}

还有更爽的方式

str1,str2,str3 := 1,2,3
var str1,str2,str3 = "1","2","3"
var str1,str2,str3 string = "1","2","3"

以后很地方会用到,函数也可以一次返回多个返回值

PS:int在32位机器当前都是32位,未来可能在64位机器上变成64位,如果要指定准确的字长,那就用int8,int16等手动指定

另外不同类型的值是不能相加的(吐槽一下Java,Java会自己默默地转然后error = =)非常强的类型比对,非常适合大型企业项目

流程控制

if条件句

a := 1
if a == 1 {
  //true
} else if a == 2{
  
} else {
  
}

另外if支持在条件句中申明一个变量

if a:= 1; a == 1 {
  //true
}

for循环

for i := 0; i < 10; i++ {
  //循环体
}

另外并没有while,用for解决

i := 0
for i != 10 {
  i++;
}

for的遍历写法,下面是遍历一个map和array

for k,v := range map {
  //循环体
}
list := []int{1,2,3,4,5}
for index,val := range list {
	fmt.Println(index,val)
}

goto走一波

i := 1
Here:
if i == 1 {
  i = 2
  goto Here
}

switch不用break自己就中断了,想要接着执行就

var i = 3
switch i {
  case 1,3:
     fmt.Println("123")
     fallthrough
  case 2:
     fmt.Println("789")
  default:
     fmt.Println("456")
}

函数

函数可以有多个返回值,支持闭包,func后面跟的第一个括号是入参,第二个括号是返回值,可以有多个哦

//第一种
c,d := func (a int,b int)(c int,d int) {
		c = a + b
		d = 4
		return
	}(1,2)
fmt.Println(c,d)
//第二种
c := func (a int,b int)(c int) {
  return a +b
}(1,2)
fmt.Println(c)
//第三种
c := func (a int,b int)(int) {
  return a + b
}(1,2)
fmt.Println(c)
//第四种
c,d := func (a int,b int)(int,int) {
  return a,b
}(1,2)
fmt.Println(c,d)

任意搭配总有一款你喜欢的,当然不用闭包也是可以的,并不强制

函数也是可以作为值和类型传进来的

type study func(int) bool //type 给函数个类型名,类似宏 函数的样子
func getAd(fun study) {
  //函数体
  study(3)
}

同样函数也支持变长参数

func getAd(arr ...int) int {
  return len(arr)
}

*和&

两者同c一样

  • *是代表指针,也可以从地址中获取内容
  • &获取一个对象的地址
i := 1
point := &i // point等于一个地址
data = *point //data == 1

另外函数中要分清楚值传递和指针传递,这是一个交换函数,很清晰

func change(a *int,b *int) {
  c := *a
  *a = *b
  *b = c
}
a := 3
b := 4
func(&a,&b)

结构体

学习过C语言的同学肯定都知道结构体,没错,就是那个东西 0.0,结构体让Go有了面向对象的概念

type SmallSoho struct {
	Name string //公有变量大写
	Sex string 
	kg int //私有变量小写
}

如何申请一个结构体

meStruct := new(SmallSoho) //可以申请一个空的结构体,返回一个指针
meStruct := &SmallSoho{} //同上
meStruct := SmallSoho{} //返回一个结构体,是类型的
meStruct := &SmallSoho{"small","男",1} //可以初始化
meStruct := &SmallSoho{Name:"SmallSoho"} //也可以键值对这样来初始化

注意这里的传递问题,在任何地方*val表示传递指针,val表示值传递,什么是值传递,什么是指针传递请自行百度^_^

注意最后两种

meStruct := &SmallSoho{"small","男",1} //可以初始化
meStruct := &SmallSoho{Name:"SmallSoho"} //也可以键值对这样来初始化

上面的那种需要传递所有的参数才可以,不然编译不通过

下面的键值对形式可以传递部分参数

数据结构

Go语言有:数组,Slice(划分),map,channel(并发编程应用)

数组就是跟其他语言一样的数组,没有什么特别的,并且长度只能写死,不能通过变量来定义

list := [10]string{}
list := [10]int{1,2,3,4,5,6,7,8,9,10}
list := []int{1,2,3}
list := [3]int{1} //后面两个默认是0

slice是划分,也就是可以通过数组得到slice,其中每一个元素都指向数组的一个元素,也可以通过make来为划分申请内存

list := []int{1,2,3}
slice := list[0:3] //左闭右开,不支持负数索引

上述情况slice的每一个元素都指向了list,修改任意其一的元素,两者均变化

slice := make([]int,4)

也可以为slice申请一块内存,这样就可以做到动态数组

map的使用方法同上,是一个映射,具体更多的参数请参考文档

m := make(map[string]int)
m["small"] = 1

channel是并发编程中用到的,附上一篇文章来看,国外大神的译文

blog.xiayf.cn/2015/05/20/…

面向对象####

基于结构体,我们可以给结构体上设置很多东西

函数名称前面可以设置函数附属的结构体,也就是绑定方法

私有方法和共有方法同样使用大小写区分

type SmallSoho struct {
	Name string //公有变量大写
	Sex string
	kg int //私有变量小写
}

func(*SmallSoho) GetName()(string) {
	return "smallSohoSolo"
}

func(*SmallSoho) GetSex()(string) {
	return "man"
}

func(SmallSoho) getkg()(int) { //注意这里的SmallSoho没加*,意味着是值传递,并且小写是私有方法
	return SmallSoho.kg
}
构造函数如何设置?

因为Go不支持重载,所以类似Java的那种多种类型的构造函数需要使用工厂模式来进行实现(具体工场请自行百度)

但是Go支持简单的构造

meStruct := &SmallSoho{"small","男",1} //可以初始化
meStruct := &SmallSoho{Name:"SmallSoho"} //也可以键值对这样来初始化

上文提到的简单的初始化时可以的,不过里面不能存有逻辑,逻辑需要使用工厂模式来做

静态方法和属性呢?

Go不支持static所以也就没有这类东西,静态的方法请自定义一个函数直接调用,静态的属性请在包中定义一个变量使用。

私有方法公有方法?

老生常谈,大小写区分,这个大小是语言层面强制,小写的方法和属性就是外部拿不到的,会error

继承

每一个结构体中可以存在匿名域

package main

import "fmt"

type C struct {
	Name string
    sex string
}

type D struct {
	C
}

func main() {
	d := &D{C{}}
	fmt.Println(d.Name)
}

这里C就是一个匿名域,匿名域可以存在没有交集的多个,匿名域可以直接点出属性和方法

但是匿名域只能存在没有交集的多个,如果有两个匿名域,并且这两个匿名域有交集,那么通过点语法会无法通过编译,除非你指定他的所属,这时候点语法会在调用交集的时候无法通过编译,举个例子:

package main

import "fmt"

type C struct {
	Name string
    sex string
}

type E struct {
	Name string
}

type D struct {
	C
	E
}

func main() {
	d := &D{}
	d.E.Name = "da" //合法的
	fmt.Println(d.E.Name)
    //d.Name = "da"
    //fmt.Println(d.Name) //不合法,因为她不知道你所要指定的是哪个
}

这里推荐使用单继承模式

接口

接口的含义跟Java一样,Go中不需要显示的去implement一下

我们定义和实现一个接口

package main

import "fmt"

type C struct {
	Name string
	sex  string
}

type E interface {
	say()
}

func (*C) say() {
	fmt.Println("123")
}

func main() {
	c := new(C)
	c.say()
}

上面的可能显示的不是很到位,再举个栗子,我们模拟一个点击监听

package main

import "fmt"

type C struct {
	Name string
	sex  string
}

type E interface {
	click()
}

func (*C) click() {
	fmt.Println("click~!~!~!")
}

func main() {
	c := new(C)
	Touch(c)
}

func Touch(e E) {
	e.click()
}

是不是很清晰,因为实现了接口,所以他可以被传递进来,另外接口在go中其实也是一个指针,是可以=赋值的,他可以指向一个实现了接口的实例,举个例子

package main

import "fmt"

type C struct {
	Name string
	sex  string
}

type E interface {
	click()
}

func (*C) click() {
	fmt.Println("click~!~!~!")
}

func main() {
	c := new(C)
	var inter E
	inter = c
	Touch(inter)
}

func Touch(e E) {
	e.click()
}

package main

我们都看到了这行

包的概念类似Java,只不过包名是可以和文件名不一样的(虽然没人这么写)

一个go app中只能有一个main包,其中存在唯一一个main方法,没有返回值也没有入参

每一个包还可以有init方法,虽然init方法不限制个数,不过init方法的调用顺序不固定,所以推荐一个包中只写一个init方法,init方法会在包被import过来的时候自动执行,并且只会执行一次,具体调用顺序如图

package main

import "fmt"

func init() {
   
}

func main() {
   fmt.Println("Hello world")
}

图片

错误处理

go的并没有try catch finally

我们需要在return中返回err在外部获取判断

cli,err := mysql.connect()
if(err != nil) {
  //处理错误
}

有些东西需要在finally中处理,比如关闭mysql连接,使用defer来进行操作,defer的语句会在函数结束的时候执行,并且一定会执行。

cli,err := mysql.connect()
defer mysql.close()
if(err != nil) {
  //处理错误
}