从实战到放弃:Golang编程之路

发表时间: 2023-02-23 16:42

golang是世界上最好的语言【呸!啥也不是】

开发环境配置

设置环境变量

如果vs code安装插件下载失败,配置一下go环境变量,设置proxy

go env -w GOPROXY=https://goproxy.io,directgo env -w GO111MODULE=on

查看go环境变量

go env

所有go的环境变量

set GO111MODULE=onset GOARCH=amd64set GOPATH=C:\Users\Administrator\goset GOPRIVATE=set GOPROXY=https://goproxy.io,directset GOROOT=c:\goset GOSUMDB=sum.golang.org……

第一个go web程序

新建main.go

输入helloweb按回车

package mainimport (    "fmt"    "net/http"    "time")func greet(w http.ResponseWriter, r *http.Request) {    fmt.Fprintf(w, "Hello World! %s", time.Now())}func main() {    http.HandleFunc("/", greet)    http.ListenAndServe(":8080", nil)}

命令行执行go run main.go

然后命令行访问服务

curl http://localhost:8080

得到以下的结果,那么第一个golang web就执行成功了

StatusCode        : 200StatusDescription : OKContent           : Hello World! 2022-02-09 20:25:07.5051433 +0800 CST m=+55.242234801

restful接口定义

导入依赖

go mod init  example/web-service-gin

产生模块管理文件

module example/web-service-gingo 1.15

main.go导入github.com/gin-gonic/gin

import (  "net/http"  "github.com/gin-gonic/gin")

执行

go get .

go.mod(类似nodejs的package.json)

module example/web-service-gingo 1.15require github.com/gin-gonic/gin v1.7.7

自动扫码导入依赖同时会发现目录产生了go.sum文件,其类似nodejs的package.json.lock文件

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=……

编写代码

