【Go】环境搭建与基本使用

🌈 个人主页:Zfox_

🔥 系列专栏:Go

目录

  • [一:🔥 环境搭建](#一:🔥 环境搭建)
      • [🎀 windows下的安装](#🎀 windows下的安装)
      • [🎀 linux下的安装](#🎀 linux下的安装)
      • [🎀 macos 下安装](#🎀 macos 下安装)
      • [🎀 开发工具的选择](#🎀 开发工具的选择)
  • [二:🔥 基本介绍](#二:🔥 基本介绍)
      • [🔧 主要特性](#🔧 主要特性)
      • [🎯 适用场景](#🎯 适用场景)
  • [三:🔥 变量定义](#三:🔥 变量定义)
  • [四:🔥 输出输入](#四:🔥 输出输入)
    • [🦋 输出](#🦋 输出)
    • [🦋 输入](#🦋 输入)
  • [五:🔥 基本数据类型](#五:🔥 基本数据类型)
      • [🎀 整数型](#🎀 整数型)
      • [🎀 浮点型](#🎀 浮点型)
      • [🎀 字符型](#🎀 字符型)
      • [🎀 字符串类型](#🎀 字符串类型)
      • [🎀 转义字符](#🎀 转义字符)
      • [🎀 多行字符串](#🎀 多行字符串)
      • [🎀 布尔类型](#🎀 布尔类型)
      • [🎀 零值问题](#🎀 零值问题)
  • [六:🔥 数组、切片、map](#六:🔥 数组、切片、map)
    • [🦋 数组](#🦋 数组)
      • [🎀 索引](#🎀 索引)
    • [🦋 切片](#🦋 切片)
      • [🎀 make 函数](#🎀 make 函数)
    • [🦋 map](#🦋 map)
      • [🎀 map取值](#🎀 map取值)
  • [七:🔥 判断语句](#七:🔥 判断语句)
    • [🦋 if语句](#🦋 if语句)
    • [🦋 switch 语句](#🦋 switch 语句)
  • [八:🔥 for 循环](#八:🔥 for 循环)
  • [九:🔥 函数](#九:🔥 函数)
    • [🦋 init 函数和 defer 函数](#🦋 init 函数和 defer 函数)
      • [init 函数](#init 函数)
      • [defer 函数](#defer 函数)
  • [十:🔥 共勉](#十:🔥 共勉)

一:🔥 环境搭建

  • 官网

https://go.dev/dl/

访问不了的就访问中文网就好了

  • go安装包下载

https://studygolang.com/dl

安装指定版本的安装包就好了

🎀 windows下的安装

windows 就选 windows-arm64.zip 就好了

然后需要将 go 的对应bin目录设置为环境变量,这一步是方便可以在命令行里面直接使用 go 命令

还需要将 go 的第三方 bin 目录设置为环境变量,一般是在用户目录下,这一步是为了以后使用 go install 安装的第三方可执行文件可以直接使用

🎀 linux下的安装

cpp 复制代码
cd /opt
wget https://studygolang.com/dl/golang/go1.21.3.linux-amd64.tar.gz
tar -xvf go1.21.3.linux-amd64.tar.gz

编辑环境变量

cpp 复制代码
vim /etc/profile

在文件后追加以下内容
export GOPROXY=https://goproxy.cn
export GOROOT=/opt/go
export PATH=$PATH:$GOROOT/bin
export GOPATH=/opt/go/pkg
export PATH=$PATH:$GOPATH/bin

退出并保存,刷新环境变量
source /etc/profile

🎀 macos 下安装

超详细❗️❗️MacBook Pro(M1)配置GO语言环境(图文超详细版)

🎀 开发工具的选择

  • 首选肯定是 goland,当然 vscode 也是可以的

https://www.jetbrains.com/zh-cn/go/download/other.html

  • vscode下载

https://code.visualstudio.com/

然后去下载 go 的插件就好了

二:🔥 基本介绍

Go(又称 Golang)是由 Google 开发的一种开源编程语言,具有以下特点:

  • 静态类型:编译时进行类型检查
  • 编译型语言:编译成机器码执行,性能接近 C/C++
  • 并发支持:内置 goroutine 和 channel,轻松实现高并发
  • 简洁语法:语法简单清晰,易于学习和使用

🔧 主要特性

  1. 高效并发

    • Goroutines:轻量级线程,占用资源少
    • Channels:用于 goroutine 间通信和同步
  2. 快速编译

    • 编译速度极快,大型项目也能快速构建
  3. 内存安全

    • 垃圾回收机制自动管理内存
    • 无悬空指针等内存安全问题
  4. 标准库丰富

    • 内置网络编程、加密、JSON 处理等常用库
  5. 跨平台支持

    • 支持多种操作系统和架构
    • 可直接在 windows 上跨平台编译出 linux 系统的可执行文件,目标系统完全不需要安装 go 环境

🎯 适用场景

  • Web 后端开发:API 服务、微服务(内置 http 库)
  • 云计算和 DevOps:Docker、Kubernetes 等知名项目都用 Go 编写
  • 网络编程:高性能服务器开发
  • 命令行工具:CLI 应用程序
  • 分布式系统:因其出色的并发特性而广泛应用于分布式系统

Go 语言以其简洁性和高性能著称,特别适合现代云计算和网络服务开发。

三:🔥 变量定义

标准的变量定义

cpp 复制代码
package main

import "fmt"

func main() {
  // 先定义,再赋值
  var name string
  name = "枫枫"
  fmt.Println(name)
  
  // var 变量名 类型 = "变量值"
  var userName string = "枫枫"
  fmt.Println(userName)
}

如果一个变量定义了,但是没有赋值,那么这个变量的值就是这个类型的 "零值"

变量类型省略

cpp 复制代码
// 省略类型
var name2 = "枫枫"
fmt.Println(name2)

简短声明

cpp 复制代码
// 声明并赋值 短声明符号
name3 := "枫枫"
fmt.Println(name3)

全局变量与局部变量

  1. 定义在函数体(包括main函数)内的变量都是局部变量,定义了就必须使用
  2. 定义在外部的变量就是全局变量,可以只定义不使用
cpp 复制代码
package main

import "fmt"

var userName = "枫枫知道" // 可以不使用

func main() {
  // var 变量名 类型 = "变量值"
  var name = "枫枫"
  // 在函数体内定义的变量,必须要使用
  fmt.Println(name)
}

定义多个变量

cpp 复制代码
package main

var (
  name     string = "枫枫"
  userName        = "枫枫知道"
)

func main() {
  var name1, name2, name3 string // 定义多个变量

  var a1, a2 = "枫枫", "知道" // 定义多个变量并赋值
  
  a3, a4 := "枫枫", "知道" // 简短定义多个变量并赋值
}

常量定义

  1. 定义的时候就要赋值
  2. 赋值之后就不能再修改了
cpp 复制代码
const name string = "枫枫" // 定义就要赋值
//name = "知道" // 不能再修改了
fmt.Println(name)
cpp 复制代码
const (
	male = 0
	famle
	unknow
	people
)

func main() {
	fmt.Println(male, famle, people)
}

值得注意的是,对于像 const 这样的场景来说,如果在对应的位置不指定具体的值,那么后面的值不会递增,而是会选择继续延续前面值的内容,也就是说,对于上述的场景来说,所有的值的结果都是 0

🎀 iota

那如果想定义一个增长该如何进行定义呢?在 go 中提供了一个关键字叫做 iota

它简化了常量用于增长数字的定义,给以上相同的值以准确的分类

cpp 复制代码
package main

import "fmt"

const (
	male = iota
	fmale
	unknow
	people
)

func main() {
	fmt.Println(male, people)
}

这里值得注意的是,iota代表的含义还有更多,比如它实际上是一个以0开始进行增长的一个值,所以这就意味着它本质上其实是可以进行更加复杂的表达式运算的,比如:

cpp 复制代码
package main

import "fmt"

const (
	male = iota * 10
	fmale
	unknow
	people
)

func main() {
	fmt.Println(male, fmale, unknow, people)
}

输出:
0 10 20 30

本质上来说,可以把对于iota的理解,定义理解为是一个0,1,2,3递增的这样的一串序列,并且这个序列并不是和c语言中的enum一样是直接进行递增的,而是从更加本质的角度来说是一种表达式中的x的值

上述的例子就是最好的说明

当你在把两个常量定义在一行的时候会发生什么?

cpp 复制代码
const (
    Apple, Banana = iota + 1, iota + 2
    Cherimoya, Durian
    Elderberry, Fig
)

对于 iota 来说,它是会在下一行进行递增,而不是直接进行引用 +1,因此上述的运行结果应该是

cpp 复制代码
// Apple: 1
// Banana: 2
// Cherimoya: 2
// Durian: 3
// Elderberry: 3
// Fig: 4
命名规范

核心思想:首字母大写的变量、函数。方法,属性可在包外进行访问

四:🔥 输出输入

🦋 输出

常用的输出函数

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("枫枫知道")
  fmt.Println(1)
  fmt.Println(true)
  fmt.Println("什么", "都", "可以", "输出")
}

格式化输出

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Printf("%v\n", "你好")          // 可以作为任何值的占位符输出
  fmt.Printf("%v %T\n", "枫枫", "枫枫") // 打印类型
  fmt.Printf("%d\n", 3)             // 整数
  fmt.Printf("%.2f\n", 1.25)        // 小数
  fmt.Printf("%s\n", "哈哈哈")         // 字符串
  // 区分空串
  fmt.Printf("%#v\n", "")           // 用go的语法格式输出,很适合打印空字符串
}

还有一个用的比较多的就是将格式化之后的内容赋值给一个变量

cpp 复制代码
var f = fmt.Sprintf("%.2f", 3.1452)
fmt.Println(f)

🦋 输入

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("输入您的名字:")
  var name string
  fmt.Scan(&name)  // 这里记住,要在变量的前面加个&, 后面讲指针会提到
  fmt.Println("你输入的名字是", name)
}
cpp 复制代码
package main

import "fmt"

func main() {
	fmt.Print("请输入你的年龄:")
	var age int
	n, err := fmt.Scan(&age)
	// age 为什么是0呢?
	fmt.Println(n, err, age)
}

五:🔥 基本数据类型

go 语言的基本数据类型有

  1. 整数形
  2. 浮点型
  3. 复数
  4. 布尔
  5. 字符串

🎀 整数型

go 语言的整数类型,具体细分有很多

cpp 复制代码
var n1 uint8 = 2 
var n2 uint16 = 2 
var n3 uint32 = 2 
var n4 uint64 = 2 
var n5 uint = 2 
var n6 int8 = 2 
var n7 int16 = 2 
var n8 int32 = 2 
var n9 int64 = 2 
var n10 int = 2 

大家只需要记住以下几点

  1. 默认的数字定义类型是int类型
  2. 带个u就是无符号,只能存正整数
  3. 后面的数字就是2进制的位数
  4. uint8 还有一个别名 byte, 一个字节=8个bit位
  5. int 类型的大小取决于所使用的平台

我是 64 位操作系统,那么我会试一下 int 是不是就是 int64 的最大上限

2 的 63 次方 -1 = 9223372036854775807

cpp 复制代码
fmt.Printf("%.0f\n", math.Pow(2, 63))
var n1 int = 9223372036854775807
fmt.Println(n1)
var n2 int = 9223372036854775808 // 看它报不报错
fmt.Println(n2)

🎀 浮点型

Go 语言支持两种浮点型数:float32 和 float64

  1. float32 的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32
  2. float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64

如果没有显式声明,则默认是float64

🎀 字符型

注意哦,是字符,不是字符串

比较重要的两个类型是 byte(单字节字符)和 rune(多字节字符)

cpp 复制代码
package main

import "fmt"

func main() {
  var c1 = 'a'
  var c2 = 97
  fmt.Println(c1) // 直接打印都是数字
  fmt.Println(c2)

  fmt.Printf("%c %c\n", c1, c2) // 以字符的格式打印

  var r1 rune = '中'
  fmt.Printf("%c\n", r1)
}
  1. 在 Go 中,字符的本质是一个整数,直接输出时,是该字符对应的 UTF-8 编码的码值
  2. 可以直接给某个变量赋一个数字,然后按格式化输出时 %c ,会输出该数字对应的 unicode 字符
  3. 字符类型是可以进行运算的,相当于一个整数,因为它都对应有 Unicode 码。

🎀 字符串类型

和字符不一样的是,字符的赋值是单引号,字符串的赋值是双引号

cpp 复制代码
var s string = "枫枫知道"
fmt.Println(s)

🎀 转义字符

一些常用的转义字符

cpp 复制代码
fmt.Println("枫枫\t知道")              // 制表符
fmt.Println("枫枫\n知道")              // 回车
fmt.Println("\"枫枫\"知道")            // 双引号
fmt.Println("枫枫\r知道")              // 回到行首
fmt.Println("C:\\pprof\\main.exe") // 反斜杠

🎀 多行字符串

cpp 复制代码
package main

import "fmt"

func main() {
  var s = `今天
天气
真好
`
  fmt.Println(s)
}

在 `` 这个里面,再出现转义字符就会原样输出了

🎀 布尔类型

布尔型数据只有 true(真)和 false(假)两个值

  1. 布尔类型变量的默认值为 false
  2. Go 语言中不允许将整型强制转换为布尔型
  3. 布尔型无法参与数值运算,也无法与其他类型进行转换

🎀 零值问题

如果我们给一个基本数据类型只声明不赋值

那么这个变量的值就是对应类型的零值,例如 int 就是 0,bool 就是 false,字符串就是 ""

cpp 复制代码
package main

import "fmt"

func main() {
  var a1 int
  var a2 float32
  var a3 string
  var a4 bool

  fmt.Printf("%#v\n", a1)
  fmt.Printf("%#v\n", a2)
  fmt.Printf("%#v\n", a3)
  fmt.Printf("%#v\n", a4)
}

六:🔥 数组、切片、map

🦋 数组

数组(Array)是一种非常常见的数据类型,几乎所有的计算机编程语言中都会用到它

  1. 数组里的元素必须全部为同一类型,要嘛全部是字符串,要嘛全部是整数
  2. 声明数组时,必须指定其长度或者大小
cpp 复制代码
package main

import "fmt" 

func main() {
  var array [3]int = [3]int{1, 2, 3}
  fmt.Println(array)
  var array1 = [3]int{1, 2, 3}
  fmt.Println(array1)
  var array2 = [...]int{1, 2, 3}
  fmt.Println(array2)
}

如果要修改某个值,只能根据索引去找然后替换

cpp 复制代码
var array1 = [3]int{1, 2, 3}
array1[0] = 10 // 根据索引找到对应的元素位置,然后替换
fmt.Println(array1)

🎀 索引

cpp 复制代码
package main

import "fmt"

func main() {
  // 以定义一个字符串数组 a b c d 为例
  var sList = []string{"a", "b", "c", "d"}
  // 正向索引就从0开始,从左往右
  /*
    a     b     c     d
    0     1     2     3
  */
  // 取值就通过索引去取
  fmt.Println(sList[0]) // 拿到a这个元素
  // 当然,有的语言支持负向索引,go不支持
  /*
      a      b     c     d
      -4    -3    -2    -1
  */
  // 如果在go语言里面,我想拿到倒数第二个元素,怎么办?
  fmt.Println(sList[len(sList)-2]) // 拿到a这个元素
}

🦋 切片

很明显啊,go 里面的数组,长度被限制死了,所以不经常用

所以 go 出了一个数组 plus,叫做 slice(切片)

切片(Slice)相较于数组更灵活,因为在声明切片后其长度是可变的

cpp 复制代码
package main

import "fmt"

func main() {
  // 定义一个字符串切片
  var list []string

  list = append(list, "枫枫")
  list = append(list, "知道")
  fmt.Println(list)
  fmt.Println(len(list)) // 切片长度
  // 修改第二个元素
  list[1] = "不知道"
  fmt.Println(list)
}

🎀 make 函数

除了基本数据类型,其他数据类型如果只定义不赋值,那么实际的值就是 nil

cpp 复制代码
// 定义一个字符串切片
var list []string
fmt.Println(list == nil) // true

那么我们可以通过make函数创建指定长度,指定容量的切片了

cpp 复制代码
make([]type, length, capacity)
cpp 复制代码
package main

import "fmt"

func main() {
  // 定义一个字符串切片
  var list = make([]string, 0)
  fmt.Println(list, len(list), cap(list))
  fmt.Println(list == nil) // false

  list1 := make([]int, 2, 2)
  fmt.Println(list1, len(list1), cap(list1))
}

为什么叫切片

因为切片是数组切出来的

cpp 复制代码
package main

import "fmt"

func main() {
  var list = [...]string{"a", "b", "c"}
  slices := list[:] // 左一刀,右一刀  变成了切片
  fmt.Println(slices)
  fmt.Println(list[1:2]) // b
}

切片排序

cpp 复制代码
var list = []int{4, 5, 3, 2, 7}
fmt.Println("排序前:", list)
sort.Ints(list)
fmt.Println("升序:", list)
sort.Sort(sort.Reverse(sort.IntSlice(list)))
fmt.Println("降序:", list)

🦋 map

Go 语言中的 map (映射、字典)是一种内置的数据结构,它是一个无序的 key-value 对的集合

map 的 key 必须是基本数据类型,value可以是任意类型

注意,map使用之前一定要初始化

cpp 复制代码
package main

import "fmt"

func main() {
  // 声明
  var m1 map[string]string
  // 初始化1
  m1 = make(map[string]string)
  // 初始化2
  m1 = map[string]string{}
  // 设置值
  m1["name"] = "枫枫"
  fmt.Println(m1)
  // 取值
  fmt.Println(m1["name"])
  // 删除值
  delete(m1, "name")
  fmt.Println(m1)
}

也可以声明并赋值

cpp 复制代码
// 声明并赋值
var m1 = map[string]string{}
var m2 = make(map[string]string)

🎀 map取值

如果只有一个参数接,那这个参数就是值,如果没有,这个值就是类型的零值

如果两个参数接,那第二个参数就是布尔值,表示是否有这个元素

cpp 复制代码
package main

import "fmt"

func main() {
  // 声明并赋值
  var m1 = map[string]int{
    "age": 21,
  }
  age1 := m1["age1"] // 取一个不存在的
  fmt.Println(age1)
  age2, ok := m1["age1"]
  fmt.Println(age2, ok)
}

七:🔥 判断语句

🦋 if语句

以年龄为例,输入的年龄在某一个区间,就输出对应的提示信息

cpp 复制代码
<=0     未出生
1-18    未成年
18-35   青年
>=35    中年

很明显,这是一个多选一的情况

我们有很多中方式来实现

中断式

package main

cpp 复制代码
import "fmt"

func main() {
  fmt.Println("请输入你的年龄:")
  var age int
  fmt.Scan(&age)

  if age <= 0 {
    fmt.Println("未出生")
    return
  }
  if age <= 18 {
    fmt.Println("未成年")
    return
  }
  if age <= 35 {
    fmt.Println("青年")
    return
  }
  fmt.Println("中年")

}

它也有个好听的名字,叫卫语句

嵌套式

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入你的年龄:")
  var age int
  fmt.Scan(&age)

  if age <= 18 {
    if age <= 0 {
      fmt.Println("未出生")
    } else {
      fmt.Println("未成年")
    }
  } else {
    if age <= 35 {
      fmt.Println("青年")
    } else {
      fmt.Println("中年")
    }
  }
}

多条件式

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入你的年龄:")
  var age int
  fmt.Scan(&age)

  if age <= 0 {
    fmt.Println("未出生")
  }
  if age > 0 && age <= 18 {
    fmt.Println("未成年")
  }
  if age > 18 && age <= 35 {
    fmt.Println("青年")
  }
  if age > 35 {
    fmt.Println("中年")
  }
}

🦋 switch 语句

还是上面那个案例,如果是用 switch 就很直观了

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入你的年龄:")
  var age int
  fmt.Scan(&age)

  switch {
  case age <= 0:
    fmt.Println("未出生")
  case age <= 18:
    fmt.Println("未成年")
  case age <= 35:
    fmt.Println("青年")
  default:
    fmt.Println("中年")
  }
}

除了这样的写法,还有枚举所有的可能值

例如

cpp 复制代码
1   星期一
2   星期二
3   星期三
cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入星期数字:")
  var week int
  fmt.Scan(&week)

  switch week {
  case 1:
    fmt.Println("周一")
  case 2:
    fmt.Println("周二")
  case 3:
    fmt.Println("周三")
  case 4:
    fmt.Println("周四")
  case 5:
    fmt.Println("周五")
  case 6, 7:
    fmt.Println("周末")
  default:
    fmt.Println("错误")
  }
}

可以理解为 case 的值就是 switch 的枚举结果

一般来说,go 的 switch 的多选一,满足其中一个结果之后,就结束 switch 了

加上 fallthrough 则可以继续执行

例如

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入你的年龄:")
  var age int
  fmt.Scan(&age)

  switch {
  case age <= 0:
    fmt.Println("未出生")
  case age <= 18:
    fmt.Println("未成年")
  case age <= 35:
    fmt.Println("青年")
  default:
    fmt.Println("中年")
  }
}

我输入一个12,我希望它能输出满足的所有条件,例如我希望它输出,未成年,青年

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入你的年龄:")
  var age int
  fmt.Scan(&age)

  switch {
  case age <= 0:
    fmt.Println("未出生")
    fallthrough
  case age <= 18:
    fmt.Println("未成年")
    fallthrough
  case age <= 35:
    fmt.Println("青年")
  default:
    fmt.Println("中年")
  }
}

八:🔥 for 循环

任何编程语言,都会有for循环,它的一般写法是

cpp 复制代码
for 初始化;条件;操作{
}

例如求1+2+...+100的和

cpp 复制代码
package main

import "fmt"

func main() {

  var sum = 0
  for i := 0; i <= 100; i++ {
    sum += i
  }
  fmt.Println(sum)
}

🦋 for循环的五种变体

传统for循环

如上

死循环

每隔1秒打印当前的时间

cpp 复制代码
package main

import (
  "fmt"
  "time"
)

func main() {

  for {
    time.Sleep(1 * time.Second)
    fmt.Println(time.Now().Format("2006-01-02 15:04:05")) // 年月日时分秒的固定格式
  }
}

while 模式

由于 golang 没有 while 循环,如果需要,则是由 for 循环稍微变化得来

cpp 复制代码
package main

import "fmt"

func main() {

  i := 0
  sum := 0
  for i <= 100 {
    sum += i
    i++
  }
  fmt.Println(sum)
}

do-while 模式

do-while 模式就是先执行一次循环体,再判断

cpp 复制代码
package main

import "fmt"

func main() {

  i := 0
  sum := 0
  for {
    sum += i
    i++
    if i == 101 {
      break
    }
  }
  fmt.Println(sum)
}

遍历切片,map

遍历切片

第一个参数是索引,第二个参数是

cpp 复制代码
package main

import "fmt"

func main() {

  s := []string{"枫枫", "知道"}
  for index, s2 := range s {
    fmt.Println(index, s2)
  }
}

遍历 map

第一个参数就是 key,第二个就是 value

cpp 复制代码
package main

import "fmt"

func main() {

  s := map[string]int{
    "age":   24,
    "price": 1000,
  }
  for key, val := range s {
    fmt.Println(key, val)
  }
}

break,continue

break 用于跳出当前循环

continue 用于跳过本轮循环

例如打印九九乘法表

cpp 复制代码
package main

import "fmt"

func main() {

  for i := 1; i <= 9; i++ {
    for j := 1; j <= i; j++ {
      fmt.Printf("%d * %d = %d\t", i, j, i*j)
    }
    fmt.Println()
  }
}

除了这样写,还能这样写

cpp 复制代码
package main

import "fmt"

func main() {

  for i := 1; i <= 9; i++ {
    for j := 1; j <= 9; j++ {
      if j > i {
        // 去掉 列比行大的数据
        continue
      }
      fmt.Printf("%d * %d = %d\t", i, j, i*j)
    }
    fmt.Println()
  }
}

九:🔥 函数

函数定义

cpp 复制代码
package main

import "fmt"

// 使用func关键字定义一个函数
func sayHello() {
  fmt.Println("hello")
}

func main() {
  // 函数()调用函数
  sayHello()
}

函数参数

cpp 复制代码
package main

import "fmt"

func add(n1 int, n2 int) {
  fmt.Println(n1, n2)
}

// 参数类型一样,可以合并在一起
func add1(n1, n2 int) {
  fmt.Println(n1, n2)
}

// 多个参数
func add2(numList ...int) {
  fmt.Println(numList)
}

func main() {
  add(1, 2)
  add1(1, 2)
  add2(1, 2)
  add2(1, 2, 3, 4)
}

函数返回值

cpp 复制代码
package main

import "errors"

// 无返回值
func fun1() {
  return // 也可以不写
}

// 单返回值
func fun2() int {
  return 1
}

// 多返回值
func fun3() (int, error) {
  return 0, errors.New("错误")
}

// 命名返回值
func fun4() (res string) {
  return // 相当于先定义再赋值
  //return "abc"
}

func main() {

}

匿名函数

cpp 复制代码
package main

import "fmt"

func main() {
  var add = func(a, b int) int {
    return a + b
  }
  fmt.Println(add(1, 2))
}

🎀 高阶函数

根据用户输入的不同,执行不同的操作

cpp 复制代码
package main

import "fmt"

func main() {
  fmt.Println("请输入要执行的操作:")
  fmt.Println(`1:登录
2:个人中心
3:注销`)
  var num int
  fmt.Scan(&num)
  var funcMap = map[int]func(){
    1: func() {
      fmt.Println("登录")
    },
    2: func() {
      fmt.Println("个人中心")
    },
    3: func() {
      fmt.Println("注销")
    },
  }
  funcMap[num]()
}

提取出来

cpp 复制代码
package main

import "fmt"

func login() {
  fmt.Println("登录")
}

func userCenter() {
  fmt.Println("个人中心")
}

func logout() {
  fmt.Println("注销")
}

func main() {
  fmt.Println("请输入要执行的操作:")
  fmt.Println(`1:登录
2:个人中心
3:注销`)
  var num int
  fmt.Scan(&num)
  var funcMap = map[int]func(){
    1: login,
    2: userCenter,
    3: logout,
  }
  funcMap[num]()
}

🎀 闭包

设计一个函数,先传一个参数表示延时,后面再次传参数就是将参数求和

例如

cpp 复制代码
fun(2)(1,2,3) // 延时2秒求1+2+3

package main

import (
  "fmt"
  "time"
)

func awaitAdd(t int) func(...int) int {
  time.Sleep(time.Duration(t) * time.Second)
  return func(numList ...int) int {
    var sum int
    for _, i2 := range numList {
      sum += i2
    }
    return sum
  }
}

func main() {
  fmt.Println(awaitAdd(2)(1, 2, 3))
}
值传递和引用传递

稍微了解过编程的都应该知道,计算机上显示的所有的数据都是在内存里面的

也就是说,我们定义的一个变量,它也在内存里面有一块地

正常情况下来说,函数的参数是将之前那块地复制了一份出来

如何证明呢

cpp 复制代码
package main

import "fmt"

func add(num int) {
  fmt.Println(&num) // 可以看到,这个n的内存地址和外面num的内存地址是明显不一样的
  num = 2           // 这里的修改不会影响外面的num
}

func main() {
  num := 20
  fmt.Println(&num)
  add(num)
  fmt.Println(num) // 20
}

也就是说,在函数里面不管怎么修改这个参数,都不会影响原来的那个值

但是,如果我需要在函数体内修改变量的值呢?

这就需要用到引用传递了

我们直接将变量的内存地址传递进去

cpp 复制代码
package main

import "fmt"

func add(num *int) {
  fmt.Println(num) // 内存值是一样的
  *num = 2         // 这里的修改会影响外面的num
}

func main() {
  num := 20
  fmt.Println(&num)
  add(&num)
  fmt.Println(num) // 成功修改 2
}

指针

上面那个案例搞清楚之后,指针也就不难了

我们只需要知道

&是取地址,*是解引用,去这个地址指向的值

🦋 init 函数和 defer 函数

init 函数

init() 函数是一个特殊的函数,存在以下特性:

  1. 不能被其他函数调用,而是在 main 函数执行之前,自动被调用
  2. init 函数不能作为参数传入
  3. 不能有传入参数和返回值
  4. 一个 go 文件可以有多个 init 函数,谁在前面谁就先执行
cpp 复制代码
package main

import "fmt"

func init() {
  fmt.Println("init1")
}
func init() {
  fmt.Println("init2")
}
func init() {
  fmt.Println("init3")
}

func main() {
  fmt.Println("main")
}

执行顺序

cpp 复制代码
init1
init2
init3
main

虽然一个 package 里面可以写任意多个 init 函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个 package 中每个文件只写一个 init 函数。

go 程序会自动调用 init() 和 main(),所以你不需要在任何地方调用这两个函数。每个 package 中的 init 函数都是可选的,但 package main 就必须包含一个 main 函数。

程序的初始化和执行都起始于 main 包。

如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。

当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。

等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数

defer 函数

  1. 关键字 defer 用于注册延迟调用
  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理
  3. 多个 defer 语句,按先进后出的方式执行,谁离 return 近谁先执行
  4. defer 语句中的变量,在 defer 声明时就决定了
cpp 复制代码
package main

import "fmt"

func Func() {
  defer fmt.Println("defer2")
  fmt.Println("func")
  defer fmt.Println("defer1")
}

func main() {
  defer fmt.Println("defer4")
  Func()
  defer fmt.Println("defer3")
}

执行顺序

cpp 复制代码
func
defer1
defer2
defer3
defer4

十:🔥 共勉

😋 以上就是我对 【Go】环境搭建与基本使用 的理解, 觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~ 😉

相关推荐
WongKyunban42 分钟前
使用Valgrind检测内存问题(C语言)
c语言·开发语言
Bin二叉42 分钟前
南京大学cpp复习——第二部分(继承)
开发语言·c++·笔记·学习
民乐团扒谱机43 分钟前
【微实验】携程评论C#爬取实战:突破JavaScript动态加载与反爬虫机制
大数据·开发语言·javascript·爬虫·c#
raoxiaoya44 分钟前
golang本地开发多版本切换,golang多版本管理,vscode切换多版本golang
开发语言·vscode·golang
wjs20241 小时前
R Excel 文件:高效数据处理的利器
开发语言
蒋士峰DBA修行之路1 小时前
红帽练习环境介绍
linux·开发语言·bash
wjs20241 小时前
PHP FTP 完全指南
开发语言
z***94841 小时前
Java进阶07 嵌套类
java·开发语言·python
w***H6501 小时前
Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
java·spring boot·后端