前 言
如果读者已经掌握一门网站开发语言,则本文可以让读者快速地上手Go语言网站开发。
目 录
1. 变量类型和类型转换
2. 流程控制
3. 结构体和函数
4. 接口和package
5. 异常处理
6. 字符串
7. 日期
8. 数组
9. 哈希表
10. 文件访问
11. 网络访问
12. 数据库访问
13. 协程(Goroutine)
1.变量类型和类型转换
1.1基本类型
// 整型
var x int32 = -1
var x uint32 = 1
// 浮点类型
var x float64 = 1.0
// 布尔类型
var x bool = true
// 字符串类型
var x string = "abc"
// UTF-8字符类型
var x string = "你好"
// 变量x的长度为6
len(x)
z := []rune(x)
// 变量z的长度为2
len(z)
1.2复合类型
// 结构体类型
type Rect struct {
w uint32
h uint32
}
x := Rect{
w:50,
h:50,
}
// 接口类型
type BookSearch interface {
do()
}
// 数组类型
x := [5]int{1,2,3,4,5}
// 切片类型
x := []int{1,2,3,4,5}
// 哈希表类型
x := make(map[string]string)
// 指针类型
var x_ptr *int32
x_ptr = &x
1.3数字转为字符串
方式1
y := fmt.Sprintf("%d", 123)
方式2
y := strconv.Itoa(123)
1.4字符串转为数字
方式1
y, error := strconv.Atoi("123")
if error == nil {
fmt.Println(y)
}
方式2
y, error := strconv.ParseFloat("123.1", 64)
if error == nil {
fmt.Println(y)
}
1.5定义常量
const X int32 = 1
1.6查看变量类型
方式1
fmt.Printf("%T", "abc")
方式2
fmt.Println(reflect.TypeOf("abc"))
2.流程控制
2.1 条件语句
if true{
}
else if true{
}
else{
}
2.2 多分支选择语句
每个case分支,不需要break。
x := 3
switch(x){
case 1:
case 2:
default:
}
2.3 循环语句
x := [3]int{1, 2, 3}
for i := 0; i < len(x); i++ {
fmt.Printf("%d,%d\n", i, x[i])
}
2.4 迭代语句
x := [3]int{1, 2, 3}
for index, item := range x {
fmt.Printf("index:%d,item:%d\n", index, item)
}
3.结构体和函数
3.1函数
func myFunc(a int, b int) int{
return a+b
}
3.2结构体
type Rect struct {
w uint32
h uint32
}
rect := Rect {w: 50, h:50}
// 使用“结构体.字段属性”形式访问
rect.w = 100
3.3类的实例方法
type Book struct {
title string
}
func (book Book) setTitleInvalid(title string) {
// 对book的字段title修改无效
book.title = title
}
func (book *Book) setTitle(title string) {
// 让book使用指针,对book的字段title修改才有效
book.title = title
}
func (book Book) getTitle() string {
return book.title
}
func main() {
book := Book{title: "go语言"}
book.setTitle("标题1")
book.setTitleInvalid("标题2")
title := book.getTitle()
// 输出文字“标题1”
fmt.Println(title)
}
3.4 类的静态方法
// 无静态方法,使用普通函数代替。
func area(w uint32, h uint32) uint32{
return w * h
}
fmt.Println(area(50,50))
3.5获取当前对象
Go语言没有其他语言中的this对象。
4.接口和package
4.1定义接口
type BookSearch interface {
do()
}
4.2 使用接口实现类的多态性
type Book struct {
title string
}
type Student struct {
title string
}
// 结构体Book实现接口BookSearch 的方法
func (book Book) do() {
fmt.Println("课本")
}
// 结构体Student实现接口BookSearch 的方法
func (student Student) do() {
fmt.Println("学生")
}
// 声明接口的变量search
var search BookSearch
// 创建Book对象并赋值给search
search = Book{title: "课本"}
// 输出文字“课本”
search.do()
// 创建Student对象并赋值给search
search = Student{title: "学生"}
// 输出文字“学生”
search.do()
4.3定义package
文件名:calc.go
package calc
// 方法Add的首字母大写
func Add(a int32, b int32) int32{
return a + b
}
4.4引用package
1、将被引用package的文件calc.go放置在项目的一个子目录/calc中
项目的目录结构:
/calc/calc.go
/main.go
2、在项目的根目录中运行命令,创建go.mod文件。
go mod init example.com/demo/v1
文件名:/go.mod
module example.com/demo/v1
go 1.16 项目的目录结构变为:
/calc/calc.go
/main.go
/go.mod
3、定义被引用package的名称“calc”
文件名:/calc/calc.go
package calc
// 方法Add的首字母大写
func Add(a int32, b int32) int32{
return a + b
}
4、导入package,形式为"module名称/package的名称”
go.mod文件中的module名称:http://example.com/demo/v1
package的名称:calc
从外部导入的名称:http://example.com/demo/v1/calc
文件名:/main.go
package main
import (
"fmt"
"example.com/demo/v1/calc"
)
func main() {
// 输出内容“3”
fmt.Println(calc.Add(1, 2))
}
5、引用package的常见错误
Q1:提示cannot find package "example/demo/v1/calc"?
A:运行命令设置Go语言的环境变量:go env -w GO111MODULE=on
Q2:提示package is not in GOROOT?
A:引用了Go语言环境变量GOROOT目录之外的package,例如import “calc”和import “calc/calc”是不正确的写法。 正确写法是“module名称/package名称”,例如import "example/demo/v1/calc"。
4.5public和private访问权限
public和private访问权限,是相对不同的package名称来说。
3条规则如下:
1. 相同的package名称,不区分public和private访问权限。
2. 同一个目录中,go文件的package名称都是相同的。
3. 方法和字段以大写字母开头,是public访问权限。以小写字母开头,是private访问权限。
package main
type Rect struct{
// private访问权限的w字段
w uint32
// public访问权限的H字段
H uint32
}
// private访问权限的add方法
func add(a int, b int) int{
return a + b
}
// public访问权限的Sub方法
func Sub(a int, b int) int{
return a - b
}
5. 异常处理
5.1捕获可预见的异常
x:="hello"
number, error := strconv.Atoi(x)
if error == nil {
fmt.Println(number)
}else {
fmt.Println(error)
}
5.2开发者主动抛出异常
x :="hello"
number, error := strconv.Atoi(x)
if error == nil {
fmt.Println(number)
}else {
panic(error) // 主动抛出异常,若上层调用不捕获该异常,则程序退出。
}
5.3捕获不可预见的异常
defer func() {
if err := recover(); err != nil {
// 输出文字“自定义异常”
fmt.Println(err)
}
}()
// 发生异常并跳转到defer func()函数内
panic("自定义异常")
// 该行代码无法被运行
fmt.Println("程序结束")
6.字符串
6.1 ASCII编码的字符数
var x string = "hello world"
z := len(x)
fmt.Println(z)
6.2 UTF-8编码的字符数
var x string = "你好"
z := []rune(x)
// 输出内容“6”
fmt.Println(len(x))
// 输出内容“2”
fmt.Println(len(z))
6.3拼接
x := "hello"
y := x + "world"
fmt.Println(y)
6.4查找
ASCII编码
y := strings.Index("hello", "e")
// 输出内容“1”
fmt.Println(y)
UTF-8编码,查找结果不正确。应该使用其他方法。
z := strings.Index("你好", "好")
// 输出内容“3”
fmt.Println(y)
6.5替换
替换1次
x := "hello hello"
y := strings.Replace(x, "e", "a", 1)
// 输出内容“hallo hello”,将查找结果中的1个替换。
fmt.Println(y)
全部替换
y := strings.Replace(x, "e", "a", -1)
// 输出内容“hallo hallo”,将查找结果全部替换。
fmt.Println(y)
6.6截取
x := "hello"
y := x[1:2]
fmt.Println(y)
6.7分割
x := "hello world"
z := strings.Split(x, " ")
fmt.Println(z)
6.8移除首尾空白
x := " hello world "
z := strings.TrimSpace(x)
fmt.Println(z)
7.日期
7.1获取当前时间
import "time"
now := time.Now()
// 输出内容“2021-01-01 00:00:00 其余部分省略”
fmt.Println(now)
seconds := now.Unix()
// 输出内容“1617958378”(10位数字,精确到秒)
fmt.Println(seconds)
nanoSeconds := now.UnixNano()
// 输出内容“1617958378000000000”(19位数字,精确到纳秒)
fmt.Println(nanoSeconds)
7.2字符串转换为日期类型
now := time.Now()
x := "2021-04-09 17:27:01"
// ParseInLocation方法的第一个输入参数必须是"2006-01-02 15:04:05"
y, error := time.ParseInLocation("2006-01-02 15:04:05", x, time.Local)
if error == nil {
fmt.Println(y)
}
7.3日期类型转换为字符串
日期格式
now := time.Now()
// format字符串必须是"2006-01-02"
date := now.Format("2006-01-02")
fmt.Println(date)
时间格式
// format字符串必须是"2006-01-02 15:04:05"
date := now.Format("2006-01-02 15:04:05")
fmt.Println(date)
7.4两个日期相差的时间
now := time.Now()
x := "2021-04-09 17:27:01"
y, error := time.ParseInLocation("2006-01-02 15:04:05", x, time.Local)
if error == nil {
x := now.Sub(y)
// 相差的多少秒
fmt.Println(x.Seconds())
}
8.数组
8.1创建
指定数组长度
arr := [4]int{1, 2, 3, 4}
让编译器推断数组长度
arr := [...]int{1, 2, 3, 4}
创建切片:切片可以增加元素,而数组不能。
arr := []int{1, 2, 3, 4}
8.2长度
arr := [4]int{1, 2, 3, 4}
fmt.Println(len(arr))
8.3连接两个以上数组
arr := [2]int{1, 2}
y := [2]int{3,4}
// 将数组转换为切片
// 输出内容“[1,2,3,4]”
fmt.Println(append(arr[:], y[:]...))
8.4变为一个字符串
arr := [4]string{"1", "2", "3", "4"}
fmt.Println(strings.Join(arr[:], ","))
8.5截取
arr := [4]int{1, 2,3,4}
// 切片格式为[起始位置:结束位置],但不包含结束位置。
fmt.Println(arr[0:4])
8.6查找
Go语言的package“strings”,没有字符串查找方法。
func indexOf(length int, detect func(i int) bool) int {
for i := 0; i < length; i++ {
if detect(i) {
return i
}
}
return -1
}
arr := [4]int{1, 2, 3, 4}
// 查找数字的位置,输出内容“1”
fmt.Println(indexOf(len(arr), func(i int) bool { return arr[i] == 2 }))
arr2 := [4]string{"1", "2", "3", "4"}
// 查找字符串的位置,输出内容“1”
fmt.Println(indexOf(len(arr2), func(i int) bool { return arr2[i] == "2" }))
8.7排序
import "sort"
arr := [4]int{1, 2, 4, 3}
sort.Slice(arr[:], func(i int, j int) bool {
return arr[i] < arr[j]
})
9.哈希表
9.1创建
// 必须使用make方法进行初始化
var map1 = make(map[string]string)
map1["a"] = "1"
map1["b"] = "2"
map1["c"] = "3"
fmt.Println(map1)
9.2添加和修改
map1["a"] = "11"
9.3删除
delete(map1, "a")
9.4根据键获取值
// 存在的键
fmt.Println(map1["a"])
// 可能不存在的键
item1, hasKey := map1["e"]
if hasKey {
fmt.Println(item1)
} else {
fmt.Println("not found")
}
9.5遍历
for key, item := range map1 {
fmt.Printf("key:%s, value:%s\n", key, item)
}
10.文件访问
10.1读取二进制内容
import "io/ioutil"
file, err := ioutil.ReadFile("./go.mod")
if err != nil {
fmt.Println(err)
} else {
// 输出内容:字节数组
fmt.Println(file)
}
10.2读取文本内容
import "io/ioutil"
file, err := ioutil.ReadFile("./go.mod")
if err != nil {
fmt.Println(err)
} else {
// 输出内容:字符串
fmt.Println(string(file))
}
11.网络访问
11.1发送Get请求
import "net/http"
url := "http://127.0.0.1"
res, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(body))
}
11.2 发送Post请求
import "net/http"
url := "http://127.0.0.1"
values := "{}"
res, err := http.Post(url, "application/json", strings.NewReader(values))
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(body))
}
11.3 设置HTTP请求头
import "net/http"
url := "http://127.0.0.1"
values := "{}"
request, _ := http.NewRequest("POST", url, strings.NewReader(values))
// 设置http请求的header
request.Header.Add("Content-type", "application/json")
client := http.Client{}
res, err := client.Do(request)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(body))
}
11.4 获取HTTP响应头
url := "http://127.0.0.1"
res, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
// 获取http响应的header
for k, v := range res.Header {
fmt.Println(k, v)
}
11.5 对象转换为JSON
import "encoding/json"
func convertObjectToJson(params map[string]interface{}) string {
data, err := json.Marshal(params)
if err != nil {
return ""
} else {
return string(data)
}
}
func main() {
var body = make(map[string]interface{})
body["a"] = "1"
body["b"] = [3]int{1, 2, 3}
body["c"] = Rect{Width: 50, Height: 50}
result := convertObjectToJson(body)
// 输出内容“{"a":"1","b":[1,2,3],"c":{"Width":50,"Height":50}}”
fmt.Println(result)
}
11.6 JSON转换为对象
type Rect struct {
Width int32
Height int32
}
type ResponseData struct {
Status int32
Data []Rect
Message string
}
func convertJsonToObject(params []byte) ResponseData {
var res ResponseData
err := json.Unmarshal(params, &res)
if err != nil {
fmt.Println(err)
}
return res
}
func main() {
jsonStr := `
{
"Status": 200,
"Data": [
{"Width":1,
"Height":1},
{"Width":2,
"Height":2}],
"Message": "ok"
}`
result := convertJsonToObject([]byte(jsonStr))
// 输出内容“{200 [{1 1} {2 2}] ok}”
fmt.Println(result)
}
12.数据库访问
12.1导入依赖的package
1、在项目目录中,运行以下命令,下载第三方package。
go get github.com/go-sql-driver/mysql
2、package名称前面的下划线“_”表示导入package时,只执行package的init()方法。
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
12.2打开数据库连接
数据库的连接字符串
username:用户名
password:密码
dbname:数据库名
// username:用户名,password:密码,dbname:数据库名
const connString string = "username:password@tcp(127.0.0.1:3306)/dbname"
// 数据库连接对象
var db *sql.DB
func open() {
var err error
db, err = sql.Open("mysql", connString)
if err != nil {
fmt.Println("Open", err)
return
}
fmt.Println("Open", "Succeed")
}
12.3添加记录
type User struct {
// 属性的首字母大写
Id string
Title string
}
func createOrUpdateRecord(user User, isUpdate bool) {
sql := ""
if isUpdate {
sql = "update team set title = ? where id = ?;"
} else {
sql = "insert into team(title, id) values(?, ?);"
}
stmt, err := db.Prepare(sql)
if err != nil {
fmt.Println("Prepare", err)
return
}
defer stmt.Close()
// 参数化查询
result, err := stmt.Exec(user.Title, user.Id)
if err != nil {
fmt.Println("Exec", err)
return
}
// 受影响行数
nums, err := result.RowsAffected()
if err != nil {
fmt.Println("RowsAffected", err)
return
}
fmt.Println("RowsAffected", nums)
}
user1 := User{Id: "1", Title: "a"}
// 添加记录
createOrUpdateRecord(user1, false)
12.4修改记录
user1 := User{Id: "1", Title: "a"}
// 修改记录和添加记录的方法相同,只是执行的SQL语句不一样。
user1.Title = "b"
createOrUpdateRecord(user1, true)
12.5查询记录
type User struct {
// 属性的首字母大写
Id string
Title string
}
// 通过反射获取结构体对象的属性
func getColumns(obj interface{}) []interface{} {
el := reflect.ValueOf(obj).Elem()
fields := el.NumField()
columns := make([]interface{}, fields)
for j := 0; j < fields; j++ {
field := el.Field(j)
columns[j] = field.Addr().Interface()
}
return columns
}
func selectRecord() {
users := []User{}
stmt, err := db.Prepare("select id, title from team limit ?,?;")
if err != nil {
fmt.Println("Prepare", err)
return
}
defer stmt.Close()
// 参数化查询
rows, err := stmt.Query(0, 5)
if err != nil {
fmt.Println("Query", err)
return
}
for rows.Next() {
user := User{}
// 因为rows.Scan的输入参数需要填写多个属性名,所以使用getColumns方法获取User对象的多个属性名。
columns := getColumns(&user)
err = rows.Scan(columns...)
if err != nil {
fmt.Println("Scan", err)
break
}
users = append(users, user)
}
if err := rows.Err(); err != nil {
fmt.Println("Err", err)
return
}
defer rows.Close()
// 输出记录信息
for _, user := range users {
fmt.Println(user)
}
}
selectRecord()
12.6删除记录
func deleteRecord(id string) {
sql := "delete from team where id = ?;"
stmt, err := db.Prepare(sql)
if err != nil {
fmt.Println("Prepare", err)
return
}
defer stmt.Close()
// 参数化查询
result, err := stmt.Exec(id)
if err != nil {
fmt.Println("Exec", err)
return
}
// 受影响行数
nums, err := result.RowsAffected()
if err != nil {
fmt.Println("RowsAffected", err)
return
}
fmt.Println("RowsAffected", nums)
}
user1 := User{Id: "1", Title: "a"}
deleteRecord(user1.Id)
12.7关闭数据库连接
var db *sql.DB
func closeDB() {
db.Close()
}
closeDB()
13.协程
13.1定义异步方法
func work1() {
fmt.Println("work1")
}
func work2() {
fmt.Println("work2")
}
func main() {
for i := 0; i < 2; i++ {
// 使用go关键字
go work1()
work2()
}}
/* 输出内容中没有“work1”,因为主线程没有等待work1线程就结束了。
work2
work2
*/
}
13.2等待异步方法
import "sync"
var waitGroup sync.WaitGroup
func work1() {
fmt.Println("work1")
// 通知线程的工作完成。
waitGroup.Done()
}
func work2() {
fmt.Println("work2")
}
func main() {
// 数字2表示等待2个线程完成。
waitGroup.Add(2)
for i := 0; i < 2; i++ {
go work1()
work2()
}
waitGroup.Wait()
/* 输出内容包含了“work2”和“work1”,因为“work1”是异步执行,所以显示在“work2”后面。
work2
work2
work1
work1
*/
}
13.3等待多个异步方法完成
waitGroup.Add()的输入参数
数字0:waitGroup.Wait()不阻塞。
数字1:waitGroup.Wait()等待1个线程完成,然后不阻塞。
数字2:waitGroup.Wait()等待2个线程都完成,然后不阻塞。
数字3:waitGroup.Wait()等待3个线程完成,但不存在第3个线程时,提示“fatal error: all goroutines are asleep - deadlock!”。
func main() {
waitGroup.Add(2)
for i := 0; i < 2; i++ {
// 循环了2次,创建了2个线程执行方法“work1”。
go work1()
work2()
}
// 阻塞当前线程
waitGroup.Wait()
}
13.4 在协程之间发送和接收消息
1、make(chan string)的channel默认不带缓冲区。
协程work2发送消息后变成阻塞状态,等待协程work1接收消息后,work2取消阻塞状态。
2、make(chan string, 10)的channel带缓冲区,方法的第2个参数表示缓冲区大小为10个字节。
协程work2发送消息不会变成阻塞状态,在发送消息的大小超过缓冲区后,协程work2开始阻塞。等待work1接收消息后,work2取消阻塞状态。
3、使用select和case语句管理多个channel:一个case语句对应一个channel。
规则如下:
如果case语句全部不运行,则select会阻塞到有一个case语句可以运行。但是,如果有default语句,则运行default语句。
如果case语句有几个可以运行,则select会随机地选择一个case语句运行,其他case语句被忽略。
var chanel chan string
func work1() {
// 一直阻塞到接收了消息
msg := <-chanel
fmt.Println("work1", msg)
}
func work2() {
msg := "hello"
// 发送消息并一直阻塞到对方接收完成
chanel <- msg
fmt.Println("work2")
}
func main() {
// 消息通道变量初始化
chanel = make(chan string)
for i := 0; i < 2; i++ {
go work1()
work2()
}
/* 输出内容
work1 hello
work2
work1 hello
work2
*/
}