package mainimport (	"net/http"	"github.com/gin-gonic/gin")type album struct {	ID     string  `json:"id"`	Title  string  `json:"title"`	Artist string  `json:"artist"`	Price  float64 `json:"price"`}var albums = []album{	{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},	{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},	{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},}func getAlbums(ctx *gin.Context) {    //返回带缩减的JSON数据	ctx.IndentedJSON(http.StatusOK, albums)}func main() {	router := gin.Default()    router.GET("/albums", getAlbums)//method:get	router.POST("/albums", getAlbums)//method:get	router.Run("localhost:8080")}

拆分文件

把GetAlbums放到controller,新建controller目录,这也是package的名称,新建albumController.go文件

package controllerimport (	"example/web-service-gin/models"	"net/http"	"github.com/gin-gonic/gin")func GetAlbums(ctx *gin.Context) {	albums := []models.Album{		{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},		{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},		{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},	}	ctx.IndentedJSON(http.StatusOK, albums)}

把Albums结构体独立到models的album.go,go导出方法和结构,都是通过首字母大写的是公开的,其他的不导出,所以album改成Album

package modelstype Album struct {	ID     string  `json:"id"`	Title  string  `json:"title"`	Artist string  `json:"artist"`	Price  float64 `json:"price"`}

此时main.go就变成了这样子

package mainimport (	"example/web-service-gin/controller"	"github.com/gin-gonic/gin")func main() {	router := gin.Default()	router.GET("/albums", controller.GetAlbums)	router.POST("/albums", controller.GetAlbums)	router.Run("localhost:8080")}

单元测试

安装依赖

golang的版本有要求,go install 1.15.x的版本没有对go install 的支持,建议升级到最新的版本

go install golang.org/dl/go1.18beta1@latest

单应测试样例

创建test文件夹,创建api_test.go,注意名称要以_test结尾

package testimport (	"bytes"	"encoding/json"	"fmt"	"io/ioutil"	"net/http"	"testing")func TestApi(t *testing.T) {	res, err := http.Get("http://localhost:8080/albums")	if err != nil {		panic(err)	}	//defer 在作用域内最后执行	defer res.Body.Close()	result, _ := ioutil.ReadAll(res.Body)	fmt.Println(string(result))}func TestPost(t *testing.T) {	data, _ := json.Marshal(struct{ Name, Age string }{})	res, err := http.Post("http://localhost:8080/albums", "application/json", bytes.NewBuffer(data))	if err != nil {		panic(err)	}    	defer res.Body.Close()	result, _ := ioutil.ReadAll(res.Body)	fmt.Println(string(result))}

mysql访问

package dbimport (	"database/sql"	"example/web-service-gin/models"	"fmt"	"log"	"github.com/go-sql-driver/mysql")func QueryData () (albumArr []models.Album,queryErr error){	var db *sql.DB;	cft :=mysql.Config{		User:"root",		Passwd: "123456",        Net:    "tcp",        Addr:   "127.0.0.1:3306",        DBName: "recodings",		AllowNativePasswords:true,	}    db, err := sql.Open("mysql", cft.FormatDSN())    if err != nil {        log.Fatal(err)    }	pingErr := db.Ping()    if pingErr != nil {        log.Fatal(pingErr)    }    fmt.Println("Connected!")	rows,dbErr:= db.Query("select * from album");	if(dbErr!=nil){	}	defer rows.Close()	var albums []models.Album	for rows.Next() {		var alb models.Album         if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {            return nil, nil        }        albums = append(albums, alb)	}	return albums,nil}

单元测试

package testimport (	"encoding/json"	"example/web-service-gin/db"	"fmt"	"testing")func TestDB(t *testing.T) {	albums, err := db.QueryData()	if err != nil {		fmt.Println(err)	}	data, _ := json.MarshalIndent(albums, "", "	")	fmt.Println(string(data))}

得到下面的结果

=== RUN   TestDBConnected![        {                "id": "1",                "title": "Blue Train",                "artist": "John Coltrane",                "price": 56.99        },        {                "id": "2",                "title": "Giant Steps",                "artist": "John Coltrane",                "price": 63.99        },        {                "id": "3",                "title": "Jeru",                "artist": "Gerry Mulligan",                "price": 17.99        },        {                "id": "4",                "title": "Sarah Vaughan",                "artist": "Sarah Vaughan",                "price": 34.98        }]--- PASS: TestDB (0.00s)PASSok      example/web-service-gin/test    (cached)

接口

接口定义

package interfacedemoimport "example/web-service-gin/models"type IAlbumService interface {	GetAlbum() []models.Album}

实现接口,AlbumService在代码上,不需要引入IAlbumService,只要AlbumService实现了所有的方法即代表AlbumService实现了接口IAlbumService(目前来看,接口没有字段的定义),如果没实现所有的方法编译器将会提示declaration: missing method GetAlbum

package interfacedemoimport "example/web-service-gin/models"type AlbumService struct {}func (service *AlbumService) GetAlbum() []models.Album {	return []models.Album{{ID: "10", Title: "Blue Train", Artist: "John Coltrane", Price: 100.99}}}

调用

package testimport (	"encoding/json"	"example/web-service-gin/interfacedemo"	"example/web-service-gin/models"	"fmt"	"testing")func TestInterface(t *testing.T) {    //接口接收实现的实例	var service interfacedemo.IAlbumService =new(interfacedemo.AlbumService)	var albums []models.Album = service.GetAlbum()	data, _ := json.MarshalIndent(albums, "", "	")	fmt.Println("data:", string(data))}

指针

&取址操作,*声明指针类型的变量或者取指针变量指向的值

num := 10//*声明指针的变量var numPtr *int = nil//&取num变量的地址numPtr = &num//*取指针变量numPtr指向的值ptrValue := *numPtr

指针是强类型的,不匹配的类型不能赋值,比如下面的语句就是错误的

var floatPtr *float32=numPtr

不过对于struct的赋值,具有隐式转换,比如下面的例子

type IPointer interface{GetPoint()}type Pointer struct {}func (pointer Pointer) GetPoint() {}func main() {    var pt *Pointer = new(Pointer)	var pt1 IPointer = *point	var pt2 IPointer = point        var pointer Pointer = Pointer{}	var pointer1 IPointer = &pointer	var pointer2 IPointer = pointer}	

如果是结构体本身,那么不存在隐式转换

var point *Pointer = new(Pointer)var point1 Pointer = *point//正确var point2 *Pointer = point//正确var point3 Pointer = point//错误

再看下面的例子

package interfacedemoimport "example/web-service-gin/models"type IAlbumService interface {	GetAlbum() []models.Album}

AlbumService和AlbumServiceImpl实现了接口IAlbumService

package interfacedemoimport "example/web-service-gin/models"type AlbumService struct {}// 通过AlbumService实现方法GetAlbum,通过该方式实现,指针(隐式转换)和实例均可给接口进行赋值func (service AlbumService) GetAlbum() []models.Album {	return []models.Album{{ID: "10", Title: "Blue Train", Artist: "John Coltrane", Price: 100.99}}}type AlbumServiceImpl struct{}// 通过AlbumServiceImpl的指针类型实现GetAlbum,只能通过指针给接口进行赋值func (service *AlbumServiceImpl) GetAlbum() []models.Album {	return []models.Album{{ID: "10", Title: "Blue Train Impl", Artist: "John Coltrane", Price: 100.99}}}

通过以下的方式调用

var service *interfacedemo.AlbumService = new(interfacedemo.AlbumService)var service1 interfacedemo.IAlbumService = service//正确,隐式转换service1 = *service//正确,AlbumService实例实现了GetAlbum,比较规范的写法var instance interfacedemo.AlbumService = interfacedemo.AlbumService{}var instance1 interfacedemo.IAlbumService = service//正确,AlbumService实例实现了GetAlbum,比较规范的写法instance1 = &instance//正确,隐式转换var serviceImpl interfacedemo.AlbumServiceImpl = interfacedemo.AlbumServiceImpl{}var serviceImpl1 interfacedemo.IAlbumService = &serviceImpl//正确	serviceImpl1 = serviceImpl//错误,AlbumServiceImpl的实例并没有实现GetAlbumvar implInstance *interfacedemo.AlbumServiceImpl = new(interfacedemo.AlbumServiceImpl)var implInstance1 interfacedemo.IAlbumService = implInstance//正确	implInstance1 = *implInstance//错误,AlbumServiceImpl的实例并没有实现GetAlbum

接口本身声明指针类型是可以的,除了赋值为nil,没办法实例化

var service *interfacedemo.IAlbumService =nil//正确    service = new(interfacedemo.AlbumService)//错误

会抛异常:cannot use new(
interfacedemo.AlbumService) (value of type *
interfacedemo.AlbumService) as *
interfacedemo.IAlbumService value in assignment

golang的指针类型跟C++的有所不同

type Pointer struct {}func main() {	var instance Pointer = Pointer{}	fmt.Println(instance,instancePtr,&instancePtr)}

得到的结果是

{} &{} 0xc000006028

&instance并不是得到一个地址,更像一个表示对instance取址的操作,&instancePtr则是instancePtr的地址,也就是指针的指针

那如果输出修改成

fmt.Println(instance,&instance,&(&instance))

&(&instance)编译器报错:invalid operation: cannot take address of (&instance) (value of type *Pointer)

&instancePtr与&(&instance)不相同,&(&instance)这样并没有给指向&instance分配地址

值得注意的是golang的结构体是值类型的,如果需要修改结构体的字段的值,需要用指针,下面来看一下这个例子

package mainimport (	"fmt")type Vertex struct {	X, Y int}func changeVertex(ver Vertex) {	ver.X = 100	ver.Y = 100}func changeVertexByPointer(ver *Vertex) {    (*ver).X = 100//*号优先级是低于.的,所以要加上(),当然,直接ver.X也是可以的	ver.Y = 100}func main() {	ver := Vertex{X: 10, Y: 10}	changeVertex(ver)	fmt.Println(ver.X, ver.Y)	changeVertexByPointer(&ver)	fmt.Println(ver.X, ver.Y)}

得到的结果是

10 10100 100

换一种写法

package mainimport (	"fmt")type Vertex struct {	X, Y int}func (ver Vertex) changeVertex() {	ver.X = 100	ver.Y = 100}func (ver *Vertex) changeVertexByPointer() {	(*ver).X = 100	ver.Y = 100}func main() {    //实例调用	ver := Vertex{X: 10, Y: 10}	ver.changeVertex()	fmt.Println(ver.X, ver.Y)	    //指针调用	(&ver).changeVertexByPointer()	fmt.Println(ver.X, ver.Y)}

通常情况下,建议使用指针,除非为了要避免修改原来实例的值。

值传递,尤其是复杂的结构体,会做拷贝处理,使用指针性能相对会更好。

接口实例化

package mainimport (	"fmt")type Vertex struct {	X, Y int}type IVertex interface {	GetResult() int}func (ver Vertex) GetResult() int {	var result int=ver.X + ver.Y	fmt.Println("result:",result)	return result}func main() {	var iv IVertex	//iv.GetResult()//这一句会在运行时抛异常:panic: runtime error: invalid memory address or nil pointer dereference	var v Vertex	iv = v    fmt.Println(iv,v)//打印{0 0}{0 0}	iv.GetResult()//结果是0	iv = Vertex{X: 10, Y: 10}	iv.GetResult()//结果20}

接口类型判断

var typeVar interface{} = "hello"t, result := typeVar.(string)fmt.Println(t, result)// hello true

typeVar.(string)如果类型是对的,第二个参数可以不写,得到的是实际的值,如果类型不正确,比如t:=typeVar.(string),将会抛异常interface conversion: interface {} is string, not int,如果t,result:=typeVar.(string),t则是0,result是false

switch case类型

var typeVar interface{} = nilswitch t := typeVar.(type) {    case int:    fmt.Println("int:", t)    default:    fmt.Printf("%T", t)}

切片

在了解切片之前,先来看看数组,切片是基于数组定义的。

package mainimport "fmt"func main() {	arr:=[6]int{1,2,3,4,5,6}	fmt.Println(arr);}

数组是个定长的,如果初始化的元素个数少于6个,那么会自动补上零值,多于6个会提示越界

数组完整的声明写法

var arr [6]int=[6]int{1,2,3,4,5,6}

那么切片是什么?切片是基于对数组的引用,slice是个结构体,注意slice本身是值传递的

type slice struct {	array unsafe.Pointer	len   int	cap   int//默认容量于len相等}

从这里可以看出,切片修改元素,那么对应的数组的值也跟着修改,多个基于这个数组的切片修改同一个元素,全部都会被修改,下面来看看以下的例子

package mainimport "fmt"func main() {	var	arr [6]int=[6]int{1,2,3,4,5}	sliceA:=arr[:]	sliceB:=arr[1:3]	sliceA[1]=10	fmt.Println(sliceB[0])//10	fmt.Println(arr[1])//10}

切片的一些操作

package mainimport "fmt"func main() {	var arr [6]int = [6]int{1, 2, 3, 4, 5, 6}	// 声明切片,arr[:3:5],第一个不写那么值是0,第二个是截取到第(下标-1)的元素,第三个是初始的容量,不写默认是数组的长度,不允许超过数组的长度	slice := arr[:3:5]	fmt.Println(slice, cap(slice), len(slice))//[1 2 3] 5 3	slice = append(slice, 10)//在末尾添加元素,返回一个新的切片	slice = append(slice, 20)	slice = append(slice, 30)	fmt.Println(slice, cap(slice), len(slice))// [1 2 3 10 20 30] 10 6  长度用完,自动扩容}

映射

go的映射实际就是java的map或者是c#的dictionary,定义是map[keyType]valueType

package mainimport (	"fmt")func main() {	var maps map[string]int = map[string]int{		"tome": 20,		"kate": 18,		"tub":  45,	}	for key, value := range maps {		fmt.Println(key, value)	}}

map的一些操作

package mainimport (	"fmt")func main() {    // 初始化	var maps Map = Map{		"tome": 20,		"kate": 18,		"tub":  45,	}	// 存在的key修改	maps["tome"]=33	// 不存在的key新增	maps["heihei"]=43	// 判断是否包含key	value,exists:=maps["join"]	fmt.Println("join is exists:",value,exists)//value是零值	value,exists =maps["kate"]	fmt.Println("kate is exists:",value,exists)//value对应的是key为kate对应的值	for key, value := range maps {		fmt.Println(key, value)	}	fmt.Println("=====================")	delete(maps,"heihei")	for key, value := range maps {		fmt.Println(key, value)	}}

结果

join is exists: 0 falsekate is exists: 18 truekate 18tub 45heihei 43tome 33=====================tome 33kate 18tub 45

泛型

1.18之前的版本是不支持泛型的,beta1开始新增了对泛型的支持

type Number interface {//类似c#里做了泛型约束    int64 | float64}

比如数字相加

package mainimport "fmt"func main() {    intResult:= Add[int](1,1)    floatResult := Add[float64](1.1,1.1)    fmt.Println("1 + 1 =",intResult)    fmt.Println("1.1 + 1.1 =",floatResult)}func Add[T int|float64](num1 T,num2 T) T{    return num1+num2}

得到

1 + 1 = 21.1 + 1.1 = 2.1

泛型类型实际是个接口类型,比如上面的例子可以这样写

package mainimport "fmt"type Number interface{    int|float64}func main() {    intResult:= Add[int](1,1)    floatResult := Add[float64](1.1,1.1)    fmt.Println("1 + 1 =",intResult)    fmt.Println("1.1 + 1.1 =",floatResult)}// Number 替代了int|float64func Add[T Number](num1 T,num2 T) T{    return num1+num2}

这种定义方式实际是限定了泛型的类型,必须显示指出泛型的类型组合。

下面来自定义接口

package mainimport "fmt"type IService interface{	GetServiceName() string}type ServiceT interface{	IService}type UserService struct{Name string}func (service UserService) GetServiceName() string{	return service.Name}type RoleService struct{Name string}func (service RoleService) GetServiceName() string{	return service.Name}func  GetService[ST ServiceT](serviceType ST) string{	return serviceType.GetServiceName()}func main()  {	serviceName:= GetService[UserService](UserService{Name: "userService"})	fmt.Println("服务名称:",serviceName)	serviceName= GetService[RoleService](RoleService{Name: "roleService"})	fmt.Println("服务名称:",serviceName)}

得到

服务名称: userService服务名称: roleService

那ServiceT改成空接口,是否能够调用?目前的版本(1.18beta2)是不支持的

type ServiceT interface{}

ServiceT是空接口会提示.\gen.go:41:14:
serviceType.GetServiceName undefined (type ST has no field or method GetServiceName)

那我们通过反射来看看是否能够找到GetServiceName?

func  GetService[ST ServiceT](serviceType ST) string{	t:= reflect.TypeOf(serviceType)//获取类型	v:=reflect.ValueOf(serviceType)//获取值		methodCount:= t.NumMethod()	for i := 0; i < methodCount; i++ {		methodName:=t.Method(i).Name		fmt.Println(methodName)	 	val:= v.MethodByName(methodName).Call(nil) //调用方法		return val[0].String()//把reflect.value转换为string类型	}	return ""}

得到输出

GetServiceName服务名称: userServiceGetServiceName服务名称: roleService

go程

go程是开启了一个线程,如果主进程结束,那么开启的go程也会跟着结束

package mainimport (	"fmt"	"time")func say(s string,count int) {	for i := 0; i < count; i++ {		time.Sleep(100 * time.Millisecond)		fmt.Println(s)	}}func main() {	go say("world",10)	   say("hello",3) }

输出结果

helloworldworldhellohello

可以看到,当hello输出3次,主进程结束,那么通过go say("world",10)调用的并没有执行完,也跟着结束了

channel

发送和接收操作在另一端准备好之前都会阻塞

信道上的发送操作总在对应的接收操作完成前发生。

若在信道关闭后从中接收数据,接收者就会收到该信道返回的零值。

从无缓冲信道进行的接收,要发生在对该信道进行的发送完成之前。

select

package mainimport (	"fmt"	"time")func main() {    //<-chan time.Time表示该channel只能发送	var tick <-chan time.Time = time.Tick(100 * time.Millisecond)    boom := time.After(500 * time.Millisecond)	for {		select {		case <-tick:			fmt.Println("tick")		case <-boom:			fmt.Println("boom!")			return		default:			fmt.Println("……")			time.Sleep(50 * time.Millisecond)		}	}}

单向channel

package mainimport (	"fmt"	"time")//ch只能接收func receive(ch chan<- string, str string) {	ch <- str}//sender只能发送,receiver只能接收func send(sender <-chan string, receiver chan<- string) {	time.Sleep(time.Second)	receiver <- <-sender}func main() {	var sender chan string = make(chan string)	var receiver chan string = make(chan string)	go receive(sender, "hi,let's go")	go send(sender, receiver)	str := <-receiver	fmt.Println(str)//输出:hi,let's go}

缓冲通道

package mainimport (	"fmt")func main() {	var ch chan string = make(chan string)	ch <- "hello"	str := <-ch	fmt.Println(str)}

以上的代码会抛出异常:fatal error: all goroutines are asleep - deadlock!

发送和接收操作在另一端准备好之前都会阻塞,这时候,无缓冲的通道,会造成死锁

package mainimport (	"fmt")func main() {	var ch chan string = make(chan string,1)	ch <- "hello"	str := <-ch	fmt.Println(str)}