1:Go语言介绍
Go即是Golang,是Google公司2009年11月正式开源的一门编程语言,根据Go语言开发者所说,近10多年,从单机时代的C语言到现在的互联网时代的Java,都没有令人特别满意的开发语言,而C++给人的感觉其实就是花了100%的精力,却只达到了60%的开发率,产出比例太低了,java和"C#"的哲学有来源于C++,并且随着硬件不断的升级,这些语言并不能充分的应用硬件和CPU,因此,一门高效,简洁的编程语言诞生了。
Go语言不仅有静态编译语言的安全和高性能,而且达到了动态语言的开发速度和易维护性,
有人形容Go语言:C + Python,说明Go语言既有C的运行速度,又有Python快速开发的能力。
Go语言是非常有潜力的语言,是因为它的应用场景是目前互联网最为热门的几个领域,比如:
Web开发,区块链开发,大型游戏服务器开发,分布式/云计算开发......
国内大厂:B站
应用开发:Google,阿里,腾讯,百度,京东,小米,360,的很多应用都是使用Go语言开发的,
2:Go解决了什么问题
1:多核硬件架构
2:超大规模分布式计算集群
3:Web开发模式导致的前所未有的开发规模和更新速度
3:Go运行环境
1:确认Go版本信息
C:\Users\Administrator>go version
go version go1.18.1 windows/amd64
2:查看Go环境
C:\Users\Administrator>go env
set GO111MODULE=on
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Administrator\AppData\Local\go-build
set GOENV=C:\Users\Administrator\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=E:\go\path\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=E:\go\path
set GOPRIVATE=
set GOPROXY=https://goproxy.cn,direct
set GOROOT=E:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=E:\go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.18.1
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\ADMINI~1\AppData\Local\Temp\go-build643624193=/tmp/go-build -gno-record-gcc-switches
4:Go编程环境下载
下载地址:https://www.jetbrains.com/go/download/download-thanks.html?platform=windows
1:LiteIDE:运行速度快,代码提示非常好用,但是调试功能不是特别好。
2:VSCode:调试功能好用,但是代码提示一般,写起来费时费力
3:Goland:各项功能都比较完善,但是收费,并且比较占用ziyuan
# 这里我使用Goland开发,如何安装Goland和配置,大家可以百度搜一下
5:Go的基本语法
5.1:变量
1:定义变量
# var 变量名 类型 = 表达式
# 注:在这里表达式其实就是在内存开辟的空间存了这个表达式
# 方法1
package main
import (
"fmt"
)
func main() {
var name string = "zhangsan"
var age int = 18
var ok bool = true
fmt.Println(name, age, ok) # 打印变量
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan 18 true
# 方法2(最常用)
package main
import (
"fmt"
)
func main() {
name := "zhangsan"
age := 18
ok := true
fmt.Println(name, age, ok)
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan 18 true
# 方法3
package main
import (
"fmt"
)
func main() {
var name string
var age int
var ok bool
name = "zhangsan"
age = 18
ok = true
fmt.Println(name, age, ok)
}
# 声明的全局变量
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan 18 true
# 定义多个同类型变量
package main
import (
"fmt"
)
func main() {
var name, age string
var ok bool
name, age = "zhangsan", "18"
fmt.Println(name, age, ok)
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan 18 false
# 定义多个变量
package main
import (
"fmt"
)
func main() {
var (
name string
age int
ok bool
)
name = "zhangsan"
age = 18
ok = true
fmt.Println(name, age, ok)
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan 18 true
# 注:声明/导入即使用,引入包或声明变量等等的必须使用,否则会报错!!!
5.2:常量
变量与常量的区别:
变量:值可以随意改变
常量:不可以改变的值
# 定义一个常量
package main
import (
"fmt"
)
func main() {
const name = "zhansgan"
fmt.Println(name)
}
# 运行
PS E:\goland\demo> go run .\main.go
zhansgan
# 尝试修改常量
package main
import (
"fmt"
)
func main() {
const name = "zhansgan"
name = "lisi"
fmt.Println(name)
}
# 运行
PS E:\goland\demo> go run .\main.go
# command-line-arguments
.\main.go:9:2: cannot assign to name (untyped string constant "zhansgan")
# 测试var进行修改
package main
import (
"fmt"
)
func main() {
var age = 100
age = 200
fmt.Println(age)
}
# 运行
PS E:\goland\demo> go run .\main.go
200
# 在变量下修改的值是OK的,这里要注意的是修改数据必须和源数据是同一类型,否则会报错
# 批量定义常量
package main
import (
"fmt"
)
func main() {
const (
name string = "zhangsan"
age int = 18
ok bool = true
)
fmt.Println(name, age, ok)
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan 18 true
# 注:在同一个代码块内,不管是变量和常量,中间的名称是不可以重复的,只能选择其一
5.3:fmt包
1:fmt.Println # 一个打印会换行的方法
2:fmt.Print # 是一个不会换行的打印
3:fmt.Printf # 是一个格式化输出的打印
# 第一个Demo(无法换行打印)
package main
import "fmt"
func main() {
fmt.Print("123")
fmt.Print("456")
}
# 运行
PS E:\goland\demo> go run .\main.go
123456
# 第二个Demo(可换行打印)
package main
import "fmt"
func main() {
fmt.Println("123")
fmt.Println("456")
}
# 运行
PS E:\goland\demo> go run .\main.go
123
456
# 第三个Demo(可以将变量或常量或者其他参数带进打印内)
package main
import "fmt"
func main() {
name := "zhangsan"
fmt.Printf("名称是:%s", name)
}
# 运行
PS E:\goland\demo> go run .\main.go
名称是:zhangsan
# 这里面会有两个常用参数:%s和%d,意思分别是:
%s:必修是string类型
%d:必须是int类型
6:init函数和main函数
6.1:init函数
在go语言中,init函数用于包(package)的初始化,该函数是Go语言的一个重要特性,具有以下特征:
1:init函数用于程序执行前做包的初始化函数,比如说初始化包里面的变量等
2:每个包可以有多个init函数
3:包的每个源文件也可以有多个init函数
4:同一个包中多个init函数的执行顺序Go语言没有明确的说明
5:不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
6:init函数不能被其他函数调用,而是在main函数执行之前自动被调用
6.2:main函数
是Go语言程序中的默认入口函数(主函数):func main(),函数体则用{}包裹,
func main() {
# 函数体
}
6.3:init函数与main函数的异同
相同点:两个函数定义时不能有任何的参数和返回值,且Go程序会自动调用。
不同点:init可以应用于任意包,并且可以重复定义多个,main函数只能用于main包中,且只能定义一个。
# main函数demo
package main
import ( # 导入包
"fmt"
)
func main() {
fmt.Println("Hello, world!") # 应用函数
}
# 运行
PS E:\goland\demo> go run .\main.go
Hello, world!
# init函数demo
package main
import (
"fmt"
)
var name string
func main() {
fmt.Println("main")
fmt.Println(name)
}
func init() {
fmt.Println("init")
name = "zhangsan"
fmt.Println(name)
}
# 运行时主要先看执行顺序,
PS E:\goland\demo> go run .\main.go
init
zhangsan
main
zhangsan
# 这里我们发现即使main在init前面,也是init函数先执行
# 接下来体现一下多次执行init函数
package main
import (
"fmt"
)
var name string
func main() {
fmt.Println("main")
fmt.Println(name)
}
func init() {
fmt.Println("init")
name = "zhangsan"
fmt.Println(name)
}
func init() {
fmt.Println("init2")
name = "lisi"
fmt.Println(name)
}
# 执行
PS E:\goland\demo> go run .\main.go
init
zhangsan
init2
lisi
main
lisi
# 这里需要注意一个点哦,就是如果是全局变量被main初始化了,那么会取最后一个init的初始化的值
# 注:main函数只能应用在main包中,其他包无法定义
# 下面这串代码是错误的
package demo # 并不是main包
import (
"fmt"
)
var name string
func main() {
name = "world"
fmt.Println("Hello", name)
}
# init函数介绍:
1:在Go语言程序执行时导入包语句会自动触发包内部的init()函数的调用
2:需要注意的是:init()函数没有参数,也没有返回值
3:init()函数在程序运行时自动被调用,不能再代码中主动调用它
4:包初始化执行的顺序:全局变量声明——————>init函数初始化——————>main函数执行
7:Golang中的关键字
Go语言中一共有25个关键字:
1:var和const:变量和常量的定义
2:package和import:导入包
3:func:用于定义函数的方法
4:return:用于从函数返回
5:defer someCode:在函数退出之前执行
6:用于并行(创建协程)
7:select:选择不同类型的通讯
8:interface:用于定义接口
9:struct:用于定义抽象数据类型
10:break/case/continue/for/fallthrough/else/if/switch/goto/default:流程控制
11:chan:用于channel通讯
12:type:用于声明自定义类型
13:map:用于声明map类型数据
14:range:用于读取slice,map,channel数据
8:命名规范
Go是一门区分大小写的语言,命名规则涉及:变量,常量,全局函数,结构,接口,方法等命名。Go语言从语法方面进了以下限定:任意需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写开头,当命名(变量,常量,类型,函数名,结构字段等等)以一个大写字母开头如:Select,那么使用这这种形式的标识符的对象被外部包代码所使用(客户端程序需要先导入这个包),这被称为导出(面向对象语言终的 public):命名如果以小写字母开头,则包对外是不可见的,但是他们在整个包的内部是可见并可用(面向对象终的private)
8.1:包名称
保持package的名字和目录保持一致,尽量采用有意义的包名,简短,有意义,尽量和标准库不产生冲突,包名应该以小写为单词,不要使用下划线和大小写混合,demo如下:
package demo
8.2:文件命名
尽量采取有有意的命名,简短,有意义,应该为小写单词,使用下划线分割各个单词,demo如下
service_list.go
8.3:结构体命名
采用驼峰命名法,首字母根据访问控制大小写,或者小写struct申明和初始化格式采用多行,demo如下:
type MainConfig struct{
Port string `json:"port"`
Host string `json:"host"`
Address string `json:"address"`
}
8.4:接口命名
命名规则基本和上面的结构体类型单个函数的结构名以"er"作为后缀,例如:Reader,Writer
1:命名以"er"结尾,如:Writer,Handler,Helper,Manager等
2:接口方法声明 = 方法名+方法签名,如:methodA (param1,param2) outputTypeList
# demo如下:
type Reader interface {
Read(p []byte) (n int, err error)
}
8.5:变量命名
和结构体类似,变量命名一般使用驼峰法,首字母根据访问控制原则使用大写或者小写。但是遇到特有名词时,需要遵循以下规则:如果变量为私有,且持有名词为首个单词,则使用小写,如appSerice,若变量类型为bool类型,则名称以Has,Is,Can或Allow开头
# demo如下:
func main() {
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool
}
8.6:常量命名
常量一般使用全部大写字母组成,使用下划线分割单词
const API_URL = "http://10.0.0.10:6443"
如果是枚举类型的常量,首先要创建相应的类型:
type Scheme string const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
)
9:基本数据类型
9.1:值类型
bool
int(32 or 64),int8,int16,int32,int64 # 占用字节数不一样,消耗内存也不一样
uint(32 or 64),uint8(byte),uint16,uint32,uint64
float32,float64
string
complex64 complex128
array # 固定长度的数组
9.2:引用类型
# 长度不固定,可以扩充
slice# 序列数组(最常用)
map # 映射
chan # 管道
9.3:内置函数
Go语言存在一些不需要导入就可以直接使用的内置函数,它们有时可以针对不同的类型进行操作,例如:len,cap和append,或必须用于操作系统级的操作,例如panic,因此它们需要直接获取编译器支持
append # 用来追加元素到数组,slice中,返回修改后的数组,slice
close # 主要用于关闭channel
delete # 从map中删除Key对应的value
panic # 停止常规的goroutine,(panic和recover,用来做错误处理)
recover # 允许程序自定义goroutine的panic动作
real # 返回complex的实部,(complex,read,imag)用于创建和操作复数
imag # 返回complex的虚部,
make # 用来分配内存,返回tyoe本身(只能应用于slice,map,channel)
new # 用来分配内容,主要用来分配值类型,比如:int,struct,返回指向type的指针
cap # capacity是容量的意思,用于取某个类型的最大容量,(只能用于切片和map)
copy # 用于复制和链接slice,返回复制的数目
len # 求长度,比如:string array,slice,channel,返回长度
print,println # 底层打印函数,在部署环境中建议使用fmt包
9.4:基本类型介绍
类型 |
长度 |
默认值 |
说明 |
bool |
1 |
false |
|
byte |
1 |
0 |
uint8 |
rune |
4 |
0 |
Unicode Code Point,int32 |
int,uint |
4或8 |
0 |
32位或64位 |
int8,uint8 |
1 |
0 |
-128 - 127,0 - 255 byte是uint8的别名 |
int16,uint16 |
2 |
0 |
-32768 - 32767,0 - 65535 |
int32,uint32 |
4 |
0 |
-21亿 - 21亿,0 - -42亿,rune是int32的别名 |
int64,uint64 |
8 |
0 |
|
float32 |
4 |
0.0 |
|
float64 |
8 |
0.0 |
|
complex64 |
8 |
|
|
complex128 |
16 |
|
|
uintptr |
4或8 |
|
与存储指针的uint32或uint64整数 |
array |
|
|
值类型 |
struct |
|
|
值类型 |
string |
|
** |
UTF-8字符串 |
slice |
|
nil |
引用类型 |
map |
|
nil |
引用类型 |
channel |
|
nil |
引用类型 |
interface |
|
nil |
接口 |
function |
|
nil |
函数 |
9.5:数字
9.5.1:数字类型
1:Golang数据类型介绍
1.1:Golang语言中数据类型分为:基本数据类型和复合数据类型
1.2:基本数据类型:整型,浮点型,布尔型,字符串
1.3:复合数据类型:数组,切片,结构体,函数,Map,通道(channel),接口
2:整型分为两大类:
2.1:有符号整型长度分为:int8,int16,int32,int64
2.2:对应无符号整型:uint8,uint16,uint32,uint64
类型 |
范围 |
占用空间 |
有无符号 |
int8 |
(-128 - 127)(-2的7次方 - 2的7次方-1) |
1个字节 |
有 |
int16 |
(-32768 - 32767)(-2的15次方 - 2的15次方-1) |
2个字节 |
有 |
int32 |
(-2147483648 - 2147483647)(-2的31次方 - 2的31次方-1) |
4个字节 |
有 |
int64 |
(-9223372036854775808 - 9223372036854775807)(-2的63次方 - 2的63此方-1) |
8个字节 |
有 |
uint8 |
(0 - 255)(0 - 2的8次方-1) |
1个字节 |
无 |
uint16 |
(0 - 65535)(0 - 2的16次方-1) |
2个字节 |
无 |
uint32 |
(0 - 4294967295)(0 - 2的32次方-1) |
4个字节 |
无 |
uint64 |
(0 - 18446744073709551615)(0 - 2的64次方-1) |
8个字节 |
无 |
9.5.2:关于字节
字节也叫byte,是计算机数据的基本存储单位
1024KB=1MB / 1024MB=1GB / 1024GB=1TB # 在服务器或电脑里,一个中文占2个字节
9.6:数字定义
9.6.1:定义数字类型
# 错误用法
# int8 超出了范围
package main
import "fmt"
func main() {
var a int8 = 4
var b int8 = 128
fmt.Println("a:", a, a)
fmt.Println("a:", b, b)
}
# 运行
PS E:\goland\demo> go run .\main.go
# command-line-arguments
.\main.go:7:15: cannot use 128 (untyped int constant) as int8 value in variable declaration (overflows)
# 正确用法(int8 -128 到 127)
package main
import "fmt"
func main() {
var b int8 = 127
fmt.Println("a:", b, b)
}
# 运行
PS E:\goland\demo> go run .\main.go
a: 127 127
# 其他的int也是同理
9.6.2:reflect.TypeOf查看数据类型
# 判断当前的数据是什么类型
package main
import (
"fmt"
"reflect" # 导包
)
func main() {
var b int8 = 127
fmt.Println(reflect.TypeOf(b)) # 打印并输出类型
}
# 运行
PS E:\goland\demo> go run .\main.go
int8
# 第二种打印方法
# 查看类型+查看数据(这里涉及到了%T和%v的使用)
# 具体的%+参数:%T:打印类型,%v:打印值,%s:打印字符串,%d:打印数字
package main
import (
"fmt"
)
func main() {
var b int8 = 127
fmt.Printf("%T: %v", b, b) # 这里用到的方法是Printf
}
# 运行
PS E:\goland\demo> go run .\main.go
int8: 127
9.6.3:布尔值
Go语言中以bool类型声明布尔型类型,布尔型其实只有(True;真)或(false:假)两个值。
# 注意:
1:布尔类型便变量默认为false。
2:Go语言中不允许整型强制转换为布尔型
3:布尔型无法参与数值计算,也无法与其他类型转换
# 具体demo如下
package main
import (
"fmt"
"reflect"
)
func main() {
a := true # 这里以最简单的方式取定义每当然也可以用 var a bool = true
fmt.Println(reflect.TypeOf(a))
}
# 运行
PS E:\goland\demo> go run .\main.go
bool
# 查看占用字节数量
package main
import (
"fmt"
"unsafe"
)
func main() {
var a bool = false
fmt.Println(unsafe.Sizeof(a)) # 这里使用到了 unsafe.Sizeof()方法
}
# 我们多定义两个不同的类型来看看效果
package main
import (
"fmt"
"unsafe"
)
func main() {
a := true
b := "张三"
var c int8 = 123
var d int16 = 12345
var e int32 = 123456789
var f int64 = 123456789012345
fmt.Println(a, b, c, d, e, f)
fmt.Println(unsafe.Sizeof(a))
fmt.Println(unsafe.Sizeof(b))
fmt.Println(unsafe.Sizeof(c))
fmt.Println(unsafe.Sizeof(d))
fmt.Println(unsafe.Sizeof(e))
fmt.Println(unsafe.Sizeof(f))
}
# 运行
PS E:\goland\demo> go run .\main.go
true 张三 123 12345 123456789 123456789012345
1
16
1
2
4
8
# 大家可以对应一下
10:字符串
10.1:字符串
Go语言里面的字符串的内部实现是用UTF-8编码,字符串的值为("")双引号中的内容,可以在Go语言的源码中直接添加非ASCII码字符串,如:
name := "zhangsan"
my_name := "张三"
10.2:多行字符串
反引号间换行将被作为字符串中的换行,但是,所有转义字符均无效,文本将被原样输出。如:
package main
import "fmt"
func main() {
name := `zhangsan
lisi
wangwu`
fmt.Println(name)
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan
lisi
wangwu
10.3:byte和rune
Go语言中有以下两种字符:
uint8:或者叫byte类型,代表ASCII码的一个类型
rune:代表一个UTF-8字符
1:字符串的底层是一个byte数组,所以可以和byte类型相互转换
2:字符串是不能修改的,字符串是由byte字节组成,所以字符串长度是byte字节的长度
3:rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成
# demo
package main
import "fmt"
func main() {
a := "Hello World"
fmt.Printf("%#v", a[:11])
}
# 运行
PS E:\goland\demo> go run .\main.go
"Hello World"
# 转换其中的值
package main
import "fmt"
func main() {
a := "你好朋友"
a_rune := []rune(a) # 转换类型为数组,然后即可读写
s_go := string(a_rune[:2]) + "Go" # 切片获取原有数据,string方法将byte数组转化成字符串然后进行替换。
fmt.Println(s_go)
}
# 运行
PS E:\goland\demo> go run .\main.go
你好Go
10.4:字符串常用操作
方法 |
介绍 |
len(str) |
求长度 |
+ / fmt.Sprintf |
拼接字符串 |
strings.Split |
分割 |
strings.Contains |
判断是否包含 |
strings.HashPrefix / strings.HashSuffix |
前缀/后缀判断 |
strings.Index() / strings.LastIndes() |
子串出现的位置 |
strings.Join(a[]string, sep string) |
join操作 |
10.4.1:len(str)
package main
import "fmt"
func main() {
a := "zhangsan"
fmt.Println(len(a))
}
# 运行
PS E:\goland\demo> go run .\main.go
8
10.4.2:+ (拼接)
package main
import "fmt"
func main() {
a := "你好!"
b := "Golang!"
fmt.Println(a + b)
}
# 运行
PS E:\goland\demo> go run .\main.go
你好!Golang!
10.4.3:strings.Split
# 通过strings.Split()方法将字符串转换成数组
package main
import (
"fmt"
"strings"
)
func main() {
a := "2022-06-23"
arr := strings.Split(a, "-")
fmt.Println(arr)
}
# 运行
PS E:\goland\demo> go run .\main.go
[2022 06 23]
package main
import (
"fmt"
"strings"
)
func main() {
a := "2022-06-23"
arr := strings.Split(a, "-")
fmt.Println(arr[0])
}
# 这样打印应该是2022,运行demo如下
PS E:\goland\demo> go run .\main.go
2022
# 查看它的类型
package main
import (
"fmt"
"strings"
)
func main() {
a := "2022-06-23"
arr := strings.Split(a, "-")
fmt.Printf("%T: %v\n", arr, arr)
}
# 运行demo
PS E:\goland\demo> go run .\main.go
[]string: [2022 06 23]
10.4.4:strings.join
# 通过strings.Join()方法将转数组换成字符串
package main
import (
"fmt"
"strings"
)
func main() {
a := "2022-06-23"
arr := strings.Split(a, "-") # 这里将字符串转换为数组,
fmt.Println(arr) # 打印以上数组
arr_join := strings.Join(arr, "+") # 这里将数组转换为字符串
fmt.Printf("%T: %v\n", arr_join, arr_join) # 打印以上数据类型与值
}
# 运行
PS E:\goland\demo> go run .\main.go
[2022 06 23]
string: 2022+06+23
10.4.5:单引号
1:组成每个字符串的元素叫做字符,可以通过遍历字符串元素获得字符,字符用单引号('')
2:uint8类型,或者叫byte类型,代表了ASCII码的一个字符
3:rune类型,代表一个UTF-8字符
# 单引号只能表示一个字符
package main
import "fmt"
func main() {
a := 'a'
name := "zhangsan"
fmt.Println(a)
fmt.Println(name)
fmt.Printf("a的值是:%c\n", a)
}
# 运行
PS E:\goland\demo> go run .\main.go
97 # 这个97对应的就是 ascii码a。因为是''所以打印的并不是字符而是ascii码
zhangsan
a的值是:a
10.4.6:字符串遍历
1:遍历字符串(使用for循环实现)
package main
import "fmt"
func main() {
name := "Hello Golang"
for i := 0; i < len(name); i++ {
fmt.Printf("%T: %v", name[i], name[i])
}
}
# 运行
PS E:\goland\demo> go run .\main.go
uint8: 72uint8: 101uint8: 108uint8: 108uint8: 111uint8: 32uint8: 71uint8: 111uint8: 108uint8: 97uint8: 110uint8: 103
# 这里有个重点就是在for循环的时候他会将你的字符串做转换
2:使用range遍历字符串
package main
import "fmt"
func main() {
name := "Hello Golang"
for index, value := range name {
fmt.Println(index, value)
}
}
# 运行
PS E:\goland\demo> go run .\main.go
0 72
1 101
2 108
3 108
4 111
5 32
6 71
7 111
8 108
9 97
10 110
11 103
# 取值
package main
import "fmt"
func main() {
name := "Hello Golang"
for _, value := range name { # _的意思可理解为占位符,在下方不引用代码不会报错。他其实是接收了数据的
fmt.Printf("%T: %v: %c\n", value, value, value) # 数字使用%d取
}
}
# 运行
PS E:\goland\demo> go run .\main.go
int32: 72: H
int32: 101: e
int32: 108: l
int32: 108: l
int32: 111: o
int32: 32:
int32: 71: G
int32: 111: o
int32: 108: l
int32: 97: a
int32: 110: n
int32: 103: g
10.5:转String
10.5.1:strconv
# int转string
package main
import (
"fmt"
"strconv"
)
func main() {
var a int = 10
fmt.Printf("%T: %v\n", a, a) # 这里是打印转换前的类型与值
sa := strconv.Itoa(a) # 转换类型
fmt.Printf("%T: %v\n", sa, sa) # 打印转换后的值与类型
}
# 运行
PS E:\goland\demo> go run .\main.go
int: 10
string: 10
# float 转 string
package main
import (
"fmt"
"strconv"
)
func main() {
var a float64 = 1.23456789
af := strconv.FormatFloat(a, 'f', 2, 64)
fmt.Printf("Type: %T Value: %v\n", af, af)
}
# 运行
PS E:\goland\demo> go run .\main.go
Type: string Value: 1.23
# bool 转 string
package main
import (
"fmt"
"strconv"
)
func main() {
var a bool = true
ab := strconv.FormatBool(a)
fmt.Printf("Type:%T,value:%v\n", ab, ab)
}
# 运行
PS E:\goland\demo> go run .\main.go
Type:string,value:true
# int64 转 string
package main
import (
"fmt"
"strconv"
)
func main() {
var a int64 = 20
ai := strconv.FormatInt(a, 10) # 第二个值为*/
fmt.Printf("Type: %T,Value: %v\n", ai, ai)
}
# 运行
PS E:\goland\demo> go run .\main.go
Type: string,Value: 20
10.5.2:string转int
package main
import (
"fmt"
"strconv"
)
func main() {
var a int = 10
sa := strconv.Itoa(a)
fmt.Printf("%T: %v\n", sa, sa)
ints, err := strconv.Atoi(sa) # 将以上转换为string的参数带转换为int
if err != nil { # 判断是否为error
fmt.Print(err) # 打印err
} else { # 若不是err
fmt.Printf("%T: %v\n", ints, ints) # 打印转换成int的类型与值
}
}
# 执行
PS E:\goland\demo> go run .\main.go
string: 10 # 转换成string
int: 10 # 转回int
11:数组Array
11.1:数组介绍
11.1.1:Array介绍
1:数组是指同一类型的额数据源的集合
2:数组中包含的每一个数据被称为数组元素(element),这种类型可以是任意的原始类型,比如:int,string等
3:一个数组包含的元素个数被称为数组的长度
4:在Golang中数组是一个长度固定的数据类型,数组的长度是类型的一部分,也就是说[5]int和[10]int是两个不同的类型
5:Golang中数组的另一个特点是占用内存的连续性,也就是说,数据中的元素是被分配到连续性的内存中的,因而索引数组的元素速度非常快
6:和数组对应的类型是Slice(切片),Slice是可以增长和收缩的动态序列,功能也更灵活
7:但是如果想理解Slice的工作原理首先要先理解数组
![image](https://img2022.cnblogs.com/blog/2222036/202206/2222036-20220624234723748-1244339393.png)
11.1.2:定义数组
package main
import "fmt"
func main() {
var a [5]int
fmt.Println(a)
}
# 运行
PS E:\goland\demo> go run .\main.go
[0 0 0 0 0]
# 注意。默认数组里面是有元素的,默认为0
# 接下来我们给数组赋值
package main
import "fmt"
func main() {
var a [5]int
a[0] = 1 # 向数组中的索引添加元素
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
fmt.Println(a)
}
# 运行
PS E:\goland\demo> go run .\main.go
[1 2 3 4 5]
# 另一种方式
package main
import "fmt"
func main() {
var a = [...]string{"bj", "sh", "gz", "sz"} # 这里...的意思是让编译器自动识别我们的数组长度
fmt.Println(a)
}
# 运行
PS E:\goland\demo> go run .\main.go
[bj sh gz sz]
# 注意:当你的索引超出了定义的元素个数的时候会报错,数组是不会自增的!
11.2:遍历数组
11.2.1:普通遍历(for)
package main
import "fmt"
func main() {
var a = [...]string{"bj", "sh", "gz", "sz"}
for i := 0; i < len(a); i++ { # 这里的i数组的索引由0开始,当索引数小于数组长度就一直循环,每次循环都+1
fmt.Println(i, a[i]) # 打印每次循环的索引和值
}
}
# 运行
PS E:\goland\demo> go run .\main.go
0 bj
1 sh
2 gz
3 sz
11.2.2:k v 遍历数组(range)
package main
import "fmt"
func main() {
var a = [...]string{"bj", "sh", "gz", "sz"}
for index, value := range a { # 这里range是go自己实现的一个方法,它的输出是两个值,一个是索引一个是值
fmt.Printf("index:%d,value:%s\n", index, value) # 用两个参数来接收数据
}
}
# 运行
PS E:\goland\demo> go run .\main.go
index:0,value:bj
index:1,value:sh
index:2,value:gz
index:3,value:sz
# 注意,这里面的 index value不是一定要用这个的当然了 index也不是一定要打印出来的,可以用以下方法
package main
import "fmt"
func main() {
var a = [...]string{"bj", "sh", "gz", "sz"}
for _, value := range a {
fmt.Printf("value:%s\n", value)
}
}
# 注意:这里的_不是代表不接数据了,他还是会接收数据,但是可以不调用!
# 运行
PS E:\goland\demo> go run .\main.go
value:bj
value:sh
value:gz
value:sz
12:切片(Slice)
12.1:切片基础
12.1.1:切片的定义
1:切片是一个拥有相同类型的元素的可变长度的序列
2:它是基于数组类型做的一层封装
3:它非常灵活,支持自动扩容
4:切片是一个引用类型,它的内部结构包含地址,长度和容量
5:声明切片类型基础语法demo如下
var a []string
# a 是切片名称
# string 是切片类型
package main
import "fmt"
func main() {
var num []string # //定义一个字符串切片
# // 使用 num和nil比较
fmt.Println(num == nil) # // true
# // 打印num
fmt.Println(num)
var name = []int{} # // 定义一个整型切片并初始化
# // 打印name
fmt.Println(name)
# // 使用name与nil比较
fmt.Println(name == nil) # // false
var c = []bool{false, true} // 定义一个布尔值切片并初始化
# // 打印c
fmt.Println(c)
# // 使用c与nil比较
fmt.Println(c == nil) # // false
}
# 运行以上代码
PS E:\goland\demo> go run .\main.go
true
[]
[]
false
[false true]
false
# 注意:切片是引用类型,不支持直接比较,只能和 nil比较,(与数组比起来,数组是有长度无容量,而切片是有容量可扩展长度)
1:切片与切片之间无法比较,我们不能使用==操作符来判断两个切片是否含有全部相等元素
2:切片唯一合法的比较是与nil对比,一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0
3:但是我们不能说一个长度和容量都为0的切片一定是nil
12.1.2:切片的本质
1:切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针,切片的长度(len)和切片的容量(cap)
2:举个栗子:数组:a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片:sa := a[:5],对应解释图如下:
![image](https://img2022.cnblogs.com/blog/2222036/202206/2222036-20220626173357302-874364128.png)
3:再来一个栗子:sa2 := a[3:6],对应解释图如下:
![image](https://img2022.cnblogs.com/blog/2222036/202206/2222036-20220626173642111-944456546.png)
12.1.3:切片的长度和容量
1:切片拥有自己的长度和容量,我们可以通过内置函数len()求长度,使用内置的cap()函数求切片的容量
2:切片的长度是它所包含的元素的个数
3:切片的容量是从它第一个元素开始数,到其底层数组元素的末尾的个数。
4:切片a的长度和容量可以通过表达式len(a)和cap(a)获取
package main
import "fmt"
func main() {
a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
len_a := len(a)
cap_a := cap(a)
fmt.Printf("len_a: %d, cap_a: %d\n", len_a, cap_a)
}
# 运行
PS E:\goland\demo> go run .\main.go
len_a: 8, cap_a: 8
12.2:切片循环
切片的循环其实是和数组一样的
12.2.1:基本遍历
package main
import "fmt"
func main() {
var a = []string{"北京", "上海", "广州"}
for i := 0; i < len(a); i++ {
fmt.Printf("%s\n", a[i])
}
}
# 运行
PS E:\goland\demo> go run .\main.go
北京
上海
广州
12.2.2:K V遍历
package main
import "fmt"
func main() {
var a = []string{"北京", "上海", "广州"}
for i, v := range a {
fmt.Printf("%d:%s\n", i, v)
}
}
# 相同的,我们用到了range()函数
# 运行
PS E:\goland\demo> go run .\main.go
0:北京
1:上海
2:广州
# 当然也适用如下(接收索引但不使用)
package main
import "fmt"
func main() {
var a = []string{"北京", "上海", "广州"}
for _, v := range a {
fmt.Printf("%s\n", v)
}
}
# 运行
PS E:\goland\demo> go run .\main.go
北京
上海
广州
12.3:append()
1:通过append()给切片添加元素
package main
import "fmt"
func main() {
# // 定义切片
names := []string{"zhangsan", "lisi"}
# // 打印长度与容量
fmt.Printf("names len: %v, cap: %v\n", len(names), cap(names))
fmt.Println(names)
# // 通过append添加元素,这里需要引用上面的names才可以
# // 切片的扩容机制是如果原切片长度(old.len)小于1024,则最终申请容量(newcap)等于原容量的两倍(doublecap);
# // 否则,最终申请容量(newcap,初始值等于 old.cap)每次增加 newcap/4,直到大于所需容量(cap)为止,然后,判断最终申请容量(newcap)是否溢出,如果溢出,最终申请容量(newcap)等于所需容量(cap);
names = append(names, "wangwu")
# // 打印长度与容量
fmt.Printf("names len: %v, cap: %v\n", len(names), cap(names))
fmt.Println(names)
}
# 运行
PS E:\goland\demo> go run .\main.go
names len: 2, cap: 2
[zhangsan lisi]
names len: 3, cap: 4
[zhangsan lisi wangwu]
# append添加多元素
package main
import "fmt"
func main() {
names := []string{"zhangsan", "lisi"}
names = append(names, "wangwu", "zhaoliu", "tianqi")
for _, name := range names {
fmt.Println(name)
}
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan
lisi
wangwu
zhaoliu
tianqi
# 切片的合并,通过append方法
package main
import "fmt"
func main() {
names1 := []string{"zhangsan", "lisi"}
names2 := []string{"wangwu", "zhaoliu"}
names1 = append(names1, names2...) # 这里的append源码说明了要将被合并的切片切出来
fmt.Println(names1)
}
# 运行
PS E:\goland\demo> go run .\main.go
[zhangsan lisi wangwu zhaoliu]
# 切片删除元素(需要自定义)
package main
import "fmt"
func main() {
names := []string{"zhangsan", "lisi", "wangwu", "zhaoliu"}
# // 删除第二个元素,这里是从0开始计数,所以就是删掉wangwu
names = append(names[:2], names[3:]...)
fmt.Println(names)
}
# 运行
PS E:\goland\demo> go run .\main.go
[zhangsan lisi zhaoliu]
# 我们再看看不使用append方法删除(这里其实是两个demo,不然会出问题的)
package main
import "fmt"
func main() {
names := []string{"zhangsan", "lisi", "wangwu", "zhaoliu"}
# // 删除最后一个元素
names = names[:len(names)-1]
fmt.Println(names)
# // 删除最后两个元素
names = names[:len(names)-2]
fmt.Println(names)
}
# 运行
PS E:\goland\demo> go run .\main.go
[zhangsan lisi wangwu]
PS E:\goland\demo> go run .\main.go
[zhangsan lisi]
# 那如何删除前面的元素呢? # 同理,这是两个demo
package main
import "fmt"
func main() {
names := []string{"zhangsan", "lisi", "wangwu", "zhaoliu"}
# // 删除第一个元素
names = names[1:]
# // 删除前两个个元素
names = names[2:]
fmt.Println(names)
}
# 没错,就是这么简单
# 运行
PS E:\goland\demo> go run .\main.go
[lisi wangwu zhaoliu]
PS E:\goland\demo> go run .\main.go
[wangwu zhaoliu]
# 那么还有个奇葩问题,我怎么删除第一个和最后一个元素呢?
package main
import "fmt"
func main() {
names := []string{"zhangsan", "lisi", "wangwu", "zhaoliu"}
# // 删除第一个和最后一个元素
names = names[1 : len(names)-1] # 当然是结合一下上面的demo咯!
fmt.Println(names)
}
# 运行
PS E:\goland\demo> go run .\main.go
[lisi wangwu]
# 那么还有个奇葩的问题,我想保留第一个或者前几个和后几个怎么办呢?
package main
import "fmt"
func main() {
names := []string{"zhangsan", "lisi", "wangwu", "zhaoliu"}
# // 保留第一个和最后一个
fmt.Println(names[0], names[len(names)-1]) # 这里0代表前几个,-1代表后几个
}
# 运行
PS E:\goland\demo> go run .\main.go
zhangsan zhaoliu
13:Map字典
13.1:Map介绍
1:map是一种无序的,基于Key Value的数据结构,Go语言中map引用类型,必须初始化才能使用
2:Go语言中map是这么定义的:map[KeyType]ValueType
2.1:KeyType代表了键的类型
2.2:ValueType代表了对应值的类型
2.3:map类型的变量默认初始值为nil,需要使用make()函数来分配内存
3:其中cap表示map的容量,该参数不是必须的
4:获取map的容量不能使用cap(),cap返回的数组切片分配的空间大小根本不能用于map
5:可以使用len()来获取map的容量
13.2:定义map
package main
import "fmt"
func main() {
# // 定义一个数据map
info := map[string]string{ # 带入上方的demo可以看到第一个 string代表key的类型,第二个string代表value的类型
"username": "admin",
"password": "123456",
}
fmt.Println(info)
}
# 运行
PS E:\goland\demo> go run .\main.go
map[name:admin passwd:123456]
13.3:map的基本使用
13.3.1:判断某个键是否存在
# 第一种方法
package main
import "fmt"
func main() {
# // 定义一个数据map
info := map[string]string{
"username": "admin",
"password": "123456",
}
# // 判断map中是否存在某个key
v, ok := info["username"] # // 首先定义出要判断的key
if ok { # // 判断key是否为真
fmt.Println(v) # // 若为真,则打印出值
} else {
fmt.Println("not found") # 否则打印"not found"
}
}
# 运行
PS E:\goland\demo> go run .\main.go
admin
# 替换username为"USERNAME"
package main
import "fmt"
func main() {
// 定义一个数据map
info := map[string]string{
"username": "admin",
"password": "123456",
}
// 判断map中是否存在某个key
v, ok := info["USERNAME"]
if ok {
fmt.Println(v)
} else {
fmt.Println("not found")
}
}
# 再次执行
PS E:\goland\demo> go run .\main.go
not found
# 第二种方法
import "fmt"
func main() {
# // 定义一个数据map
info := map[string]string{
"username": "admin",
"password": "123456",
}
# // 判断map中是否存在某个key
# // 这里接收参数的ok是一个bool,如果值存在则是true,不存在则是false,它也是判断key的唯一标准
# // 然而第一个是查询key的值本身,第二个是布尔值,就依赖布尔值来判断这个值是否存在!
if _, ok := info["username"]; ok {
fmt.Println("username存在")
} else {
fmt.Println("username不存在")
}
}
13.3.2:delete()函数
1:使用delete()内建函数从map中删除一组键值对,delete()函数的格式:delete(map对象,key)
2:其中:
2.1:map对象表示要删除键值对的map对象
2.2:key表示要删除的键值对的key
package main
import "fmt"
func main() {
info := map[string]string{
"username": "admin",
"password": "123456",
}
# // 将password字段从map中删除
delete(info, "password")
fmt.Println(info)
}
# 运行
PS E:\goland\demo> go run .\main.go
map[username:admin]
13.4:map遍历
13.4.1:k v 遍历
package main
import "fmt"
func main() {
info := map[string]string{
"username": "admin",
"password": "123456",
}
for k, v := range info {
fmt.Printf("%v: %v\n", k, v)
}
}
# 这里和前面我们学到的都是一样的,使用range返回值并使用两个参数收即可
# 运行
PS E:\goland\demo> go run .\main.go
username: admin
password: 123456
13.4.2:只读Key
package main
import "fmt"
func main() {
info := map[string]string{
"username": "admin",
"password": "123456",
}
for k := range info {
fmt.Println(k, info[k])
}
}
# 运行
PS E:\goland\demo> go run .\main.go
password 123456
username admin
14:指针
14.1:关于指针
1:搞明白Go语言中指针需要先知道三个概念:指针地址,指针类型,指针取值
1.1:指针地址(&a)
1.2:指针取值(*&a)
1.3:指针类型(&a) ----> *int (改变数据传指针)
2:变量的本质是给存储数据的内存地址起了一个好记的别名
3:比如我们定义一个 a := 10,这个时候可以直接通过a这个变量来读取内存中保存的a这个值
4:在计算机底层a这个变量其实对应了一个内存地址
5:指针也是一个变量,但是它是一种特殊的变量,它存储的数值不是一个普通的数值,而是另一个变量的内存地址
6:Go语言中中的指针操作:&(取地址),*(根据地址取值),记住这两个符号就可以了
package main
import "fmt"
func main() {
a := 10
fmt.Println(&a) # // 获取内存地址
fmt.Printf("%d\n", &a) # // 取指针地址
fmt.Printf("%d\n", *&a) # // 取指针的值
fmt.Printf("%T\n", &a) # // 获取指针类型
}
# 运行
PS E:\goland\demo> go run .\main.go
PS E:\goland\demo> go run .\main.go
0xc0000140a8 # 内存地址
824633802920 # 对应的指针地址
10 # 指针的值
*int # 指针的类型
![image](https://img2022.cnblogs.com/blog/2222036/202206/2222036-20220628004521165-1578842888.png)
14.2:&取变量地址
14.2.1:&符取地址操作
package main
import "fmt"
func main() {
var a int = 10
var b = &a
var c = *&a
fmt.Println(a) # // 打印a的值
fmt.Println(b) # // 把a的内存地址赋值给b并打印b的值
fmt.Println(c) # // 从内存地址内取值并赋值给c并打印c的值
}
# 运行
PS E:\goland\demo> go run .\main.go
10
0xc0000140a8
10
14.2.2:b := &a图解
![image](https://img2022.cnblogs.com/blog/2222036/202206/2222036-20220628222806971-629186301.png)
14.3:new和make
14.3.1:执行报错
1:执行下面代码会引起panic,为什么
2:在Go语言对于引用类型的变量,我们在使用时不仅要声明它,还要为它分配内存空间,否则我们的值就无法存储
3:而对于值类型的声明则不需要分配内存空间,是因为它们在声明的时候已默认分配好了内存空间
4:如果要分配内存,就要用到 new和make了
5:Go语言中 new和make是两个内建函数,主要用于分配内存
package main
import "fmt"
func main() {
var info map[string]string
info["name"] = "Zhangsan"
fmt.Println(info)
}
# 这个代码执行报错如下
panic: assignment to entry in nil map
14.3.2:new和make比较
1:new和make是两个内置函数,主要用来创建并分配类型的内存
2:make和new的区别
2.1:make的作用主要用于创建slice,map,和channel等内置函数的数据结构
2.1.1:make只能针对内置数据类型申请内存
2.1.2:返回的是数据值的本身
2.2:new的作用主要为类型申请一片内存空间,并返回指向这片内存空间的指针
2.2.1:new主要是给结构体struct等非内置数据类型申请空间
2.2.2:返回一个指针的数据类型
# 通过make方法申请
package main
import "fmt"
func main() {
a := make([]int, 3, 10)
# // []int:指定切片的类型
# // 3:指定的切片的默认长度3
# // 10:默认的容量10
fmt.Println(a)
fmt.Println(len(a), cap(a))
}
# 运行
PS E:\goland\demo> go run .\main.go
[0 0 0]
3 10
# 通过new方法申请
package main
import "fmt"
func main() {
a := make([]int, 3, 10)
fmt.Println(a)
fmt.Println(len(a), cap(a))
b := new([]int)
fmt.Printf("make:%T ==> new:%T", a, b)
}
# 通过make申请的类型是值类型,但是通过new申请的就是一个指针类型,运行如上代码
PS E:\goland\demo> go run .\main.go
[0 0 0]
3 10
make:[]int ==> new,*[]int
# make初始化的值可以直接append写入数据
package main
import "fmt"
func main() {
a := make([]int, 3, 10)
a = append(a, 1) # // 可以直接使用append写入数据
fmt.Println(a)
fmt.Println(len(a), cap(a))
b := new([]int)
fmt.Println(b)
fmt.Printf("make:%T ==> new:%T", a, b)
}
# 执行如下
PS E:\goland\demo> go run .\main.go
[0 0 0 1]
4 10
make:[]int ==> new:*[]int
# 则new方法不可以,因为指针类型无法直接使用写入,所以我们需要下面的操作
package main
import "fmt"
func main() {
a := make([]int, 3, 10)
a = append(a, 1)
fmt.Println(a)
fmt.Println(len(a), cap(a))
b := new([]int)
*b = append(*b, 1) # 因为类型为指针类型,所以需要使用*b来表示这个类型为指针
fmt.Println(b)
fmt.Printf("make:%T ==> new:%T", a, b)
}
# 运行如下
PS E:\goland\demo> go run .\main.go
[0 0 0 1]
4 10
&[1]
make:[]int ==> new:*[]int
14.3.3:New函数
# 系统数据类型分配空间
package main
import "fmt"
func main() {
# // new 实例化int
age := new(int)
*age = 10
fmt.Println(*age)
# // new实例化切片
slice := new([]int)
*slice = []int{1, 2, 3}
fmt.Println(*slice)
# // new实例化map
userinfo := new(map[string]int)
*userinfo = map[string]int{"name": 1, "age": 2}
(*userinfo)["name"] = 3
fmt.Println(userinfo)
}
# 运行
PS E:\goland\demo> go run .\main.go
10
[1 2 3]
&map[age:2 name:3]
# new 实战内容(自定义类型分配空间)
package main
import "fmt"
func main() {
var s *Student
s = new(Student)
s.Name = "张三"
s.Age = 18
fmt.Println(s)
}
type Student struct {
Name string
Age int
}
# 为自定义类型分配内存
# 运行
PS E:\goland\demo> go run .\main.go
&{张三 18}
14.3.4:Make函数
1:make函数也是用于分配内存的,但是和new不同,他只用于chan,map,以及slice的内存创建
2:而且它返回的类型是这三个类型的本身,而不是它们的指针类型
3:因为三个类型都是引用类型,所以就没必要返回它们的指针了
# demo如下
package main
import "fmt"
func main() {
# // 切片长度为1,预留空间为10
a := make([]int, 1, 10)
b := make(map[string]string)
c := make(chan int, 1)
fmt.Println(a, b, c)
}
# 运行
PS E:\goland\demo> go run .\main.go
[0] map[] 0xc00004e070
4:当我们为slice分配内存的时候,应该尽量估算slice的最大长度
5:通过给make传第三个参数的方式来给slice预留好内存空间
6:这样可以避免二次分配内存带来的开销,大大提高程序性能