Go 语言学习一篇入门

Goloang的优缺点

Goloang的优点

极其简单的部署方式

  • 可直接编译成机器码
  • 不依赖其他库
  • 直接运行即可部署

语言层面的并发

  • 天生的基因支持
  • 充分的利用多核

强大的标准库

  • runtime 系统调度机制
  • 高效的GC垃圾回收
  • 丰富的标准库

Goloang的不足

  • 包管理,大部分包都在github
  • 无泛化类型(Golang 1.18+已经支持泛型)
  • 所有Excepiton 都用Error来处理(比较有争议)。
  • C 的降级处理,并非无缝,没有C 降级到asm那么完美(序列化问题)

关于编译运行

Go 复制代码
package main//包含 main 函数的一定要包这个

// import "fmt"
// import "time"

import(
	"fmt"
	"time"
)

func main(){//函数的 { 一定是和函数名在同一行的这是语法规定,不然有编译错误
	fmt.Println("hello Go!")//可;可不;
	fmt.Print("hello Go!\n")

	time.Sleep(1*time.Second);
}

01. 变量

Go 复制代码
//变量
func main(){
	//1.默认
	var a int
	var aa,bb int = 100,"fasgds"
	fmt.Println("a=",a)//a=0
	//2.初始化
	var b int=100
	fmt.Printf("b=%d,type of b=%T\n",b,b);
	//3.初始化的时候自动推导
	var c = 12.6
	fmt.Printf("c=%f,type f c=%T\n",c,c);
	//4.(常用)省区var 直接自动匹配 ------ 但是只能用在函数体内,全局不可以
	d:=1000
	fmt.Println("d=",d)
	fmt.Printf("type of d=%T\n",d);
	time.Sleep(1*time.Second);
}

0.2 常量

但是iota只能在const()里面使用,进行累加效果

go可以多参数返回(返回值可以形参的形式或者直接return多个值)

0.3 defer

defer------defer就相当于析构函数,并且执行在return之后

04. 数组

go的数组,但是定长数组传参很麻烦,只能是形参声明的特定的那一个长度,而且是值拷贝

05. 动态数组slice

go的动态数组 slice(切片)

slice的声明方式

slice使用方式

  1. 切片容量的增加(扩容 5->10->20)
  1. 切片容量的截取

06. map

go的map声明方式

go的map使用方式(传参的时候和slice一样是引用传参)

07. struct

struct的基本定义与使用

和typedef相似

08. go中类的表示与封装

但是大小写,对本包没有区别

Go 复制代码
//封装
type Hero struct{
	nAme string
	Sex string
	Lv int
}

func (this* Hero) GetName()string{
	fmt.Println("Name=",this.nAme);
	return this.nAme
}

func (this* Hero)SetName(name string){
	this.nAme=name
}

func main(){
	hero := Hero{nAme:"zhangsan",Sex:"nan",Lv:7}
	hero.GetName()
	hero.SetName("lisi")

	fmt.Println(hero)
}

09. go的继承

Go 复制代码
//继承
type Animal struct{
	sex string
	age int
}

type Cat struct{
	Animal //-------------直接把要继承的类名写下来就可以了
	color string//猫的颜色
}

func (this* Cat) SetCat(sex string,age int,color string)  {
	this.sex=sex
	this.age=age
	this.color=color
}

func (this* Cat) ShowCat(){
	fmt.Println("cat sex:",this.sex," cat age",this.age," cat color",this.color)
}

func (this* Cat) GetCatColor() string {
	//fmt.Println(this.color)
	return this.color
}

func main(){
	var cat Cat
	cat.color="blue"

	cat.SetCat("nv",3,"yel")
	cat.ShowCat()
	// var ret = cat.GetCatColor()
	// fmt.Println(ret)
	// fmt.Println("--------")
}

10. go的多态

go的多态就是需要先有一个 interface 的接口,这里面就放需要多态的方法,子类去重写的时候不需要像cpp一样继承,直接重写就相当于继承了 ,但是注意一定要把接口全部重写才可以。

