Go语言简介

Go语言(Golang)是 Google 开发的一种静态强类型、编译型语言。go语言可以简单的写成 Golanguage。

学习方向

  • 区块链研发工程师
  • Go服务端/游戏软件工程师
  • Golang分布式/云计算软件工程师

执行流程

编译后执行

如果是对源码编译后再执行,go的执行流程:

image.png

通过编译器将其编译成机器可以识别的二进制码文件。

直接执行

如果对源码直接执行,go的执行流程:

image.png

开发注意事项

  1. Go源文件以”go”为扩展名
  2. Go应用程序的执行入口是main()方法
  3. Go语言严格区分大小写
  4. Go方法由一条条语句构成,每个语句后面不需要分号,体现出go的简洁性
  5. Go编译器是一行行进行编译的,不能把多条语句合并成同一个,不然会报错
  6. go语言定义的变量或者 import 包如果没有使用,代码不能通过

转义字符

\t   一个制表位,实现对齐的功能
\n   换行符
\\   一个\
\"   一个"
\r   一个回车

注释

用于注解说明程序的文字就是注释,提高代码的阅读性;注释是一个程序员必须具有的良好编程习惯。

  • 行注释 //
  • 块注释 /**/

标识符与关键字

标识符指编程中使用的变量名、函数名、类型名等。关键字和保留字都不建议用作变量名。

关键字

指编程语言中预先定义好的、具有特殊含义的标识符。Go 有 25 个关键字:

    break        default      func         interface    select
    case         defer        go           map          struct
    chan         else         goto         package      switch
    const        fallthrough  if           range        type
    continue     for          import       return       var

保留字(预定义标识符)

Go 有 37 个保留字(预定义标识符),它们在语言中已被定义,但允许作为变量名重新使用(不建议):

    append     bool         byte        cap          close       complex     complex64     complex128
    copy       false        float32     float64      imag        int         int8          int16
    int32      int64        iota        len          make        new         nil           panic
    print      println      real        recover      string      true        uint          uint8
    uint16     uint32       uint64      uintptr

变量与常量

变量

概念

变量相当于内存中的一个数据存储空间的表示。基本使用步骤:

  • 声明变量
  • 给变量赋值
  • 使用变量

使用方式

  1. 指定变量类型,声明后不赋值,使用默认值
var i int
fmt.Println("i=", i)
  1. 根据值自行判断变量类型
var i = 10
fmt.Println("i=", i)
  1. 省略 var,注意::= 左侧的变量不应该是已经声明过的,否则导致编译错误
i := 10
fmt.Println("i=", i)
  1. 多变量声明
//有时候需要声明多个变量
var n1, n2, n3 int
fmt.Println("n1=", n1, "n2=", n2, "n3=", n3)
 
//多变量不同类型
var n1, name, n3 = 100, "tom", 888
//或者
n1, name, n3 = 100, "tom", 888
  1. 全局声明
var (
    n1    = 300
    n2    = 200
    name2 = "mary"
)

常量

相比于变量,常量是指恒定不变的量,多用于定义程序运行期间不会改变的那些值。

const pi = 3.1415
const e = 2.7182

多个常量也可以一起声明:

const (
    pi = 3.1415
    e  = 2.7182
)

const 同时声明多个常量时,如果省略了值则表示和上一行相同。Go 还提供了 iota 常量生成器,用于实现类似枚举的功能:

const (
    a = iota  // 0
    b         // 1
    c         // 2
)

数据类型

基本数据类型

  • 数值型
    • 整数类型(int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64)
    • 浮点类型(float32, float64)
    • 复数类型(complex64 和 complex128)
  • 字符型(没有专门的字符型,使用 byte 来保存单个字母字符)
  • 布尔型(bool)
  • 字符串(string)[官方将 string 归属到基本数据类型]

整数类型

用于存放整数值的,比如 0, -1, 1114444 等。

类型描述
uint8无符号 8位整数(0到255)
uint16无符号 16位整数(0到65535)
uint32无符号 32位整型 (0 到 4294967295)
uint64无符号 64位整型 (0 到 18446744073709551615)
int8有符号 8位整数(-128到127)
int16有符号 16位整型 (-32768 到 32767)
int32有符号 32位整型 (-2147483648 到 2147483647)
int64有符号 64位整型 (-9223372036854775808 到 9223372036854775807)

