1.环境准备
官网下载安装: Go官网 1.19.6 大概是135M
安装之后 bin目录需要添加到系统环境变量中 用户变量 中也会添加一个GOPATH的路径,需要进行修改 因为它是用来左存储的,之后些的代码、下载的依赖包等等都是存放在里面的
要使用go首先需要配置几个环境变量:
GOROOT、GOPATH
GOROOT:go语言所在的目录,用于全局执行go相关的命令E:\Go
系统环境变量path中也需要配置
E:\Go\bin
GOPATH:工作目录,工程代码存放的位置,此目录下,一个文件夹就是一个工程用户变量中和系统变量中都可以配置 但是路径要一样 防止不生效
可以新建一个目录用户存放:这里新建的是:E:\GoWorks
该目录下又新建了src、pkg、bin这三个目录
GOPATH:E:\GoWorks
GOPROXY:需要配置代理,访问速度会更快
地址:goproxy.io/zh/ 可以去看文档
GOPROXY=proxy.golang.org,direct
go env 可以检查环境变量的配置是否正确
配置完环境变量,我们就可以开始写代码了
开启go modules,命令行执行以下命令:
go env -w GO111MODULE=on
设置国内代理,命令行执行以下命令:
go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
编辑器的话 可以直接使用IDEA,也可以安装GoLand 或使用VSCode
2.入门 HelloWord
打开GOLand,新建一个项目test,再新建文件hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello Word")
}
运行项目,输出如下:
这样就算入门了,哈哈
3 基础语法
3.1变量
3.1.1 变量定义
var 变量名 变量类型
var a int
定义的同时赋值
var a, b = 3, 5
变量定义:类型自动推断
var a, b, c, s = 1, 2, true, "def"
简短的定义 : 这种方式只能在函数内部进行定义
a, b, c, s := 1, 2, true, "der"
函数外部可以用括号省略重复的var
var aa = 1
var bb = 2
var ss = "def"
var (
aa = 1
bb = 2
ss = "def"
)
函数外部的变量并不是全局变量,它是一个包内部的变量 作用域就为它所在的包内
3.1.2 常量
通过const 定义,没有明确的类型,通过使用上下文确定其类型
const 变量名 [类型] = 值
-
显式类型定义:
const b string = "abc" -
隐式类型定义:
const b = "abc"多个同类型的可以简写:
const name1, name2 = value1, value2
3.2 if-else
if 后有大括号{}括起来的代码块,否则就忽略该代码块继续执行后续的代码。if后面不需要括号 ,else必须写在}后面
示例:读取文件
package main
import (
"fmt"
"io/ioutil"
)
// 分支语句的例子
func main() {
branchIfElse()
}
func branchIfElse() {
const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}
可以简化:
func branchIfElse2() {
const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
//这样定义的contents不能在if else外面访问
}
if总结:
if的条件里可以赋值
if的条件里赋值的变量作用域就在这个if里面
if语句可以嵌套:
如果存在第三个分支,则可以使用下面这种三个独立分支的形式:
if condition1 {
// condition1 满足 执行
} else if condition2 {
// condition1 不满足 condition2满足 执行
}else {
// condition1和condition2都不满足 执行
}
else if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else if 结构
3.3 循环-for
go语言中的循环语句只支持 for关键字,
第1种写法:for条件里不需要括号
sum := 0
//i := 0; 赋初值,i<10 循环条件 如果为真就继续执行 ;i++ 后置执行 执行后继续循环
for i := 0; i < 10; i++ {
sum += i
}
第2种写法:写在for后面的 { } 中 for的条件里可以省略初始条件、结束条件、递增表达式 但是要记得跳出
不跳出就是死循环 相当于其他语言的 while True
sum := 0
for {
sum++
if sum > 100 {
//break是跳出循环
break
}
}
第3种写法:
func testfor3() {
n := 10
for n > 0 {
n--
fmt.Println(n)
}
}
第4种写法
func testfor4() {
step := 2
//初值可以省略,但是;必须有,但是这样写step的作用域就比较大了,脱离了for循环
for ; step > 0; step-- {
fmt.Println(step)
}
}
3.4 switch
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。除非使用fallthrough
switch var1 {
case val1:
...
case val2:
...
default:
...
}
变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。
类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。
case 后面可以有多个值 ,用逗号隔开
/* 定义局部变量 */
var grade string = "B"
var score int = 90
switch score {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
//swtich后面如果没有条件表达式,则会对true进行匹配
switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" )
}
fmt.Printf("你的等级是 %s\n", grade )
3.5 数组
数组定义
var 数组变量名 [元素数量]Type
- 数组变量名:数组声明及使用时的变量名。
- 元素数量:数组的元素数量,可以是一个表达式,但最终通过编译期计算的结果必须是整型数值,元素数量不能含有到运行时才能确认大小的数值。
- Type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。
//默认数组中的值是类型的默认值
var arr [5]int
数组赋值:
-
初始化的时候赋值
var arr [3]int = [3]int{1,2,3} //如果第三个不赋值,就是默认值0 var arr [3]int = [3]int{1,2} //可以使用简短声明 arr := [3]int{1,2,3} //如果不写数据数量,而使用...,表示数组的长度是根据初始化值的个数来计算 arr := [...]int{1,2,3} -
通过索引下标赋值
var arr [3]int arr[0] = 5 arr[1] = 6 arr[2] = 7
数组是定长的,不可更改,在编译阶段就决定了
多维数组
数组属于值类型,所以多维数组的所有维度都会在创建时自动初始化零值,多维数组尤其适合管理具有父子关系或者与坐标系相关联的数据 声明多维数组:
//array_name 为数组的名字,array_type 为数组的类型,size1、size2 等等为数组每一维度的长度。
var array_name [size1][size2]...[sizen] array_type
二维数组是最简单的多维数组,二维数组本质上是由多个一维数组组成的。
// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1 和 3 的元素
array = [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化数组中指定的元素
array = [4][2]int{1: {0: 20}, 3: {1: 41}}
3.6 切片
切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。
与数组不同的是,无法通过切片类型来确定其值的长度。
每个切片值都会将数组作为其底层数据结构。
Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合。
从连续内存区域生成切片,格式如下:
slice [开始位置 : 结束位置]
语法说明如下:
- slice:表示目标切片对象;
- 开始位置:对应目标切片对象的索引;
- 结束位置:对应目标切片的结束索引。
从数组生成切片,代码如下:
var a = [3]int{1, 2, 3}
//a[1:2] 生成了一个新的切片
fmt.Println(a, a[1:2])
从数组或切片生成新的切片拥有如下特性:
- 取出的元素数量为:结束位置 - 开始位置;
- 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
- 当缺省开始位置时,表示从连续区域开头到结束位置
(a[:2]); - 当缺省结束位置时,表示从开始位置到整个连续区域末尾
(a[0:]); - 两者同时缺省时,与切片本身等效
(a[:]); - 两者同时为 0 时,等效于空切片,一般用于切片复位
(a[0:0])。
注意:超界会报运行时错误,比如数组长度为3,则结束位置最大只能为3
切片复制
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。
copy() 函数的使用格式如下:
copy( destSlice, srcSlice []T) int
其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。
示例:
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
3.7 map
map 是一种无序的键值对的集合。
map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
可以像迭代数组和切片那样迭代它。不过,map 是无序的,我们无法决定它的返回顺序,这是因为 map 是使用 hash 表来实现的。
map 是引用类型,声明方式如下:
//[keytype] 和 valuetype 之间允许有空格。
var mapname map[keytype]valuetype
其中:
- mapname 为 map 的变量名。
- keytype 为键类型。
- valuetype 是键对应的值类型。
在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 键值对的数目。 map的另外一种创建方式:
make(map[keytype]valuetype)
切记不要使用new创建map,否则会得到一个空引用的指针 map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity,格式如下:
make(map[keytype]valuetype, cap)
例如:
map2 := make(map[string]int, 100)
删除 delete
使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:
delete(map, 键)
map 为要删除的 map 实例,键为要删除的 map 中键值对的键。
scene := make(map[string]int)
// 准备map数据
scene["cat"] = 66
scene["dog"] = 4
scene["pig"] = 960
delete(scene, "dog")
for k, v := range scene {
fmt.Println(k, v)
}
Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。
3.8 range
for range 结构是Go语言特有的一种的迭代结构,for range 可以遍历 数组、切片、字符串、map 及管道(channel)
for key, val := range coll {
...
}
val始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值
字符串也可以使用for range:
str := "这是字符串"
//因为一个字符串是 Unicode 编码的字符(或称之为 rune )集合
//char 实际类型是 rune 类型
for pos, char := range str {
fmt.Println(pos,char)
}
每个 rune 字符和索引在 for range 循环中是一一对应的,它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。
通过 for range 遍历的返回值有一定的规律:
- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- channel只返回管道内的值。
3.9 指针示例
通过指针不仅可以取值,也可以修改值。
package main
func main(){
// 利用指针修改值
var num = 10
modifyFromPoint(num)
fmt.Println("未使用指针,方法外",num)
var num2 = 22
newModifyFromPoint(&num2) // 传入指针
fmt.Println("使用指针 方法外",num2)
}
func modifyFromPoint(num int) {
// 未使用指针
num = 10000
fmt.Println("未使用指针,方法内:",num)
}
func newModifyFromPoint(ptr *int) {
// 使用指针
*ptr = 1000 // 修改指针地址指向的值
fmt.Println("使用指针,方法内:",*ptr)
}
3.10 结构体
带类型的字段的集合 可以使用定义好的结构名称进行初始化一个结构体变量 结构体成员也可以称为“字段”,这些字段有以下特性:
- 字段拥有自己的类型和值;
- 字段名必须唯一;
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型。
使用关键字 type 可以将各种基本类型定义为自定义类型,基本类型包括整型、字符串、布尔等。结构体是一种复合的基本类型,通过 type 定义为自定义类型后,使结构体更便于使用。
结构体的定义格式如下:
type 类型名 struct {
字段1 字段1类型
字段2 字段2类型
…
}
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- struct{}:表示结构体类型,
type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。 - 字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
- 字段1类型、字段2类型……:表示结构体各个字段的类型。
示例:
type Point struct {
X int
Y int
}
实例化形式:
结构体本身是一种类型,可以像整型、字符串等类型一样,以 var 的方式声明结构体即可完成实例化。
var ins T
T 为结构体类型,ins 为结构体的实例。
package main
import "fmt"
type Point struct {
X int
Y int
}
func main() {
//使用.来访问结构体的成员变量,结构体成员变量的赋值方法与普通变量一致。
var p Point
p.X = 1
p.Y = 2
fmt.Printf("%v,x=%d,y=%d",p,p.X,p.Y )
}
3.11 错误处理
错误处理在go语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。 不同于Java使用的异常。go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的if else来处理错误,
在函数里面,我们可以在那个函数的返回值类型里面,后面加一个error,就代表这个函数可能会返回错误。 那么在函数实现的时候,return需要同时return两个值,要么就是如果出现错误的话,那么可以return nil和一个error。如果没有的话,那么返回原本的结果和nil。
3.12 字符串处理
在标准库strings包,里面有很多常用的字符串工具函数,比如contains判断一个字符串里面是否有包含另一个字符串,count字符串计数,index查找某个字符串的位置。join连接多个字符串 peat重复多个字符串replace替换字符用
func main(){
a :"hello"
fmt.Println(strings.Contains(a,"ll"))
/true
fmt.Println(strings.Count(a,"l"))
5479
//2
fmt.Println(strings.HasPrefix(a,"he"))
/true
fmt.Println(strings.HasSuffix(a,"llo"))
/true
fmt.Println(strings.Index(a,"ll"))
//2
fmt.Println(strings.Join([]string{"he","llo"},"-"))/he-llo
fmt.Println(strings.Repeat(a,2))
/hellohello
fmt.Println(strings.Replace(a,"e","E",-1))
/hEllo
fmt.Println(strings.Split("a-b-c","-")
//a b c
fmt.Println(strings.ToLower(a))
/hello
fmt.Println(strings.ToUpper(a))
/HELLO
fmt.Printin(len(a))
b:="你好"
//5
fmt.Println(len(b))//6
3.13 字符串格式化
在标准库的fmt包,里面有很多的字符串格式相关的方法,比如printf这个类似于C语言里面的printf函数。不同的是,在go语言里面的话,你可以很轻松地用%v来打印任意类型的变量,而不需要区分数字字符串。你也可以用 %+V打印详细结果.%#v则更详细 示例:
func main(){
s :"hello"
n:=123
p point{1,2}
fmt.Println(s,n)//hello 123
fmt.Println(p)
//f12}
fmt.Printf("s=%v\n",s)/s=hello
fmt.Printf("n=%v\n",n)//n=123
fmt.Printf("p=%v\n",p)//p={1 2)
fmt.Printf("p=%+v\n",p)//p=(x:1 y:2)
fmt.Printf("p=%#v\n",p)//p=main.point{x:1,y:2)
f:=3.141592653
fmt.Println(f)
//3.141592653
fmt.Printf("%.2f\n",f)//3.14
}
3.14 JSON处理
go语言里面的JSON操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用JSON.marshaler去序列化,变成一个 SON的字符串
序列化之后的字符串也能够用JSON.unmarshaler去反序列化到一个空的变量里面 这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用json tag等语法来去修改输出SON结果里面的字段名,
3.15 处理
time.now()获取当前时间,time.date()构造一个带时区的时间
func main(){
now :time.Now()
fmt.Println(now)//2022-03-2718:04:59.433297+0800CSTm=+0.000087933
t:=time.Date(2022,3,27,1,25,36,0,time.UTC)
t2:=time.Date(2022,3,27,2,30,36,0,time.UTC)
fmt.Println(t)
//2022-03-2701:25:36+0000UTC
fmt.Println(t.Year()t.Month(),t.Day()t.Hour(),t.Minute())//2022 March 27 1 25
fmt.Println(t.Format("2006-01-0215:04:05"))
//2022-03-2701:25:36
diff:=t2.Sub(t)
fmt.Println(diff)
//1h5m0s
fmt.Println(diff.Minutes(),diff.Seconds())//65 3900
t3,err:=time.Parse("2006-01-0215:04:05","2022-03-2701:25:36")
if err !nil
panic(err)
fmt.Println(t3 =t)
//true
fmt.Println(now.Unix())/1648738080
}
3.16 数值解析
下面我们来学习一下字符串和数字之间的转换。在go语言当中,关于字符串和数字类型之间的转换都在STR conv这个包下,这个包是string convert这两个单词的缩写 我们可以用parselnt或者parseFloat来解析一个字符串。parseint参数 我们可以用Atoi把一个十进制字符串转成数字。可以用itoA把数字转成字符串。 如果输入不合法,那么这些函数都会返回error
func main(){
f,_:strconv.ParseFloat("1.234",64)
fmt.Println(f)//1.234
n,_:strconv.ParseInt("111",10,64)
n,=strconv.ParseInt("0x1000",0,64)
fmt.Println(n)//4096
n2,:=strconv.Atoi("123")
fmt.Println(n2)/123
n2,err strconv.Atoi("AAA")
fmt.Println(n2,err)//0 strconv.Atoi:parsing "AAA":invalid syntax
}
3.17 进程信息
在go里面,我们能够用os.argv来得到程序执行的时候的指定的命令行参数。 比如我们编译的一个二进制文件,command。后面接abcd来启动,输出就是os.argv会是一个长度为5的slice,第一个成员代表二进制自身的名字。 我们可以用so.getenv来读取环境变量。 exec
import(
"fmt"
"0s"
"os/exec")
func main(){
/go run example/20-env/main.go a b c d
fmt.Println(os.Args
//[/var/folders/8p/n34xxfnx38dg8bv_x8162t_m0000gn/T/go
build3406981276/6001/exe/main a b c d]
fmt.Println(os.Getenv("PATH"))///usr/local/go/bin..
fmt.Println(os.Setenv("AA","BB"))
buf,err exec.Command("grep","127.0.0.1","/etc/hosts").Combinedoutput(
if err !nil{
panic(err)
}
fmt.Println(string(buf))//127.0.0.1
}