本篇概要:
1. 环境搭建;
下载地址:https://golang.google.cn/dl/
# 下载安装包
cd /usr/local/src
wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz
# 解压
tar zxvf go1.14.2.linux-amd64.tar.gz
# 拷贝到 /usr/local 下
mv /usr/local/src/go /usr/local
# 配置环境变量
# macos:vim ~/.bash_profile
vim /etc/profile.d/go.sh
# 写入以下内容
# GOROOT:go 安装文件的路径
# * GOPATH:go 的一些工具或下载,import包都会用到这个目录
# 比如 import "某包",在 GOROOT 里没有,就会去 GOPATH 去寻找
# 然后手动创建以下三个目录
# src 存放源代码(各种文件夹、.go 等)
# pkg 编译时生成的中间文件(比如:.a)
# bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,也可以不加)
export GOPATH=/Users/go
export GOROOT=/usr/local/go
export PATH=$PATH:$GOPATH/bin:$GOROOT/bin
# 保存退出,使其生效
source /etc/profile.d/go.sh
# 验证安装
go version
# 返回如下,搞定
go version go1.14.2 linux/amd64
操作
- 创建项目文件夹和代码文件
# src 文件夹下创建项目文件夹的代码文件
mkdir -p /Users/go/src/mygo
vim /Users/go/src/mygo/index.go
- 编写
/Users/go/src/mygo/index.go
// 类似于 php 的 namespace
package main
// 引入系统包
import "fmt"
// 入口函数
func main() {
fmt.Print("hello world")
}
运行
# 项目目录运行
cd /Users/go/src/mygo/
go run index.go
# 输出:hello world
# go build 运行
go build
# 没有输出,在同级目录生成了一个可执行程序 mygo.exe(目录名称)
# go install 运行
go install
# /Users/go/src/mygo/mygo.exe 消失
# /Users/go/bin/ 目录下生成了 mygo.exe,可以直接进入目录执行
2. 语法套路:类型、定义函数、返回值;
类型,主要分
- 基本类型(数字,字符串,布尔类型)
- 复合类型(数组 Array,结构 struct )
- 引用类型(指针,切片,函数、通道)
- 接口类型
字符串
// 使用 var 定义变量
var str string
str = "abc"
//定义变量,并指明类型,同时赋值
var str string = "abc"
// string 可以去掉,系统会自动推断类型
var str = "abc"
// 定义变量,并赋值,系统会自动推断类型
// 只能在函数内使用
str := "abc"
函数
// 一般函数
func getMe() string {
return "lisi"
}
// 支持参数
func getMe(prefix string) string {
return prefix + "lisi"
}
// 返回多个值
// main 函数不能有参数,也不能有返回值
func main() {
var name,age = getMe("程序员")
fmt.Println(name)
fmt.Println(age)
}
func getMe(prefix string) (string, int) {
return prefix + "lisi", 19
}
// 综合
func getMe(prefix , address string) (string, int) {
// 参数的定义方式类似: var abc,bcd string
name := prefix + "lisi " + address
age := 24
return name, age
}
func main() {
// 写法 1:
fmt.Println(getMe("Student ", "live in BJ"))
// 返回 Student lisi live in BJ 24
// 写法 2:
var a,b = getMe("Student ", "live in BJ")
// a 对应返回的第一个 string 参数对应 name, b 返回 int 参数对应 age
fmt.Println(a)
fmt.Println(b)
// 返回
// Student lisi live in BJ
// 24
}
3. 包引用、“实体类”使用(struct);
包最直观的的作用就是协调和组织代码,方便归类和复用,提高代码的可维护性
3.1 使用包;
# 首先在的 GOPATH 的 src 下创建一个文件夹,比如叫 com.test(建议 xxx.xx.xx)
cd /Users/go/src/
# 创建顶级包
mkdir com.test
# 接下来用 Goland 的直接打开 src 目录 (注意是直接打开 src,而不是下面的包)
# 打开 /Users/go/src/com.test
# 再创建子包 services(创建服务:用户、新闻相关业务逻辑服务)
mkdir /Users/go/src/com.test/services
touch /Users/go/src/com.test/services/UserService.go
touch /Users/go/src/com.test/services/NewsService.go
# 接下去运行,需要一个入口函数 / 包
# 创建文件夹 appmain,专门放入口函数
mkdir /data/go/src/com.test/appmain
touch /data/go/src/com.test/appmain/main.go
文件 /Users/go/src/com.test/services/NewsService.go
package services
func GetNews() string {
return "news info "
}
文件 /data/go/src/com.test/services/UserService.go
// 如果在文件夹里创建 .go 文件,默认 package [文件夹名]
package services
// package 名称是否要和文件夹名称一样?不一定
// package abc:会报错"在一个文件夹内不能出现不同包名"
// 想要不报错,把其它文件的 package 也改成 abc
// 但是一般情况下 package 会创建成个文件夹一样的名字,方便代码阅读和维护
func GetUser() string {
// 在 UserService 调用 NewsService
// 同包调用:不需要 import,直接调用
// 同一个包,函数名不能冲突
news := GetNews()
return "user info " + news
}
文件 /Users/go/src/com.test/appmain/main.go
// package appmain
// 和之前的 package appmain 不一样
// 之前写的入口文件有两个关键点
// 第一是 package,是个特殊的 package, 它本身就是个 main 包
package main
import (
// 这里的包引用是文件夹路径
// 就算把 UserService 的导包改成 package abc,这里依然不变
"com.test/services"
// 在 /usr/local/go/src/ 下有 fmt 文件夹,里面有相关函数,比如 print
"fmt"
)
func main() {
// 异包调用函数,异包的函数首字母大写:getUser() 改成 GetUser()
// 这里的应用和包名有关系
// 如果异包的包名改成了 package abc。这里就是 abc.GetUser()
result := services.GetUser() +
services.GetNews()
fmt.Print(result)
// 输出:user info news info news info
}
3.2 Go 的“实体类”;
# golang 不像 java 或PHP,有 class 类等面向对象的概念
# 一般可以使用 struct 来实现类似的感觉
type User struct {
Uid int
Uname string
}
# 这好比有个类叫做 User,里面有两个属性 uid 和 uname, 也没有 public、protected、private 修饰符
创建文件 /Users/go/src/com.test/models/UserModel.go
package models
// 创建结构体
// 如果要外包调用,首字母必须大写
type UserModel struct {
// 外包调用结构体
// 属性必须首字母大写才能被调用
Uid int
Uname string
}
// u 是一个参数
// ToString 是方法名
// 返回 string 类型
// 相当于一个类方法
func (u UserModel) ToString() string {
return "用户名是 " + u.Uname
}
// 无效方法,之后说明
func (u UserModel) SetValue(id int, name string) {
// 修改值不会修改所指向的内存,所以修改无效
// 如果要修改 UserModel 改为 *UserModel
u.Uname = name
u.Uid = id
}
// 这是一个纯函数
func ToString() string {
return "测试字符串 "
}
文件 /data/go/src/com.test/services/UserService.go
package services
import "com.test/models"
func GetUser() string {
// 调用结构体
user := new(models.UserModel)
user.Uname = "Jerry "
// 修改无效
user.SetValue(123, "Tom")
return user.ToString()
}
文件 /Users/go/src/com.test/appmain/main.go
调用,返回结果:用户名是 Jerry
4. 指针类型;
概念
- 每个座位就是一个内存,由系统开辟。如果不开辟,椅子是没有的
- 每个椅子后面的编号(三排一座、三排二座)这些好比就是内存地址。切换到计算机,就是十六进制的字符(比如:0xc03201b1a2)
- 椅子上可能会坐人,坐在上面的人就是数据
- 标签“局长专座” 就是定义的变量
- 内存地址(椅子后面的编号比如:三排一座)指向内存空间(某一个椅子),内存空间有数据(椅子上坐的一个人)、会放什么值
- 如何操作地址和如何获取、如何对内存开辟?比如电影院有八个位置,由于人比较多,不得不再增加一个位置,于是在电影院再放一张椅子,这就是开辟内存。开辟内存有了椅子之后,一定要写上一个标签,比如二排四座,这就是内存地址
- 有个“局长专座”。之前要坐位置,需要找到“几排几座”,找到位置坐下来。但是有的位置是领导坐的,只能是领导坐的,所以会在椅子上再去贴上一个标签比如“局长专座”,以后要找局长,直接就去找“局长专座”的标签的位置。如果硬要和程序对应,就定义为“变量”,“变量”就是贴上去的标签
- 变量、内存地址和内存之间的映射是由相关的程序进行实现的
直接去记内存地址(0xc03201b1a2)是很麻烦的,也不可能这么做。所以很多高级语言会有:变量,并且隐藏了内存地址和变量之间的映射关系
// 定义字符串变量,这里的 name 就相当是“局长专座”
// 电影院一张椅子已经贴上了“局长专座”的标签,已经有地址(内存地址)了
// 在这行代码里看不到类似“0xc03201b1a2”,已经隐藏了内存地址
// 只需要对标签赋值
// 这时候内存地址是不知道的
name := "Jerry"
// 如果需要看一下内存地址(几排几座)的话
// 加“&”,就可以获得地址
// 有时候局长会在电影院里调整座位,内存地址会发生变化
// 但是标签和值在代码里的展示部分是不会有变化的
fmt.print(&name)
// 已经知道了地址,比如 0x12d492d,想获取坐在上面的人是谁
//这就是取值的过程
name:="Jerry"
// 先取地址,后取值
fmt.Print(*&name)
// 输出 Jerry
// “*” 符号好比起到了取值的作用。
// “&” 取地址
// 上面两者互为反操作
- 文件
/Users/go/src/com.test/appmain/main.go
package main
import "fmt"
func main() {
// 定义一个标签叫 name,也就是变量,坐上去的这个人就是 Jerry,就是数据
// 这时候内存地址是不知道的
var name string="Jerry"
// 输出 Jerry
fmt.Print(name)
// 返回内存地址
// 没有任何可理解的业务意义,一般不会对内存地址进行直接操作
// 输出 0xc000010200 (每次输出可能会不一样)
fmt.Print(&name)
// 取地址的值
// 返回值:输出 Jerry
fmt.Print(*&name)
}
指针类型
普通变量
// 拥有地址,系统已经开辟了内存,只不过是空字符串
// 好比电影院已经有了位置和地址(几排几座),但是座位上的人不在
var name string
// 返回空
fmt.Println(name)
定义指针类型
// 未初始化指针变量是 nil,内存还没有开辟空间
// 好比领导告诉电影院管理员,需要加一个位置,所以管理员准备加一个位置
// 实际上椅子还没有搬过来,椅子上是几排几座也不知道
// 这里的 name 就是地址(几排几座),标签(局长专座)
var name *string;
// 返回<nil>
fmt.Println(name)
// 怎么让变量有,这时候就要去买椅子
// 开辟内存的过程就是领导让电影院管理员去购买椅子
// 使用关键词 new 开辟内存并且返回内存地址
// 开辟内存,并返回内存地址,于是 name 这个指针变量就有内存地址了
// 这个过程就好比有了一个座位,有座位就可以往电影院随便放,就可以获取位置了(几排几座)
name = new(string)
// 不能 name = "Jerry",因为 name 是内存地址/地址、标签(几排几座),不可能让人坐在标签上
// 取值是 *name ,赋值也得加“*”
*name = "Jerry"
// 返回内存地址
fmt.Println(name)
// 返回值
// *name 取值,取座位上的人、或者指向另外一个座位
fmt.Println(*name)
- 文件
/Users/go/src/com.test/appmain/main.go
package main
import "fmt"
func main() {
// 定义指针变量
var name *string
// 开辟内存空间赋值给指针,指针就有内存地址了
name = new(string)
// 赋值
*name = "Jerry"
// 取值
fmt.Println(*name)
}
5. 函数及参数传递;
示例 1
package main
import "fmt"
func main() {
// 定义变量
var me string = "Jerry"
// 定义指针变量,没有初始化,之后设置成 me 所在的地址
// 现在已经存在是一个内存块,有 me 的值
var u *string
// u 赋值给 me 的地址
// 在指针变量里,设置地址只要直接写变量
// 普通变量,取地址需要在前面加“&”,取值要加“*”
u = &me
// 地址一样,输出 Jerry Jerry
fmt.Println(me, *u)
// 任意修改其中一个值
// 指针变量 u 设置为 me 的地址,因此只要 “改变” me 的值
// 则 u 的值也会改变
// me 改掉,
// me = "Tom",等于把 me 所在的指针地址指向一个新的内存块,块的值就是 Tom
// u 的值也发生了改变
me = "Tom"
// 输出 Tom Tom
fmt.Println(me, *u)
// 注意:字符串是不可变的。修改字符串值不是把原有的值给改了,只不过是指针的指向
// 在并发操作中,如果直接在原对象值上改,会出现线程不安全
// 现在这样操作是直接产生一个新对象,然后把指针指向新对象,就不需要加锁
// 指向新的内存对象
*u= "Perry"
// 输出 Perry Perry
fmt.Println(me, *u)
}
示例 2
package main
import "fmt"
func main() {
// 定义变量
var me string = "Jerry"
fmt.Println(me) // Jerry
// 向函数传递 me 所指向的值
// 修改值不会修改 me 所指向的内存
// 所以在 test() 函数里修改 p 的值 没有任何变化
test1(me)
fmt.Println(me) // Jerry
// 传地址
test2(&me)
fmt.Println(me) // abc
}
func test1(p string) {
// 指向新内存,值为 abc
p = "abc"
}
// 如果需要让 main 函数里的 me 产生变化
// test2() 的 p 参数设置为指针
func test2(p *string) {
// 把所指向的值给改了,并不是改指针
// 指向到了 abc
*p = "abc"
}
// 输出 Jerry Jerry abc
示例 3
package main
import "fmt"
func main() {
// 定义指针变量
u := new(string)
*u = "Stark"
// 打印值:Stark
fmt.Println(*u)
// 打印地址:0xc000010200
fmt.Println(u)
// 传地址 u,*u 是传值
test2(u)
fmt.Println(*u) // abc
test3(u)
// 输出指针变量 u 的地址
// 指针地址没有发生变化
// 之前使用普通变量,按值传递
// 传指针,也是传的是"指针所指向的值",并没有把指针给改掉
fmt.Println(u) // 0xc000010200
}
// 如果需要让 main 函数里的 me 产生变化
// test2() 的 p 参数设置为指针
func test2(p *string) {
// 把所指向的值给改了,并不是改指针
// 指向到了 abc
*p = "abc"
}
func test3(p *string) {
// 把指针地址改成 nil
// 在指针没有进行初始化的时候,它的值就是 nil
p = nil
}
// 输出
// Stark
// 0xc000010200
// abc
// 0xc000010200
6. 结构体;
6.1 设置一个实体类、初始化;
在 Go 中实现类似 OOP 的功能使用结构体:可以把结构体看做是一堆应该放在一起才更有意义的 类型集合
// 在其它语言中就是一个实体类
package main
import "fmt"
type NewsModel struct {
NewsID int
NewsTitle, NewsContent string
}
func main() {
// 初始化方式 1
// 变量初始化,类型是 NewsModel
// 类似 var abc string
// 结构体已经在 var 的时候已经在内存了开辟了空间
// 对成员结构进行了初始化,只不过是 0 值
var news NewsModel
// 赋值
news.NewsID = 123
news.NewsTitle = "title"
// 没有赋值的时候,打印返回 {0 }, 0 就是 int,空就是两个 string
// 结构体可以直接打印,根据一定的格式显示
fmt.Println(news) // {123 title }
// 初始化方式 2:定义的时候直接初始化
// var news2 NewsModel = NewsModel{123,"title", "content"}
// news2 := NewsModel{123,"title","content"}
news2 := NewsModel{NewsTitle: "title2", NewsID: 11}
fmt.Println(news2) // {11 title2 }
// 初始化方式 3:指针方式
var news3 *NewsModel
news3 = new(NewsModel)
// 设置值
// (*news3).NewsID = 233
news3.NewsTitle = "news3title"
// 取出来的是值 news3title,而不是地址
fmt.Println(news3.NewsTitle)
// 结构体内部,成员变量的地址 0xc0000901e8
fmt.Println(&(news3.NewsTitle))
// 指针变量初始化,参数需要写全
*news3 = NewsModel{1344,"news4title", "news4content"}
// news3 = &NewsModel{1344,"news4title", "news4content"}
fmt.Println(news3) // &{1344 news4title news4content}
}
6.2 使用第三方包、JSON 化结构体;
使用第三方包、避免重复造车轮
- 官方 JSON 包性能差、易用性低
- 使用第三方 JSON 包、性能不错:https://github.com/pquerna/ffjson
# 下载包
go get -u github.com/pquerna/ffjson
# 除了 github 还能从自己的 git 中导入的包并编译(中间文件),会保存在 gopath 的第一个工作区中
# -u :如果本地已经存在该包,则依然强行更新
示例
package main
import (
"fmt"
"github.com/pquerna/ffjson/ffjson"
)
type NewsModel struct {
NewsID int
NewsTitle string
}
// 定义方法
func (news NewsModel) ToJSON() (string) {
result, err := ffjson.Marshal(news)
if err != nil {
// fmt.printIn(err.Error())
return err.Error()
} else {
// 字节数组转 string
// 返回 {"NewsID":123,"NewsTitle":"title"}
return string(result)
}
}
func main() {
news := NewsModel{123, "title"}
fmt.Println(news.ToJSON())
}
// 如果将以上结构体单独写入类 /Users/go/src/com.test/models/NewsModel.go
// 则调用方式:
// news := models.NewsModel{NewsID: 123, NewsTitle: "title", NewsContent: "content"}
// var news models.NewsModel
// news.NewsID = 1
// news.NewsTitle = "title"
// fmt.Println(news.ToJSON())
6.3 继承、数组;
Go 没有 extends、implements 等关键字,但是依然可以使用一些方式来完成面向对象编程里的继承:结构体嵌套
- 母类
/Users/go/src/com.test/models/NewsModel.go
package models
import "github.com/pquerna/ffjson/ffjson"
type NewsModel struct {
NewsID int
NewsTitle string
}
// 定义方法
func (news NewsModel) ToJSON() string {
result, err := ffjson.Marshal(news)
if err != nil {
return err.Error()
} else {
return string(result)
}
}
- 子类继承母类
/Users/go/src/com.test/submodels/SportsNews.go
package submodels
import (
"com.test/models"
"github.com/pquerna/ffjson/ffjson"
)
type SportsNews struct {
Tags []string // array
// 下面这种写法只是结构体的组合,并不实现继承功能
// News models.NewsModel
// 在 main() 方法中
// var sn submodels.SportsNews
// sn.News.NewsID 只能用过类寻找到变量
// 下面这种写法只有类型(NewsModel 类型),不加入任何名称,是匿名的
// 这样就可以实现继承的感觉
models.NewsModel
// sn.NewsID = 123
// sn.NewsTitle = "title"
// fmt.Println(sn.ToJSON())
}
// 覆盖母类的方法,就直接执行子类的方法
func (sn SportsNews) ToJSON() string {
result, err := ffjson.Marshal(sn)
if err != nil {
return err.Error()
} else {
return string(result)
}
}
- 文件
/Users/go/src/com.test/appmain/main.go
package main
import (
"com.test/submodels"
"fmt"
)
func main() {
// 调用结构体
//news := models.NewsModel{123,"title"}
//fmt.Println(news.ToJSON())
// 1. 继承调用
var sn submodels.SportsNews
sn.NewsID = 112233
sn.NewsTitle = "BigNewsTitle"
sn.Tags = []string{"足球", "篮球", "游泳"}
// 打印返回 {"NewsID":112233,"NewsTitle":"BigNewsTitle"}
// 执行母类的 ToJSON() 方法, tags 没有打印出来
// 子类方法覆盖后,返回
// {"Tags":["足球","篮球","游泳"],"NewsID":112233,"NewsTitle":"BigNewsTitle"}
fmt.Println(sn.ToJSON())
// *2. 数组的使用
// var arr []int
// var arr []models.NewsModel
var arr []string = []string{"a", "b", ""}
fmt.Println(arr, len(arr)) // [a b ] 3
// 先设定数组长度
// 数组长度设定后,不能改变
var arr2 [3]string
arr2[0] = "aa"
arr2[2] = "cc"
fmt.Println(arr2) // [aa cc]
// 对索引进行赋值
var arr3 [3]string = [3]string{0:"aaa", 2: "ccc"}
fmt.Println(arr3) // [aaa ccc]
// 对数组进行操作,要用到切片
}
7. interface 接口;
7.1 实现接口,简单工厂模式;
- 新增
/Users/go/src/com.test/services/IService.go
,专门放服务接口
package services
// 把代码抽象到 interface 里
type IService interface {
Get (id int) (string)
}
- 新增
/Users/go/src/com.test/services/NewsService.go
,专门放服务接口
package services
type NewsService struct {
}
// 普通写法: func (ns NewsService) Get(id int) (string) {
// 指针写法:
func (ns *NewsService) Get(id int) (string) {
return "单个新闻内容"
}
- 文件
/Users/go/src/com.test/appmain/main.go
package main
import (
"com.test/services"
"fmt"
)
func main() {
// 前面是接口,后面是具体的实现类
//var service services.IService = services.NewsService{}
var service services.IService = new(services.NewsService) // 此写法兼容指针调用
fmt.Println(service.Get(123))
}
7.2 简单工厂模式;
-
设计模式中:工厂模式有几种类型(简单工厂、工厂方法、抽象工厂)
-
基本特征:掩盖 new 过程;会有一个工厂类,且一般会有个方法接受参数,根据参数来决定实例化什么类
-
可能损耗一些性能,但是可维护性高
-
/Users/go/src/com.test/services/IService.go
package services
// 工厂模式
type ServiceFactory struct {
}
// 普通方法
func NewsServiceFactory() (*ServiceFactory) {
return &ServiceFactory{}
}
func (sf ServiceFactory) Create(name string) (IService) {
switch name {
case "news":
return &NewsService{}
case "user":
return &UserService{}
default:
return nil
}
}
/Users/go/src/com.test/services/NewsService.go
package services
type NewsService struct {
}
// 指针写法:
func (ns *NewsService) Get(id int) (string) {
return "单个新闻内容"
}
- 文件
/Users/go/src/com.test/appmain/main.go
package main
import (
"com.test/services"
"fmt"
)
func main() {
// 工厂调用
// 调用方法1:(不推荐)
var sevice11 services.IService = new(services.ServiceFactory).Create("news")
fmt.Println(sevice11.Get(123))
// 调用方法2:
var sevice22 services.IService = services.NewsServiceFactory().Create("news")
fmt.Println(sevice22.Get(123))
}
7.3 包构造函数、自动注册接口(代码技巧);
调整结构:
- 新增
/Users/go/src/com.test/core/IService.go
package core
type IService interface {
Get (id int) (string)
}
// init
var myService IService
func SetService(service IService) {
myService = service
}
func GetService() (IService) {
return myService
}
新增 /Users/go/src/com.test/services/ServiceFactory.go
package services
import "com.test/core"
// 工厂模式
type ServiceFactory struct {
}
// 普通方法
func NewsServiceFactory() (*ServiceFactory) {
return &ServiceFactory{}
}
func (sf *ServiceFactory) Create(name string) (core.IService) {
switch name {
case "news":
return &NewsService{}
case "user":
return &UserService{}
default:
return nil
}
}
// 写了 init() 函数后 ,main() 函数就不需要写 SetService
func init() {
core.SetService(NewsServiceFactory().Create("news"))
}
- 文件
/Users/go/src/com.test/appmain/main.go
package main
import (
"com.test/core"
_ "com.hua/services" // 可前置加 s 为别名,加 "_” 为不调用
// . "com.test/services" // 改成”.“,调用就不需要前缀
"fmt"
)
// 入口函数 main()
// 还有 init() 函数(包函数),能够应用于所有的 package,可以有多个,均会执行,不可以有参数和返回值
// main() 函数引入包时,
//(1)初始化包常量(变量);
//(2)执行包内 init() 函数;
// (3) 执行 main() 函数
func main() {
var sevice core.IService = services.NewsServiceFactory().Create("news")
fmt.Println(sevice.Get(123))
// 调用
core.SetService( services.NewsServiceFactory().Create("news"))
fmt.println(core.GetService().Get(123))
// 不需要抵用 SetService(),因为已经写了 init() 函数
fmt.Println(core.GetService().Get(123))
}