Go 复制代码
//多态
type AnimalIF interface{
	Sleep()
	GetColor() string//获取动物颜色
	GetType() string//获取动物类型
}

type Cat struct{
	color string//猫的颜色
}

func (this* Cat)Sleep(){
	fmt.Println("cat is sleep")
}

func (this* Cat)GetColor()string{
	return this.color
}

func (this* Cat)GetType()string{
	return "Cat"
}

//-----------

type Dog struct{
	color string//狗的颜色
}

func (this* Dog)Sleep(){
	fmt.Println("dog is sleep")
}

func (this* Dog)GetColor()string{
	return this.color
}

func (this* Dog)GetType()string{
	return "Dog"
}

//法二:
func ShowAnimal(animal AnimalIF/*接口的数据类型,父类指针*/){
	animal.Sleep()
	fmt.Println("animal color:",animal.GetColor())
	fmt.Println("animal type",animal.GetType())
}

func main(){
	//法一:
	// var animal AnimalIF//接口的数据类型,父类指针
	// animal = &Cat{"blue"} //注意&
	// animal.Sleep()
	// animal = &Dog{"yel"}
	// animal.Sleep()	
	//法二: 运用统一的方法去调用
	cat := Cat{"blue"}
	dog := Dog{"yel"}
	ShowAnimal(&cat)
	ShowAnimal(&dog)
}

11. interface

interface空接口万能类型与类型断言机制(三种作用:定义一般的普通接口(多态),空接口(万能接口),进行断言(判断类型,与万能接口配套用))

断言:断言有两步,一:得到动态类型type,二:判断type是否实现了目标接口。

arg.(string):判断此时arg指向的变量是否是string类型的(称为类型断言)

变量的内置pair结构详细说明

就是任何一个变量,里面都是有一个pair,称 type 和 value,type里面有两种,只能选其一,一个是属于 static type(像int,string),另一个是 concrete type(具体类型,就是interface所指向的具体类型,这种类型是系统看得见的类型)

关于pair再一个例子:就是打开了一个文件描述符,然后赋值给io里面的读变量和写变量,赋值的过程中,pair里面所存储的数据是不会变的(文件描述符指针类型,和指向的文件描述符),然后是把打开的tty(终端),赋值给了r(读方法),再将 r 赋值(w=r.(io.Writer)是r先强制转换为Writer,然后赋值给我)给w,然后用w去写数据到终端

12. go反射reflect机制用法

反射的两个重要接口,反射就是为了获取当前变量的type和value

基本类型反射

复杂类型反射

Go 复制代码
package main

import (
	"fmt"
	"reflect"
)

type User struct{
	Id int
	Name string
	Age int
}

func (this User)Call(){
	fmt.Println("user is called..")
	fmt.Printf("%v\n",this)
}

func main(){
	user := User{1,"ace",18}

	DoFiledAndMethod(user)
}

func DoFiledAndMethod(input interface{}){
	//获取input的type
	inputType := reflect.TypeOf(input)
	fmt.Println("input TypeOf:",inputType.Name())
	//获取input的value
	inputValue := reflect.ValueOf(input)
	fmt.Println("input ValueOf:",inputValue)

	//通过type获取里面的字段
	//1. 通过interface的reflect.Type,通过type得到NumFiled,进行遍历
	//2. 得到每个filed数据类型
	//3. 通过filed有一个Interface()方法得到对应的value
	for i:=0; i<inputType.NumField();i++{
		field := inputType.Field(i);
		value := inputValue.Field(i).Interface()

		fmt.Println("----------")
		fmt.Printf("%s: %v = %v\n",	field.Name,field.Type,value)
	}

	//通过type 获取里面的方法,调用
	for i:=0; i<inputType.NumMethod();i++{
		fmt.Println("****1***")
		m:=inputType.Method(i)
		fmt.Println("****2***")
		fmt.Printf("%s:%v\n",m.Name,m.Type)
	}
}

