Golang初识到入门(上)

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

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

3:再来一个栗子:sa2 := a[3:6],对应解释图如下:

image

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

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

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:这样可以避免二次分配内存带来的开销,大大提高程序性能
posted @ 2022-06-29 18:50  Layzer  阅读(382)  评论(0编辑  收藏  举报