特殊整型

类型描述
uint32位操作系统上就是uint32;64位为uint64
int32位操作系统上就是int32;64位为int64
uintptr无符号整型,用于存放指针

数字字面量语法(Number literals syntax)

Go 1.13 版本之后引入了数字字面量语法,这样便于开发者以二进制、八进制或十六进制浮点数的格式定义数字,例如:

v := 0b00101101,代表二进制的 101101,相当于十进制的 45。 v := 0o377,代表八进制的 377,相当于十进制的 255。 v := 0x1p-2,代表十六进制的 1 除以 2²,也就是 0.25。

而且还允许我们用 _ 来分隔数字,比如说:v := 123_456 表示 v 的值等于 123456。

package main
 
import "fmt"
 
func main() {
	// 十进制
	var a int = 10
	fmt.Printf("%d \n", a) // 10
	fmt.Printf("%b \n", a) // 1010  占位符%b表示二进制
 
	// 八进制  以0开头
	var b int = 077
	fmt.Printf("%o \n", b) // 77
 
	// 十六进制  以0x开头
	var c int = 0xff
	fmt.Printf("%x \n", c) // ff
	fmt.Printf("%X \n", c) // FF
}

浮点型

Go语言支持两种浮点型数:float32 和 float64。这两种浮点型数据格式遵循 IEEE 754 标准:

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

打印浮点数时,可以使用 fmt 包配合动词 %f,代码如下:

package main
 
import (
	"fmt"
	"math"
)
 
func main() {
	fmt.Printf("%f\n", math.Pi)
	fmt.Printf("%.2f\n", math.Pi)
}

字符型

组成每个字符串的元素叫做”字符”,可以通过遍历或者单个获取字符串元素获得字符。字符用单引号(‘)包裹起来,如:

var a = '中'
var b = 'x'

Go 语言的字符有以下两种:

  1. uint8 类型,或者叫 byte 型,代表一个 ASCII码 字符。
  2. rune 类型,代表一个 UTF-8 字符。

当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型实际是一个 int32。

Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。

// 遍历字符串
func traversalString() {
	s := "hello沙河"
	for i := 0; i < len(s); i++ { //byte
		fmt.Printf("%v(%c) ", s[i], s[i])
	}
	fmt.Println()
	for _, r := range s { //rune
		fmt.Printf("%v(%c) ", r, r)
	}
	fmt.Println()
}

输出:

104(h) 101(e) 108(l) 108(l) 111(o) 230(æ) 178(²) 153() 230(æ) 178(²) 179(³)
104(h) 101(e) 108(l) 108(l) 111(o) 27801(沙) 27827(河)

因为 UTF8 编码下一个中文汉字由 3~4 个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。

字符串底层是一个 byte 数组,所以可以和 []byte 类型相互转换。字符串是不能修改的。字符串是由 byte 字节组成,所以字符串的长度是 byte 字节的长度。rune 类型用来表示 utf8 字符,一个 rune 字符由一个或多个 byte 组成。

布尔值

Go语言中以 bool 类型进行布尔型数据,布尔型数据只有 true(真) 和 false(假) 两个值。

注意:

  • 布尔类型默认为 false
  • Go语言不允许将整型数据强制转换为布尔型
  • 布尔型无法直接参加数值运算,也无法与其他数据进行转换

字符串

Go语言以原生数组出现,使用的字符串就像使用其他原生数据类型一样。Go语言的字符串内部实现使用 UTF-8 编码。字符串的值为 "" 内的内容,可以在源码中直接添加非 ASCII 字符。

s1 := "hello"
s2 := "你好"

多行字符串

s1 := `第一行
第二行
第三行
`
fmt.Println(s1)

字符串的常见操作

方法介绍
len(str)求长度
+ 或 fmt.Sprintf拼接字符串
strings.Split分割
strings.Contains判断是否包含
strings.HasPrefix, strings.HasSuffix前缀/后缀判断
strings.Index(), strings.LastIndex()子串出现的位置
strings.Join(a[]string, sep string)join操作

