完成以下问题, 表示你完成了一门编程语言的学习。
以下涉及了项目中常用的主题,可根据具体场景选择完成。
1.入门
1.1 官网地址,特性和应用场景有哪些
官网
社区
特性:简洁、快速、静态类型、富有表现力、并发性很好
场景:web服务、区块链、云服务、游戏后台等
Golang并不是严格面向对象,更多的是面向接口,所以数据类型不能用点的方式
1.2 安装与运行
参考 安装
下载安装即可
运行
创建工程
// 创建目录
mkdir hello
cd hello
// 创建mod文件,为工程创建包名
go mod init example/hello
新建
main.go
文件 ```go package main
import “fmt”
func main() { fmt.Println(“Hello, World!”) }
3. 运行
`go run main.go`
> 更多命令可参考 go help
<a name="yGXyL"></a>
### 1.3 镜像(选答)
```c
# 启用 Go Modules 功能
go env -w GO111MODULE=on
# 配置 GOPROXY 环境变量,以下三选一
# 1. 七牛 CDN
go env -w GOPROXY=https://goproxy.cn,direct
# 2. 阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
# 3. 官方
go env -w GOPROXY=https://goproxy.io,direct
1.4 编辑器(选答)
1.5 包管理工具
第三方库网站
安装、升级、卸载第三方库
只能安装和升级,不能卸载
升级和安装的方式一相同
有两种方式:
// 方式一:在工程目录下执行
go get -u github.com/gin-gonic/gin
// 方式二:在代码中输入到包语句
import "rsc.io/quote"
// 然后执行
go mod tidy
// 如果克隆新工程,用第二种方式比较好
-u 表示自动更新包,而且当go get的时候会自动获取该包依赖的其他第三方包 使用 Go get 安装的第三方库会放到GOPATH目录的pkg/mod目录中
1.6 书籍推荐
2.概览
2.1 基本语法
// 包名,一般和文件名相同
package main
// 导入包
import "fmt"
import "github.com/gin-gonic/gin"
// 常量
const (
LiLy = 1
)
func main() {
fmt.Println("Hello, World!")
fmt.Println(LiLy)
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
// if
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
// for
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
// 遍历键值对
for key, value := range oldMap {
newMap[key] = value
}
// 遍历数组
sum := 0
for _, value := range array {
sum += value
}
// switch
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
// 函数,可以返回多个参数
// 小写字母开头的私有
// 大写字母开头的公有
func (file *File) Write(b []byte) (n int, err error) {
return 0, err
}
// defer 在函数结束之前调用
// 用于释放资源,使用栈保存,先进后出
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// 输出:4 3 2 1 0
// panic
// 类似抛出异常
// 是一个内建函数,可以中断原有的控制流程,进入一个panic状态中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。panic可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
// Recover
// 是一个内建的函数,可以让进入panic状态的goroutine恢复过来。
// recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic状态,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
2.2 面向对象
// 包名,一般和文件名相同
package main
// 导入包
import "fmt"
func main() {
p := Person{name: "Ren", age: 30}
p.run()
}
// 结构体
type Person struct {
name string
age int
}
func (p Person) run() {
s := fmt.Sprintf("%s who is %d years old running", p.name, p.age)
fmt.Println(s)
}
// 继承
// 通过匿名字段的方式, 当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
// 当然也可以重写方法
package main
import "fmt"
type Skills []string
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段,struct
Skills // 匿名字段,自定义的类型string slice
int // 内置类型作为匿名字段
speciality string
}
func main() {
// 初始化学生Jane
jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
// 现在我们来访问相应的字段
fmt.Println("Her name is ", jane.name)
fmt.Println("Her age is ", jane.age)
fmt.Println("Her weight is ", jane.weight)
fmt.Println("Her speciality is ", jane.speciality)
// 我们来修改他的skill技能字段
jane.Skills = []string{"anatomy"}
fmt.Println("Her skills are ", jane.Skills)
fmt.Println("She acquired two new ones ")
jane.Skills = append(jane.Skills, "physics", "golang")
fmt.Println("Her skills now are ", jane.Skills)
// 修改匿名内置类型字段
jane.int = 3
fmt.Println("Her preferred number is", jane.int)
}
2.3 接口
不需要显示说明实现了某个接口,它没有继承或子类或“implements”关键字,只是通过约定的形式,隐式的实现interface 中的方法即可
会在编译时期检查是否实现
// 定义一个接口
type Man interface {
sayHello(name string)
}
type Human struct {
name string
age int
phone string
}
// 隐式实现
// 至于为什么,更多的可能是简洁
func (h Human) sayHello(name string) {
fmt.Printf("Hello %s, this is %s", name, h.name)
}
2.4 并发
goroutine
Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。
goroutine通过 go 关键字实现。
设计原则:不要通过共享来通信,而要通过通信来共享。
go hello(a, b, c)
channels
用于goroutine之间数据的通信。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。
// 创建
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
// 第二个参数表示缓冲数量
ch := make(chan string, 5)
channel通过操作符 <- 来接收和发送数据
ch <- v // 发送v到channel ch.
v := <-ch // 从ch中接收数据,并赋值给v
示例:
单次获取channel
package main
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total // send total to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
// 必须使用 make 创建channel
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x + y)
}
循环读取channel
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
// 关闭channel
// 应该在生产者的地方关闭channel
close(c)
}
func main() {
c := make(chan int, 10)
// 这里注意缓冲是10,发送不能超过10次
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
循环读取多个channel
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
2.5 测试
- 创建以
_test.go
结尾的文件; - 导入
testing
包; - 创建以
Test
开头的测试方法; - 执行
go test
。
举例:
import (
"testing"
"regexp"
)
// TestHelloName calls greetings.Hello with a name, checking
// for a valid return value.
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b`+name+`\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
// 如果有错误就报错,并没有提供对比的方法
t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
2.6 make、new
new
返回类型零值的指针make
用于内建类型(map、slice 和channel)的内存分配,而且只能创建这三种类型,make返回初始化后的(非零)值
原因:本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。我理解为这三种类型是封装类型,必须被初始化,又不想总是写构造函数,所以新增了make关键字。
2.7 Module
3.数据类型
3.1 数字
// 包名,一般和文件名相同
package main
// 导入包
import "fmt"
import "math"
func main() {
// 整数,加减乘除余
var x int = 3
var y int = 5
fmt.Println("x =", x)
fmt.Println("y =", y)
fmt.Println("x + y =", x + y)
fmt.Println("x - y =", x - y)
fmt.Println("x * y =", x * y)
fmt.Println("x / y =", x / y)
fmt.Println("x % y =", x % y)
// 小数点后两位,四舍六入
// 输出 2.68
var z float64 = 2.6789124
fmt.Println(fmt.Sprintf("%.2f", z))
// 转为整数,去掉小数部分
fmt.Println("z =", int(z))
// 指数
p := math.Pow(2, 3)
fmt.Println("p=", p)
// 接口转数字,转其他的类型也类似
var a interface{}
anum := a.(int)
}
3.2 字符
func str() {
var str string = "abcdefg"
// 格式化
var s string = fmt.Sprintf("my name is %s", str)
fmt.Println(s)
// 拼接
fmt.Println(str + "ijk")
// 截取
fmt.Println(str[1:])
fmt.Println(str[:len(str)-3])
// 长度
fmt.Println("str length =", len(str))
// 遍历
for _, v := range str {
// v 是ASCII码数值,需要转为字符串
fmt.Println("char =", string(v))
}
// 分割
arr := strings.Split(str, "c")
fmt.Println(arr)
// 替换
rep := strings.ReplaceAll(str, "a", "x")
fmt.Println(rep)
// 正则表达式
reg, err := regexp.Compile("H.*d")
if err != nil {
panic(err)
}
// 是否包含
b := reg.MatchString("aHellod")
fmt.Println(b)
// 返回匹配的子串
a := reg.FindString("adbHzd")
fmt.Println(a)
}
3.3 列表
数组
- 数组不能改变长度
- 数组之间的赋值是值的赋值,传入的其实是该数组的副本,而不是它的指针
- 用得更多的是slice切片,即动态数组 ```go
var arr [10]int // 声明了一个int类型的数组,默认都是0
arr[0] = 42 // 数组下标是从0开始的
arr[1] = 13 // 赋值操作
// 遍历
for _, v := range arr {
fmt.Println(v)
}
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
c := […]int{4, 5, 6} // 可以省略长度而采用...
的方式,Go会自动根据元素个数来计算长度
**slice(动态数组)**<br />slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度<br />slice是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,比如a变了,b也会变<br />slice包含了三个元素
- 一个指针,指向数组中slice指定的开始位置
- 长度length,即slice的长度
- 最大长度cap,也就是slice开始位置到数组的最后位置的长度
```go
// 定义
// 默认长度和容量都是0
var fslice []int
// 或者使用make,指定其长度为 3 个元素,容量为 5 个元素
// 默认值都是0
slice := make([]int, 3, 5)
// 创建字符串切片
// 其长度和容量都是 3 个元素
myStr := []string{"Jack", "Mark", "Nick"}
操作
package main
// 导入包
import (
"fmt"
"sort" // sort包中的Slice方法可以排序任何类型
)
func main() {
// golang 对 slice 的操作全在 [:] 中 和 append
// 所以很多操作需要自己写
// 关于 [:] 操作,和 Python 一样,左包含右不包含
arr := make([]int, 2, 10)
printSlice(arr)
// 新增
// 头部插入
arr = append([]int{1}, arr...)
printSlice(arr)
// 尾部插入
arr = append(arr, 2)
printSlice(arr)
// 任意位置插入
// 官方没有提供现成的函数,必须创建一个新的切片
i := 2
newArr := make([]int, len(arr)+1)
copy(newArr[:i], arr[:i])
newArr[i] = 3
copy(newArr[i+1:], arr[i:])
printSlice(newArr)
// 删除
// 按位置
p := 3
newArr = append(newArr[:p-1], newArr[p:]...)
printSlice(newArr)
// 按元素
newArr = delItem(newArr, 2)
printSlice(newArr)
// 修改
// 按位置
newArr[1] = 2
printSlice(newArr)
// 包含
c := contains(newArr, 1)
fmt.Println(c)
// 排序,使用sort包
sort.Slice(newArr, func(i, j int) bool {
return newArr[i] < newArr[j]
})
printSlice(newArr)
}
// 包含,可以用二分法查找
func contains(arr []int, s int) bool {
for _, v := range arr {
if v == s {
return true
}
}
return false
}
func delItem(vs []int, s int) []int {
// 也可以正向
maxIdx := len(vs) - 1
for i := maxIdx; i >= 0; i-- {
if s == vs[i] {
vs = append(vs[:i], vs[i+1:]...)
}
}
return vs
}
func printSlice(arr []int) {
fmt.Println(arr)
}
3.4 字典(键值对)
其底层存储方式为数组,在存储时key不能重复,当key重复时,value进行覆盖,我们通过key进行hash运算(可以简单理解为把key转化为一个整形数字)然后对数组的长度取余,得到key存储在数组的哪个下标位置,最后将key和value组装为一个结构体,放入数组下标处
hash冲突的常见解决方法
线性探测,字面意思就是按照顺序来,从冲突的下标处开始往后探测,到达数组末尾时,从数组开始处探测,直到找到一个空位置存储这个key,当数组都找不到的情况下回扩容
拉链,简单理解为链表,当key的hash冲突时,我们在冲突位置的元素上形成一个链表,通过指针互连接,当查找时,发现key冲突,顺着链表一直往下找,直到链表的尾节点
package main
import "fmt"
func main() {
fmt.Printf("%s\n", "hello world")
// 声明
numbers := make(map[string]int)
numbers["one"] = 1 //赋值
numbers["ten"] = 10 //赋值
numbers["three"] = 3
fmt.Println(numbers) // 读取数据
// 声明与赋值
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
fmt.Println(rating)
// 新增
rating["Java"] = 6
fmt.Println(rating)
// 删除
delete(rating, "Java")
fmt.Println(rating)
// 包含
if _, ok := rating["Python"]; ok {
fmt.Println("key is Python, value is", rating["Python"])
}
// 遍历
for key, value := range rating {
s := fmt.Sprintf("key is %s, value is %.2f", key, value)
fmt.Println(s)
}
// 排序
// 只能遍历后将键或者值放slice里再排序
}
没有提供集合的数据结构
3.5 栈和队列
4.练习
4.1 时间
package main
// 导入包
import (
"fmt"
"time"
)
const TimeFormat = "2006-01-02 15:04:05"
func main() {
// 当前时间戳
timeStep := time.Now().Unix()
fmt.Println("时间戳:", timeStep)
// 格式化
now := time.Now().Format(TimeFormat)
fmt.Println("当前时间:", now)
// 格式化时间转时间戳
t, err := time.Parse(TimeFormat, now)
if err != nil {
fmt.Println(err)
}
fmt.Println("格式化时间转时间戳", t.Unix())
// 时间戳转格式化时间
t1 := time.Unix(timeStep, 0)
fmt.Println("时间戳转格式化时间:", t1.Format(TimeFormat))
}
4.2 文件与目录
4.2.1 文件
只要获取文件对象,就可以做操作了
自定义长度
// os.open
f, err := os.OpenFile("example.py", os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
const size int = 128
var b [size]byte
for {
n ,err := f.Read(b[:])
if err != nil {
fmt.Println("read file err:", err.Error())
break
}
fmt.Printf("读到了%d个字节:%s\n", n, string(b[0:n]))
if n < size {
break
}
}
按行
// bufio.ReadString
f, err := os.Open("example.py")
if err != nil {
fmt.Println("errors=", err.Error())
return
}
defer f.Close()
buff := bufio.NewReader(f)
for {
// 这里的参数是byte类型,所以用单引号
line, err := buff.ReadString('\n')
if err == io.EOF {
fmt.Println("read file EOF")
break
}
if err != nil {
fmt.Println("err=", err)
}
fmt.Println("line=", line)
}
全部读取
ll, err := ioutil.ReadFile("example.py")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(all))
写入
// 其实读文件不过是该函数指定的只读模式 penFile(name, O_RDONLY, 0)
f, err := os.OpenFile("a.log", os.O_APPEND | os.O_RDWR | os.O_CREATE, 0644)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
f.WriteString(time.Now().String() + "\n")
// 或者使用缓存
writer := bufio.NewWriter(f)
writer.WriteString("write buffer")
writer.Flush()
其他操作
// 文件大小
fileInfo, err := os.Stat("hello_1_test.go")
if err != nil {
fmt.Println(err)
}
fmt.Println(fileInfo.Size())
// 删除文件
if err:=os.Remove("hello_test.go");err!=nil {
fmt.Println(err)
}
// 修改文件名
if err:=os.Rename("hello_test.go", "hello_1_test.go");err!=nil {
fmt.Println(err)
}
4.2.2 目录
// 当前工作目录
cwd, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
fmt.Println(cwd)
// 目录操作用 "path/filepath" 库
// 列出文件与目录
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
fmt.Println("path=", path)
fmt.Println("name=", info.Name())
return nil
})
if err != nil {
fmt.Println(err)
}
// 绝对路径
// Getwd() 返回的也是绝对路径
abs, err := filepath.Abs("assert/os.txt")
if err != nil {
fmt.Println(err)
}
// 删除目录
if err:=os.RemoveAll("assert");err!=nil {
fmt.Println(err)
}
// 拼接路径
osText := filepath.Join(cwd, "assert", "os.txt")
// 分离路径
cwdDir := filepath.Dir(osText)
fmt.Println(cwdDir)
// 分离文件名
baseName := filepath.Base(osText)
fmt.Println(baseName)
// 后缀名
extName := filepath.Ext(baseName)
fmt.Println(extName)
// 同时获得路径与文件名
dir, file := filepath.Split(osText)
fmt.Println(dir, "\t", file)
// 是否存在
// 这里使用了 nil 和 IsNotExist 双重判断
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
// 这里其实是判断错误类型是不是 ErrNotExist
if os.IsNotExist(err) {
return false, nil
}
//这个函数是判断错误类型是不是 ErrExist
//os.IsExist(err)
return false, err
}
// 复制文件
// 也可以全部读再全部写,使用 ioutil 包,但是对内存有要求
// 性能上差距不大
func copyFile2(srcFile, destFile string)(int64,error){
file1,err:=os.Open(srcFile)
if err != nil{
return 0,err
}
file2,err:=os.OpenFile(destFile,os.O_WRONLY|os.O_CREATE,os.ModePerm)
if err !=nil{
return 0,err
}
defer file1.Close()
defer file2.Close()
// 会创建缓冲区
return io.Copy(file2,file1)
}
// 删除目录
os.RemoveAll("path")
4.3 加密
MD5加密, SHA1的用法是一样的
package main
import (
"crypto/md5"
"fmt"
"io"
"log"
"os"
)
func main() {
// 字符串
data := []byte("These pretzels are making me thirsty.")
fmt.Printf("%x\n", md5.Sum(data))
// 文件
f, err := os.Open("hello_1_test.go")
if err != nil {
log.Fatal(err)
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
log.Fatal(err)
}
fmt.Printf("%x", h.Sum(nil))
}
4.4 网络服务
package main
import (
"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) // 第二个参数是路由对象,不传则表示使用http包默认的路由器
}
4.5 数据库操作
package main
import (
"database/sql"
"fmt"
"github.com/go-sql-driver/mysql" // 数据库驱动
"log"
"os"
)
// 连接后的对象
var db *sql.DB
type Album struct {
ID int64
Title string
Artist string
Price float32
}
// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
// An albums slice to hold data from returned rows.
var albums []Album
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close()
// Loop through rows, using Scan to assign column data to struct fields.
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
// An album to hold data from the returned row.
var alb Album
row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
if err == sql.ErrNoRows {
return alb, fmt.Errorf("albumsById %d: no such album", id)
}
return alb, fmt.Errorf("albumsById %d: %v", id, err)
}
return alb, nil
}
// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
return id, nil
}
func main() {
// Capture connection properties.
cfg := mysql.Config{
User: os.Getenv("DBUSER"),
Passwd: os.Getenv("DBPASS"),
Net: "tcp",
Addr: "127.0.0.1:33306",
DBName: "recordings",
}
// Get a database handle.
var err error
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
albums, err := albumsByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
// Hard-code ID 2 here to test the query.
alb, err := albumByID(2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)
albID, err := addAlbum(Album{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)
}
4.6 数据处理
package main
import (
"encoding/json"
"fmt"
)
// 如果字段名不一致则可以使用注解
type Dog struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// object -> json
d1 := Dog{ID: 1, Name: "Petter"}
pb, err := json.MarshalIndent(d1, "", " ")
if err != nil {
fmt.Println("marshl err=", err.Error())
return
}
fmt.Println(string(pb))
// json -> object
var d2 Dog
myDog := `{"id":2, "Name":"mydog"}`
err = json.Unmarshal([]byte(myDog), &d2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(d2)
}
5 项目
5.1 Gin 框架
5.2 GORM 框架
待补充