13. go反射解析结构体标签Tag

这个玩意就是相当于注释或者mysql的comment

关于标签的使用:是键盘1左边的按键``,然后里面是

key value(例子: `key1:"value1"` `key2:"value2"`)

(取标签的时候key肯定是已知的,通过协议定制的)

结构体标签在json中的运用

发现json解析出来的结果就是我们刚才标签里面设置的内容

13. goroutine

创建goroutine(直接 go 一个方法就可以了,和进程线程一样,当main goroutine结束的时候,子goroutine也结束)

在一个 go routine 里面提前退出

14. channel(专门用来提供goroutine直接相互通信的)(类似之前的IPC通信)

1. channel的定义

2. channal有缓冲与无缓冲同步问题

(make(chan int,3)这里的3如果没有就是没有缓冲)

对于下面的打印顺序我们是无法保证的,但是"子go程结束"一定是"num=0"之后

3. channal的关闭特点

这里没有对channal进行关闭,导致goroutine执行完之后报错了

4. channal和range

这个代码就是,range尝试去c中读数据,c中如果有数据,就等待c的结果,有了就给data,然后进行一次for循环,等待 goroutine close 之后,和退出for循环

5. channal与select(相当于多路转接)

几个case,就是监听几个channal

15. Go Modules模式

GOPATH工作模式有一些弊端,下载使用Go Modules模式

Go Modules模式基础环境说明(go 1.11以上才可以用)

go mod 命令

go env -w GO111MODULE=on(设置之后go mod 模式就会生效)

(之前导包需要手动下载,现在有go mod之后只需要在代码中写上import的github路径,就会自动在github上面帮我们下载这个包,GPPOXY就代表我们从哪个第三方托管平台去下载)

go env -w GOPROXY=https://goproxy.cn,direct(博主设置的七牛云的/export GOSUMDB=sum.golang.org)(多了个direct是指:当当前配置的地方拉取不到需要的包,就会指示go重定向到源地址去抓取)

GoModules 初始化项目

(如果是用原来的方法导入,就需要cd $GOPATH/src ,到指定位置 go get .../name)

首先在需要go mod 的模块进行初始化

go mod init github.com/sder/moudles_test(后面跟的这个名称就是后面其他人使用的时候 import导入的名称)(生成了go.mod就说明初始化成功了,然后就可以正常写代码了)

Go 复制代码
package main

import (
	"fmt"
	"github.com/aceld/zinx/ziface"
	"github.com/aceld/zinx/znet"
)

// PingRouter MsgId=1 
type PingRouter struct {
	znet.BaseRouter
}

//Ping Handle MsgId=1
func (r *PingRouter) Handle(request ziface.IRequest) {
	//read client data
	fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
}

func main() {
	//1 Create a server service
	s := znet.NewServer()

	//2 configure routing
	s.AddRouter(1, &PingRouter{})

	//3 start service
	s.Serve()
}

就像博主这样,导入了两个包,里面有很多这两个包的方法,直接运行是不可以的,然后我们现在只需要:

把需要用到的,也就是代码里面 import 的

go get github.com/aceld/zinx/ziface

go get github.com/aceld/zinx/znet

导入之后我们的gomod就多了我们需要的包以及版本信息(indirect表示间接依赖,就是说只用了需要包的一部分,并不是需要用到整个包),同时还多了一个go.sum文件

对于go.sum里面,h1:加上hash值代表hash了整个包,验证整个包的完整性

go.mod加上hash,表示的是这个mod文件的hash值

而我们现在的库是给我们下载到了 $GOPATH/pkg/mod

这个文件夹里面就有当前的模块名

现在这种方式就可以在任何地方不用一定把当前项目放在 $GOPATH/src 路径中,现在只需要把go mod 加上 ,指定使用哪个包,然后 go get 包就可以了

修改项目mudule指定版本的依赖关系

16. Go项目------及时通信系统

main.go

Go 复制代码
package main


func main(){
	server := NewServer("127.0.0.1",8080)
	server.Start()
}

user.go

Go 复制代码
package main

import (
	"net"
	"strings"
)

type User struct{
	Name string
	Addr string
	C chan string //用来接收广播消息
	conn net.Conn //socket通信的连接

	server *Server//当前用户所属的server
}

//创建一个用户的API
func NewUser(conn net.Conn,server *Server) *User{
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name: userAddr,
		Addr: userAddr,
		C: make(chan string),
		conn: conn,
		server: server,
	}

	//启动当前user channal消息的goroutine
	go user.ListenMessage()

	return user
}

//用户上线的业务
func (this *User) Online(){
	//用户上线,将用户加入到onlineMap中
	this.server.mapLock.Lock()
	this.server.OnLineMap[this.Name] = this
	this.server.mapLock.Unlock()

	//广播用户上线消息
	this.server.BroadCast(this, "已上线")
}

//用户下线的业务
func (this *User) Offline(){
	//用户下线,将用户从onlineMap中删除
	this.server.mapLock.Lock()
	delete(this.server.OnLineMap, this.Name)
	this.server.mapLock.Unlock()

	//广播用户下线消息
	this.server.BroadCast(this, "已下线")
}

//给当前User对应的客户端发送消息
func (this *User) SendMsg(msg string){
	this.conn.Write([]byte(msg))
}

//用户发送消息的业务
func (this *User) DoMessage(msg string){
	if msg == "who"{
		//查询当前在线用户
		this.server.mapLock.Lock()
		for _, user := range this.server.OnLineMap{
			onlineMsg := "[" + user.Addr + "]" + user.Name + ": 在线...\n"
			this.SendMsg(onlineMsg)
		}
		this.server.mapLock.Unlock()
	}else if len(msg) > 7 && msg[:7] == "rename|"{//rename|张三
		//消息格式:rename|张三
		newName := strings.Split(msg, "|")[1]
		//判断name是否存在
		_, ok := this.server.OnLineMap[newName]
		if ok{
			this.SendMsg("当前用户名被使用\n")
		}else{
			this.server.mapLock.Lock()
			delete(this.server.OnLineMap, this.Name)
			this.server.OnLineMap[newName] = this
			this.server.mapLock.Unlock()

			this.Name = newName
			this.SendMsg("您已经更新用户名:" + this.Name + "\n")
		}
	}else if len(msg) > 4 && msg[:3] == "to|"{
		//消息格式:to|张三|消息内容
		remoteName := strings.Split(msg, "|")[1]
		if remoteName == ""{
			this.SendMsg("消息格式不正确,请使用\"to|张三|消息内容\"格式\n")
			return
		}
		//根据用户名得到对方User对象
		remoteUser, ok := this.server.OnLineMap[remoteName]
		if !ok{
			this.SendMsg("该用户名不存在\n")
			return
		}
		//获取消息内容,通过对方的User对象将消息内容发送过去
		content := strings.Split(msg, "|")[2]
		if content == ""{
			this.SendMsg("无消息内容,请重发\n")
			return
		}
		remoteUser.SendMsg(this.Name + "对您说:" + content + "\n")
	}else{
		this.server.BroadCast(this, msg)
	}
}

//监听当前User channal的方法,一旦有消息,就直接发送给对端客户端
func (user *User) ListenMessage(){
	for{
		msg := <- user.C
  		user.conn.Write([]byte(msg + "\n"))
	}
}

server.go

Go 复制代码
package main

import(
	"fmt"
	"net"
	"sync"
	"time"
)

type Server struct{
	Ip string
	Port int

	//在线用户列表
	OnLineMap map[string]*User
	mapLock sync.RWMutex

	//消息广播的channel
	Message chan string
}

//创建一个server的接口
func NewServer(ip string, port int) *Server{
	server := &Server{
		Ip: ip,
		Port: port,

		OnLineMap: make(map[string]*User),
		Message: make(chan string),
	}
	return server
}  

//监听Message广播消息channal的goroutine,一旦有消息就发送给全部的在线User
func (this *Server)ListenMessage(){
	for{
		msg := <-this.Message
		//将msg发送给全部的在线User
		this.mapLock.Lock()
		for _,cli := range this.OnLineMap{
			cli.C <- msg
		}
		this.mapLock.Unlock()
	}
}

//广播用户上线消息的方法
func (this *Server) BroadCast(user *User, msg string){
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg

	this.Message <- sendMsg
}



func (this *Server) Handler(conn net.Conn){
	//fmt.Println("connect success")

	user := NewUser(conn,this)

	user.Online()

	//监听用户是否活跃的chananl
	isLive := make(chan bool)

	//接受客户端发送的消息
	go func(){
		buf := make([]byte, 4096)
		for{
			n, err := conn.Read(buf)
			if n == 0{
				user.Offline()
				return
			}
			if err != nil && n == 0{
				fmt.Println("conn read error", err)
				return
			}

			//提取用户的消息,去除'\n'
			msg := string(buf[:n-1])
			
			//用户针对msg进行消息处理
			user.DoMessage(msg)

			//用户的任意消息,代表当前用户是活跃的
			isLive<-true
		}
	}()
	//当前handler阻塞
	for{
		select {
			case<-isLive:
				//当前用户是活跃的,应该重置定时器
				//不做任何事情,为了激活select,更新下面的定时器
			case <-time.After(time.Second*5):
				//用户已经超时
				//当前的User应该强制关闭	
				user.SendMsg("你被踢了")
				//销毁用的资源
				close(user.C)
				//关闭连接
				conn.Close()
				//退出当前的Handler

				return//runtime.Goexit()
		}
	}
}

//启动服务器的方法
func (this *Server) Start(){
	//socket listen
	listener,err := net.Listen("tcp",fmt.Sprintf("%s:%d",this.Ip,this.Port))
	if err != nil{
		fmt.Println("listen error", err)
		return
	}
	//Close listener socket
	defer listener.Close()

	//启动监听的goutine
	go this.ListenMessage()	

	//accept
	for{
		conn, err := listener.Accept()
		if err != nil{
			fmt.Println("accept error", err)
			continue
		}
		//do handler
		go this.Handler(conn)
	}
}

client.go

Go 复制代码
package main

import (
	"fmt"
	"net"
	"flag"
	"io"
	"os"
)

type Client struct{
	ServerIp string
	ServerPort int
	Name string
	conn net.Conn
	flag int//当前client的模式
}

//创建客户端
func NewClient(serverIp string,serverPort int) *Client{
	//创建客户端对象
	client := &Client{
		ServerIp: serverIp,
		ServerPort:serverPort,
		flag:999,
	}

	//链接server	
	conn,err := net.Dial("tcp",fmt.Sprintf("%s:%d",serverIp,serverPort))
	if err!=nil{
		fmt.Println("net Dial error:",err)
		return nil
	}

	client.conn =conn

	//返回对象
	return client
}

//处理server回应的消息,直接显示在标准输出
func (client *Client) DealResponse(){
	//一旦client.conn有数据,就直接copy到stdout标准输出上,永久阻塞监听
	io.Copy(os.Stdout,client.conn)
	//等价于
	//for{				
	//	buf := make([]byte,4096)
	//	n,err := client.conn.Read(buf)
	//	fmt.Println(string(buf[:n]))
	//}
}

func (client *Client) menu()bool{
	var flag int

	fmt.Println("1.公聊模式")
	fmt.Println("2.私聊模式")
	fmt.Println("3.更改用户名")
	fmt.Println("0.退出")

	fmt.Println("请输入指令:")
	fmt.Scanln(&flag)

	if flag >= 0 && flag <= 3{
		client.flag = flag
		return true
	}else{
		fmt.Println(">>>请输入正确的指令")
		return false
	}

}

