Go语言案例(一)音乐播放器

  • 一、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存在一些问题,比如

  1. 当我们 go get 第三方包 时,GoPath 会在 GOPATH 路径上安装第三方包,下载下来的包会乱七八糟的放在你的工作路径scr下面,和你自己写的源码混在一起。
  2. 项目结构固定死,bin/pkg/scr必须放在同一个目录下,比如像我前面发生的爆红,一部分原因就是 我的代码必须放在 GOPATH 里,才能运行,所以我才需要把全局GOPATH改为局部GOPATH。

如果我们使用GOPATH将会发生以下情况:

  1. 要么不同的项目使用不同的GOPATH,这个时候大家互不干扰,看上去还挺和谐的,但是如果不同项目使用到了同一个包,那么就是各自放到各自的GOPATH,内存空间占用大,其实这个包可以大家共用的。
  2. 要么不同项目使用同一个 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 := 1024fmt.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 stringgender stringage 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: truefmt.Printf("%t \n", false)  //output: false
}

func main() {fmt.Printf("%s \n", []byte("Hello, Golang"))  // output: Hello, Golangfmt.Printf("%s \n", "Hello, Golang")     // output: Hello, Golangfmt.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: 48656c6c6f2c20476f6c616e67fmt.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 mainimport ("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 mainimport (
"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 stringName stringArtist stringSource stringType 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 stringcompanyAddr string
}type staff struct {name stringage intgender stringposition string
}

嵌套完之后

type staff struct {name stringage intgender stringposition stringcompany   // 匿名字段!!嵌套重点
}

在 Go 语言中,函数名的首字母大小写非常重要,它被来实现控制对方法的访问权限。

  • 当方法的首字母为大写时,这个方法对于所有包都是Public,其他包可以随意调用

  • 当方法的首字母为小写时,这个方法是Private,其他包是无法访问的。


(二)接口

在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。

Go语言的接口并不是其他语言(C++、Java、C#等)中所提供的接口概念。

  1. 侵入式接口 主要表现在 实现类需要 明确声明 自己实现了某个接口。
  2. 非侵入式接口 ——Go语言使用
    在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。

举个栗子
先定义一个商品(Good)的接口,意思是一个类型或者结构体,只要实现了settleAccount() 和 orderInfo() 两个方法,那这个类型/结构体就是一个商品。

type Good interface {settleAccount() intorderInfo() string
}

然后我们定义两个结构体,分别是手机和赠品。

type Phone struct {name stringquantity intprice int
}type FreeGift struct {name stringquantity intprice 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 语言中,是通过接口来实现的多态。
什么是多态呢? 比如在好学生这个接口下,不同人对好学生的定义不同,有些人认为只要成绩好就是好学生,有些人认为综合发展才是好学生,不同的人有不一样的看法,这就是多态。


接口还可以进行赋值

  1. 将对象实例赋值给接口
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) boolAdd(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)就可以被读取到。

  1. 将一个接口赋值给另一个接口
    只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的。
    如果接口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 <name><artist><source><type>")}}case "remove":if len(tokens) == 3 {MusicMN.Remove(tokens[2])} else {fmt.Println("USAGE: lib remove <name>")}default:fmt.Println("Unrecognized lib command:", tokens[1])}
}func handlePlayCommand(tokens []string) {if len(tokens) != 2 {fmt.Println("USAGE: play <name>")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 <name><artist><source><type> -- Add a music to the music lib lib remove <name> -- Remove the specified music from the lib play <name> -- Play the specified music `)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 toolimport "errors"type MusicEntry struct {Id stringName stringArtist stringSource stringType 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 intfor 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 intprogress int
}
func (p *MP3Player)Play(source string) {fmt.Println("Playing MP3 music", source)p.progress = 0for p.progress < 100 {time.Sleep(100 * time.Millisecond) // 假装正在播放fmt.Print(".")p.progress += 10}fmt.Println("\nFinished playing", source)
}

play.go

package toolimport "fmt"
type Player interface {Play(source string)
}func Play(source, mtype string) {var p Playerswitch 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

Go语言案例(一)音乐播放器相关推荐

  1. C语言实现的音乐播放器

    C语言实现音乐播放器 #include <stdio.h> #include<dirent.h> #include<stdlib.h> #include<un ...

  2. Android 编程案例-本地音乐播放器源码及使用注意事项

    说一下代码在用的时候注意事项以及在运行的时候可能遇到的问题: 首先代码可以在创建相应文件后直接复制,这个案例用到了RecyclerView,所以需要先添加依赖.添加下面两个: implementati ...

  3. C语言实现简易音乐播放器

    注:在显示音乐播放器的图形界面时,根据的是页面的坐标来显示,故在运行时终端的大小应为Xshell全屏时终端的大小.(否则页面会显示的混乱) 具体运行效果: 原理: 切割歌词,存入数组,通过sleep函 ...

  4. 项目: 用C语言写一个音乐播放器

    目录 最终效果 代码 资源地址 最终效果 代码 /************ 音乐文件中 1开头的是周杰伦的歌 2开头的是林俊杰的歌 3开头的是许嵩的歌 *************/ #include& ...

  5. jQuery案例-网页音乐播放器01

    这是个比较麻烦的项目,先预热一波,这两天正式更新. 笔者打算分五六个博客来更新完整个项目.本案例是B站李南江老师的网课中一个项目,老师讲的非常不错,推荐.

  6. 基于易语言写QQ音乐播放器

    恕我json类用不了,我用了网友的水淼json类,很高兴他开源了,思路最重要!! https://c.y.qq.com/soso/fcgi-bin/client_search_cp?n=15& ...

  7. ESP32 也能实现精美UI 使用 HaaS UI Lite (Python)打造音乐播放器

       音乐播放器是大家日常使用最多的工具之一,类似的音频播放场景还有在医院/银行经常见到的排队叫号系统.工业场景中常用的的操作提示装置等等. 1.简介    本文将基于HaaS UI Lite开发框架 ...

  8. TXT音乐播放器与DirectSound与C++,开发笔记与EXE免费下载(一)

    一.前言 之前提到,使用C语言开发TXT音乐播放器.使用PlaySound方法播放wav文件时,无法同时播放多个wav文件:当开始播放下一个wav文件时,之前正在播放的wav文件就会停止,导致音乐播放 ...

  9. Android复习12【广播接收者-BroadcastReceiver(简单案例-发送广播、静态注册、动态注册、本地广播、代码示例(别处登陆踢用户下线)、常用系统广播总结、音乐播放器)】

    2020-04-28[11周-周二] 音乐播放器Android代码下载:https://wws.lanzous.com/ifqzihaxvij 目   录 简单案例-发送广播 2)动态注册实例(监听网 ...

最新文章

  1. 从城市治理到城市“智”理,AI 不仅是城市管理的“眼睛”
  2. Apache 配置关闭文件目录浏览
  3. leetcode-67-二进制求和
  4. 文献记录(part9)--A biclustering-based method for market segmentation using customer pain points
  5. 2.两数相加 golang
  6. 分享网页到微信朋友圈的官方接口
  7. -9 逆序输出一个整数的各位数字_逆序对个数(归并排序)
  8. web前端教程:css实现容器高度适应屏幕高度
  9. Android 系统定时管理器AlarmManager的使用
  10. string的一些基本操作和骚操作
  11. Android Studio酷炫插件(一)——自动化快速实现Parcelable接口序列化
  12. [Android Pro] RecyclerView实现瀑布流效果(二)
  13. Deepgreen DB 是什么(含Deepgreen和Greenplum下载地址)
  14. 海思Hi3798MV310机顶盒芯片Datasheet-基本信息
  15. 码农故事1——受惠寄居遇驴友 维艰窘境见人心
  16. 获取当前格林威治时间
  17. guzzle php,PHP中使用Guzzle进行API测试
  18. 开启FTP的21端口
  19. 【Linux】创建、修改和删除用户组(groupadd | groupmod | groupdel)
  20. rocketMQ启动可视化页面没有主题(Topic)的问题

热门文章

  1. 如何实现网页分享到微信,微博,空间
  2. 红米9a手动root方法
  3. 天河CAD2012打开服务器文件,AutoCAD无法启动,或启动中途闪退
  4. SQL----关于三种其他类型转字符串类型的函数
  5. 只要5分钟!学会自己打造多系统合集的DVD安装光盘
  6. 如何使用 K8s 实现跨集群管理,这篇文章告诉你了!赶紧收藏
  7. Android实战—Broadcast音乐盒(三)
  8. java迅雷下载excel,Asp.net生成Excel文件并下载(更新:解决使用迅雷下载页面而不是文件的问题)...
  9. 人工神经网络的算法原理,深度神经网络工作原理
  10. win7用计算机名无法访问局域网,Win7系统在局域网内无法访问文件内容的解决方法...