Go

Go Web 编程

使用 Go 语言创建 Web 应用

Posted by Jerry Chen on July 22, 2021

实践内容参考 build-web-application-with-golang

一些规划

GOPATH 目录

$GOPATH 目录下有三个子目录:

  • src 存放源代码(如 .go .c .h .s 等);
  • pkg 存放编译后生成的文件(如 .a);
  • bin 存放编译后生成的可执行文件;

代码目录结构

$GOPATH 下的 src 目录是接下来开发程序的主要目录,所有的应用包或可执行应用都会在下面新建一个项目目录,比如 $GOPATH/src/demo;

  • 应用包示例:
    • 目录:$GOPATH/src/demo_pack;
    • 文件:$GOPATH/src/demo_pack/xxx.go 中有 package demo_pack,一般建议 package 名称和目录名称保持一致;
  • 可执行应用示例:
    • 目录:$GOPATH/src/demo_elf;
    • 文件:$GOPATH/src/demo_elf/xxx.go 中有 package main

了解 Go 语言的两种包管理方式

[推荐] gomod 方式

使用 go mod 管理项目,就不需要强制把项目放到 $GOPATH/src 目录下,可以在磁盘的任何位置新建应用包(应用包目录中需要有 go.mod 文件指明依赖路径);

开启方法:

给 GO111MODULE 赋值为 off 即使用 gopath 方式;

1
go env -w GO111MODULE=on
gopath 方式

网上有很多教程都是使用的这种方式,所以没有 go mod init xxx 的操作,也不需要 go.mod 文件。特点是项目需要强制放在 $GOPATH/src 目录下;

开启方法:

1
go env -w GO111MODULE=off

搭建一个简单工程

下面的工程都以 gomod 为包管理方式进行管理;

新建一个应用包 mymath

  1. 在 $GOPATH/src 目录下新建目录 mymath;

    1
    2
    
    cd $GOPATH/src
    mkdir mymath
    
  2. 在 mymath 中新建文件 sqrt.go,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // $GOPATH/src/mymath/sqrt.go 源码
    // 一般模块包建议 package xxx 中 xxx 和目录名称保持一致
    package mymath
       
    func Sqrt(x float64) float64{
        z := 0.0
        for i:= 0; i < 1000; i++ {
            z -= (z * z - x)/(2 * x)
        }
        return z
    }
    
  3. 编译安装应用 mymath,只需在应用包目录执行:

    1
    2
    3
    4
    5
    
    # 创建 go.mod 文件(模块名默认是当前文件夹名称,可以使用 go mod init xxx 指定其他名称)
    go mod init
       
    # 进行安装
    go install
    

    安装完成后就可以在 $GOPATH/pkg/${GOOS}_${GOARCH} 目录找到 mymath.a 文件:

新建可执行应用包 mathapp

  1. 在 $GOPATH/src 目录下新建目录 mathapp;

    1
    2
    
    cd $GOPATH/src
    mkdir mathapp
    
  2. 在 mathapp 中新建文件 main.go,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // $GOPATH/src/mathapp/main.go 源码
    package main
       
    import(
    	"mymath"
        "fmt"
    )
       
    func main() {
        fmt.Printf("Hello, world. Sqrt(2)=%v\n", mymath.Sqrt(2))
    }
    
  3. 编译安装应用 mathapp,只需在应用包目录执行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # 创建 go.mod 文件(模块名默认是当前文件夹名称,可以使用 go mod init xxx 指定其他名称)
    go mod init
    # 编辑目标依赖路径
    go mod edit -replace mymath=../mymath
    # 处理依赖(下载依赖项和剔除未用依赖)
    go mod tidy
       
    # 进行安装
    go install
    

    安装完成后就可以在 $GOPATH/bin 目录找到 mathapp 可执行文件,运行效果如下:

Go 语言基础

定义变量

定义单个变量:

1
2
// 定义一个名称为 variableName,类型为 type 的变量
var variableName type

定义多个变量:

1
2
// 定义三个类型都是 type 的变量
var vname1, vname2, vname3 type