字符串的修改

要修改字符串,需要先将其转换成 []rune 或 []byte,完成后再转换为 string。无论哪种转换,都会重新分配内存,并复制字节数组。

func changeString() {
	s1 := "big"
	// 强制类型转换
	byteS1 := []byte(s1)
	byteS1[0] = 'p'
	fmt.Println(string(byteS1))
 
	s2 := "白萝卜"
	runeS2 := []rune(s2)
	runeS2[0] = '红'
	fmt.Println(string(runeS2))
}

复杂数据类型

  • 指针
  • 数组
  • 结构体
  • 管道(channel)
  • 函数
  • 切片(slice)
  • 接口(interface)
  • map

类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。

强制类型转换的基本语法如下:

T(表达式)

其中,T 表示要转换的类型。表达式包括变量、复杂算子和函数返回值等。

比如计算直角三角形的斜边长时使用 math 包的 Sqrt() 函数,该函数接收的是 float64 类型的参数,而变量 a 和 b 都是 int 类型的,这个时候就需要将 a 和 b 强制类型转换为 float64 类型。

func sqrtDemo() {
	var a, b = 3, 4
	var c int
	// math.Sqrt()接收的参数是float64类型,需要强制转换
	c = int(math.Sqrt(float64(a*a + b*b)))
	fmt.Println(c)
}

运算符

Go 语言内置的运算符主要有:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符。

算术运算符

运算符描述
+相加
-相减
*相乘
/相除
%求余

注意:++(自增)和 --(自减)在 Go 语言中是单独的语句,并不是运算符,且只能写在变量后面。

关系运算符

运算符描述
==检查两个值是否相等
!=检查两个值是否不相等
>检查左边值是否大于右边值
<检查左边值是否小于右边值
>=检查左边值是否大于等于右边值
⇐检查左边值是否小于等于右边值

逻辑运算符

运算符描述
&&逻辑 AND,两边的操作数都是 True,则为 True,否则为 False
||逻辑 OR,两边的操作数有一个 True,则为 True,否则为 False
!逻辑 NOT,如果条件为 True,则为 False,否则为 True

位运算符

运算符描述
&按位与
|按位或
^按位异或
&^位清除(AND NOT)
<<左移
>>右移

赋值运算符

运算符描述
=简单赋值
+=相加后再赋值
-=相减后再赋值
*=相乘后再赋值
/=相除后再赋值
%=求余后再赋值
<⇐左移后赋值
>>=右移后赋值
&=按位与后赋值
|=按位或后赋值
^=按位异或后赋值

流程控制

条件语句

if 语句

func ifDemo() {
	score := 65
	if score >= 90 {
		fmt.Println("A")
	} else if score > 75 {
		fmt.Println("B")
	} else {
		fmt.Println("C")
	}
}

if 还支持在条件前声明变量(作用域仅限该 if 语句块):

if age := 18; age >= 18 {
	fmt.Println("成年")
}

switch 语句

switch 用于条件分支判断。Go 中每个 case 默认带有 break,不会自动往下执行;如果需要继续执行下一个 case,需使用 fallthrough。

func switchDemo(score int) {
	switch score / 10 {
	case 10:
		fmt.Println("满分")
	case 9:
		fmt.Println("A")
	case 8:
		fmt.Println("B")
	case 7:
		fmt.Println("C")
	default:
		fmt.Println("D")
	}
}

一个 case 可以同时判断多个值,使用逗号分隔:

switch n := 2; n {
case 1, 3, 5, 7, 9:
	fmt.Println("奇数")
case 2, 4, 6, 8:
	fmt.Println("偶数")
}

循环结构

Go 语言中只有 for 一种循环语句,但可以满足 while、do-while 等场景的需求。

for 基本形式

for 初始语句; 条件表达式; 结束语句 {
	// 循环体
}
 
// 示例
for i := 0; i < 10; i++ {
	fmt.Println(i)
}

for…range

for range 用于遍历切片、数组、字符串、map、通道等。返回索引和值(或键值对)。

