完成以下问题, 表示你完成了一门编程语言的学习。
以下涉及了项目中常用的主题,可根据具体场景选择完成。
1.入门
1.1 官网地址,特性和应用场景有哪些
官网
社区
特性:简洁、快速、静态类型、富有表现力、并发性很好
场景:web服务、区块链、云服务、游戏后台等
Golang并不是严格面向对象,更多的是面向接口,所以数据类型不能用点的方式
1.2 安装与运行
参考 安装
下载安装即可
运行
创建工程
// 创建目录mkdir hellocd 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. 七牛 CDNgo 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")}// ifif err := file.Chmod(0664); err != nil {log.Print(err)return err}// forsum := 0for i := 0; i < 10; i++ {sum += i}// 遍历键值对for key, value := range oldMap {newMap[key] = value}// 遍历数组sum := 0for _, value := range array {sum += value}// switchfunc 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 stringage 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 mainimport "fmt"type Skills []stringtype Human struct {name stringage intweight int}type Student struct {Human // 匿名字段,structSkills // 匿名字段,自定义的类型string sliceint // 内置类型作为匿名字段speciality string}func main() {// 初始化学生Janejane := 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 = 3fmt.Println("Her preferred number is", jane.int)}
2.3 接口
不需要显示说明实现了某个接口,它没有继承或子类或“implements”关键字,只是通过约定的形式,隐式的实现interface 中的方法即可
会在编译时期检查是否实现
// 定义一个接口type Man interface {sayHello(name string)}type Human struct {name stringage intphone 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 mainimport "fmt"func sum(a []int, c chan int) {total := 0for _, v := range a {total += v}c <- total // send total to c}func main() {a := []int{7, 2, 8, -9, 4, 0}// 必须使用 make 创建channelc := make(chan int)go sum(a[:len(a)/2], c)go sum(a[len(a)/2:], c)x, y := <-c, <-c // receive from cfmt.Println(x, y, x + y)}
循环读取channel
package mainimport ("fmt")func fibonacci(n int, c chan int) {x, y := 1, 1for i := 0; i < n; i++ {c <- xx, y = y, x + y}// 关闭channel// 应该在生产者的地方关闭channelclose(c)}func main() {c := make(chan int, 10)// 这里注意缓冲是10,发送不能超过10次go fibonacci(cap(c), c)for i := range c {fmt.Println(i)}}
循环读取多个channel
package mainimport "fmt"func fibonacci(c, quit chan int) {x, y := 1, 1for {select {case c <- x:x, y = y, x + ycase <-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 = 3var y int = 5fmt.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.68var z float64 = 2.6789124fmt.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// 定义// 默认长度和容量都是0var fslice []int// 或者使用make,指定其长度为 3 个元素,容量为 5 个元素// 默认值都是0slice := 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 := 2newArr := make([]int, len(arr)+1)copy(newArr[:i], arr[:i])newArr[i] = 3copy(newArr[i+1:], arr[i:])printSlice(newArr)// 删除// 按位置p := 3newArr = append(newArr[:p-1], newArr[p:]...)printSlice(newArr)// 按元素newArr = delItem(newArr, 2)printSlice(newArr)// 修改// 按位置newArr[1] = 2printSlice(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) - 1for 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 mainimport "fmt"func main() {fmt.Printf("%s\n", "hello world")// 声明numbers := make(map[string]int)numbers["one"] = 1 //赋值numbers["ten"] = 10 //赋值numbers["three"] = 3fmt.Println(numbers) // 读取数据// 声明与赋值rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }fmt.Println(rating)// 新增rating["Java"] = 6fmt.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.openf, err := os.OpenFile("example.py", os.O_RDWR, 0666)if err != nil {fmt.Println(err)return}defer f.Close()const size int = 128var b [size]bytefor {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.ReadStringf, 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}// 这里其实是判断错误类型是不是 ErrNotExistif 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 mainimport ("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 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) // 第二个参数是路由对象,不传则表示使用http包默认的路由器}
4.5 数据库操作
package mainimport ("database/sql""fmt""github.com/go-sql-driver/mysql" // 数据库驱动"log""os")// 连接后的对象var db *sql.DBtype Album struct {ID int64Title stringArtist stringPrice 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 []Albumrows, 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 Albumif 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 Albumrow := 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 entryfunc 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 errordb, 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 mainimport ("encoding/json""fmt")// 如果字段名不一致则可以使用注解type Dog struct {ID int `json:"id"`Name string `json:"name"`}func main() {// object -> jsond1 := 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 -> objectvar d2 DogmyDog := `{"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 框架
待补充