定义变量并初始化:

1
2
// 定义一个名称为 variableName,类型为 type 的变量,初始化值为 value
var variableName type = value

同时定义并初始化多个变量:

1
2
3
4
5
/*
  定义三个类型都是 type 的变量,并分别进行初始化
  vname1 初值为 v1,vname2 初值为 v2,vname3 初值为 v3
*/
var vname1, vname2, vname3 type = v1, v2, v3

简化版本 1,不写类型,让编译器推导:

1
2
3
4
5
6
/*
  定义三个变量,并分别进行初始化
  vname1 初值为 v1,vname2 初值为 v2,vname3 初值为 v3
  编译器会根据值自动推导类型
*/
var vname1, vname2, vname3 = v1, v2, v3

简化版本 2,不写 var、type,将 = 改为 :=

:= 取代 var 和 type 的形式叫做简短声明;

1
2
3
4
5
6
/*
  定义三个变量,并分别进行初始化
  vname1 初值为 v1,vname2 初值为 v2,vname3 初值为 v3
  编译器会根据值自动推导类型
*/
vname1, vname2, vname3 := v1, v2, v3

定义常量

所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。

1
2
3
4
5
6
7
8
// 一般不需要指定常量的类型
const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"

// 如果需要,也可以明确指定常量的类型
const Pi float32 = 3.1415926

基础类型

Boolean
1
2
3
4
5
6
7
var isActive bool  // 全局变量声明
var enabled, disabled = true, false  // 忽略类型的声明
func test() {
	var available bool  // 一般声明
	valid := false      // 简短声明
	available = true    // 赋值操作
}
数值类型

Go 同时支持 intuint,也有直接定义好位数的类型:rune, int8, int16, int32, int64byte, uint8, uint16, uint32, uint64。其中 runeint32 的别称,byteuint8 的别称。

字符串

Go 中字符串都采用 UTF-8 字符集进行编码。字符串用一对双引号("")或反引号括起来定义,它的类型是 string

1
2
3
4
5
6
7
var frenchHello string  // 声明变量为字符串的一般方法
var emptyString string = ""  // 声明了一个字符串变量,初始化为空字符串
func test() {
	no, yes, maybe := "no", "yes", "maybe"  // 简短声明,同时声明多个变量
	japaneseHello := "Konichiwa"  // 同上
	frenchHello = "Bonjour"  // 常规赋值
}

修改字符串的方法:

1
2
3
4
5
s := "hello"
c := []byte(s)  // 将字符串 s 转换为 []byte 类型
c[0] = 'c'
s2 := string(c)  // 再转换回 string 类型
fmt.Printf("%s\n", s2)

使用 + 操作符连接两个字符串:

1
2
3
4
s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)

修改字符串的方法:

1
2
3
s := "hello"
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
fmt.Printf("%s\n", s)
错误类型

Go 内置有一个 error 类型,专门用来处理错误信息,Go 的 package 里面还专门有一个包 errors 来处理错误:

1
2
3
4
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
	fmt.Print(err)
}

一些技巧

分组声明

原始代码:

1
2
3
4
5
6
7
8
9
10
import "fmt"
import "os"

const i = 100
const pi = 3.1415
const prefix = "Go_"

var i int
var pi float32
var prefix string

分组形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import(
	"fmt"
	"os"
)

const(
	i = 100
	pi = 3.1415
	prefix = "Go_"
)

var(
	i int
	pi float32
	prefix string
)
iota 枚举

Go 里面有个关键字 iota,是一个数字,初值是 0(每遇到一个 const 关键字重置为 0),每增加一行它的值会加 1;

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
package main

import (
	"fmt"
)

const (
	x = iota // x == 0
	y = iota // y == 1
	z = iota // z == 2
	w        // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
)

const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0

const (
	h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)

const (
	a       = iota //a=0
	b       = "B"
	c       = iota             //c=2
	d, e, f = iota, iota, iota //d=3,e=3,f=3
	g       = iota             //g = 4
)

func main() {
	fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}

明天继续补充~