s := "hello沙河"
for i, r := range s {
	fmt.Printf("%d:%c ", i, r)
}

模拟 while 循环

省略初始语句和结束语句:

n := 0
for n < 5 {
	fmt.Println(n)
	n++
}

无限循环

省略所有部分即为无限循环:

for {
	// 循环体
}

break 和 continue

  • break:跳出当前循环,结束整个循环。
  • continue:跳过本次循环,直接进入下一次循环。
for i := 0; i < 10; i++ {
	if i == 3 {
		continue // 跳过 3
	}
	if i == 7 {
		break // 遇到 7 时结束循环
	}
	fmt.Println(i)
}

配合标签,break/continue 可以跳出/跳到多重嵌套循环:

label:
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if i+j > 3 {
				break label // 直接跳出外层循环
			}
		}
	}

使用goto快速跳转

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。 例如双层嵌套的for循环要退出时:

func gotoDemo1() {
	var breakFlag bool
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 设置退出标签
				breakFlag = true
				break
			}
			fmt.Printf("%v-%v\n", i, j)
		}
		// 外层for循环判断
		if breakFlag {
			break
		}
	}
}

使用goto语句能简化代码:

func gotoDemo2() {
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 设置退出标签
				goto breakTag
			}
			fmt.Printf("%v-%v\n", i, j)
		}
	}
	return
	// 标签
breakTag:
	fmt.Println("结束for循环")
}

数组

数组是同一种数据类型元素的集合。在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。基本语法:

var a[3]int
//定义:var 数组变量名 [元素数量]T

基础数组

初始化

# 方法一
// 初始化数组时可以使用初始化列表来设置数组元素的值。
func main() {
	var testArray [3]int                        //数组会初始化为int类型的零值
	var numArray = [3]int{1, 2}                 //使用指定的初始值完成初始化
	var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
	fmt.Println(testArray)                      //[0 0 0]
	fmt.Println(numArray)                       //[1 2 0]
	fmt.Println(cityArray)                      //[北京 上海 深圳]
}

# 方法二
// 按照上面的方法每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度,例如:

func main() {
	var testArray [3]int
	var numArray = [...]int{1, 2}
	var cityArray = [...]string{"北京", "上海", "深圳"}
	fmt.Println(testArray)                          //[0 0 0]
	fmt.Println(numArray)                           //[1 2]
	fmt.Printf("type of numArray:%T\n", numArray)   //type of numArray:[2]int
	fmt.Println(cityArray)                          //[北京 上海 深圳]
	fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}

# 方法三
//我们还可以使用指定索引值的方式来初始化数组,例如:
func main() {
	a := [...]int{1: 1, 3: 5}
	fmt.Println(a)                  // [0 1 0 5]
	fmt.Printf("type of a:%T\n", a) //type of a:[4]int
}

数组的遍历

我们还可以使用指定索引值的方式来初始化数组,比如:

func main(){
    var a = [...]string{"北京","上海","深圳"}
    //方法一:for循环遍历
    for i:=0;i<len(a);i++{
        fmt.Println(a[i])
    }
    
    //方法二:for range遍历
    for index,value:=range a{
        fmt.Println(index,value)
    }
}

多维数组

初始化

func main(){
    a:=[3][2]string{
        {"北京","上海"},
        {"广州","深圳"},
        {"成都","重庆"},
    }
    fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
    fmt.Println(a[2][1]) //支持索引取值:重庆
}

遍历

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	for _, v1 := range a {
		for _, v2 := range v1 {
			fmt.Printf("%s\t", v2)
		}
		fmt.Println()
	}
}

注意:数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值

func modifyArray(x [3]int) {
	x[0] = 100
}

func modifyArray2(x [3][2]int) {
	x[2][0] = 100
}
func main() {
	a := [3]int{10, 20, 30}
	modifyArray(a) //在modify中修改的是a的副本x
	fmt.Println(a) //[10 20 30]
	b := [3][2]int{
		{1, 1},
		{1, 1},
		{1, 1},
	}
	modifyArray2(b) //在modify中修改的是b的副本x
	fmt.Println(b)  //[[1 1] [1 1] [1 1]]
}

切片

切片是一个拥有相同元素的可变长度的序列,是基于数据类型做的一层封装,非常灵活,支持自动扩容 切片是一个引用类型,内部结构包括地址、长度和容量。切片一般用于快速地操作一块数据集合

定义

声明切片类型的定义如下:

var name []T

其中:

  • name:表示变量名
  • T:表示切片中的元素类型
func main(){
    //声明切片类型
    var a []string
    var b=[]int{}
    var c=[]bool{flase,true}
    fmt.Println(a) //[]
    fmt.Println(b) //[]
    fmt.Println(c) //[false true]
    fmt.Println(a == nil) //true
    fmt.Println(b == nil) //false
    fmt.Println(c == nil) //false
}

基于数组定义切片

由于切片的顶层还是数组,所以我们可以通过数组定义切片

func main(){
    //基于数组定义切片
    a:=[5]int{55,56,57,58,59}
    b:=a[1:4]  //基于数组a的切片,包括a[1]、a[2]、a[3]
    fmt.Println(b)
    fmt.Printf("type of b:%T\n",b)
}

还支持下列的操作

c:=a[1:]
d:=a[:4]
e:=a[:]

切片再切片

对切片再执行切片表达式时(切片再切片),high的上限边界是切片的容量cap(a),而不是长度。常量索引必须是非负的,并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。如果low和high两个指标都是常数,它们必须满足low <= high。如果索引在运行时超出范围,就会发生运行时panic。

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
	s2 := s[3:4]  // 索引的上限是cap(s)而不是len(s)
	fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}

使用make()函数构造切片

我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:

make([]T, size, cap)

其中:

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

注意:

  1. 切片是不能直接比较的,切片唯一合法的比较操作是和nil比较。一个nil值的切片没有底层数组,一个nil值的长度和容量都是0,但是我们不能说一个长度和容量都是0的切片一定是nil,判断一个切片是不是0要通过len(s)==0判断
    var s1 []int //声明int切片,数据不在内存中
    s2:=[]int{}  //声明并初始化,长度容量都是0
    s3:=make([]int,0)
    

操作

注意:切片一定要初始化之后才能使用

遍历

func main(){
    s := []int{1,3,5}
    for i:=0;i<len(s);i++{
        fmt.Println(i,s[1])
    }
    
    for index,value := range s{
        fmt.Println(index,value)
    }
}

添加元素

Go语言的内部函数append()可以为切片动态添加元素。每个元素会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增幅的元素的时候,切片就会按照一定的策略进行”扩容”,此时该切片指向的底层数组就会更换

func main(){
    //append()添加元素和切片扩容
    var numSlice []int
    for i:=0;i<10;i++{
        numslice = append(numslice,i)
        fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n",numSlice,len(numSlice),cap(numSlice),numslice)
    }
    
    //append还支持一次性添加多个元素
    numSlice=append(a,1,2,3,4,5)
    fmt.Println(numSlice)
}

复制切片

func main(){
    a:=[]int{1,2,3,4,5}
    b := a
    fmt.Println(a) //[1 2 3 4 5]
    fmt.Println(b) //[1 2 3 4 5]
    b[0]=1000
    fmt.Println(a) //[1000 2 3 4 5]
    fmt.Println(b) //[1000 2 3 4 5]
}

切片是引用类型,a和b其实都指向了同一块内存地址

所以我们需要copy来把数组复制到其他内存

func main(){
    a := []int{1,2,3,4,5}
    b := make([]int,5,5)
    copy(b,a)
}

map

map是一种无序的基于key-value的数据结构,Go语言中map是引用类型,必须初始化才能使用

定义

map[KeyType]ValueType

其中

  • KeyType:表示键的类型
  • ValueType:表示键所对应的值的类型

一开始map类型为nil,需要使用make()函数来分配内存

make(map[KeyType]ValueType,[cap])

其中cap用来表示map的容量,该参数不是必须的,但是在初始化的时候我们就应该指定一个合适的变量

添加键值对