//查询在线用户
func (client *Client) SelectUsers(){
	sendMsg := "who\n"
	_,err := client.conn.Write([]byte(sendMsg))
	if err!=nil{
		fmt.Println("conn Write error:",err)
		return
	}
}

//私聊模式
func (client *Client) PrivateChat(){
	client.SelectUsers()

	fmt.Println(">>>请输入聊天对象[用户名],exit退出:")
	var remoteName string
	fmt.Scanln(&remoteName)

	for remoteName != "exit"{
		fmt.Println(">>>请输入消息内容,exit退出:")
		var chatMsg string
		fmt.Scanln(&chatMsg)
		for chatMsg != "exit"{
			//消息不为空
			if len(chatMsg) != 0{
				//消息不为空
				sendMsg := "to|" + remoteName + "|" + chatMsg + "\n"
				_,err := client.conn.Write([]byte(sendMsg))
				if err!=nil{
					fmt.Println("conn Write error:",err)
					break
				}
			}
			
			chatMsg = ""
			fmt.Println(">>>请输入消息内容,exit退出:")
			fmt.Scanln(&chatMsg)
		}
		client.SelectUsers()
		fmt.Println(">>>请输入聊天对象[用户名],exit退出:")
		fmt.Scanln(&remoteName)
	}
}

//共聊模式
func (client *Client) PublicChat(){
	//提示用户输入消息
	var chatMsg string
	fmt.Println(">>>请输入聊天内容,exit退出")
	fmt.Scanln(&chatMsg)
	for chatMsg != "exit"{
		//发送给服务器
		if len(chatMsg) != 0{
			_,err := client.conn.Write([]byte(chatMsg + "\n"))
			if err!=nil{
				fmt.Println("conn Write error:",err)
				break
			}
		}
	
		chatMsg = ""
		fmt.Println(">>>请输入聊天内容,exit退出")
		fmt.Scanln(&chatMsg)
	}
}

//客户端版的更新用户名
func (client *Client) UpdateName()bool{
	fmt.Println(">>>请输入用户名:")
	fmt.Scanln(&client.Name)

	sendMsg := "rename|" + client.Name + "\n"
	_,err := client.conn.Write([]byte(sendMsg))
	if err!=nil{
		fmt.Println("conn Write error:",err)
		return false
	}

	return true
} 

func (client *Client) Run(){
	for client.flag != 0{
		//判断当前客户端的模式如果非法输入就一直死循环
		for client.menu() != true{
		}
		switch client.flag{
		case 1:
			//公聊模式
			fmt.Println(">>>公聊模式")
			client.PublicChat()
			break
		case 2:
			//私聊模式
			fmt.Println(">>>私聊模式")
			client.PrivateChat()
			break
		case 3:
			//更改用户名
			fmt.Println(">>>更改用户名")
			client.UpdateName()
			break
		}
	}
}

var serverIp string
var serverPort int

//./client -ip 127.0.0.1 -port 8080
//init操作就是把这两个形参绑定到flag这个包中
func init(){
	//flag库运行我们绑定命令行参数 第二个参数就是上面的 -ip 第三个是默认值 
	//第四个是用户输入 ./client -h(help)的时候的提升
	flag.StringVar(&serverIp,"ip","127.0.0.1","设置服务器IP地址(默认为127.0.0.1)")
	flag.IntVar(&serverPort,"port",8080,"设置服务器端口(默认是8080)")
}

func main(){
	//命令行解析
	flag.Parse()

	client := NewClient(serverIp,serverPort)
	if client == nil{
		fmt.Println(">>>连接服务器失败....")
		return
	}

	//单独开启一个goroutine处理server的回执消息
	go client.DealResponse()

	fmt.Println(">>>连接服务器成功...")

	//启动客户端的业务
	client.Run()
}

本文根据小破站作者 刘丹冰Aceld 8小时专职Go 所著

在此感谢该博主讲的很棒!

最后的最后,创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖

相关推荐
湫ccc15 分钟前
《Python基础》之字符串格式化输出
开发语言·python
Red Red18 分钟前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust