This commit is contained in:
Shikong 2021-09-11 18:30:18 +08:00
parent 4a486b5aa4
commit 1e4676b7b7
51 changed files with 7799 additions and 0 deletions

55
notes/1.1.工具命令.md Normal file
View File

@ -0,0 +1,55 @@
#### 0x00 Go工具命令
##### get 命令
该命令可以借助`代码管理工具`通过远程拉取或更新代码包及其依赖包,并自动`完成编译和安装`,整个过程类似安装App一样;
目前支持的动态获取远程代码包的有`BitBucket、GitHub、Google Code 和 Launchpad`,所以为了 go get 命令能正常工作您需要安装与远程包仓库匹配的代码管理工具,如 `Git、SVN、HG` 等,由于它自动根据不同的域名调用不同的源码工具,对应关系如下:
```
BitBucket (Mercurial Git)
GitHub (Git)
Google Code Project Hosting (Git, Mercurial, Subversion)
Launchpad (Bazaar)
```
该命令内部实际上分成了两步操作:
- 第一步是下载源码包假设远程包的路径格式
```
github.com/golang/go
```
```
#比如github网站的项目包路径都有一个共同的标准
github.com - 网站域名:表示代码托管的网站,类似于电子邮件 @ 后面的服务器地址。
golang - 作者或机构: 表明这个项目的归属,一般为网站的用户名,如果需要找到这个作者下的所有项目,可以直接在网站上通过搜索“域名/作者”进行查看。( 似于电子邮件 @ 前面的部分。)
go - 项目名称 :每个网站下的作者或机构可能会同时拥有很多的项目,图中标示的部分表示项目名称。
```
- 第二步是执行 go instal
参数介绍:
```
$go get [options]
* -d 只下载不安装
* -f 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中已经获取了的包(`对于本地 fork 的包特别有用`)
* -fix 在获取源码之后先运行 fix然后再去做其他的事情
* -t 同时也下载需要为运行测试所需要的包
* -u 下载丢失的包,但不会更新已经存在的包
* -v 显示操作流程的日志及信息,方便检查错误
* -insecure 允许使用不安全的 HTTP 方式进行下载操作
```
基础实例:
```
#例如想获取 go 的源码并编译,然后他会编译安装到 GOPATH 目录中
$ go get github.com/davyxu/tabtoy
```

766
notes/1.入门篇.md Normal file
View File

@ -0,0 +1,766 @@
**目录一览:**
- 0x00 前言简述
- - What,什么是Go语言?
- Why,为啥要用Go语言?
- How,Go语言发展前景?
- 0x01 Go语言开发环境搭建
- - Hello World
- Http Web Server
- Echo 类似命令程序
- (1) Ubuntu桌面版
- (2) 开发编辑器
- 版本平台
- 开发环境
- Let,Go
- 0x02 Go 项目结构
- - (1)适合个人开发者
- (2)适合企业开发场景
- (3)目前流行的项目结构
![img](assets/4aa545dccf7de8d4a93c2b2b8e3265ac0a26d216.png@progressive.webp)
# 0x00 前言简述
**为什么你应该学习Go语言**
> 世界上已经有太多太多的编程语言了为什么又出来一个Go语言
> 因为其编程语言的良好特性和Google公司强势推广以及以Go语言相关开源项目火爆, 比如 `etcd、kubernetes、Prometheus` 等项目, 其次是硬件性能提高则我们需要高性能的软件
- 1.参考 Golang 编程语言排行榜 : https://www.tiobe.com/tiobe-index
![WeiyiGeek.2010-2021年Go编程趋势](assets/f5e12e4882753fee5ba818396bd935485663654d.png@942w_741h_progressive.webp)
- 2.硬件限制(摩尔定律)已然失效
> 摩尔定律当价格不变时集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍性能也将提升一倍。 换言之每一美元所能买到的电脑性能将每隔18-24个月翻一倍以上。
![WeiyiGeek.1975-2015年处理器集体管数量](assets/bb07174f845a5c5495945a63f03d5870a832d1d7.png@900w_624h_progressive.webp)
从上面的图表可以看出近十年单线程性能和处理器频率保持稳定。我们不能像之前一样把添加更多晶体管当成是解决方案因为在较小规模上一些量子特性开始出现如隧道效应并且因为在同样小的空间里放置更多晶体管的代价非常昂贵每1美元可以添加的晶体管数量开始下降。
制造商开始从其他方面提高处理器的性能, 但这中解决方案也有其自身的局限性。因为成本原因我们不能无限制的为计算机添加内核,也无法无限制的添加缓存来提高性能,因为缓存越大,取值的性能越低。
```shell
* 1.向处理器添加越来越多的内核如四核和八核CPU。
* 2.发明了超线程技术。
* 3.为处理器添加了更多缓存以提高性能。
```
Tips : 一旦我们没有办法在硬件上一直取得突破,我们只能提高软件的性能或者说我们需要高性能的软件。
如上所述硬件制造商正在为处理器添加越来越多的内核以提高性能。所有数据中心都在这些处理器上运行更重要的是今天的应用程序使用多个微服务来维护数据库连接消息队列和维护缓存。因此开发的软件和编程语言应该可以轻松地支持并发性并且应该能够随着CPU核心数量的增加而可扩展。
**发展历史**
描述: 大多数现代编程语言如JavaPython等都来自90年代的单线程环境。虽然一些编程语言的框架在不断地提高多核资源使用效率例如 Java 的 Netty 等,但仍然需要开发人员花费大量的时间和精力搞懂这些框架的运行原理后才能熟练掌握。
- Go 于 2007年9月由于`罗伯特·格瑞史莫、罗勃·派克及肯·汤普逊于开始设计,[3]稍后伊恩·兰斯·泰勒Ian Lance Taylor、拉斯·考克斯Russ Cox`加入项目。
![WeiyiGeek.Go核心开发团队](assets/da92a01501b0356acdde8107417b0a067d23cf91.png@942w_459h_progressive.webp)
- Go 于 2009年11月正式宣布推出版本1.0在2012年3月发布之后Go广泛应用于Google的产品以及许多其他组织和开源项目。
- Go 于 2009年11月10日由Google发布推出成为开放源代码项目支持Linux、macOS、Windows等操作系统。2005 年 AMD 速龙发布第一款民用级多核处理器,所以当时多核处理器已经上市。
![WeiyiGeek.为现代软件工程而生的Go](assets/6d4bdcba8912f91f836f5f48691e2f0cb6437457.png@942w_479h_progressive.webp)
Tips : 在2016年Go 被软件评价公司TIOBE选为`"TIOBE 2016年最佳语言"`。
# What,什么是Go语言?
描述:`Go又称Golang[4]-Google-go-language`是Google开发的一种`静态强类型`、`编译型`、`并发型`,并具有`垃圾回收功能、优良的并行设计`的编程语言, 其被誉为21世纪的C语言, 因为其Go的语法接近C语言但对于变量的声明有所不同。
Go的并行计算模型是`以东尼·霍尔`的通信顺序进程CSP为基础采取类似模型的其他语言包括`Occam`和`Limbo`Go也具有这个模型的特征比如通道传输通过`goroutine`和通道等并行构造可以建造线程池和管道等。
- (1) 与 `C++` 相比 Go 并不包括如`枚举、异常处理、继承、泛型、断言、虚函数`等功能,但增加了`切片(Slice) 型、并发、管道、垃圾回收功能、接口等`特性的语言级支持以及支持垃圾回收功能。
- (2) 不同于 JavaGo原生提供了关联数组也称为`哈希表Hashes`或`字典Dictionaries`)。
**Goroutine 并行设计**
描述:
透过Goroutine能够让程序以异步的方式运行而不需要担心一个函数导致程序中断因此Go也非常地适合网络服务。
假设有个程序,里面有两个函数:
```go
func main() {
// 假設 loop 是一個會重複執行十次的迴圈函式。
// 迴圈執行完畢才會往下執行。
loop()
// 執行另一個迴圈。
loop()
}
```
如此就不需要等待该函数运行完后才能运行下一个函数。
```go
func main() {
// 透過 `go`,我們可以把這個函式同步執行,
// 如此一來這個函式就不會阻塞主程式的執行。
go loop()
// 執行另一個迴圈。
loop()
}
```
**参考来源:**
Wiki 百科 : https://wiki.weiyigeek.top/w/index.php?search=go&title=Special:搜索&go=前往
官方网站 :
- https://golang.org/
- https://golang.google.cn/
# Why,为啥要用Go语言?
**Go 语言有何特点:**
- 0.Go语言语法简单易学、代码风格统一`(自动格式化)`、支持垃圾回收功能、执行性能好、是企业级编程语言。
> Go 语言简单易学,学习曲线平缓,不需要像 C/C++ 语言动辄需要两到三年的学习期。
> Go 语言的风格类似于C语言。其语法在C语言的基础上进行了大幅的简化去掉了不需要的表达式括号循环也只有 for 一种表示方法,就可以实现数值、键值等各种遍历。
> Go 语言提供了一套格式化工具`go fmt`, 在开发环境或者编辑器在保存时,都会使用格式化工具进行修改代码的格式化,这样就保证了不同开发者提交的代码都是统一的格式。
- 1.Go语言让你用写Python代码的开发效率编写C语言代码 (`编程范型: 编译型,可平行化,结构化,指令式`)。
- 2.Go语言从底层原生支持并发无须第三方库、开发者的编程技巧和开发经验。
> Go语言的并发是基于 goroutine`类似于线程,但并非线程` 的。可以将 goroutine 理解为一种虚拟线程。Go 语言运行时会参与调度 goroutine并将 goroutine 合理地分配到每个 CPU 中最大限度地使用CPU性能。开启一个goroutine的消耗非常小`大约2KB的内存`你可以轻松创建数百万个goroutine。
> goroutine的特点:
\* 具有可增长的分段堆栈。这意味着它们只在需要时才会使用更多内存。 * 启动时间比线程快。 * 原生支持利用channel安全地进行通信。 * 共享数据结构时无需使用互斥锁。
- 3.Go语言性能强悍同`C,C++`一样Go语言也是编译型的语言它直接将人类可读的代码编译成了处理器可以直接运行的二进制文件执行效率更高性能更好。
解释型语言Shell 语言、Python 语言、PHP 语言 编译型语言C 语言、C++ 语言、Go 语言
![WeiyiGeek.编译型的语言](assets/dbe8a06f8a3d61f2e167d7b3ee3c5ad49a1f8d86.png@942w_566h_progressive.webp)
由下图可以看出Go 语言在性能上更接近于 Java 语言,虽然在某些测试用例上不如经过多年优化的 Java 语言,但毕竟 Java 语言已经经历了多年的积累和优化。Go 语言在未来的版本中会通过不断的版本优化提高单核运行性能。
![WeiyiGeek.常见编程语言运行性能比较](assets/6a1c46098135c9607151fade0505268e2bbfacd1.png@942w_249h_progressive.webp)
数据来源https://benchmarksgame-team.pages.debian.net/benchmarksgame/
**Go 语言的缺陷**
Tips : Go 2.0 版本将支持泛型,对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。
# How,Go语言发展前景?
目前Go语言已经⼴泛应用于人工智能、云计算开发、容器虚拟化、⼤数据开发、数据分析及科学计算、运维开发、爬虫开发、游戏开发等领域。
Go语言简单易学天生支持并发完美契合当下高并发的互联网生态并且在多核并发上拥有原生的设计优势, 很多公司,特别是中国的互联网公司,即将或者已经完成了使用 Go 语言改造旧系统的过程。经过 Go 语言重构的系统能使用更少的硬件资源获得更高的并发和I/O吞吐表现。充分挖掘硬件设备的潜力也满足当前精细化运营的市场大环境。
所以当下程序开发者应该抓住趋势,要学会做一个领跑者而不是跟随者。
国内Go语言的需求潜力巨大目前无论是国内大厂还是新兴互联网公司基本上都会有Go语言的岗位需求。
**Go 在互联网企业应用以及使用它的公司:**
- Go在百度内部应用百度流量入口BFE、自动驾驶、百度只能小程序、百度APP
- Go在腾讯内部应用蓝鲸运维平台、微服务框架TarsGo、云平台
- Go在知乎内部应用知乎全站重构
![WeiyiGeek.应用Go语言的公司](assets/13e14c6da60930aa4dad336ce26f83145c64476e.png@942w_261h_progressive.webp)
现在就开始你的Go语言学习之旅吧人生苦短lets Go, 下面我们将进行Go开发环境安装实践。
# 0x01 Go语言开发环境搭建
描述: 本章节主要讲解安装Go语言及搭建Go语言开发环境。
Tips : Go语言1.11版本之后开启go mod模式之后就不再强制需要配置GOPATH了。
Tips : Go语言1.14版本之后,推荐使用go modules管理依赖也不再需要把代码写在GOPATH目录下了之前旧版本的教程戳这个链接。
**下载地址:**
Go官网下载 : https://golang.org/dl/
Go官方镜像站推荐https://golang.google.cn/dl/ & https://studygolang.com/dl
# 版本平台
描述: Windows平台和Mac平台推荐下载可执行文件版Linux平台下载压缩文件版。
当前 2021年7月28日 22:43:15 最新版本为1.16.16版。
- Source: 源代码编译安装-https://dl.google.com/go/go1.16.6.src.tar.gz
- Binary: 二进制解压安装-https://dl.google.com/go/go1.16.6.linux-amd64.tar.gz
Tips 由于时间的推移的1.16.6版本号可能并不是最新的但总体来说安装教程是类似的Go语言更新迭代比较快推荐使用较新版本体验最新特性。
**Windows**
描述: 此安装实例以 64位 Win10 系统安装 `Go 1.14.1` 可执行文件版本为例, 双击下载好的文件,然后按照下图的步骤安装即可。
![WeiyiGeek.Windows](assets/27fe8a5dd8aa47fa49b7a5db789310e2c545ffa8.png@942w_624h_progressive.webp)
**Linux**
如果不是要在Linux平台敲go代码就不需要在Linux平台安装Go我们开发机上写好的go代码只需要跨平台编译详见文章末尾的跨平台编译好之后就可以拷贝到Linux服务器上运行了这也是go程序跨平台易部署的优势。
```shell
#1.下载二进制源码包并将将下载的源码包解压至 /usr/local目录
wget https://dl.google.com/go/go1.16.6.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.16.6.linux-amd64.tar.gz
#2.将 /usr/local/go/bin 目录添加至PATH环境变量
#在/root/.profile进行添加
export GOROOT=/usr/local/go # 安装目录
#GOROOT 第三方包的安装包路径
export GOPATH=/home/go/ # 项目路径一般指向src
#需要BIN目录和GOPATH
export PATH=$PATH:$GOROOT/bin
#3.验证是否安装成功, 可以打开终端窗口输入go version命令查看安装的Go版本。
go env
go version go version go1.16.6 linux/amd64
#4.go语言程序编译运行
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
#5.编译运行hello.go
go build hello.go
go run hello.go && ./hello
```
![WeiyiGeek.Go-Binary](assets/b83caffa0941d9b3c929e09be07098d5e8563960.png@906w_1031h_progressive.webp)
# 开发环境
描述: 由于本人习惯于使用Ubuntu作为开发环境此处Go语言的学习环境以`Ubuntu 20.04 Desktop`版本为例进行。
# (1) Ubuntu桌面版
- Ubuntu 20.04 LTS Desktop 操作系统 : https://ubuntu.com/download/desktop
- Ubuntu 20.04 LTS Desktop 基本配置 : https://mp.weixin.qq.com/s/6Ip[AV5](https://www.bilibili.com/video/AV5)-Hdi-mpbH9XlTCOw
```shell
# (1) xrdp 安装与配置 : https://mp.weixin.qq.com/s/JA04lJrQETDXP0N_mrthJg
# xfce4 桌面
# echo xfce4-session > ~/.xsession
# (2) ZSH 命令行终端优化 https://mp.weixin.qq.com/s/8i0K2D-hp51U6UFOd25c2Q
# (3) 中文输入法安装 :
- https://pinyin.sogou.com/linux/help.php
- https://srf.baidu.com/site/guanwang_linux/index.html
```
- Ubuntu 20.04 LTS Desktop 安装配置 Go 语言:
```shell
# 环境变量
vim /etc/profile.d/golang-env.sh
export GOROOT=/home/weiyigeek/app/program/go
export GOPATH=/home/weiyigeek/app/program/project/go
export PATH=${PATH}:${GOROOT}/bin
# 创建 bin,pkg,src
mkdir -vp ${GOPATH}/{bin,pkg,src}
mkdir: 已创建目录 'go'
mkdir: 已创建目录 'go/bin' # 用于存放编译后生成的可执行文件
mkdir: 已创建目录 'go/pkg' # 用于存放编译生成的归档文件
mkdir: 已创建目录 'go/src' # 用于存储源码文件代理包以及自开发包
# 重开Shell验证版本
source /etc/profile
go version
# go version go1.16.6 linux/amd64
```
**环境变量:**
描述: 利用 `go env` 命令查看相关go语言相关的环境变量
```shell
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/weiyigeek/.cache/go-build"
GOENV="/home/weiyigeek/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/weiyigeek/app/program/project/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/weiyigeek/app/program/project/go"
GOPRIVATE=""
GOPROXY="https://goproxy.cn,direct"
GOROOT="/home/weiyigeek/app/program/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/weiyigeek/app/program/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.16.6"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3886050940=/tmp/go-build -gno-record-gcc-switches"
```
**常用变量解析:**
- GOROOT : 指定安装Go语言开发包的解压路径。
- GOPATH : 指定外部Go语言代码开发工作区目录, 从`Go 1.8`版本开始Go开发包在安装完成后会为GOPATH设置一个默认目录并且在`Go 1.14`及之后的版本中启用了`Go Module`模式之后,不一定非要将代码写到`GOPATH`目录下所以也就不需要我们再自己配置GOPATH了使用默认的即可。
```shell
# - 在 Go 1.8 版本之前GOPATH环境变量默认是空的。从 Go 1.8 版本开始Go 开发包在安装完成后会为 GOPATH设置一个默认目录参见下述。
# GOPATH在不同操作系统平台上的默认值
平台 GOPATH默认值 举例
Windows %USERPROFILE%/go C:\Users\用户名\go
Unix $HOME/go /home/用户名/go
# - 同时,我们将 GOROOT下的bin目录及GOPATH下的bin目录都添加到环境变量中。
# - 配置环境变量之后需要重启你电脑上已经打开的终端。例如cmd、VS Code里面的终端和其他编辑器的终端
```
- GOPROXY : 指定代理Go语言从公共代理仓库中快速拉取您所需的依赖代码建议 Go > 1.13)。
```shell
# - 1. goproxy.io 是全球最早的 Go modules 镜像代理服务之一, 采用 CDN 加速服务为开发者提供依赖下载, 该服务由一批热爱开源, 热爱 Go 语言的年轻人开发维护。
# - 2. goproxy.cn 中国最可靠的 Go 模块代理, 由七牛云 CDN 在全球范围内加速我们的服务。
# - 3. goproxy.baidu.com 百度Go Module代理仓库服务, 你可以利用该代理来避免DNS污染导致的模块拉取缓慢或失败的问题加速你的构建。
# Go 1.13 及以上(推荐)
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go env -w GOPROXY=https://goproxy.io,direct
$ go env -w GOPROXY=https://goproxy.baidu.com/,direct
# Bash (Linux or macOS)
# 配置 GOPROXY 环境变量
export GO111MODULE=on
export GOPROXY=https://goproxy.io,direct
# 还可以设置不走 proxy 的私有仓库或组,多个用逗号相隔(可选)
export GOPRIVATE=git.mycompany.com,github.com/my/private
# PowerShell (Windows)
# 配置 GOPROXY 环境变量
$env:GO111MODULE = "on"
$env:GOPROXY = "https://goproxy.io,direct"
# 还可以设置不走 proxy 的私有仓库或组,多个用逗号相隔(可选)
$env:GOPRIVATE = "git.mycompany.com,github.com/my/private"
```
# (2) 开发编辑器
描述: Go采用的是UTF-8编码的文本文件存放源代码理论上使用任何一款文本编辑器都可以做Go语言开发这里推荐使用`VS Code`和`Goland`。
- VS Code 是微软开源的编辑器
- Goland 是jetbrains出品的付费IDE。
**2.1 Visual Studio Code**
描述: VS Code全称Visual Studio Code是微软公司开源的一款免费现代化轻量级代码编辑器支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,支持插件扩展,支持 Win、Mac 以及 Linux平台。
Tips : 虽然不如某些IDE功能强大但是它添加Go扩展插件后已经足够胜任我们日常的Go开发, 并支持三大主流平台都支持,请根据自己的电脑平台选择对应的安装包。
VS Code 官方下载地址https://code.visualstudio.com/Download
- Step 1.在Ubuntu中安装deb格式的 `VS code` 开发软件。
```shell
$ wget https://az764295.vo.msecnd.net/stable/c3f126316369cd610563c75b1b1725e0679adfb3/code_1.58.2-1626302803_amd64.deb
$ dpkg -i ode_1.58.2-1626302803_amd64.deb
$ code
1.58.2
c3f126316369cd610563c75b1b1725e0679adfb3
x64
```
- Step 2.VS Code 基础 Go 开发环境配置
```shell
# (1) 安装中文简体插件:
> 点击左侧菜单栏最后一项管理扩展,在搜索框中输入 chinese 选中结果列表第一项点击install安装。
> 安装完毕后右下角会提示重启 `VS Code`,重启之后你的`VS Code`就显示中文啦!
# (2) 安装Go扩展:
> 现在我们要为我们的`VS Code`编辑器安装Go扩展插件让它支持Go语言开发,在搜索框中输入 chinese 。
# (3) 安装Go语言开发工具包:
> 在Go语言开发的时候为我们提供诸如代码提示、代码自动补全等功能。
> 在此之前请先设置GOPROXY打开终端执行以下命令: go env -w GOPROXY=https://goproxy.cn,direct
> Windows平台按下`Ctrl+Shift+P`Mac平台按`Command+Shift+P`这个时候VS Code界面会弹出一个输入框如下图
> 我们在这个输入框中输入 `go:install`,下面会自动搜索相关命令,我们选择 `Go:Install/Update Tools`这个命令,按下图选中并会回车执行该命令(或者使用鼠标点击该命令)
> 在弹出的窗口选中所有,并点击“确定”按钮,进行安装。
# Tools environment: GOPATH=/home/weiyigeek/app/program/project/go
# Installing 10 tools at /home/weiyigeek/app/program/project/go/bin in module mode.
# gopkgs
# go-outline
# gotests
# gomodifytags
# impl
# goplay
# dlv
# dlv-dap
# staticcheck
# gopls
> 最终会在终端中显示 successful 则为安装成功。
```
![WeiyiGeek.Go开发依赖软件](assets/5c060ab80e0342858f4e36fceec46a776bc74f33.png@942w_773h_progressive.webp)
- Step 3.编辑器主题变更,依次点击设置->颜色主题,然后在弹出的窗口选择你喜欢的主题, 本人比较喜欢使用Dark 暗黑的主题。
- Step 4.配置VSCode开启自动保存,依次点击 文件->首选项->设置,打开设置页面就能看到自动保存相关配置如下图,可以根据自己的喜好选择自动保存的方式:
- Step 5.配置代码片段快捷键,还是按Ctrl/Command+Shift+P,按下图输入>snippets选择命令并执行, 然后在弹出的窗口点击选择go选项然后弹出如下代码页面
```shell
# 用法:
"代码块主要名称":{
"prefix": "快捷键简写",
"body": "这里是按快捷键插入的代码片段",
"description": "这里放提示信息的描述"
}
# 示例: 其中$0表示最终光标提留的位置。 举个例子我这里创建了两个快捷方式一个是输入pln就会在编辑器中插入fmt.Println()代码输入plf就会插入fmt.Printf("")代码。
{
"initcode":{
"prefix": "init",
"body": [
"package main",
"import \"fmt\"",
"func main(){",
"fmt.Println($0)",
"}",
],
"description": "快速插入一个简单的Go Main 示例代码块"
},
"println":{
"prefix": "pln",
"body":"fmt.Println($0)",
"description": "Println,Line feed printing"
},
"printf":{
"prefix": "ptf",
"body": "fmt.Printf(\"$0\")",
"description": "printf,Normal printout"
},
"sprintf":{
"prefix": "sptf",
"body": "fmt.Sprintf(\"$0\")",
"description": "将拼接的字符串返回给变接收量"
}
}
```
![WeiyiGeek.自动保存与代码片段快捷键](assets/4c3bdce2b26e8bc3baf4eb341161acd8edc0fcdd.png@942w_270h_progressive.webp)
- Step 6.把上面的代码按下图方式粘贴到配置文件中保存并关闭配置文件即可。然后我们打开一个go文件测试一下效果
GIF
![WeiyiGeek.代码片段快捷键效果](assets/205355dda9ee9e48b7c23c03ddbfa8b9d27c04ce.gif@1s.webp)
# Let,Go
描述: 此处是学习一门新语言的常规操作输出 Hello World , 现在我们来创建第一个Go项目 hello.go
**初始化项目: go mod init**
描述: 使用go module模式新建项目时我们需要通过 `go mod init 项目名`命令对项目进行初始化该命令会在项目根目录下生成go.mod文件。
例如我们使用hello作为我们第一个Go项目的名称执行如下命令` go mod init hello`。
\```bash
\# go.mod
module hello
go 1.16
\```
# Hello World
描述: 接下来在该目录中创建一个helloWorld.go文件
```go
// 声明 main 包表明当前是一个可执行程序
package main
// 导入内置 fmt 包供内部函数使用
import "fmt"
// Main 函数程序入口主函数
func main() {
// 终端输出打印语句
fmt.Println("Hello World, Let's Go.")
/**
此处为多行注释
**/
fmt.Println("人生苦短现在开启你的Go语言学习之旅吧。")
}
```
Tips : 我们可以在`VScode`配置go项目的Debug调试模式便可以直接使用`快捷键F5`进行快速测试编写的程序。
![WeiyiGeek.VScode调试Go程序](assets/409d6915ad09c30f3dbfbf4c4b78931649a1a8c0.png@942w_566h_progressive.webp)
Tips : 我们知道作为编译型的语言Go与C语言一样需要编译然后再运行。
Tips : Go 语言函数外的语句必须以关键字开头。
**编译: go Build**
描述: `go build` 命令表示将源代码编译成可执行文件。
在hello目录下执行`go build`(指定.go文件)或者在其他目录执行以下命令`go build helloworld`(项目需要在GOROOT路径的src目录之中),因为go编译器会去 GOPATH 的src目录下查找你要编译的hello项目
```shell
# - 目录下执行
➜ Day01 pwd
/home/weiyigeek/develop/github-project/Go/Day01
➜ Day01 go build
➜ Day01 ./Day01
Hello World, Let Go.
人生苦短现在开启你的Go语言学习之旅吧。
# - 指定main包所在的.go文件
➜ Day01 go build HelloWorld.go
➜ Day01 ./HelloWorld
Hello World, Lets Go.
人生苦短现在开启你的Go语言学习之旅吧。
# - 使用-o参数来指定编译后得到的可执行文件的名字
➜ Day01 go build -o ahelloworld.
➜ Day01 ./ahelloworld
Hello World, Let's Go.
人生苦短现在开启你的Go语言学习之旅吧。
```
Tips : 如上述编译得到的可执行文件会保存在执行编译命令的当前目录下会有 HelloWorld 可执行文件如果是windows平台会在当前目录下找到HelloWorld.exe可执行文件。
**编译&运行: go Run**
描述: 我们也可以直接执行程序,该命令本质上也是先编译再执行。
```shell
➜ Day01 go run HelloWorld.go
Hello World, Let's Go.
人生苦短现在开启你的Go语言学习之旅吧。
```
**编译&安装软件包&依赖项: go Install**
描述: go install 表示安装的意思它先编译源代码得到可执行文件然后将可执行文件移动到GOPATH的bin目录下。因为我们的环境变量中配置了GOPATH下的bin目录所以我们就可以在任意地方直接执行可执行文件了。
```shell
➜ Day01 go install # 生成 Day01 可执行文件
➜ Day01 go install HelloWorld.go # 生成 HelloWorld 可执行文件
➜ Day01 ls ${GOROOT}
➜ Day01 ls ${GOPATH}/bin/
# Day01 dlv dlv-dap gomodifytags go-outline gopkgs goplay gopls gotests HelloWorld impl staticcheck
➜ Day01 /home/weiyigeek/app/program/project/go/bin/Day01
Hello World, Let's Go.
人生苦短现在开启你的Go语言学习之旅吧。
```
**跨平台编译: CGO_ENABLED / GOOS / GOARCH**
描述: 默认我们go build的可执行文件都是当前操作系统可执行的文件如果我想在windows下编译一个linux下可执行文件那需要怎么做呢
只需要指定目标操作系统的平台和处理器架构即可例如Windows平台cmd下按如下方式指定环境变量编译出的可以执行文件则可以在Linux 操作系统 amd64 处理器中执行,然后再执行go build命令得到的就是能够在Linux平台运行的可执行文件了。
```shell
SET CGO_ENABLED=0 # 禁用CGO
SET GOOS=linux # 目标平台是linux
SET GOARCH=amd64 # 目标处理器架构是amd64
```
注意如果你使用的是PowerShell终端那么设置环境变量的语法为 $ENV:CGO_ENABLED=0。
不同平台快速交叉编译:
```shell
# 目标平台是linux
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
# 目标平台Windows
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
# 目标平台Mac
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
```
简单实践: 在Liunx平台上编译出在`Windows`上运行的`helloWorld.exe`可执行文件。
```shell
# Linux
➜ Day01 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o Win-HelloWorld.exe
➜ Day01 ls
HelloWorld.go Win-HelloWorld.exe
# Windows: 拷贝后执行
PS D:\Temp> .\Win-HelloWorld.exe
Hello World, Let's Go.
人生苦短现在开启你的Go语言学习之旅吧。
```
Tips : 对比不同平台交叉编译后的可执行文件大小。
```shell
➜ Day01 ls -la --ignore HelloWorld.go
-rwxrwxr-x 1 weiyigeek weiyigeek 1937799 7月 30 03:23 helloworld # ~ 1.9 MB
-rwxrwxr-x 1 weiyigeek weiyigeek 2027936 7月 30 03:24 Mac-HelloWorld # ~ 2.0 MB
-rwxrwxr-x 1 weiyigeek weiyigeek 2098688 7月 30 02:58 Win-HelloWorld.exe # ~ 2.1 MB
```
# Http Web Server
描述: 透过Go仅需几行代码就完成HTTP网页服务器的实现。
```go
package main
import (
"io"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello world!")
}
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8000", nil)
}
```
# Echo 类似命令程序
描述: 用Go去实现一个像Unix中的Echo命令程序。
```go
package main
import (
"os"
"flag"
)
var omitNewline = flag.Bool("n", false, "don't print final newline")
const (
Space = " "
Newline = "\n"
)
func main() {
flag.Parse() // Scans the arg list and sets up flags
var s string = ""
for i = 0; i < flag.NArg(); i++ {
if i > 0 {
s += Space
}
s += flag.Arg(i)
}
if !*omitNewline {
s += Newline
}
os.Stdout.WriteString(s)
}
```
# 0x02 Go 项目结构
在进行Go语言开发的时候我们的代码总是会保存在`$GOPATH/src`目录下。在工程经过`go build、go install`或`go get`等指令后,会将下载的第三方包源代码文件放在 `$GOPATH/src` 目录下,产生的二进制可执行文件放在 `$GOPATH/bin`目录下,生成的中间缓存文件会被保存在 `$GOPATH/pkg` 下。
Tips : 如果我们使用版本管理工具`Version Control SystemVCS。常用如Git/Svn`来管理我们的项目代码时,我们只需要添加`$GOPATH/src`目录的源代码即可, bin 和 pkg 目录的内容无需版本控制。
通常来讲`GOPATH`目标下文件目录组织架构的设置常常有以下三种:
# (1)适合个人开发者
描述: 我们知道源代码都是存放在GOPATH的src目录下那我们可以按照下图来组织我们的代码。
![WeiyiGeek.适合个人开发者](assets/e850cd44113c98ed6b73060c17c415810c6fa1f1.png@930w_558h_progressive.webp)适合个人开发者
# (2)适合企业开发场景
描述: 此种目录结构设置更适合企业开发环境,以代码仓库为前缀并以公司内部组织架构为基准,其次是项目名称,最后是各个模块开发的名称。
![WeiyiGeek.适合企业开发场景](assets/8e2305d5dc1de204fa366cac4abac9dd9d674016.png@942w_410h_progressive.webp)适合企业开发场景
# (3)目前流行的项目结构
描述: Go语言中也是通过包来组织代码文件我们可以引用别人的包也可以发布自己的包但是为了防止不同包的项目名冲突我们通常使用顶级域名来作为包名的前缀这样就不担心项目名冲突的问题了。
因为不是每个个人开发者都拥有自己的顶级域名所以目前流行的方式是使用个人的github用户名来区分不同的包。
![WeiyiGeek.目前流行的项目结构](assets/6430f2bbc96a68ddeb83b826ab9fc5fd41f368d7.png@942w_452h_progressive.webp)目前流行的项目结构
- 举例说明: 张三和李四都有一个名叫studygo的项目那么这两个包的路径就会是
```go
import "github.com/zhangsan/studygo"
import "github.com/lisi/studygo"
```
- 举例说明: 同样如果我们需要从`githuab`上下载别人包的时候如:`go get github.com/jmoiron/sqlx`, 那么这个包会下载到我们本地GOPATH目录下的`src/github.com/jmoiron/sqlx`。
总结说明: 后续我们的开发学习示例基本按照第三种项目结构进行。

0
notes/10.基础总结.md Normal file
View File

896
notes/2.基础篇.md Normal file
View File

@ -0,0 +1,896 @@
**本章目录:**
**0x00 Go语言基础之变量和常量**
1.标识符
2.关键字
3.变量-Var
变量声明
变量初始化
匿名变量
4.常量 - Const
iota - 常量计数器
**0x01 Go语言基础之基本数据类型**
1.整型
特殊整型
数字字面量语法Number literals syntax
2.浮点型
3.复数
4.布尔值
5.字符串
字符串转义符
多行字符串
字符串的常用操作
6.byte和rune类型
7.类型转换
![img](assets/4aa545dccf7de8d4a93c2b2b8e3265ac0a26d216.png@progressive.webp)
# 0x00 Go语言基础之变量和常量
描述: 变量和常量是编程中必不可少的部分,也是很好理解的一部分。
# 1.标识符
描述: 在编程语言中标识符就是程序员定义的具有特殊意义的词,比如`变量名、常量名、函数名`等等。 Go语言中标识符由字母数字和_(下划线组成并且只能以字母和_开头。 举几个例子:`abc, _, _123, a123`。
编程语言中常用的三种命名规则而Go语言推荐使用驼峰法式命名。
```shell
# 下划线连接
student_name
# 小驼峰法式 (推荐方式)
studentName
# 大驼峰法式
StudentName
```
2.关键字
描述: 关键字是指编程语言中预先定义好的具有特殊含义的标识符。 关键字和保留字都不建议用作变量名。
- Go语言中有25个关键字:
```shell
* var const 变量和常量的声明
* var varName type 或者 varName : = value
* package and import: 导入
* func 用于定义函数和方法
* return :用于从函数返回
* defer someCode :在函数退出之前执行
* go : 用于并行
* select 用于选择不同类型的通讯
* interface 用于定义接口
* struct 用于定义抽象数据类型
* break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
* chan 用于channel通讯
* type 用于声明自定义类型
* map 用于声明map类型数据
* range 用于读取slice、map、channel数据
```
- 此外 Go语言中还有37个保留字。
```shell
# Constants:
true false iota nil
# Types:
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
# Functions:
make len cap new append copy close delete
complex real imag
panic recover
```
# 3.变量-Var
**Q: 变量的来历?**
> 答: 程序运行过程中的数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。
**Q: 变量类型**
> 答:变量Variable的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展编程语言已经基本形成了一套固定的类型常见变量的数据类型有整型、浮点型、布尔型等。
Tips : Go语言中的每一个变量都有自己的类型并且变量必须经过声明才能开始使用。
# 变量声明
> 答: Go语言中的变量需要声明后才能使用同一作用域内不支持重复声明。
Go语言的变量声明标准格式为`var 变量名 变量类型`
```shell
# 单一声明: 变量声明以关键字var开头变量类型放在变量的后面行尾无需分号。
var name string
var age int
var isOk bool
# 批量声明: 每声明一个变量就需要写var关键字会比较繁琐go语言中还支持批量变量声明。
var (
a string
b int
c bool
d float32
)
```
# 变量初始化
Go语言在声明变量的时候会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,或者我们也可在声明变量的时候为其指定初始值。例如:
- 1.整型和浮点型变量的默认值为0。
- 2.字符串变量的默认值为空字符串。
- 3.布尔型变量默认为false。
- 4.切片、函数、指针变量的默认为nil。
变量初始化的标准格式如下:`var 变量名 类型 = 表达式`
```go
//# 单一变量初始化
var name string = "WeiyiGeek"
var age int = 18
//# 批量变量初始化
var name, age = "WeiyiGeek", 20
```
**类型推导**
描述: 有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。
```go
var name = "WeiyiGeek"
var age = 18
```
**短变量声明**
描述: 在函数内部可以使用更简略的`:=`方式声明并初始化变量。
```go
func main() {
count := 10
username := "WeiyiGeek"
}
```
匿名变量
描述: 在使用多重赋值时,如果想要`忽略某个值`,可以使用`匿名变量anonymous variable- 特殊变量`。并且匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。 (`在Lua等编程语言里匿名变量也被叫做哑元变量。`)
匿名变量用一个`下划线(_)`表示,例如:
```go
func foo() (int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}
```
**示例演示:**
```go
package main
import "fmt"
// 变量声明(单一-全局)
var singleName string
var notUseVar bool
// 变量声明(批量-全局)
var (
multiName string
multiAge int8
)
func main() {
// 对声明后的变量赋值
singleName = "Weiyi_"
multiName = "Geek"
multiAge = 18
// 变量初始化(局部)
var name string = "WeiyiGeek"
var sex, addr = "boy", "China"
// 类型推导变量
var flag = true
var count = 1024
// 简短变量声明(此种类型只能在函数中使用)
briefCount := 65535
fmt.Printf("My Name is %s, Sex is %s , Address: %s\n", name, sex, addr)
fmt.Println("Alias Name :", singleName, multiName, " Age is :", multiAge)
fmt.Print("类型推导 ", flag, count)
fmt.Println(", 简短变量 ", briefCount)
}
```
输出结果:
```go
API server listening at: 127.0.0.1:42954
My Name is WeiyiGeek, Sex is boy , Address: China
Alias Name : Weiyi_ Geek Age is : 18
类型推导 true 1024, 简短变量 65535
```
Tips : Go语言中变量必须先声明后使用而且声明变量`(非全局变量)`后必须使用,如有不使用的变量编译时报错。
Tips :函数外的每个语句都必须以关键字开始 (`var、const、func`) 等
Tips : `:=`不能使用在函数外。
Tips : `匿名变量或者叫哑元变量(_)`多用于占位表示忽略值即当有些数据必须用变量接收但又不使用它时可以采用_来接收改值。
Tips : 变量在同一个作用域中`代码块({})`中不能重复声明同名的变量。
# 4.常量 - Const
描述:常量是在程序运行期间恒定不变的值,多用于定义程序运行期间不会改变的那些值。常量的声明和变量声明非常类似,只是`把var换成了const`,常量在定义的时候必须赋值。
常量声明初始化格式:
```go
// 单一声明: 声明了pi和e这两个常量之后在整个程序运行期间它们的值都不能再发生变化了。
const pi = 3.1415
const e = 2.7182
// 批量声明
const (
pi = 3.1415
e = 2.7182
)
// 批量声明(如果省略了值则表示和上面一行的值相同)
// 常量n1、n2、n3的值都是100。
const (
n1 = 100
n2
n3
)
```
# iota - 常量计数器
描述: iota是go语言的常量计数器只能在常量的表达式中使用。
Tips : iota在const关键字出现时将被重置为0, const中每新增一行常量声明将使iota计数一次 (`iota可理解为const语句块中的行索引`)。
应用场景: 使用iota能简化定义在定义枚举时很有用。
下面示例中几个常见的iota示例:
- 1.使用`_`跳过某些值
```go
const (
n1 = iota //0
n2 //1
_
n4 //3
)
```
- 2.iota声明中间插队
```go
const (
n1 = iota //0
n2 = 100 //100
n3 = iota //2
n4 //3
)
const n5 = iota //0
```
- 3.多个iota定义在一行
```go
const (
a, b = iota + 1, iota + 2 //1,2
c, d //2,3
e, f //3,4
)
```
- 4.定义数量级 (这里的<<表示左移操作1<<10表示将1的二进制表示向左移10位也就是由1变成了10000000000也就是十进制的1024同理2<<2表示将2的二进制表示向左移2位也就是由10变成了1000也就是十进制的8
```go
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
```
**示例演示:**
```go
package main
import "fmt"
// 单一常量声明
const pi = 3.1415926535898
// 批量常量声明
const (
e = 2.7182
flag = false
)
// 特殊批量常量声明
const (
a = 1
b
_
c
)
// iota 常量计数器
const (
_ = iota // 0
d, e1 = iota + 1, iota + 2 // 2,3 常量名称不能重复
f, g = iota + 1, iota + 2 // 3,4
)
const (
_ = iota // 0
KB = 1 << (10 * iota) // 1024
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
func main() {
fmt.Println("pi :", pi)
fmt.Println("e :", e, " , flag:", false)
fmt.Println("特殊批量常量声明:", a, b, c)
fmt.Println("iota 常量计数器 :", d, e1, f, g)
fmt.Println("文件体积大小 :", KB, MB, GB, TB, PB)
}
```
执行结果:
```go
pi : 3.1415926535898
e : 2.7182 , flag: false
特殊批量常量声明: 1 1 1
iota 常量计数器 : 2 3 3 4
文件体积大小 : 1024 1048576 1073741824 1099511627776 1125899906842624
```
Tips : 常量声明后不能在程序中进行重新赋值更改。
# 0x01 Go语言基础之基本数据类型
**基本数据类型:**
Go语言中有丰富的数据类型除了基本的整型、浮点型、布尔型、字符串外还有数组、切片、结构体、函数、map、通道channel等。
Tips : Go 语言的基本类型和其他语言大同小异。
# 1.整型
整型分为以下两个大类: 按长度分为:`int8、int16、int32、int64` 对应的无符号整型:`uint8、uint16、uint32、uint64`
其中,`uint8`就是我们熟知的`byte`型,`int16`对应C语言中的`short`型,`int64`对应C语言中的`long`型。
![img](assets/4a251c64e46b578d48f7bbd228aaa7cda333967e.png@938w_554h_progressive.webp)整型取值范围
**特殊整型**
![img](assets/41174379a6968c2226a0fc60f0b19d2ce045dd0d.png@806w_260h_progressive.webp)
**注意:** 在使用`int`和 `uint`类型时不能假定它是32位或64位的整型而是考虑`int`和`uint`可能在不同平台上的差异。
**注意事项:** 获取对象的长度的内建`len()`函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用`int`来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用`int`和 `uint`
# 数字字面量语法Number literals syntax
Go1.13版本之后引入了数字字面量语法,这样便于开发者以`二进制、八进制或十六进制浮点数`的格式定义数字,例如:
- `v := 0b00101101` 代表二进制的 101101相当于十进制的 45。
- `v := 0o377`,代表八进制的 377相当于十进制的 255。
`- v := 0x1p-2`,代表十六进制的 1 除以 2²也就是 0.25。
而且还允许我们用 `_` 来分隔数字,比如说: `v := 123_456` 表示 v 的值等于 123456。
简单示例: 我们可以借助fmt函数来将一个整数以不同进制形式展示。
```go
package main
import "fmt"
func main(){
// 十进制以不同的进制展示
var a int = 10
fmt.Printf("%b \n", a) // 1010 占位符%b表示二进制
fmt.Printf("%o \n", a) // 12 占位符%o表示八进制
fmt.Printf("%d \n", a) // 10 占位符%d表示十进制
fmt.Printf("0x%x \n", a) // 0xa 占位符%x表示十六进制
// 八进制(以0开头)
var b int = 077
fmt.Printf("%b \n", b) // 111111
fmt.Printf("%o \n", b) // 77
fmt.Printf("%d \n", b) // 63
fmt.Printf("0x%x \n", b) // 0x3f
// 十六进制(以0x开头)
var c int = 0xff
fmt.Printf("0x%x \n", c) // 0xff
fmt.Printf("0X%X \n", c) // 0xFF
// 数字字面量语法Number literals syntax
binary := 0b1111
octal := 0o17
digital := 15
hexadecimal := 0xf
specialhexa := 0x8p-2 // 8 / 2^2 = 2
underline := 10_24
fmt.Printf("binary : %b , digital %d\n", binary, binary)
fmt.Printf("octal : %o , digital %d\n", octal, octal)
fmt.Printf("digital type (变量类型): %T,digital %d\n", digital, digital)
fmt.Printf("hexadecimal : %x, digital %d, specialhexa : %f\n", hexadecimal, hexadecimal, specialhexa)
fmt.Printf("underline : %d \n", underline)
}
```
输出结果:
```go
# Int 整型示例:
1010
12
10
0xa
111111
77
63
0x3f
0xff
0XFF
binary : 1111 , digital 15
octal : 17 , digital 15
digital type (变量类型): int,digital 15
```
# 2.浮点型
Go语言支持两种浮点型数`float32`和`float64`。
这两种浮点型数据格式遵循`IEEE 754`标准:
- `float32` 的浮点数的最大范围约为 `3.4e38`,其常量定义:`math.MaxFloat32`。
- `float64` 的浮点数的最大范围约为 `1.8e308`,其常量定义:`math.MaxFloat64`。
简单示例: 打印浮点数时,可以使用`fmt`包配合动词`%f`,代码如下:
```go
package main
import (
"fmt"
"math"
)
func main() {
var floatnumber float64 = 1024.00
fmt.Printf("数据类型: %T , floatnumber: %.1f\n", floatnumber, floatnumber)
fmt.Printf("%f,%.2f\n", math.Pi, math.Pi) // 保留小数点后两位
fmt.Printf("float32的浮点数的最大范围 :%d ~ %f\n", 0, math.MaxFloat32)
fmt.Printf("float64的浮点数的最大范围 :%d ~ %f\n", 0, math.MaxFloat64)
}
```
执行结果:
```go
数据类型: float64 , floatnumber: 1024.0
3.141593,3.14
float32的浮点数的最大范围 :0 ~ 340282346638528859811704183484516925440.000000
float64的浮点数的最大范围 :0 ~ 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
```
# 3.复数
描述: 复数有实部和虚部complex64的实部和虚部为32位complex128的实部和虚部为64位。
例如: complex64和complex128类型变量输出示例
```go
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1) // (1+2i)
fmt.Println(c2) // (2+3i)
```
# 4.布尔值
Go语言中以`bool`类型进行声明布尔型数据,布尔型数据只有`true`和`false`两个值。
```go
fmt.Println("# 4.布尔型示例:")
var flag bool = true
fmt.Printf("数据类型: %T ,任意类型输出: %v", flag, flag) // 数据类型: bool ,任意类型输出: true
```
**注意:**
1. 布尔类型变量的默认值为`false`。
2. Go 语言中不允许将整型强制转换为布尔型.
3. 布尔型无法参与数值运算,也无法与其他类型进行转换。
# 5.字符串
描述: Go语言中的字符串`以原生数据类型`出现使用字符串就像使用其他原生数据类型int、bool、float32、float64 等)一样。
Go 语言里的字符串的内部实现使用`UTF-8`编码。 字符串的值为`双引号(")`中的内容可以在Go语言的源码中直接添加非ASCII码字符例如
```go
s1 := "hello"
s2 := "你好"
c1 := 'g'
c2 := 'o'
```
Tips : Go 语言中用双引号包裹的是`字符串`,而单引号包裹的是`字符`。
# 字符串转义符
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
![img](assets/85747a2e196b326742c407ae2a3f5287b43838fb.png@590w_440h_progressive.webp)
举个例子我们要打印一个Windows平台下的一个文件路径
```go
package main
import (
"fmt"
)
func main() {
s1 := "'c:\\weiyigeek\\go\\hello'"
fmt.Println("str :=",s1)
fmt.Println("str := \"c:\\Code\\weiyigeek\\go.exe\"")
}
```
# 多行字符串
Go语言中要定义一个多行字符串时就必须使用`反引号`字符:
```go
s1 := `第一行
第二行
第三行
`
s2 := `c:\weiyigeek\go\hello` // 注意点此处没用转义符(\) 也能输出路径
fmt.Println(s1,s2)
```
Tips: 反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
# 字符串的常用操作
![img](assets/478fe9a354e7c36e7cf403019faed805097c970f.png@651w_483h_progressive.webp)
示例演示:
```go
// 字符串型示例
func stringdemo() {
// 字符
c1 := 'a'
c2 := 'A'
// 字符串 (单行与多行以及转义)
s1 := "Name"
s2 := "姓名"
s3 := `
这是一个
多行字符串案例!
This is mutlilineString Example
Let's Go // 特点:原样输出
`
// 转义演示
s4 := "'c:\\weiyigeek\\go\\hello'"
s5 := `c:\weiyigeek\go\hello`
fmt.Printf("c1 char : %c,\t c2 char %c -> digital : %d\n", c1, c2, c2)
fmt.Println(s1, s2)
fmt.Println(s3)
fmt.Println(s4, s5)
// 字符串常用函数
fmt.Println("s1 String length:", len(s1), "s2 string length:", len(s2))
info := fmt.Sprintf("%s (%s): %s", s1, s2, "WeiyiGeek")
fmt.Println("Infomation : "+"个人信息", info)
fmt.Println("字符串分割 :", strings.Split(s5, "\\"))
fmt.Println("判断字符串是否包含go", strings.Contains(s3, "go"))
fmt.Println(strings.HasPrefix(s1, "N"), strings.HasSuffix(s1, "e"))
fmt.Println(strings.Index(s4, "weiyigeek"), strings.LastIndex(s4, "weiyigeek"))
s6 := strings.Split(s5, "\\")
fmt.Println("字符串间隔符 : ", strings.Join(s6, "-"))
}
```
执行效果:
```shell
# 5.字符串型示例:
c1 char : a, c2 char A -> digital : 65
Name 姓名
这是一个
多行字符串案例!
This is mutlilineString Example
Let's Go // 特点:原样输出
'c:\weiyigeek\go\hello' c:\weiyigeek\go\hello
s1 String length: 4 s2 string length: 6
Infomation : 个人信息 Name (姓名): WeiyiGeek
字符串分割 : [c: weiyigeek go hello]
判断字符串是否包含go false
true true
4 4
字符串间隔符 : c:-weiyigeek-go-hello
```
# 6.byte和rune类型
描述: 组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:
```shell
var a = '中'
var b = 'x'
c := 'a'
```
Go 语言的字符有以下两种:
- `uint8`类型,或者叫 byte 型,代表了`ASCII码`的一个字符1B
- `rune`类型,代表一个 `UTF-8字符`, 并且一个rune字符由一个或多个byte组成3B~4B
Tips : 当需要处理中文、日文或者其他复合字符时,则需要用到`rune`类型。`rune`类型实际是一个`int32`。
Go 使用了特殊的 rune 类型来处理 Unicode让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。
简单示例:
```shell
// 遍历字符串
func traversalString() {
s := "hello沙河"
// byte 类型
for i := 0; i < len(s); i++ {
fmt.Printf("%v(%c) ", s[i], s[i])
}
fmt.Println()
// rune 类型
for _, r := range s {
fmt.Printf("%v(%c) ", r, r)
}
fmt.Println()
}
```
结果输出:
```shell
// byte 类型 (中文乱码)
104(h) 101(e) 108(l) 108(l) 111(o) 230(æ) 178(²) 153() 230(æ) 178(²) 179(³)
// rune 类型 (中文正常)
104(h) 101(e) 108(l) 108(l) 111(o) 27801(沙) 27827(河)
```
**Q: 为什么出现上述情况?**
答: 因为UTF8编码下`一个中文汉字由3~4个字节`4*8bit组成所以我们不能简单的按照字节去遍历一个包含中文的字符串否则就会出现上面输出中第一行的结果。
Tips : 字符串底层是一个byte数组所以可以和`[]byte`类型相互转换。字符串是不能修改的字符串是由byte字节组成所以字符串的长度是byte字节的长度。
# 7.类型转换
描述Go语言中只有强制类型转换没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。
强制类型转换的基本语法如下:
T(表达式) # 其中T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.
Tips : Boolen 类型不能强制转换为整型。
**整型转浮点型**
比如计算直角三角形的斜边长时使用math包的Sqrt()函数该函数接收的是float64类型的参数而变量a和b都是int类型的这个时候就需要将a和b强制类型转换为float64类型。
```shell
func sqrtDemo() {
var a, b = 3, 4
var c int
// math.Sqrt() 接收的参数是float64类型需要强制转换
c = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println(c)
}
```
Tips : 在Go语言中不同类型的值不能直接赋值例如float32类型变量a的值不能直接赋值给floa64类型变量b的值。
**字符串类型转换**
描述: 如果修改字符串,需要先将其转换成`[]rune`或`[]byte`,完成后再转换为`string`。无论哪种转换,`都会重新分配内存,并复制字节数组`。
```shell
func changeString() {
s1 := "big"
// 强制类型转换
byteS1 := []byte(s1)
byteS1[0] = 'p'
fmt.Println(string(byteS1))
s2 := "白萝卜"
runeS2 := []rune(s2)
runeS2[0] = '红'
fmt.Println(string(runeS2))
}
```
Tips : 在一个字符串中如果既有中文也存在英文,我们则可以使用`byte[]类型`(1B)来存放ASCII码表示的字符(0~255),如果是中文则使用`rune[]`(4B)类型来存放或者周转。
**案例演示:**
```shell
// Byte与Rune类型示例
func brdemo() {
var c1 = 'a' // int32 类型
var c2 = 'A' // int32 类型
z1 := '中' // int32 类型
z2 := '文' // int32 类型
z3 := "中" // string 类型 (双引号)
// 字符不同格式输出
fmt.Printf("字符 %d (%c) , %d (%c) \n", c1, c1, c2, c2)
fmt.Printf("中文字符 %d (%v) = %c , %d (%v) = %c \n", z1, z1, z1, z2, z2, z2)
fmt.Printf("单双引号不同类型 : c1 = %c (%T) , z2 = %c (%T) , z3 = %s (%T) \n", c1, c1, z2, z2, z3, z3)
// 中英文字符串修改
s1 := "a和我都爱中国"
s2 := "为 Hello 中国 World,Go 语言 学习"
// 将字符类型转化为byte类型
c3 := byte(c2)
fmt.Printf("强制转化类型 : c2 = %c (%T) , byte(c2) = %c (%T) \n", c2, c2, c3, c3)
// 将字符串类型转化为string类型
r1 := []rune(s1) // 强制转化字符串为一个rune切片
r1[0] = '您' // 注意此处需传入为字符
fmt.Println("修改后中文字符串输出(未类型转换)", r1)
fmt.Println("修改后中文字符串输出(已类型转换)", s1, string(r1)) // 强制转化rune切片为字符串
// 将整型转化成为浮点数类型
// 计算直角三角形的斜边长
var a, b = 3, 4
var c int = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println("计算直角三角形的斜边长 (a=3,b=4) c =", c)
// 统计字符串中中文个数
res := []rune(s2)
reslen := len(res)
count := 0
for i := 0; i < reslen; i++ {
if res[i] > 255 {
count++
}
}
fmt.Printf("字符串:%s (Length = %d),一共有 %d 个中文字符", s2, reslen, count)
}
```
执行结果:
```shell
# 6.Byte与Rune型示例:
字符 97 (a) , 65 (A)
中文字符 20013 (20013) = 中 , 25991 (25991) = 文
单双引号不同类型 : c1 = a (int32) , z2 = 文 (int32) , z3 = 中 (string)
强制转化类型 : c2 = A (int32) , byte(c2) = A (uint8)
修改后中文字符串输出(未类型转换) [24744 21644 25105 37117 29233 20013 22269]
修改后中文字符串输出(已类型转换) a和我都爱中国 您和我都爱中国
计算直角三角形的斜边长 (a=3,b=4) c = 5
字符串:为 Hello 中国 World,Go 语言 学习 (Length = 25),一共有 7 个中文字符
```

View File

@ -0,0 +1,457 @@
**本章目录:**
**0x02 Go语言基础之运算符**
1.分类说明
2.算术运算符
3.关系运算符
4.逻辑运算符
5.位运算符
6.赋值运算符
**0x03 Go语言基础之流程控制**
1.if else(分支结构)
if条件判断基本写法
if条件判断特殊写法
2.for(循环结构)
3.for range(键值循环)
4.switch case(选择语句)
5.goto(跳转到指定标签)
6.break(跳出循环)
7.continue(继续下次循环)
![img](assets/4aa545dccf7de8d4a93c2b2b8e3265ac0a26d216.png@progressive.webp)
# 0x02 Go语言基础之运算符
**什么是运算符?**
> 答: 运算符用于在程序运行时执行数学或逻辑运算。
# 1.分类说明
描述: 与其他编程语言类似 Go 语言内置的运算符有如下几种
\- 1. 算术运算符
\- 2. 关系运算符
\- 3. 逻辑运算符
\- 4. 位运算符
\- 5. 赋值运算符
# 2.算术运算符
![img](assets/6b536e2396cd0da41aa7e26d9c1a2f9cacecdb74.png@288w_446h_progressive.webp)
**注意:** `++`(自增)和`--`自减在Go语言中是单独的语句并不是运算符。
# 3.关系运算符
![img](assets/6ac4a9e0b61cd0685e7ca10670986601d4275669.png@942w_551h_progressive.webp)
Tips : Go 语言是强类型的所以必须相同类型变量才能进行比较。
# 4.逻辑运算符
![img](assets/87cb44700a637253cb3fdb0a2fb2c0d65736a603.png@942w_399h_progressive.webp)
# 5.位运算符
描述: 位运算符对整数在内存中的二进制位进行操作。
![img](assets/fed8f7e698d83114c8224e3823c6f8e031e8962d.png@942w_515h_progressive.webp)
# 6.赋值运算符
![img](assets/775cbe23ce1a1ce9ea9cb5ac0290a35aac9e5f95.png@908w_882h_progressive.webp)
**示例演示:**
```go
a += 1 // a = a + 1
a %= 3 // a = a % 3
a <<= 4 // a = a << 4
a ^= 5 // a = a ^ 5
```
# 0x03 Go语言基础之流程控制
**Q: 什么是流程控制**
> 流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。
Tips : Go语言中最常用的流程控制有`if`和`for`,而`switch`和`goto`主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。
# 1.if else(分支结构)
# if条件判断基本写法
描述: Go语言中`if`条件判断的格式如下:
```go
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else{
分支3
}
```
当表达式1的结果为`true`时执行分支1否则判断表达式2如果满足则执行分支2都不满足时则执行分支3。 if判断中的`else if`和`else`都是可选的,可以根据实际需要进行选择。
Go语言规定与`if`匹配的左括号`{`必须与`if和表达式`放在同一行,`{`放在其他位置会触发编译错误。 同理,与`else`匹配的`{`也必须与`else`写在同一行,`else`也必须与上一个`if`或`else if`右边的大括号在同一行。
示例1:
```go
func ifDemo1() {
score := 65
if score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C") // 输出结果
}
}
```
# if条件判断特殊写法
描述: if条件判断还有一种特殊的写法可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,举个例子:
示例2
```go
func ifDemo2() {
score := 88 // 注意变量作用域的影响
if score := 65; score >= 90 {
fmt.Println("A", score)
} else if score > 75 {
fmt.Println("B", score)
} else {
fmt.Println("C", score) // 输出结果
}
fmt.Println("score : ", score)
}
```
执行结果:
```go
C 65
score : 88
```
**思考题:** 上下两种写法的区别在哪里?
> 答: 上述 示例1 与 示例2 区别在于定义 score 变量位置得不同, 示例1中的score变量在函数代码块中有效, 而示例2中的score变量作用域只在`if...else`代码块中有效。
# 2.for(循环结构)
描述: Go 语言中的所有循环类型均可以使用`for`关键字来完成。
for循环的基本格式如下
```go
for 初始语句;条件表达式;结束语句{
循环体语句
}
```
条件表达式返回`true`时循环体不停地进行循环,直到条件表达式返回`false`时自动退出循环。
func forDemo() { for i := 0; i < 10; i++ { fmt.Println(i) }}
for循环的初始语句可以被忽略但是初始语句后的分号必须要写例如
func forDemo2() { i := 0 for ; i < 10; i++ { fmt.Println(i) }}
for循环的初始语句和结束语句都可以省略例如
func forDemo3() { i := 0 for i < 10 { fmt.Println(i) i++ }}
for无限循环这种写法类似于其他编程语言中的`while`,在`while`后添加一个条件表达式,满足条件表达式时持续循环,否则结束循环。
例如: for循环可以通过`break`、`goto`、`return`、`panic`语句强制退出循环。
```go
for {
循环体语句
}
```
# 3.for range(键值循环)
描述: Go语言中可以使用`for range`遍历数组、切片、字符串、map 及通道channel
通过`for range`遍历的返回值有以下规律:
\* 1. 数组、切片、字符串返回索引和值。
\* 2. map返回键和值。
\* 3. 通道channel只返回通道内的值。
**
**
**简单示例:**
```go
s1 := "Hello,Go 输出的是中文"
for i, v := range s1 {
fmt.Printf("Index : %d ,Value : %s , Number : %v \n", i, string(v), v)
}
```
**输出结果:**
```go
Index : 0 ,Value : H , Number : 72
Index : 1 ,Value : e , Number : 101
Index : 2 ,Value : l , Number : 108
Index : 3 ,Value : l , Number : 108
Index : 4 ,Value : o , Number : 111
Index : 5 ,Value : , , Number : 44
Index : 6 ,Value : G , Number : 71
Index : 7 ,Value : o , Number : 111
Index : 8 ,Value : , Number : 32
Index : 9 ,Value : 输 , Number : 36755
Index : 12 ,Value : 出 , Number : 20986
Index : 15 ,Value : 的 , Number : 30340
Index : 18 ,Value : 是 , Number : 26159
Index : 21 ,Value : 中 , Number : 20013
Index : 24 ,Value : 文 , Number : 25991
```
# 4.switch case(选择语句)
描述: 使用`switch`语句可方便地对大量的值进行条件判断,即简化大量判断。
```go
func switchDemo1() {
finger := 3
switch finger {
case 1:
fmt.Println("大拇指")
case 2:
fmt.Println("食指")
case 3:
fmt.Println("中指")
case 4:
fmt.Println("无名指")
case 5:
fmt.Println("小拇指")
default:
fmt.Println("无效的输入!")
}
}
```
Go语言规定每个`switch`只能有一个`default`分支, 但一个分支可以有多个值多个case值中间使用英文逗号分隔。
```go
func testSwitch3() {
switch n := 7; n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}
}
```
分支还可以使用表达式这时候switch语句后面不需要再跟判断变量。例如
```go
func switchDemo4() {
age := 30
switch {
case age < 25:
fmt.Println("好好学习吧")
case age > 25 && age < 35:
fmt.Println("好好工作吧")
case age > 60:
fmt.Println("好好享受吧")
default:
fmt.Println("活着真好")
}
}
```
`fallthrough`语法: 可以执行满足条件的case的下一个case是为了兼容C语言中的case设计的值得学习
```go
func switchDemo5() {
s := "a"
switch {
case s == "a":
fmt.Println("a")
fallthrough
case s == "b":
fmt.Println("b")
case s == "c":
fmt.Println("c")
default:
fmt.Println("...")
}
}
```
结果输出:
a b
# 5.goto(跳转到指定标签)
描述: `goto`语句通过标签进行代码间的无条件跳转。`goto`语句可以在快速跳出循环、避免重复退出上有一定的帮助, Go语言中使用`goto`语句能简化一些代码的实现过程。
例如:双层嵌套的for循环要退出时
```go
func gotoDemo1() {
var breakFlag bool
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
breakFlag = true
break
}
fmt.Printf("%v-%v\n", i, j)
}
// 外层for循环判断
if breakFlag {
break
}
}
}
```
使用`goto`语句能简化代码:
```go
func gotoDemo2() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
goto breakTag
}
fmt.Printf("%v-%v\n", i, j)
}
}
return
// 标签
breakTag:
fmt.Println("正结束for循环")
fmt.Println("已结束for循环")
}
```
执行结果:
```go
0-0
0-1
正结束for循环
已结束for循环
```
# 6.break(跳出循环)
描述: `break`语句可以结束`for`、`switch`和`select`的代码块。
`break`语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的`for`、`switch`和 `select`的代码块上。 举个例子:
```go
func breakDemo1() {
BREAKDEMO1:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
break BREAKDEMO1
}
fmt.Printf("%v-%v\n", i, j)
}
}
fmt.Println("...")
}
```
# 7.continue(继续下次循环)
描述: `continue`语句可以结束当前循环,开始下一次的循环迭代过程,仅限在`for`循环内使用。
`continue`语句后添加标签时,表示开始标签对应的循环。例如:
```go
func continueDemo() {
forloop1:
for i := 0; i < 5; i++ {
// forloop2:
for j := 0; j < 5; j++ {
if i == 2 && j == 2 {
continue forloop1
}
fmt.Printf("%v-%v\n", i, j)
}
}
}
```

966
notes/4.数组.md Normal file
View File

@ -0,0 +1,966 @@
**本章目录:**
**0X00 Go语言基础之数组**
1.数组定义
2.数组初始化
3.数组的遍历
4.多维数组
二维数组的定义
二维数组的遍历
5.数组是值类型
**0X01 Go语言基础之切片**
1.切片定义
2.切片长度与容量
3.切片表达式
简单切片表达式
完整切片表达式
4.切片遍历
5.切片的本质
6.make() 方法构造切片
7.append() 方法切片添加元素
8.copy()方法复制切片
9.从切片中删除元素
10.切片相关操作
判断切片是否为空
切片不能直接比较
切片的赋值拷贝
切片的扩容策略
# 0X00 Go语言基础之数组
描述: 本文主要介绍Go语言中数组array及它的基本使用。
**Q: 什么是Array(数组)?**
> 答: 学过C语言的同学都知道数组是同一种数据类型元素的集合。 在Go语言中数组从声明时就确定使用时可以修改数组成员但是数组大小不可变化。
# 1.数组定义
基本语法:
```go
var 数组变量名 [元素数量]T
// 定义一个长度为3元素类型为int的数组a
var a [3]int
```
比如:`var a [5]int` 数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。注意: `[5]int`和`[10]int`是不同的类型。
```go
var 数组变量名 [元素数量]T
// 定义一个长度为3元素类型为int的数组a
var a [3]int
```
Tips :数组可以通过下标进行访问,下标是从`0`开始,最后一个元素下标是:`len-1`访问越界下标在合法范围之外则触发访问越界会panic。
# 2.数组初始化
数组的初始化也有很多方式。
**1.方法一**
初始化数组时可以使用初始化列表来设置数组元素的值。
```go
func main() {
var testArray [3]int //数组会初始化为int类型的零值
var numArray = [3]int{1, 2} //使用指定的初始值完成初始化
var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
fmt.Println(testArray) //[0 0 0]
fmt.Println(numArray) //[1 2 0]
fmt.Println(cityArray) //[北京 上海 深圳]
}
```
**2.方法二**
按照上面的方法每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度,例如:
```go
func main() {
var testArray [3]int
var numArray = [...]int{1, 2}
var cityArray = [...]string{"北京", "上海", "深圳"}
fmt.Println(testArray) //[0 0 0]
fmt.Println(numArray) //[1 2]
fmt.Printf("type of numArray:%T\n", numArray) //type of numArray:[2]int
fmt.Println(cityArray) //[北京 上海 深圳]
fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}
```
**3.方法三(非常值得学习)**
我们还可以使用指定索引值的方式来初始化数组,例如:
```go
func main() {
a := [...]int{1: 1, 3: 5}
b := [...]int{1:100,9:200} // [0 100 0 0 0 0 0 0 200 ]
fmt.Println(a) // [0 1 0 5]
fmt.Printf("type of a:%T\n", a) // type of a:[4]int
}
```
# 3.数组的遍历
遍历数组a有以下两种方法
```go
func main() {
var a = [...]string{"北京", "上海", "深圳"}
// 方法1for循环遍历
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
// 方法2for range遍历
for index, value := range a {
fmt.Println(index, value)
}
}
```
# 4.多维数组
Go语言是支持多维数组的我们这里以二维数组为例数组中又嵌套数组
# 二维数组的定义
```go
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
fmt.Println(a[2][1]) //支持索引取值:重庆
}
```
**注意:** 多维数组**只有第一层**可以使用`...`来让编译器推导数组长度。例如:
```go
//支持的写法
a := [...][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
//不支持多维数组的内层使用...
b := [3][...]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
```
# 二维数组的遍历
```go
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
// 方式1. for range 方式
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s\t", v2)
}
fmt.Println()
}
}
```
输出:
北京 上海
广州 深圳
成都 重庆
# 5.数组是值类型
描述: 数组是值类型,`赋值和传参会复制整个数组`。因此改变副本的值,不会改变本身的值。
```go
// go 语言中默认传参是值传递(拷贝原变量的值即 Ctrl+c 、Ctrl+v
func modifyArray(x [3]int) {
x[0] = 100
}
func modifyArray2(x [3][2]int) {
x[2][0] = 100
}
func main() {
a := [3]int{10, 20, 30}
modifyArray(a) //在modify中修改的是a的副本x不会更改数组a的元素
fmt.Println(a) //[10 20 30]
b := [3][2]int{
{1, 1},
{1, 1},
{1, 1},
}
modifyArray2(b) //在modify中修改的是b的副本x不会更改数组b的元素
fmt.Println(b) //[[1 1] [1 1] [1 1]]
}
```
**注意:**
1. 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
2. `[n]*T`表示指针数组,`*[n]T`表示数组指针 。
示例演示:
```go
package main
import "fmt"
func main() {
// 定义一个长度为3元素类型为int的数组a
var a [2]int // 默认为0
var a1 [2]string // 默认为空
var a2 [2]bool // 默认为false
var a3 [2]float64 // 默认为0
fmt.Printf("a 数组类型 %T , 元素: %v\n", a, a)
fmt.Printf("a1 数组类型 %T , 元素: %v\n", a1, a1)
fmt.Printf("a2 数组类型 %T , 元素: %v\n", a2, a2)
fmt.Printf("a3 数组类型 %T , 元素: %v\n", a3, a3)
// 数组初始化
// 方式1.使用初始化列表来设置数组元素的值
var b = [3]int{1, 2} // 三个元素,未指定下标元素的其值为 0
var c = [3]string{"Let's", "Go", "语言"}
// 方式2.根据初始值的个数自行推断数组的长度
var d = [...]float32{1.0, 2.0}
e := [...]bool{true, false, false}
// 方式3.使用指定索引值的方式来初始化数组
var f = [...]int{1: 1, 3: 8} // 只有 下标为1的其值为1下标为3的其值为8初开之外都为0
g := [...]string{"Weiyi", "Geek"}
fmt.Printf("b 数组类型 %T , 元素: %v\n", b, b)
fmt.Printf("c 数组类型 %T , 元素: %v\n", c, c)
fmt.Printf("d 数组类型 %T , 元素: %v\n", d, d)
fmt.Printf("e 数组类型 %T , 元素: %v\n", e, e)
fmt.Printf("f 数组类型 %T , 元素: %v\n", f, f)
fmt.Printf("f 数组类型 %T , 元素: %v\n", g, g)
// 数组指定元素获取
fmt.Println("c[1] 元素获取 : ", c[1])
// 数组遍历
// 方式1
alen := len(c)
for i := 0; i < alen; i++ {
fmt.Printf("c[%d]: %s ", i, c[i])
}
fmt.Println()
// 方式2
for i, v := range c {
fmt.Printf("c[%d]: %s ", i, v) // 注意如果是切片类型需要强转为string
}
fmt.Println()
// 多维数组
// 方式1
s1 := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
// 方式2
s2 := [...][2]string{
{"Go", "C"},
{"PHP", "Python"},
{"Shell", "Groovy"},
}
fmt.Println(s1[2][1]) //支持索引取值:重庆
fmt.Println(len(s1), s1) //[[北京 上海] [广州 深圳] [成都 重庆]]
fmt.Println(len(s2), s2)
// 多维数组遍历
// 方式1
s1len := len(s1)
for i := 0; i < s1len; i++ {
s1length := len(s1[i])
for j := 0; j < s1length; j++ {
fmt.Printf("s1[%d][%d] = %v ", i, j, s1[i][j])
}
}
fmt.Println()
// 方式2 (推荐方式)
for i, v1 := range s2 {
for j, v2 := range v1 {
fmt.Printf("s2[%d][%d] = %v ", i, j, v2)
}
}
fmt.Println()
// 多维数组元素更改
s1[1][0] = "Test"
s1[1][1] = "Change"
fmt.Println(s1)
}
```
执行结果:
```go
a 数组类型 [2]int , 元素: [0 0]
a1 数组类型 [2]string , 元素: [ ]
a2 数组类型 [2]bool , 元素: [false false]
a3 数组类型 [2]float64 , 元素: [0 0]
b 数组类型 [3]int , 元素: [1 2 0]
c 数组类型 [3]string , 元素: [Let's Go 语言]
d 数组类型 [2]float32 , 元素: [1 2]
e 数组类型 [3]bool , 元素: [true false false]
f 数组类型 [4]int , 元素: [0 1 0 8]
f 数组类型 [2]string , 元素: [Weiyi Geek]
c[1] 元素获取 : Go
c[0]: Let's c[1]: Go c[2]: 语言
c[0]: Let's c[1]: Go c[2]: 语言
s1[2][1] 元素获取 : 重庆
3 [[北京 上海] [广州 深圳] [成都 重庆]]
3 [[Go C] [PHP Python] [Shell Groovy]]
s1[0][0] = 北京 s1[0][1] = 上海 s1[1][0] = 广州 s1[1][1] = 深圳 s1[2][0] = 成都 s1[2][1] = 重庆
s2[0][0] = Go s2[0][1] = C s2[1][0] = PHP s2[1][1] = Python s2[2][0] = Shell s2[2][1] = Groovy
[[北京 上海] [Test Change] [成都 重庆]]
```
# 0X01 Go语言基础之切片
描述: 本文主要介绍Go语言中切片slice及它的基本使用。
**Q: 为什么要引入切片这个特性?**
描述: 因为`数组的长度是固定的并且数组长度属于类型的`一部分,所以数组有很多的局限性。
例如:
```go
func arraySum(x [3]int) int{
sum := 0
for _, v := range x{
sum = sum + v
}
return sum
}
```
这个求和函数只能接受`[3]int`类型,其他的都不支持。 再比如,
a := [3]int{1, 2, 3}
数组a中已经有三个元素了我们不能再继续往数组a中添加新元素了, 所以为了解决上述问题我们引入了Python一样切片的编程语言特性。
# 1.切片定义
描述: `切片Slice`是一个拥有相同类型元素的可变长度的序列。它是`基于数组类型做的一层封装`。
特点:
- 切片它非常灵活,支持自动扩容。
- 切片是一个引用类型,它的内部结构包含`地址`、`长度`和`容量`。切片一般用于快速地操作一块数据集合。
声明切片类型的基本语法如下:
```go
var name []T
// 关键字解析
- name:表示变量名
- T:表示切片中的元素类型
```
Tips : 在定义时可看出与数组定义`var array [number]T`间的区别,其不需要设置元素个数。
举个例子:
```go
func main() {
// 声明切片类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化
fmt.Println(a) //[]
fmt.Println(b) //[]
fmt.Println(c) //[false true]
fmt.Println(a == nil) //true
fmt.Println(b == nil) //false
fmt.Println(c == nil) //false
// fmt.Println(c == d) //切片是引用类型不支持直接比较只能和nil比较
}
```
# 2.切片长度与容量
描述: 切片拥有自己的长度和容量,我们可以通过使用内置的`len()`函数求长度,使用内置的`cap()`函数求切片的容量。
```go
// 切片长度与容量
var lth = []int{}
var lth64 = []float64{1, 2, 3}
fmt.Println("切片长度", len(lth), ",切片容量", cap(lth)) // 切片长度 0 ,切片容量 0
fmt.Println("切片长度", len(lth64), ",切片容量", cap(lth64)) // 切片长度 3 ,切片容量 3
```
# 3.切片表达式
描述: 切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。
它有两种变体一种指定low和high两个索引界限值的简单的形式另一种是除了low和high索引界限值外还指定容量的完整的形式。
# 简单切片表达式
描述: 切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的`low`和`high`表示一个索引范围`(左包含,右不包含)`也就是下面代码中从数组a中选出`1<=索引值<4`的元素组成切片s得到的切片`长度=high-low`,容量等于得到的切片的底层数组的容量。
```go
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[1:3] // s := a[low:high]
fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) // 5 - 1
}
```
输出:
s:[2 3] len(s):2 cap(s):4
为了方便起见,可以省略切片表达式中的任何索引。省略了`low`则默认为0省略了`high`则默认为切片操作数的长度:
```go
a[2:] // 等同于 a[2:len(a)]
a[:3] // 等同于 a[0:3]
a[:] // 等同于 a[0:len(a)]
```
**注意:**对于数组或字符串,如果`0 <= low <= high <= len(a)`则索引合法否则就会索引越界out of range
Tips : 对切片再执行切片表达式时(`切片再切片``high`的上限边界是切片的容量`cap(a)`,而不是长度。
**常量索引**必须是非负的并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。如果`low`和`high`两个指标都是常数,它们必须满足`low <= high`。如果索引在运行时超出范围,就会发生运行时`panic`。
```go
func main() {
a := [5]int{1, 2, 3, 4, 5}
s1 := a[1:3] // s1 := a[low:high]
fmt.Printf("s1:%v len(s1):%v cap(s1):%v\n", s1, len(s1), cap(s1))
s2 := s[3:4] // 索引的上限是cap(s)而不是len(s)
fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}
```
输出:
```go
s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1
```
完整切片表达式
描述: 对于数组指向数组的指针或切片a(注意不能是字符串)支持完整切片表达式:
a[low : high : max]
描述: 上面的代码会构造与简单切片表达式`a[low: high]`相同类型、相同长度和元素的切片。另外它会将得到的结果切片的容量设置为`max-low`。在完整切片表达式中只有第一个索引值low可以省它默认为0。
```go
func main() {
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
fmt.Printf("t:%v len(t):%v cap(t):%v\n", t, len(t), cap(t))
}
```
输出结果:
```go
t:[2 3] len(t):2 cap(t):4
```
Tips : 完整切片表达式需要满足的条件是`0 <= low <= high <= max <= cap(a)`,其他条件和简单切片表达式相同。
# 4.切片遍历
描述: 切片的遍历方式和数组是一致的,支持索引遍历和`for range`遍历。
```go
func main() {
s := []int{1, 3, 5}
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
for index, value := range s {
fmt.Println(index, value)
}
}
```
# 5.切片的本质
描述: 切片的本质就是对底层数组的封装,它包含了三个信息:`底层数组的指针、切片的长度len和切片的容量cap`。
举个例子,现在有一个数组`a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}`,切片`s1 := a[:5]`,相应示意图如下。
![WeiyiGeek.slice_01](assets/9df3291e8cba1df403b00f4dd67ca100e0b52897.png@942w_447h_progressive.webp)
切片`s2 := a[3:6]`,相应示意图如下:
![WeiyiGeek.slice_02](assets/cd6e549aff74fe8aa8bc262566706e0b8f39b342.png@942w_447h_progressive.webp)
Tips 由上面两图可知切片的容量是`数组长度 - 切片数组起始索引下标`,例如 a[1:] = 8 - 1 其容量为7
# 6.make() 方法构造切片
描述: 我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的`make()`函数,格式如下:
格式说明:
```go
make([]T, size, cap)
# 参数说明
- T:切片的元素类型
- size:切片中元素的数量
- cap:切片的容量
```
举个例子:
```go
func main() {
a := make([]int, 2, 10)
fmt.Println(a) //[0 0]
fmt.Println(len(a)) //2
fmt.Println(cap(a)) //10
}
```
上面代码中`a`的内部存储空间已经分配了10个但实际上只用了2个。 容量并不会影响当前元素的个数,所以`len(a)`返回2`cap(a)`则返回该切片的容量。
# 7.append() 方法切片添加元素
描述: Go语言的内建函数`append()`可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
```go
func main(){
var s []int
s = append(s, 1) // [1]
s = append(s, 2, 3, 4) // [1 2 3 4]
s2 := []int{5, 6, 7}
s = append(s, s2...) // [1 2 3 4 5 6 7]
}
```
**注意:** 通过var声明的零值切片可以在`append()`函数直接使用,无需初始化。
```go
var s []int
s = append(s, 1, 2, 3)
```
**注意:** 没有必要像下面的代码一样初始化一个切片再传入`append()`函数使用,
```go
s := []int{} // 没有必要初始化
s = append(s, 1, 2, 3)
var s = make([]int) // 没有必要初始化
s = append(s, 1, 2, 3)
```
描述: 每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在`append()`函数调用时所以我们通常都需要用原变量接收append函数的返回值。
举个例子:
```go
func main() {
//append()添加元素和切片扩容
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
}
```
输出情况:
```go
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000
```
从上面的结果可以看出:
1. `append()`函数将元素追加到切片的最后并返回该切片。
2. 切片numSlice的容量按照`124816`这样的规则自动进行扩容每次扩容后都是扩容前的2倍。
append() 函数还支持一次性追加多个元素,例如:
```go
var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) // [北京 上海 广州 深圳 成都 重庆]
```
# 8.copy()方法复制切片
描述: 首先我们来看一个问题
```go
func main() {
a := []int{1, 2, 3, 4, 5}
b := a
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 1000
fmt.Println(a) //[1000 2 3 4 5]
fmt.Println(b) //[1000 2 3 4 5]
}
```
Tips : 由于切片是引用类型所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
Go语言内建的`copy()`函数可以迅速地将一个切片的数据复制到另外一个切片空间中,`copy()`函数的使用格式如下:
```go
copy(destSlice, srcSlice []T)
# 参数:
- srcSlice: 数据来源切片
- destSlice: 目标切片
```
举个例子:
```go
func main() {
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
}
```
# 9.从切片中删除元素
描述: Go语言中并没有删除切片元素的专用方法我们可以使用切片本身的特性来删除元素。 代码如下:
```go
func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) // [30 31 33 34 35 36 37]
}
```
总结一下就是要从切片a中删除索引为`index`的元素,操作方法是`a = append(a[:index], a[index+1:]...)`
# 10.切片相关操作
# 判断切片是否为空
描述: 要检查切片是否为空,请始终使用`len(s) == 0`来判断,而不应该使用`s == nil`来判断。
```go
d := [5]int{1, 2, 3, 4, 5}
// 判断切片是否为空
if len(d) != 0 {
fmt.Println("变量 d 切片不为空: ", d)
}
```
# 切片不能直接比较
描述: 切片之间是不能比较的,我们不能使用`==`操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和`nil`比较。 一个`nil`值的切片并没有底层数组,一个`nil`值的切片的长度和容量都是0。
但是我们不能说一个长度和容量都是0的切片一定是`nil`,例如下面的示例:
```go
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
```
所以要判断一个切片是否是空的,要是用`len(s) == 0`来判断,不应该使用`s == nil`来判断。
# 切片的赋值拷贝
描述: 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
```go
func main() {
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
}
```
# 切片的扩容策略
描述: 可以通过查看`$GOROOT/src/runtime/slice.go`源码,其中扩容相关代码如下:
```go
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
```
从上面的代码可以看出以下内容:
- 首先判断如果新申请容量cap大于2倍的旧容量old.cap最终容量newcap就是新申请的容量cap
- 否则判断如果旧切片的长度小于1024则最终容量(newcap)就是旧容量(old.cap)的两倍newcap=doublecap
- 否则判断如果旧切片的长度大于等于1024则最终容量newcap从旧容量old.cap开始循环增加原来的1/4即`newcap=old.cap,for {newcap += newcap/4}`直到最终容量newcap大于等于新申请的容量(cap),即`newcap >= cap`
- 如果最终容量cap计算值溢出则最终容量cap就是新申请容量cap
Tips : 需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如`int`和`string`类型的处理方式就不一样。
**示例演示:**
```javascript
package main
import "fmt"
func main() {
// 切片声明与定义
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
// - 切片 a 变量值为空/零值。
if a == nil {
fmt.Println("a 切片元素:", a)
}
fmt.Println("b 切片元素:", b)
fmt.Println("c 切片元素:", c)
// 切片长度与容量
var lth = []int{}
var lth64 = []float64{1, 2, 3}
fmt.Println("切片长度", len(lth), ",切片容量", cap(lth))
fmt.Println("切片长度", len(lth64), ",切片容量", cap(lth64))
// 切片表达式
d := [5]int{1, 2, 3, 4, 5}
s := [5]string{"Let", "'s", "Go", "语言", "学习"}
s1 := d[1:3] // s := d[low(包含):high(不包含)] == d[1] d[2]
s2 := d[2:] // 等同于 a[2:5] == d[2] d[3] d[4]
s3 := d[:3] // 等同于 a[0:3] == d[0] d[1] d[2]
s4 := d[:] // 等同于 a[0:5] == d[0] d[1] d[2] d[3] d[4]
s5 := s[1:4:5] // 等同于 s[1:4] == s[1] s[2] s[3]
fmt.Printf("s1:%v len(s1):%v cap(s1):%v\n", s1, len(s1), cap(s1)) // 注意此种情况 { 2 .. 5 容量为 4 }
fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2)) // { 3 .. 5 容量为 3 }
fmt.Printf("s3:%v len(s3):%v cap(s3):%v\n", s3, len(s3), cap(s3)) // 注意此种情况 { 1 .. 5 容量为 5 }
fmt.Printf("s4:%v len(s4):%v cap(s4):%v\n", s4, len(s4), cap(s4)) // { 1 .. 5 容量为 5}
fmt.Printf("s5:%v len(s5):%v cap(s5):%v\n", s5, len(s5), cap(s5)) // s5:['s Go 语言] len(s5):3 cap(s5):4
// 判断切片是否为空
if len(d) != 0 {
fmt.Println("变量 d 切片不为空: ", d)
}
// 切片遍历
for i, v := range s {
fmt.Printf("i: %d, v: %v , 切片指针地址: %p \n", i, v, &v)
}
fmt.Println()
// make() 构造切片
e := make([]int, 2, 10)
fmt.Printf("e:%v len(e):%d cap(e):%d \n", e, len(e), cap(e)) // 长度 2容量为 10
// append() 添加元素 {7,8,9}
f := append(e, 7, 8, 9) // f:[0 0 7 8 9] len(f):5 cap(f):10
f = append(f, e...) // 追加切片
fmt.Printf("f:%v len(f):%d cap(f):%d \n", f, len(f), cap(f)) // 长度 7容量为 10
// copy() 复制切片
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 7, 7)
copy(slice2, slice1)
slice2[6] = 2048
fmt.Println("slice1 : ", slice1, "\t slice2 :", slice2)
// 切片赋值拷贝
slice3 := make([]int, 3)
slice4 := slice3
slice4[0] = 1024
slice4[2] = 4096
fmt.Printf("slice3 : %v, ptr : %p \n", slice3, slice3)
fmt.Printf("slice4 : %v, ptr : %p \n", slice4, slice4)
}
```
**执行结果:**
```javascript
a 切片元素: []
b 切片元素: []
c 切片元素: [false true]
切片长度 0 ,切片容量 0
切片长度 3 ,切片容量 3
s1:[2 3] len(s1):2 cap(s1):4
s2:[3 4 5] len(s2):3 cap(s2):3
s3:[1 2 3] len(s3):3 cap(s3):5
s4:[1 2 3 4 5] len(s4):5 cap(s4):5
s5:['s Go 语言] len(s5):3 cap(s5):4
变量 d 切片不为空: [1 2 3 4 5]
i: 0, v: Let , 切片指针地址: 0xc000010270
i: 1, v: 's , 切片指针地址: 0xc000010270
i: 2, v: Go , 切片指针地址: 0xc000010270
i: 3, v: 语言 , 切片指针地址: 0xc000010270
i: 4, v: 学习 , 切片指针地址: 0xc000010270
e:[0 0] len(e):2 cap(e):10
f:[0 0 7 8 9 0 0] len(f):7 cap(f):10
slice1 : [1 2 3 4 5] slice2 : [1 2 3 4 5 0 2048]
slice3 : [1024 0 4096], ptr : 0xc000018288
slice4 : [1024 0 4096], ptr : 0xc000018288
```
Tips 总结: 数组是值类型,且包含元素的类型和元素个数,需注意元素的个数`(数组长度)`属于数组类型的一部分。

400
notes/5.Map篇.md Normal file
View File

@ -0,0 +1,400 @@
**
本章目录:**
- 0x02 Go语言基础之Map映射
- - 1.Map 声明定义
- 2.Map 基础使用
- 3.Map 键值遍历
- 4.Map 键值删除
- 5.值为map类型的切片
- 6.值为切片类型的map
- 7.示例演示
![img](assets/71bf2cd56882a2e97f8b3477c9256f8b09f361d3.png@progressive.webp)
# 0x02 Go语言基础之Map映射
描述: Go语言中提供的映射关系容器为map, 其内部使用散列表(hash)实现。
# 1.Map 声明定义
描述: Map 是一种无序的基于`key-value`的数据结构, 并且它是引用类型,所以必须初始化值周才能进行使用。
**语法定义:**
```javascript
map[KeyType]ValueType
// # 参数说明:
- KeyType:表示键的类型。
- ValueType:表示键对应的值的类型。
```
Tips : map类型的变量`默认初始值为nil`,需要使用`make()函数来分配内存`。语法为:`make(map[KeyType]ValueType, [cap])`, 其中cap表示map的容量该参数虽然不是必须的但是我们应该在初始化map的时候就为其指定一个合适的容量。
# 2.Map 基础使用
描述Map 中的数据都是成对出现的其Map的基本使用示例代码如下:
```go
// 1.采用Make初始化Map类型的变量。
scoreMap := make(map[string]int, 8)
scoreMap["小明"] = 100
fmt.Println(scoreMap["小明"])
fmt.Printf("type of a:%T\n", scoreMap)
// 2.在声明时填充元素。
userInfo := map[string]string{
"username": "WeiyiGeek",
"password": "123456",
}
fmt.Println(userInfo)
```
# 3.Map 键值遍历
描述: 在进行Map类型的变量遍历之前我们先学习判断map中键是否存在。
**(1) 键值判断**
描述: 判断Map中某个键是否存在可以采用如下特殊写法: `value, ok := map[key]`
```go
scoreMap := make(map[string]int)
scoreMap["小明"] = 100
value, ok := scoreMap["张三"]
if ok {
fmt.Println("scoreMap 存在该 '张三' 键")
} else {
fmt.Println("scoreMap 不存在该键值")
}
```
**(2) 键值遍历**
描述: Go 语言中不像Python语言一样有多种方式进行遍历, 大道至简就 `for...range` 遍历 `Map` 就可以搞定。
```go
scoreMap := make(map[string]int)
scoreMap["Go"] = 90
scoreMap["Python"] = 100
scoreMap["C++"] = 60
// 遍历 k-v 写法
for k, v := range scoreMap {
fmt.Println(k, v)
}
// 遍历 k 写法
for k := range scoreMap {
fmt.Println(k)
}
// 遍历 v 写法
for _, v := range scoreMap {
fmt.Println(v)
}
```
Tips 遍历map时的元素顺序与添加键值对的顺序无关。
# 4.Map 键值删除
描述: 我们可使用 `delete() 内建函数` 从map中删除一组键值对, `delete()` 函数的格式如下: `delete(map, key)`
其中 `map:`表示要删除键值对的map, `key:` 表示要删除的键值对的键。
```go
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
delete(scoreMap, "小明" ) // 将`小明:100`从map中删除
for k,v := range scoreMap{
fmt.Println(k, v)
}
```
# 5.值为map类型的切片
描述: 第一次看到时可能比较绕其实可以看做在切片中存放Map类型变量。
简单示例:
```go
func demo3() {
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println()
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[1] = make(map[string]string, 10)
mapSlice[2] = make(map[string]string, 10)
mapSlice[0]["name"] = "WeiyiGeek"
mapSlice[0]["sex"] = "Man"
mapSlice[1]["姓名"] = "极客"
mapSlice[1]["性别"] = "男"
mapSlice[2]["hobby"] = "Computer"
mapSlice[2]["爱好"] = "电脑技术"
for i, v := range mapSlice {
//fmt.Printf("index:%d value:%v\n", i, v)
for _, value := range v {
fmt.Printf("index:%d value:%v\n", i, value)
}
}
}
```
执行结果:
```go
index:0 value:map[]
index:1 value:map[]
index:2 value:map[]
index:0 value:Man
index:0 value:WeiyiGeek
index:1 value:极客
index:1 value:男
index:2 value:Computer
index:2 value:电脑技术
```
# 6.值为切片类型的map
描述: 同样在Map中存放切片类型的数据。
代码演示了map中值为切片类型的操作
```go
// 值为切片类型的map
func demo4() {
var sliceMap = make(map[string][]string, 3)
var key = [2]string{"Country", "City"}
fmt.Println("初始化 sliceMap 其值 : ", sliceMap)
for _, v := range key {
// 判断键值是否存在如果不存在则初始化一个容量为2的切片
value, ok := sliceMap[v]
if !ok {
value = make([]string, 0, 2)
}
if v == "Country" {
value = append(value, "中国", "台湾")
} else {
value = append(value, "北京", "上海")
}
// 将切片值赋值给Map类型的变量
sliceMap[v] = value
}
```
执行结果:
```go
初始化 sliceMap 其值 : map[]
map[City:[北京 上海] Country:[中国 台湾]]
```
Tips : 非常重要、重要 `Slice切片与Map` 在使用时一定要做初始化操作(在内存空间申请地址)。
# 7.示例演示
**1.Map类型的基础示例**
```go
func demo1() {
// 1.Map 定义
var a1 map[string]int8 // (未分配内存)
fmt.Println("Map 类型 的 a1 变量 :", a1)
if a1 == nil {
fmt.Println("默认初始化的Map类型的a1变量值: nil")
}
// 2.基本使用利用Make进行分配内存空间存储Map。
b1 := make(map[string]string, 8)
b1["姓名"] = "WeiyiGeek"
b1["性别"] = "男|man"
b1["爱好"] = "计算机技术"
b1["出生日期"] = "2021-08-08"
// 指定输出
fmt.Printf("b1['姓名'] = %v \n", b1["姓名"])
// 整体输出
fmt.Printf("Map b1 Type: %T , Map b1 Value: %v \n", b1, b1)
// 3.在声明时填充元素。
c1 := map[string]string{
"username": "WeiyiGeek",
"sex": "Man",
"hobby": "Computer",
}
// 指定输出
fmt.Printf("c1['username'] = %v \n", c1["username"])
// 整体输出
fmt.Printf("Map c1 Type: %T , Length : %d , Map c1 Value: %v \n", c1, len(c1), c1)
// 4.判断c1中的键值时候是否存在 sex Key.
value, ok := c1["sex"]
if ok {
fmt.Println("c1 Map 变量中存在 'sex' 键 = ", value)
} else {
fmt.Println("c1 Map 变量中不存在 sex 键")
}
// 5.遍历Map
for k, v := range b1 {
fmt.Println(k, "=", v)
}
// 6.删除指定键值对例如删除c1中的hobby键值。
delete(c1, "hobby")
fmt.Printf("Map 现存在的键 : ")
for k := range c1 {
fmt.Print(k, " ")
}
}
```
执行结果:
```go
Map 类型 的 a1 变量 : map[]
默认初始化的Map类型的a1变量值: nil
b1['姓名'] = WeiyiGeek
Map b1 Type: map[string]string , Map b1 Value: map[出生日期:2021-08-08 姓名:WeiyiGeek 性别:男|man 爱好:计算机技术]
c1['username'] = WeiyiGeek
Map c1 Type: map[string]string , Length : 3 , Map c1 Value: map[hobby:Computer sex:Man username:WeiyiGeek]
c1 Map 变量中存在 'sex' 键 = Man
出生日期 = 2021-08-08
姓名 = WeiyiGeek
性别 = 男|man
爱好 = 计算机技术
Map 现存在的键 : username sex
```
**2.按照指定顺序遍历map**
```go
func demo2() {
rand.Seed(time.Now().UnixNano()) //初始化随机数种子
// 申请并初始化一个长度为 200 的 Map
var scoreMap = make(map[string]int, 200)
for i := 0; i < 20; i++ {
key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
value := rand.Intn(100) //生成0~99的随机整数
scoreMap[key] = value
}
//取出map中的所有key存入切片keys
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
//对切片进行排序
sort.Strings(keys)
//按照排序后的key遍历map
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}
```
执行结果:
```go
stu00 4
stu01 48
stu02 66
stu03 18
stu04 13
stu05 89
stu06 80
stu07 16
stu08 11
stu09 26
stu10 42
stu11 45
stu12 24
stu13 47
stu14 92
stu15 77
stu16 12
stu17 16
stu18 17
stu19 76
```
Tips : 探究上述示例中`Array 数组、Slice 切片、Map 映射`有序与无序输出演示。
```go
func demo5() {
// Array
var Arr = [...]int{1, 2, 6, 4, 5}
// Slice
var Sli = []int{1, 2, 6, 4, 5}
// Map
var Map = map[string]int{
"a1": 1,
"b2": 2,
"c3": 3,
"d6": 6,
"e5": 5,
}
fmt.Printf("Type : %T, Value : %v \n", Arr, Arr)
for _, A := range Arr {
fmt.Printf("%v ", A)
}
fmt.Println()
fmt.Printf("Type : %T, Value : %v \n", Sli, Sli)
for _, S := range Sli {
fmt.Printf("%v ", S)
}
fmt.Println()
fmt.Printf("Type : %T, Value : %v \n", Map, Map)
for _, M := range Map {
fmt.Printf("%v ", M)
}
}
```
执行结果:
```go
Type : [5]int, Value : [1 2 6 4 5]
1 2 6 4 5
Type : []int, Value : [1 2 6 4 5]
1 2 6 4 5
Type : map[string]int, Value : map[a1:1 b2:2 c3:3 d6:6 e5:5]
1 2 3 6 5
```

1461
notes/6.指针、函数.md Normal file

File diff suppressed because it is too large Load Diff

1797
notes/7.错误处理篇.md Normal file

File diff suppressed because it is too large Load Diff

665
notes/8.接口篇.md Normal file
View File

@ -0,0 +1,665 @@
**本章目录:**
- 0X00 Go语言基础之接口
- - 一个类型实现多个接口
- 多个类型实现同一接口
- 1.接口类型
- 2.接口的定义
- 3.接口类型变量
- 4.接口实现之值接收者和指针接收者
- 5.接口与类型
- 6.接口嵌套
- 7.空接口
- 8.接口之类型断言
![img](assets/4aa545dccf7de8d4a93c2b2b8e3265ac0a26d216.png@progressive.webp)
# 0X00 Go语言基础之接口
**Q: 在开发编程中您有可能遇到以下场景?**
> 答: 我不关心变量是什么类型,只关心能调用它的什么方法,此时我们可以采用接口`(Interface)`类型进行解决相关问题。
# 1.接口类型
描述: 在Go语言中接口interface是一种类型一种抽象的类型, 其定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
如 interface 是一组 method 的集合,是`duck-type programming`的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
Tips: 为了保护你的Go语言职业生涯请牢记接口interface是一种类型。
**Q: 为什么要使用接口?**
在我们编程过程中会经常遇到:
- 比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们能不能把它们当成“支付方式”来处理呢?
- 比如三角形,四边形,圆形都能计算周长和面积,我们能不能把它们当成“图形”来处理呢?
- 比如销售、行政、程序员都能计算月薪,我们能不能把他们当成“员工”来处理呢?
例如:面的代码中定义了猫和狗然后它们都会叫你会发现main函数中明显有重复的代码如果我们后续再加上猪、青蛙等动物的话我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢
```go
type Cat struct{}
func (c Cat) Say() string { return "喵喵喵" }
type Dog struct{}
func (d Dog) Say() string { return "汪汪汪" }
func main() {
c := Cat{}
fmt.Println("猫:", c.Say()) // 猫: 喵喵喵
d := Dog{}
fmt.Println("狗:", d.Say()) // 狗: 汪汪汪
}
```
Go语言中为了解决类似上面的问题就设计了接口这个概念。接口区别于我们之前所有的具体类型接口是一种抽象的类型。当你看到一个接口类型的值时你不知道它是什么唯一知道的是通过它的方法能做什么。
# 2.接口的定义
描述: Go语言提倡面向接口编程,每个接口由数个方法组成,接口的定义格式如下:
```go
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
}
```
**参数说明:**
- 接口名使用type将接口定义为自定义的类型名。`Go语言的接口在命名时一般会在单词后面添加er`如有写操作的接口叫Writer有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
- 方法名当方法名首字母是大写且这个接口类型名首字母也是大写时这个方法可以被接口所在的包package之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
**基础示例:**
```go
type writer interface{
Write([]byte) error
}
```
Tips: 当你看到这个接口类型的值时你不知道它是什么唯一知道的就是可以通过它的Write方法来做一些事情。
Tips :实现接口的条件, 即一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说接口就是一个需要实现的方法列表。
# 3.接口类型变量
**Q: 那实现了接口有什么用呢?**
> 答: 接口类型变量能够存储所有实现了该接口的实例,接口类型变量实际上你可以看做一个是一个合约。
**基础示例:**
```go
// 定义一个接口类型writer的变量w。
var w writer // 声明一个writer类型的变量w
```
Tips 观察下面的代码体味此处_的妙用
```go
// 摘自gin框架routergroup.go
type IRouter interface{ ... }
type RouterGroup struct { ... }
var _ IRouter = &RouterGroup{} // 确保RouterGroup实现了接口IRouter
```
**示例演示:**
```go
package main
import "fmt"
// 接口声明定义以及约定必须实现的方法
type speaker interface {
speak()
eat(string)
}
// 人结构体
type person struct{ name, language string }
func (p person) speak() {
fmt.Printf("我是人类,我说的是%v, 我叫%v\n", p.language, p.name)
}
func (p person) eat(food string) { fmt.Printf("喜欢的食物: %v\n", food) }
// 猫结构体
type cat struct{ name, language string }
func (c cat) speak() {
fmt.Printf("动物猫,说的是%v, 叫%v\n", c.language, c.name)
}
func (c cat) eat(food string) { fmt.Printf("喜欢的食物: %v\n", food) }
// 狗结构体
type dog struct{ name, language string }
func (d dog) speak() {
fmt.Printf("动物狗,说的是%v, 叫%v\n", d.language, d.name)
}
func (d dog) eat(food string) { fmt.Printf("喜欢的食物: %v\n", food) }
func talk(s speaker) {
s.speak()
}
// (1) 接口基础使用演示
func demo1() {
p := person{"WeiyiGeek", "汉语"}
c := cat{"小白", "喵喵 喵喵..."}
d := dog{"阿黄", "汪汪 汪汪...."}
talk(p)
talk(c)
talk(d)
}
// (2) 接口类型的使用(可看作一种合约)方法不带参数以及方法带有参数
func demo2() {
// 定义一个接口类型writer的变量w。
var s speaker
fmt.Printf("Type %T\n", s) // 动态类型
s = person{"接口类型-唯一", "汉语"} // 动态值
fmt.Printf("\nType %T\n", s) // 动态类型
s.speak()
s.eat("瓜果蔬菜")
s = cat{"接口类型-小白", "喵喵..."} // 动态值
fmt.Printf("\nType %T\n", s) // 动态类型
s.speak()
s.eat("fish")
s = dog{"接口类型-阿黄", "汪汪..."} // 动态值
fmt.Printf("\nType %T\n", s) // 动态类型
s.speak()
s.eat("bone")
}
func main() {
demo1()
fmt.Println()
demo2()
}
```
**执行结果:**
```go
我是人类,我说的是汉语, 我叫WeiyiGeek
动物猫,说的是喵喵 喵喵..., 叫小白
动物狗,说的是汪汪 汪汪...., 叫阿黄
Type <nil>
Type main.person
我是人类,我说的是汉语, 我叫接口类型-唯一
喜欢的食物: 瓜果蔬菜
Type main.cat
动物猫,说的是喵喵..., 叫接口类型-小白
喜欢的食物: fish
Type main.dog
动物狗,说的是汪汪..., 叫接口类型-阿黄
喜欢的食物: bone
```
注意: 带参数和不带参数的函数,在接口中实现的不是同一个方法,所以当某个结构体中没有完全实现接口中的方法将会报错。
# 4.接口实现之值接收者和指针接收者
**Q: 使用值接收者实现接口和使用指针接收者实现接口有什么区别呢?**
-
- 1. 值接收者实现接口: `结构体类型和结构体指针类型的变量都可以存储`由于因为Go语言中有对指针类型变量求值的语法糖结构体指针变量内部会自动求值`取指针地址中存储的值`)。
-
- 1. 指针接收者实现接口: `只能存储结构体指针类型的变量`
我们通过下面的例子进行演示:
```go
package main
import (
"fmt"
)
// 接口类型声明
// (1) 值接收者实现接口
type Mover interface {
move()
}
type dog struct{}
func (d dog) move() { fmt.Println("值接收者实现接口 -> 狗...移动....") } // 关键点
// 使用值接收者实现接口之后不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量.
func demo1() {
var m1 Mover
var d1 = dog{} // 值类型
m1 = d1 // m1可以接收dog类型的变量
fmt.Printf("Type : %#v \n", m1)
m1.move()
var d2 = &dog{} // 指针类型
m1 = d2 // x可以接收指针类型的(*dog)类型的变量
fmt.Printf("Type : %#v \n", m1)
m1.move()
}
// (2)指针接收者实现接口
type Runer interface{ run() }
type cat struct{}
func (c *cat) run() { fmt.Println("指针接收者实现接口 -> 猫...跑....") }
// 此时实现run接口的是*cat类型所以不能给m1传入cat类型的c1此时x只能存储*dog类型的值。
func demo2() {
var m1 Runer
var c1 = cat{}
//m1不可以接收dog类型的变量
// m1 = c1 // 报错信息: cannot use c1 (variable of type cat) as Runer value in assignment: missing method run (run has pointer receiver)compilerInvalidIfaceAssign
fmt.Printf("Type : %#v \n", c1)
//m1只能接收*dog类型的变量
var c2 = &cat{}
m1 = c2
fmt.Printf("Type : %#v \n", c2)
m1.run()
}
func main() {
demo1()
fmt.Println()
demo2()
}
```
**执行结果:**
```go
Type : main.dog{}
值接收者实现接口 -> 狗...移动....
Type : &main.dog{}
值接收者实现接口 -> 狗...移动....
Type : main.cat{}
Type : &main.cat{}
指针接收者实现接口 -> 猫...跑....
```
**面试题: 注意这是一道你需要回答“能”或者“不能”的题!**
问: 首先请观察下面的这段代码,然后请回答这段代码能不能通过编译?
```go
package main
import "fmt"
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "man" {
talk = "你好,帅哥"
} else {
talk = "您好,美女"
}
return
}
func main() {
var peo People = Student{} // 此处为关键点
think := "woman"
fmt.Println(peo.Speak(think))
}
```
答案: 是不行会报 `./interface.go:21:6: cannot use Student{} (type Student) as type People in assignment: Student does not implement People (Speak method has pointer receiver) (exit status 2)`错误,由于指针接收者实现接口必须是有指针类型的结构体实例化对象以及其包含的方法。
# 5.接口与类型
# 一个类型实现多个接口
描述: 一个结构体类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
例如: 狗可以叫也可以动,我们就分别定义Sayer接口和Mover接口
```go
// Sayer 接口
type Sayer interface { say() }
// Mover 接口
type Mover interface { move() }
// dog既可以实现Sayer接口也可以实现Mover接口。
type dog struct { name string }
// 实现Sayer接口
func (d dog) say() { fmt.Printf("%s会叫 汪汪汪\n", d.name) }
// 实现Mover接口
func (d dog) move() { fmt.Printf("%s会动 \n", d.name) }
func main() {
var a = dog{name: "旺财"}
var x Sayer = a // 将dog类型赋予给Sayer接口类型的变量x此时它可以调用say方法
var y Mover = a // 将dog类型赋予给Mover接口类型的变量y此时它可以调用move方法
x.say() // 旺财会叫 汪汪汪
y.move() // 旺财会动
}
```
# 多个类型实现同一接口
描述: Go语言中不同的类型还可以实现同一接口,比如我们前面Person、Cat、Dog结构体类型中实现的Speak()方法。
例如我们定义一个Mover接口它要求结构体类型中必须有一个move方法, 如狗可以动,汽车也可以动。
```go
// Mover 接口
type Mover interface { move() }
type dog struct { name string }
type car struct { brand string }
// dog类型实现Mover接口
func (d dog) move() { fmt.Printf("%s会跑\n", d.name) }
// car类型实现Mover接口
func (c car) move() { fmt.Printf("%s速度120迈\n", c.brand) }
func main() {
var x Mover
var a = dog{name: "旺财"}
x = a
x.move() // 旺财会跑
var b = car{brand: "保时捷"}
x = b
x.move() // 保时捷速度120迈
}
```
非常注意: 并且一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
**示例演示:**
```go
package main
import "fmt"
// 接口类型
type android interface {
telephone(int64)
music()
}
// 结构体声明 实现music方法
type mp3 struct{}
// 实现接口中的方法
func (m *mp3) music() { fmt.Println("播放音乐.....")}
// 结构体声明
type mobilephone struct {
production string
mp3 // 嵌入mp3结构体并拥有它的方法
}
// 实现接口中的方法
func (mb *mobilephone) telephone(number int64) { fmt.Printf("%v 手机, 正在拨打 %v 电话....\n", mb.production, number)}
func main() {
// android 接口类型
var a android
// 指针类型结构体变量mb
var mp = &mobilephone{production: "小米"}
a = mp
fmt.Printf("Type : %#v\n", a) // android 接口类型变量输出
a.telephone(10086)
a.music()
}
```
**执行结果:**
```go
Type : &main.mobilephone{production:"小米", mp3:main.mp3{}}
小米 手机, 正在拨打 10086 电话....
播放音乐.....
```
# 6.接口嵌套
描述: 接口与接口间可以通过嵌套创造出新的接口,嵌套得到的接口的使用与普通接口一样这里我们让cat实现animal接口。
**示例演示:**
```go
// Sayer 接口
type Sayer interface {say()}
// Mover 接口
type Mover interface {move()}
// 接口嵌套
type animal interface {
Sayer
Mover
}
// cat 结构体
type cat struct {
name string
}
// 接口方法的实现
func (c cat) say() {fmt.Printf("%v 喵喵喵",c.name)}
func (c cat) move() {fmt.Printf("%v 猫会动",c.name)}
func main() {
var x animal
x = cat{name: "花花"}
x.move() //喵喵喵
x.say() //猫会动
}
```
# 7.空接口
**空接口的定义**
描述: 空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口, 该类型的变量可以`存储任意类型的变量`。他会在我们以后GO编程中常常出现。
例如:
```go
// interface 是关键字,并不是类型。
// 方式1.但一般不会采用此种方式
var empty interface{}
// 方式2.我们可以直接忽略接口名称(空接口类型)
interface{}
```
**空接口的应用**
-
- 1. 空接口作为函数的参数: 使用空接口实现可以接收任意类型的函数参数。
-
- 1. 空接口作为map的值: 使用空接口实现可以保存任意值的字典。
**示例演示:**
```go
package main
import "fmt"
// (1) 空接口作为函数参数
func showType(a interface{}) { fmt.Printf("参数类型:%T, 参数值:%v\n", a, a) }
func main() {
// (2) 空接口作为map的值
var m1 map[string]interface{} // 类似于Java中的 Map<String,Object> m1
m1 = make(map[string]interface{}) // 为Map申请一块内存空间
// 可以存储任意类型的值
m1["name"] = "WeiyiGeek"
m1["age"] = 20
m1["sex"] = true
m1["hobby"] = [...]string{"Computer", "NetSecurity", "Go语言编程学习"}
fmt.Printf("#空接口作为map的值\n%#v", m1)
fmt.Println(m1)
fmt.Printf("\n#空接口作为函数参数\n")
showType(nil)
showType([]byte{'a'})
showType(true)
showType(1024)
showType("我是一串字符串")
}
```
执行结果:
```go
#空接口作为map的值
map[string]interface {}{"age":20, "hobby":[3]string{"Computer", "NetSecurity", "Go语言编程学习"}, "name":"WeiyiGeek", "sex":true}
map[age:20 hobby:[Computer NetSecurity Go语言编程学习] name:WeiyiGeek sex:true]
#空接口作为函数参数
参数类型:<nil>, 参数值:<nil>
参数类型:[]uint8, 参数值:[97]
参数类型:bool, 参数值:true
参数类型:int, 参数值:1024
参数类型:string, 参数值:我是一串字符串
```
Tips : 因为空接口可以存储任意类型值的特点所以空接口在Go语言中的使用十分广泛。
# 8.接口之类型断言
描述: 空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
**接口值**
描述: 一个接口的值`(简称接口值)`是由`一个具体类型`和`具体类型的值`两部分组成的,这两部分分别称为接口的`动态类型`和`动态值`。
我们来看一个具体的例子:
```go
var w io.Writer
w = nil
w = os.Stdout
w = new(bytes.Buffer)
```
请看下图分解:
![WeiyiGeek.动态类型与动态值](assets/df0b4dfdfcd79b3a5c181b7f529c41504dddece8.png@942w_761h_progressive.webp)
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:`x.(T)`,其中:
-
- 1. x表示类型为interface{}的变量
-
- 1. T表示断言x可能是的类型。
该语法返回两个参数,`第一个参数是x转化为T类型后的变量``第二个值是一个布尔值若为true则表示断言成功为false则表示断言失败`。
**示例演示**
```go
package main
import "fmt"
// 示例1.采用if进行判断断言
func assert(x interface{}) {
v, ok := x.(string) // v 接受是string类型
if ok {
fmt.Printf("assert successful : %v, typeof %T\n", v, v)
} else {
fmt.Printf("assert failed 非 string 类型! : %v, typeof %T\n", x, x)
}
}
func demo1() {
var x interface{}
x = "WeiyiGeek"
assert(x) // assert successful : WeiyiGeek, typeof string
x = 1024
assert(x) // assert failed 非 string 类型! : 1024, typeof int
}
// 示例2.如果要断言多次就需要写多个if判断这个时候我们可以使用switch语句来实现
func justifyType(x interface{}) {
switch v := x.(type) {
case string:
fmt.Printf("x is a stringvalue is %v\n", v)
case int:
fmt.Printf("x is a int is %v\n", v)
case bool:
fmt.Printf("x is a bool is %v\n", v)
default:
fmt.Println("unsupport type")
}
}
func demo2() {
var x interface{}
x = "i'm string"
justifyType(x)
x = 225
justifyType(x)
x = true
justifyType(x)
}
func main() {
demo1()
fmt.Println()
demo2()
}
```
执行结果:
```go
assert successful : WeiyiGeek, typeof string
assert failed 非 string 类型! : 1024, typeof int
x is a stringvalue is i'm string
x is a int is 225
x is a bool is true
```
**接口总结:**
描述: 关于需要注意的是只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口,不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

336
notes/9.包.md Normal file
View File

@ -0,0 +1,336 @@
**本章目录:**
- 0x01 Go语言基础之包
- - 1.包的定义
- 2.包的导入
- 3.包的可见性
- 4.自定义包名
- 5.匿名导入包
- 6.包init()初始化函数
- 7.示例演示
![img](assets/4aa545dccf7de8d4a93c2b2b8e3265ac0a26d216.png@progressive.webp)
# 0x01 Go语言基础之包
描述: 在工程化的Go语言开发项目中Go语言的源码复用是建立在包package基础之上的, 可以提高开发效率,使用其他开发者已经写好的代码(站在巨人的肩膀上)。
# 1.包的定义
描述: Go语言的包`package`是多个Go源码的集合是一种高级的代码复用方案Go语言为我们提供了很多内置包如`fmt、os、io`等。
我们还可以根据自己的需要创建自己的包,一个包可以简单理解为一个存放`.go`文件的文件夹。
该文件夹下面的所有go文件都要在代码的第一行添加如下代码声明该文件归属的包。
```go
package 包名
// graphical.go
package area
```
**注意事项:**
- 一个文件夹下面直接包含的文件只能归属一个package同样一个package的文件不能在多个文件夹下。
- 包名可以不和文件夹的名字一样,`但可以与.go文件名称一致`,包名不能包含 - 符号并且严格按照变量命名的规则进行。
- 在导入包时应该从包的GOPATH/src后的路径开始写起其以`/`作为分隔符。
- 包名为main的包为应用程序的入口包这种包编译后会得到一个可执行文件而编译不包含main包的源代码则不会得到可执行文件。
# 2.包的导入
描述: 要在代码中引用其他包的内容需要使用import关键字导入使用的包。具体语法如下:
import "包的路径"
**示例演示**
```go
// 单行导入的格式如下:
import "包1"
import "包2"
// 多行导入的格式如下:
import (
"包1"
"包2"
)
// 实际案例
import (
"fmt" // golang 内置包
"math/rand" // golang 内置包
"github.com/mattn/go-sqlite3" // golang 项目的工程组织规范
)
```
Tips: 使用`go get`导入github上的package, 以 go-sqlite3 为例采用go get将package进行下载`go get github.com/mattn/go-sqlite3`,此时该包对应的物理路径是 `$GOPATH/src/github.com/mattn/go-sqlite3`, 此外在你也可以手动进行下载项目到`$GOPATH/src`
**注意事项:**
- import导入语句通常放在文件开头包声明语句的下面。
- 导入的包名需要使用双引号包裹起来,并且如果是多个包需要使用`()`进行包含。
- 包名是从`$GOPATH/src/`后开始计算的,使用/进行路径分隔。
- Go语言中禁止循环导入包。
# 3.包的可见性
描述: 如果想在一个包中引用另外一个包里的标识符`(如变量、常量、类型、函数等)`时该标识符必须是对外可见的public
在Go语言中只需要将标识符的首字母大写就可以让标识符对外可见了。
举个例子, 我们定义一个包名为pkg2的包代码如下
```go
package pkg2
import "fmt"
// # 包变量可见性探究
// 1.首字母小写,外部包不可见,只能在当前包内使用
var a = 100
// 2.首字母大写外部包可见,可在其他包中使用
const Mode = 1
// 3.首字母大写,外部包可见,可在其他包中使用
func Add(x, y int) int {
return x + y
}
// 4.首字母小写,外部包不可见,只能在当前包内使用
func age() {
var Age = 18 // 函数局部变量,外部包不可见,只能在当前函数内使用
fmt.Println(Age)
}
// 5.首字母小写,外部包不可见,只能在当前包内使用
type person struct {
name string
}
// 6.结构体中的字段名和接口中的方法名如果首字母都是大写,外部包可以访问这些字段和方法
type Student struct {
Name string //可在包外访问的方法
class string //仅限包内访问的字段
}
type Payer interface {
init() //仅限包内访问的方法
Pay() //可在包外访问的方法
}
```
# 4.自定义包名
描述: 在导入包名的时候我们还可以为导入的包设置别名。通常用于导入的包名太长或者导入的包名冲突的情况。
具体语法格式如下:
import 别名 "包的路径"
**示例演示:**
```go
// 1.单行导入方式定义别名:
import "fmt"
import calc "github.com/studygo/pkg_test"
func main() {
fmt.Println(calc.Add(100, 200))
fmt.Println(calc.Mode)
}
// 2.多行导入方式定义别名:
import (
"fmt"
m "github.com/studygo/pkg_test"
)
func main() {
fmt.Println(m.Add(100, 200))
fmt.Println(m.Mode)
}
```
# 5.匿名导入包
描述: 如果只希望导入包,而不使用包内部的数据时,可以使用匿名导入包。
具体的格式如下:`import _ "包的路径"`
Tips: 匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中。
**补充说明:**
我们可以通过如下格式省略包前前缀,使用想过与同一个.go文件函数类似但是常常不建议这样使用可以会与当前文件中的某些相同方法的冲突。
具体的格式如下:`import . "包的路径"`, 示例如下
```go
import (
. "fmt"
)
func main() {
Println("我是fmt内置包的函数....")
}
```
# 6.包init()初始化函数
描述: 在Go语言程序执行时导入包语句会自动触发包内部`init()`函数的调用。
**语法格式:**
```go
package custompackage
func init() {
fmt.Println("custompackage init() execute....")
}
```
**init()函数执行顺序**
- 通常包初始化执行的顺序,如下图所示:
![WeiyiGeek.包init函数执行时机](assets/e0340a28184fc7c34680178c16c61fc984a7ce99.png@942w_590h_progressive.webp)
- 但是实际项目中Go语言包会从main包开始检查其导入的所有包每个包中又可能导入了其他的包。Go编译器由此构建出一个树状的包引用关系再根据引用顺序决定编译顺序依次编译这些包的代码。
在运行时被最后导入的包会最先初始化并调用其init()函数,如下图示:
![WeiyiGeek.多包中初始化函数执行顺序](assets/101eef1b4984a49abfc27a360911e81eed92d771.png@942w_512h_progressive.webp)
**注意事项:**
- init() 函数没有参数也没有返回值。
- init() 函数在程序运行时自动被调用执行,不能在代码中主动调用它。
# 7.示例演示
**工程项目结构:**
```go
// 自定义包的.go文件
➜ pkg pwd & ls
/home/weiyigeek/app/program/project/go/src/weiyigeek.top/custom/pkg/demo1 //(from $GOPATH))
demo1.go
// 调用自定义包的.go文件
➜ pkg ls
weiyigeek.top/studygo/Day04/packagemain.go
```
不多说上代码:
```go
// # weiyigeek.top/custom/pkg/demo1/demo1.go #
package demo
import "fmt"
// 静态变量
const FLAG = true
// 基础变量
var Flag = 1
// 包初始化函数
func init() {
fmt.Println("This is a package demo ")
Flag = 1024 // 注意点
}
// 包函数
func Show() {
var msg = " 我是函数内部的变量 "
fmt.Printf("FLAG => %v, Flag => %v\nmsg:%v\n", FLAG, Flag, msg)
}
// 结构体
type Person struct{ Name string }
func (p Person) paly() {
fmt.Printf("%v 正在打游戏....", p.Name)
}
// 接口
type IPerson interface{ paly() }
func Exec(i IPerson) {
i.paly()
}
```
调用自定义包.go文件:
```go
package main
import (
"fmt" // 不建议如. "fmt" 此使用
demo "weiyigeek.top/custom/pkg/demo1"
)
func main() {
fmt.Println(demo.Flag)
fmt.Println(demo.FLAG)
demo.Show()
demo.Exec(demo.Person{Name: "Weiyieek"})
}
```
执行结果:
```go
This is a package demo
1024
true
FLAG => true, Flag => 1024
msg: 我是函数内部的变量
Weiyieek 正在打游戏....
```
**包总结:**
- 我们可以在`GOPATH/src`路径下按照golang项目工程组织规范进行创建自定义包。
- 自定义包中需要外部调用访问的(如变量、常量、类型、函数等),必须首字母进行大写。
- 导入自定义包时我们可以自定义别名,但是需要满足命名规则已经不能与当前目录名称重名。
- 多个包都定义init()函数时,从调用的最后一包中递归向上执行输出。
**错误说明:**
- Step 1.引入自定义包的时候报 go.mod file not found in current directory or any parent directory 错误.
```go
go: go.mod file not found in current directory or any parent directory; see 'go help modules' (exit status 1)
no required module provides package weiyigeek.top/custom/pkg/demo1; to add it: go get weiyigeek.top/custom/pkg/demo1 (compile)go-staticcheck
```
问题原因: go的环境设置问题其次查看`GOPATH`目录中src为存放第三方go包。
解决办法: `go env -w GO111MODULE=auto`
- Step 2.main redeclared in this block (see details)compiler
错误信息:
```go
main redeclared in this block (see details)compilerDuplicateDecl 03datatype.go(151, 6): main redeclared in this block
```
原因分析: 在学习study go时候会创建许多.go文件,并且在同一个目录下每个.go的文件里面都有package main,也就是main函数,这就是问题所在。
解决办法: 同一个目录下面不能有多个package main调整或者创建多个文件夹分别放入对应的文件下执行即可。

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB