一、GOPATH 与 Go Mod
二、常用的标准库
- (一)ftm
- (二)os/exec
三、项目中涉及语法概念
- (一)结构体
- (二)接口
四、源码附录
- 参考文档
一、GOPATH 与 Go Mod
(小白学习go,可能存在问题,欢迎大家批评指正~)
最开始,我的项目结构如下:
结果发现,main.go中 import包tool 爆红,明明路径都写对了,为什么还是报错呢?原来是gopath配置的问题,gopath就是用来管理包的。
于是,我开始配置gopath。
GOPATH官方文档解释是:GOPATH 环境变量指定了你的工作空间位置。
它或许是你在开发Go代码时, 唯一需要设置的环境变量。
① 打开设置,发现使用的是全局GOPATH
但本项目的生成地址并不是全局GOPATH所在的地址,因此需要修改为本项目所在的地址:
② 既然选择使用gopath,那么就要使用GOPATH的目录
GOPATH目录一般为:
--bin # 存放编译后的可执行文件,也就是.exe文件
--pkg # 依赖包编译后的*.a文件
--src # 存放源码文件,以代码包为组织形式
由此我对项目结构进行了修改,此时导入tool包就不会爆红了
③ 运行项目
由于是新建的项目,第一次的运行配置尚未添加
我们不需要手动进行配置,直接找到main函数选择运行即可
此时他会自动生成配置项
上述便完成了整个项目在GOPATH下的运行,但是GOPATH存在一些问题,比如
- 当我们
go get 第三方包
时,GoPath 会在 GOPATH 路径上安装第三方包,下载下来的包会乱七八糟的放在你的工作路径scr下面,和你自己写的源码混在一起。 - 项目结构固定死,bin/pkg/scr必须放在同一个目录下,比如像我前面发生的爆红,一部分原因就是 我的代码必须放在 GOPATH 里,才能运行,所以我才需要把全局GOPATH改为局部GOPATH。
如果我们使用GOPATH将会发生以下情况:
- 要么不同的项目使用不同的GOPATH,这个时候大家互不干扰,看上去还挺和谐的,但是如果不同项目使用到了同一个包,那么就是各自放到各自的GOPATH,内存空间占用大,其实这个包可以大家共用的。
- 要么不同项目使用同一个 GOPATH,此时解决了重复依赖占用空间的问题,但是不同项目的源码又会混杂在一起,还夹杂着第三方包,简直乱的不行。
但若此时使用go mod来管理包,就会变得异常方便和整洁,而且使用go mod,scr可以不用和bin/pkg放在同一个目录下,能保证在不同地方构建,获得的依赖模块是一致的,也就是说,无论我的go文件在哪里,都可以引用包。
使用go mod 管理项目,就不需要非得把项目放到GOPATH指定目录下,你可以在你磁盘的任何位置新建一个项目,都能用了。
go mod就是在 GoPath 之外再弄一个 GoModule 目录(就是 go mod init
的那个目录),这样自己的代码安装在 GoModule 目录里,第三方则安装在 GoPath 目录里。
下面来看看go mod怎么用
Modules官方定义为:
模块是相关Go包的集合。
modules是源代码交换和版本控制的单元。
go命令直接支持使用modules,包括记录和解析对其他模块的依赖性。
modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件。
① 首先打开cmd,输入go env
查看
② 设置 GO111MODULE=ON
③ 打开项目终端,输入go mod init 项目名
,创建go.mod
④ 创建成功
其实我们完全不用这么麻烦,直接在新建项目的时候,创一个含有mod的项目就可以:
我原本认为,在我把gopath的项目变成go mod项目之后,我就不需要再管本地项目包导入的问题了,但是我后面发现,即便使用go mod,我在导入tool包时依旧爆红,于是参考了下面两篇文章,但是都不能解决
go mod 怎么导入本地其它项目的包
go mod 如何导入本地的包
最终发现是我路径写错的问题:
如果项目结构长这样,那么调用tool包就应该这样调用:
就目前来看,局部GOPATH的设置可以使得我们直接使用 “tool” 导入项目包,而如果使用全局GOPATH,那么我们就要使用相对路径来导入本地包。
二、常用的标准库 (一)ftm
func main() {
正常打印字符串和变量,不会进行格式化,不会自动换行,需要手动添加 \n 进行换行,多个变量值之间不会添加空格
fmt.Print("hello", "world\n")
//正常打印字符串和变量,不会进行格式化,多个变量值之间会添加空格,并且在每个变量值后面会进行自动换行
fmt.Println("hello", "world")
//可以按照自己需求对变量进行格式化打印。
需要手动添加 \n 进行换行
fmt.Printf("hello world\n")
}
helloworld
hello world
hello world
func main() {
n := 1024
fmt.Printf("%d 的 2 进制:%b \n", n, n)
fmt.Printf("%d 的 8 进制:%o \n", n, n)
fmt.Printf("%d 的 10 进制:%d \n", n, n)
fmt.Printf("%d 的 16 进制:%x \n", n, n)
}
1024 的 2 进制:10000000000
1024 的 8 进制:2000
1024 的 10 进制:1024
1024 的 16 进制:400
type Profile struct {
name string
gender string
age int
}
func main() {
var people = Profile{name:"wangbm", gender: "male", age:27}
fmt.Printf("%v \n", people) // output: {wangbm male 27}
fmt.Printf("%T \n", people) // output: main.Profile
// 打印结构体名和类型
fmt.Printf("%#v \n", people) // output: main.Profile{name:"wangbm", gender:"male", age:27}
fmt.Printf("%+v \n", people) // output: {name:wangbm gender:male age:27}
fmt.Printf("%% \n") // output: %
}
{wangbm male 27}
main.Profile
main.Profile{name:"wangbm", gender:"male", age:27}
{name:wangbm gender:male age:27}
%
func main() {
fmt.Printf("%t \n", true) //output: true
fmt.Printf("%t \n", false) //output: false
}
func main() {
fmt.Printf("%s \n", []byte("Hello, Golang")) // output: Hello, Golang
fmt.Printf("%s \n", "Hello, Golang") // output: Hello, Golang
fmt.Printf("%q \n", []byte("Hello, Golang")) // output: "Hello, Golang"
fmt.Printf("%q \n", "Hello, Golang") // output: "Hello, Golang"
fmt.Printf("%q \n", `hello \r\n world`) // output: "hello \r\n world"
fmt.Printf("%x \n", "Hello, Golang") // output: 48656c6c6f2c20476f6c616e67
fmt.Printf("%X \n", "Hello, Golang") // output: 48656c6c6f2c20476f6c616e67 //每个字节两个字符
}
Hello, Golang
Hello, Golang
"Hello, Golang"
"Hello, Golang"
"hello \r\n world"
48656c6c6f2c20476f6c616e67
48656C6C6F2C20476F6C616E67
(二)os/exec
1. 只执行命令,不获取结果
os/exec
用于执行命令的库,exec.Command 函数返回一个 Cmd 对象
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("ls", "-l", "/var/log/")
err := cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
}
2. 执行命令,并获取结果
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("ls", "-l", "/var/log/")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}
如果是如下代码:
exec.Command("ls", "-l", "/var/log/*.log")
那么会报错,因为go不认识通配符*,而是将*.log当做一个文件了
三、项目中涉及语法概念 (一)结构体
type MusicEntry struct {
Id string
Name string
Artist string
Source string
Type string
}
type MusicManager struct {
musics []MusicEntry //以数组切片作为结构体成员
}
//这里*是重点
func (m *MusicManager) Len() int {
return len(m.musics)
}
其中Len是方法名,而(m *MusicManager) :表示将 Len 方法与 MusicManager 的实例绑定。
我们把 MusicManager 称为方法的接收者,而 m表示实例本身,它相当于 Python 中的 self,在方法内可以使用 m.属性名 的方法来访问实例属性。
结构体赋值:
music1 := &MusicEntry{"1", "My Heart Will Go On", "Celion Dion", "http://xxxxxxxxx", "MP3"}
为什么要用*呢?——(m *MusicManager)
当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。
至此,我们知道了两种定义方法的方式:
- 以值做为方法接收者
- 以指针做为方法接收者
那我们如何进行选择呢?以下几种情况,应当直接使用指针做为方法的接收者。
- 你需要在方法内部改变结构体内容的时候
- 出于性能的问题,当结构体过大的时候
- 结构体往往和指针关联
Go中的结构体当然也可以进行嵌套
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
position string
}
嵌套完之后
type staff struct {
name string
age int
gender string
position string
company // 匿名字段!!嵌套重点
}
在 Go 语言中,函数名的首字母大小写非常重要,它被来实现控制对方法的访问权限。
-
当方法的首字母为大写时,这个方法对于所有包都是Public,其他包可以随意调用
-
当方法的首字母为小写时,这个方法是Private,其他包是无法访问的。
(二)接口
在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。
接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。
Go语言的接口并不是其他语言(C++、Java、C#等)中所提供的接口概念。
- 侵入式接口 主要表现在 实现类需要 明确声明 自己实现了某个接口。
- 非侵入式接口 ——Go语言使用
在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。
举个栗子:
先定义一个商品(Good)的接口,意思是一个类型或者结构体,只要实现了settleAccount() 和 orderInfo() 两个方法,那这个类型/结构体就是一个商品。
type Good interface {
settleAccount() int
orderInfo() string
}
然后我们定义两个结构体,分别是手机和赠品。
type Phone struct {
name string
quantity int
price int
}
type FreeGift struct {
name string
quantity int
price int
}
他俩分别实现了Good中的方法:
// Phone
func (phone Phone) settleAccount() int {
return phone.quantity * phone.price
}
func (phone Phone) orderInfo() string{
return "您要购买" + strconv.Itoa(phone.quantity)+ "个" +
phone.name + "计:" + strconv.Itoa(phone.settleAccount()) + "元"
}
// FreeGift
func (gift FreeGift) settleAccount() int {
return 0
}
func (gift FreeGift) orderInfo() string{
return "您要购买" + strconv.Itoa(gift.quantity)+ "个" +
gift.name + "计:" + strconv.Itoa(gift.settleAccount()) + "元"
}
此时就说明,这俩在Go语言看来都是商品
再举个栗子:
假设我们有如下接口:每个接口都有各自 必须要实现的方法
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
这里我们定义了一个File类,并实现有Read()、Write()、Close()等方法。
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Close() error
那么就说明,File类不仅仅实现了IFile 接口,同时也实现了IReader 、IWriter 、ICloser 接口。
意思就是说,File类就是一个文件,同时File类也是一个阅读器,也是一个编辑器,也是一个关闭器。
尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类实现了这些接口定义的方法,可以进行赋值:
var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)
这样的好处在于:
不用为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦合。
接口由使用方按自身需求来定义,使用方无需关心是否有其他模块定义过类似的接口。
同时,在 Go 语言中,是通过接口来实现的多态。
什么是多态呢? 比如在好学生这个接口下,不同人对好学生的定义不同,有些人认为只要成绩好就是好学生,有些人认为综合发展才是好学生,不同的人有不一样的看法,这就是多态。
接口还可以进行赋值
- 将对象实例赋值给接口
var xxx 接口名 = &实例对象
为什么要取地址呢?这是因为Go会将 无 * 转成 有 * 的,反之则不成立
举个栗子
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}
//相应地,我们定义接口LessAdder,如下:
type LessAdder interface {
Less(b Integer) bool
Add(b Integer)
}
Go语言可以根据下面的函数:
func (a Integer) Less(b Integer) bool
自动生成一个新的Less()方法:
func (a *Integer) Less(b Integer) bool {
return (*a).Less(b)
}
而且你赋值的是
var n LessAdder = &a
而不是
var n LessAdder = a
这个时候使用的是&,此时(*a)就可以被读取到。
- 将一个接口赋值给另一个接口
只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的。
如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。
四、源码附录
main.go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"tool"
)
var MusicMN *tool.MusicManager
var id int = 1
var ctrl, signal chan int
func handleLibCommands(tokens []string) {
switch tokens[1] {
case "list":
for i := 0; i < MusicMN.Len(); i++ {
e, _ := MusicMN.Get(i)
fmt.Println(i+1, ":", e.Name, e.Artist, e.Source, e.Type)
}
case "add": {
if len(tokens) == 6 {
id++
MusicMN.Add(&tool.MusicEntry{strconv.Itoa(id),
tokens[2], tokens[3], tokens[4], tokens[5]})
} else {
fmt.Println("USAGE: lib add )
}
}
case "remove":
if len(tokens) == 3 {
MusicMN.Remove(tokens[2])
} else {
fmt.Println("USAGE: lib remove " )
}
default:
fmt.Println("Unrecognized lib command:", tokens[1])
}
}
func handlePlayCommand(tokens []string) {
if len(tokens) != 2 {
fmt.Println("USAGE: play " )
return
}
e := MusicMN.Find(tokens[1])
if e == nil {
fmt.Println("The music", tokens[1], "does not exist.")
return
}
tool.Play(e.Source, e.Type)
}
func main() {
fmt.Println(`
Enter following commands to control the player:
lib list -- View the existing music lib
lib add )
MusicMN = tool.NewMusicManager()
r := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter command-> ")
rawLine, _, _ := r.ReadLine()
line := string(rawLine)
if line == "q" || line == "e" {
break
}
tokens := strings.Split(line, " ")
if tokens[0] == "lib" {
handleLibCommands(tokens)
} else if tokens[0] == "play" {
handlePlayCommand(tokens)
} else {
fmt.Println("Unrecognized command:", tokens[0])
}
}
}
manager.go
package tool
import "errors"
type MusicEntry struct {
Id string
Name string
Artist string
Source string
Type string
}
type MusicManager struct {
musics []MusicEntry
}
func NewMusicManager() *MusicManager {
return &MusicManager{make([]MusicEntry, 0)}
}
func (m *MusicManager) Len() int {
return len(m.musics)
}
func (m *MusicManager) Get(index int) (music *MusicEntry, err error) {
if index < 0 || index >= len(m.musics) {
return nil, errors.New("Index out of range.")
}
return &m.musics[index], nil
}
func (m *MusicManager) Find(name string) *MusicEntry {
if len(m.musics) == 0 {
return nil
}
for _, m := range m.musics {
if m.Name == name {
return &m
}
}
return nil
}
func (m *MusicManager) Add(music *MusicEntry) {
m.musics = append(m.musics, *music)
}
func (m *MusicManager) Remove(name string) *MusicEntry {
var index int
for i, m := range m.musics {
if m.Name == name {
index=i
}
}
removedMusic := &m.musics[index]
m.musics = append(m.musics[:index],m.musics[index+1:]...)
return removedMusic
}
manager_test.go
package tool
import (
"testing"
)
func TestOps(t *testing.T) {
mm := NewMusicManager()
if mm == nil {
t.Error("NewMusicManager failed.")
}
if mm.Len() != 0 {
t.Error("NewMusicManager failed, not empty.")
}
m0 := &MusicEntry{
"1", "My Heart Will Go On", "Celion Dion", "http://xxxxxxxxx", "MP3"}
mm.Add(m0)
if mm.Len() != 1 {
t.Error("MusicManager.Add() failed.")
}
m := mm.Find(m0.Name)
if m == nil {
t.Error("MusicManager.Find() failed.")
}
if m.Id != m0.Id || m.Artist != m0.Artist || m.Name != m0.Name || m.Source != m0.Source || m.Type != m0.Type {
t.Error("MusicManager.Find() failed. Found item mismatch.")
}
m, err := mm.Get(0)
if m == nil {
t.Error("MusicManager.Get() failed.", err)
}
m = mm.Remove("My Heart Will Go On")
if m == nil || mm.Len() != 0 {
t.Error("MusicManager.Remove() failed.", err)
}
}
MP3.go
package tool
import (
"fmt"
"time"
)
type MP3Player struct {
stat int
progress int
}
func (p *MP3Player)Play(source string) {
fmt.Println("Playing MP3 music", source)
p.progress = 0
for p.progress < 100 {
time.Sleep(100 * time.Millisecond) // 假装正在播放
fmt.Print(".")
p.progress += 10
}
fmt.Println("\nFinished playing", source)
}
play.go
package tool
import "fmt"
type Player interface {
Play(source string)
}
func Play(source, mtype string) {
var p Player
switch mtype {
case "MP3":p = &MP3Player{}
default:
fmt.Println("Unsupported music type", mtype)
return
}
p.Play(source)
}
输入与输出结果:
http://golang.iswbm.com/en/latest/c05/c05_02.html
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)