func main(){
  scoreMap := make(map[string])
  scoreMap["张三"]=90
  scoreMap["小明"]=100
  fmt.Println(scoreMap)
  fmt.Println(scoreMap["小明"])
  fmt.Printf("type of a:%T\n",scoreMap)
  
  //声明map的同时完成初始化
  b := map[int]bool{
      1:true,
      2:false,
  }
  
  //判断是否存在map键值对,如果存在,赋值到value,同时ok赋值true
  value,ok :=scoreMap["张二狗子"]
  if ok {
      fmt.Println("张二狗子在scoreMap中",value)
  }else{
      fmt.Println("查无此人!")
  }
}

Map的遍历

//map是无关的,键值对和添加的顺序无关
for k,v:=range scoreMap{
    fmt.Println(k,v)
}
 
//只遍历map中的key
for k:=range scoreMap{
    fmt.Println(k)
}
 
//只遍历map中的value
for _,v :=range scoreMap{
	fmt.Println(v)
}

Map的删除

var scoreMap=make(map[stirng]int,8)
soremap["沙河纳扎"]=100
soremap["沙河小王子"]=200
 
delete(scoreMap,"沙河小王子")
fmt.Println(scoreMap)

扩展

元素类型为map的切片

func{
    var mapSlice = make([]map[string]int,0,8) //只是完成了切片的初始化
    //还需要完成内部map元素的初始化
    mapslice[0]=make(map[string]int,8)
    mapSlice[0]["沙河小王子"]=100
}

值为切片的map

func{
    var sliceMap=make(map[string][]int,8)//只完成了map的初始化
    v,ok := sliceMap["中国"]
    if ok{
        fmt.Println(sliceMap["中国"])
    }else{
        sliceMap["中国"]=make([]int,0,8) //完成了对切片的初始化
        sliceMap["中国"][0]
    }
}

函数

函数定义

使用 func 关键字定义函数,基本语法:

func 函数名(参数列表) (返回值列表) {
	// 函数体
}
 
// 示例:求两个整数的和
func add(x int, y int) int {
	return x + y
}

当多个连续的参数类型相同时,可以只在最后写一次类型:

func add(x, y int) int {
	return x + y
}

多返回值

Go 函数支持多返回值,常用于返回结果和错误:

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("除数不能为0")
	}
	return a / b, nil
}

命名返回值

返回值可以命名,并在函数体中直接使用:

func swap(x, y string) (a, b string) {
	a = y
	b = x
	return // 裸 return,自动返回命名返回值
}

可变参数

参数名后加 ... 表示可变参数,在函数内是一个切片:

func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

defer 语句

defer 语句用于延迟执行一个函数调用,常用于资源释放、关闭文件等收尾工作。defer 的执行顺序是”后进先出”(LIFO)。

func main() {
	fmt.Println("开始")
	defer fmt.Println("1")
	defer fmt.Println("2")
	defer fmt.Println("3")
	fmt.Println("结束")
}
// 输出:开始 结束 3 2 1

函数进阶

变量作用域

全局变量

全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效,在函数中可以访问到全局变量

package main

import "fmt"

//定义全局变量num
var num int64=10

func testNum(){
    num:=100
    fmt.Printf("num=%d\n",num)
}

fun main(){
    testNum()
}

匿名函数

匿名函数就是没有函数名的函数,匿名函数多用于实现函数回调和闭包

定义格式如下

func(参数)(返回值){
    //函数体
}

立即执行函数

func main(){
    func(x,y,int){
        fmt.Println(x+y)
    }(10,20)//匿名函数定义加()直接执行
    
}

可以在函数内部包含函数,实现函数回调

func a func(){
    name := "沙河娜扎"
    return func{
        fmt.Println("Hello",name)
    }
}

func main(){
    r:=a()
    r()//相当于执行了a函数内部的匿名函数
}

闭包:

func a(name string) func(){
    return func{
        fmt.Println("Hello",name)
    }
}

func main(){
    r:=a("沙河小王子") //r此时就是闭包
    r()//相当于执行了a函数内部的匿名函数
}

内置函数

内置函数介绍
close主要用来关闭channel
len用来求长度,比如string、array、slice、map、channel
new用来分配内存,主要用来分配值类型
make用来分配内存,主要用来分配引用类型
append用来追加元素到数组、slice中
panic和recover用来处理错误