第一章 Goland介绍
一、Goland文档
1、Golang官网
go官网地址是:go.dev/
go官网对go的介绍是:
Build simple, secure, scalable systems with Go.(用Go构建简单,安全,可扩展的系统。)
An oper-source programming language supported by Google.(谷歌支持的一种开源编程语言。)Easy to learn and great for teams.(易于学习,非常适合团队使用。)Build-in concurrency and a robust standart library.(内置并发性和健壮的标准库。)Large ecosystem of partners, communities, and tools.(由合作伙伴、社区和工具组成的大型生态系统。。)
2、Golang中文网
3、Golang中文文档
二、go特点
Go特点:
- 既能达到静态语言的安全和性能,又到了动态语言开发维护的高效率。
- Go语言的每一个文件都要归属一个包,而不能单独存在。
- 从语言层面支持高并发,实现简单。
- 轻量级线程,可实现大并发处理,高效利用多核。
- 基于CPS并发模型实现。
- 吸收了管道通信机制,形成了Go语言特有的管道Channel。
- 函数可以返回多个值。
- 新的创新,比如切片slice(类似动态数组)、延时执行defer等。
Go应用方向:
- 区块链
- Go服务器端
- 游戏软件
- 分布式/云计算
三、Go语言开发环境搭建
golang官网:go.dev/dl/
golang稳定版:go1.20.x
1.Windows下安装
go文件目录:
说明:
api:api接口bin:go.exe(编译和运行go程序)godoc.exe(生成文档)gofmt.exe(格式化代码)src:go源代码
2.配置环境变量
- GOROOT:go的安装路径,例如:
D:\program\go-1.20.5 - PATH:添加go的\bin目录,例如:
%GOROOT%\bin - GOPATH:go的工作目录,将来写go项目代码的工作路径,例如:
D:\code\go
3.开发工具
开发工具推荐使用Jetbrains 的 GoLand 也可以使用vscode
www.jetbrains.com.cn/go/download…
四、go项目结构:
当前go社区比较推荐的结构化目录结构是 github.com/golang-stan…。虽然它并不是官方和社区的规范,但因为组织方式比较合理,被很多go开发人员接受。
1.基本代码结构
hello.go
package main
import "fmt"
func main(){
fmt.Println("Hello world")
}
编译:go build hello.go
运行:hello.exe
直接执行:go run hello.go 类似于执行python脚本
第二章 基本变量与类型
1、基础语法
1.注释
// 单行注释
/* 多行注释 */
2、变量与常量
1.变量定义
1.1 定义并赋值
var 变量名 类型 = 值
// 例如:
var name string = "张三"
// 简写1
var 变量名 = 值
var name = "张三"
// 简写2 这种方式只能用于函数体内,不能用于全局变量的声明
变量名 := 值/表达式
name := "张三"
1.2 定义不赋值
var 变量名 类型
// 例如:
var name string
var age int
1.3 多个定义
var 变量名,变量名 类型
// 例如:
var name1,name2 string
1.4 批量定义
var (
变量名 类型
变量名 类型
变量名 类型
……
)
// 例如:
var(
a int
b string
c []float32
d float64
e *int
)
// 或
var(
a 18
b "张三"
c [12.3, 156.3, 5.6]
d 12.5685744
)
1.5 指针
var 变量名 *类型
// 例如:
var name *string
var age *int
1.6 简短格式
(go语言自动类型判断) 这种方式只能用于函数体内,不能用于全局变量的声明
变量名 := 表达式/值
// 例如:
name := "张三"
2.常量
常量尽量都使用大写的定义,定义之后不允许修改
2.1 单个定义
const PI = 3.14
const PATH string = "http:www.baidu.com"
2.2 定义一组常量
const C1, C2, C3 = 100, 3, "haha"
2.3 定义常量的集合
const (
NAME = "xiaoming"
LINKMODE = 1
)
2.4 常量集合初始值
一组常量,如果某个常量没有给初始值,就会默认和上一个常量保持一致的值
const (
a int = 100
b
c string = "ruby"
d
e
)
3.输出格式
- 输出数字
num = 30
fmt.Println("%d", num)
- 输出浮点数
fl = 2.36952
fmt.Println("%.f", fl)
//输出
//2.369520
//保留两位
fmt.Println("%.2f", fl)
//输出
//2.36
- 输出字符
name = "张三"
fmt.Println("%s", name)
- 输出类型
num = 30
fmt.Println("%T", num)
3、数据类型
go 有两种大类型:基本数据类型和派生数据类型/复合数据类型。
基本数据类型有:
- 整型(有符号
int?、无符号uint?) - 浮点型(
float32、float64) - 布尔型(bool)
- 字符串(string)
- 复数
派生数据类型/复杂数据类型:
- 指针
- 数组
- 结构体
- 管道
- 函数
- 切片
- 接口
- map
3.1 基本数据类型
基本数据类型分为:整型(有符号、无符号)、浮点型、复数、布尔型(bool)、字符串(string)
3.1.1 整型
整型分为有符号和无符号,有符号最左边的一位符号位0表示正数,1表示负数,默认值为0。
① 有符号
| 类型 | 占用字节 | 取值范围 | 取值范围 |
|---|---|---|---|
| int8 | 1 | -2^7 ~ (2^7)-1 | -128 ~ 127 |
| int16 | 2 | -2^15 ~ (2^15)-1 | -32768 ~ 32767 |
| int32 | 4 | -2^31 ~ (2^31)-1 | -2147483648 ~ 2147483647 |
| int64 | 8 | -2^63 ~ (2^63)-1 | -9223372036854775808 ~ 9223372036854775807 |
② 无符号
| 类型 | 占用字节 | 取值范围 | 取值范围 |
|---|---|---|---|
| uint8 | 1 | 0 ~ (2^8)-1 | 0 ~ 255 |
| uint16 | 2 | 0 ~ (2^16)-1 | 0 ~ 65535 |
| uint32 | 4 | 0 ~ (2^32)-1 | 0 ~ 4294967295 |
| uint64 | 8 | 0 ~ (2^64)-1 | 0 ~ 18446744073709551615 |
③ 其他整数类型
| 类型 | 占用字节 | 有无符号 | 取值范围 | 取值范围 |
|---|---|---|---|---|
| int 在32位系统中 | 4 | 有 | -2^31 ~ (2^31)-1 | -2147483648 ~ 2147483647 |
| int 在64位系统中 | 8 | 有 | -2^63 ~ (2^63)-1 | -9223372036854775808 ~ 9223372036854775807 |
| uint 在32位系统中 | 4 | 无 | 0 ~ (2^32)-1 | 0 ~ 4294967295 |
| uint 在64位系统中 | 8 | 无 | 0 ~ (2^64)-1 | 0 ~ 18446744073709551615 |
| rune | 4 | 有 | 等价于int32 | 0 ~ 4294967295 |
| byte | 1 | 无 | 等价于uint8 | 0 ~ 255 |
3.1.2 浮点型
float32 精确到小数点后7位,float64精确到小数点后15位,默认值为0。
浮点型 IEEE-754标准:
| 类型 | 占用字节 | 取值范围 | 精确位数 |
|---|---|---|---|
| float32 | 4 | 1.4E-45 ~ 3.4E38 | 精确到小数点后7位 |
| float64 | 8 | 4.9E-324 ~ 1.8E308 | 精确到小数点后15位 |
注意: 由于精度的缘故,在使用==或者!=比较浮点数时应当非常小心。
3.1.3 布尔型
| 类型 | 占用字节 | 取值范围 | 默认值 |
|---|---|---|---|
| bool | 1 | true或者false | false |
- 在一些判断的应用中,例如
==,>,<,<=,>=,&&,||等都会产生bool值,并且在go语言中,只有两个相同类型的值才可以进行比较,若其中一个值为常量,另一个值可以不是常量,但类型必须和该常量的类型相同。 - 布尔型数据只有 true 和 false,且不能参与任何计算以及类型转换。
3.1.3 字符串
字符串是由一串固定长度连接起来的字符序列
可以是byte类型或int类型标识
例如:
var s1 byte = 'A'
fmt.Println(s1)
将输出:
65
因为byte存储的是ASCII编码,输出的是A的ASSCII码值
int表示 例如:
var s2 int = '中'
fmt.Println(s2)
将输出:
20013
输出20013是因为int表示字符时是以Unicode UTF-8编码表示的,中的unicode码值为20013
注意:string 类型的变量一旦赋值,其值中的内容不能改变,即string类型下字符数组中的值不能修改,只能将string整个重新赋值。
例如:
var s1 string = "中国"
s1 = "good" //这里是可以成功执行的
s1[0] = 'a' // 此处无法通过编译:cannot assign to s1[0] (value of type byte)
fmt.Println(s1)
① 转义字符
| 转义符 | 含义 | unicode值 |
|---|---|---|
| \b | 退格(backspace) | \u0008 |
| \n | 换行 | \u000a |
| \r | 回车(光标回到这一样的最前面) | \u000d |
| \t | 制表符(tab) | \u0009 |
| \" | "(表示一个普通的文本双引号) | \u0022 |
| \' | '(表示一个普通的文本单引号) | \u0027 |
| \\ | \(表示一个普通的文本反斜杠) | \u005c |
② 原样输出
同js中的反引号
例如:
var s2 string = `
package main
import "fmt"
func main(){
\t
\n
\r
\b
"
}
`
将输出:
package main
import "fmt"
func main(){
\t
\n
\r
\b
"
}
3.1.4 复数
复数包含两个部分:实数和虚数,
例如,一个复数是18.5i。
在Golang中,复数用两种类型表示:
| 类型 | 占用字节 | 表述数据 |
|---|---|---|
| complex64 | - | 表示float32实数和虚数数据 |
| complex128 | - | 表示float64实数和虚数数据 |
3.2 基本数据类型转换
Go在不同类型的变量之间赋值时需要显示转换(强制转换),并且只有显示转换(强制转换)。数值转换需要注意大转小可能会溢出
语法:表达式T(v)将v类型转换为T类型
- T:转换的数据类型
- v:转换的变量
注意: 和java不同,int8和int32这种向上或者向下转换同样需要强制转换,go不支持向上转。
例如:
var n1 int8 = 12
var n2 int32 = int32(n1) + 34 // 将int8转换为int32类型也必须使用强制转换
var n3 int32 = 256352
var n4 int8 = int8(n3) + 127 // 将int32转换为int8类型也必须使用强制转换
var n5 int = 100
var n6 float32 = float32(n5)
byte转string
var n7 byte = 'a'
var n8 string = string(n7)
string转byte
var n5 string = "A"
var n6 byte = byte(n5[0])
fmt.Printf("n6 类型:%T,n6 = %v", n6, n6)
//输出:n6 类型:uint8,n6 = 65
3.2.1 基本数据类型转字符串
基本数据类型转字符串有两种方式:
fmt.sprintf("%参数", 表达式/变量)返回string类型- 使用
strconv包函数
转换格式:
完整文档地址:Go语言标准库中文文档 fmt 包
通用:
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 值的类型的Go语法表示
%% 百分号
布尔值:
%t 单词true或false
整数:
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"
浮点数与复数的两个组分:
%b 无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
字符串和[]byte:
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f)
%X 每个字节用两字符十六进制数表示(使用A-F)
指针:
%p 表示为十六进制,并加上前导的0x
没有%u。整数如果是无符号类型自然输出也是无符号的。类似的,也没有必要指定操作数的尺寸(int8,int64)。
宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下:
%f: 默认宽度,默认精度
%9f 宽度9,默认精度
%.2f 默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0
① fmt.sprintf()转换:
package main
import (
"fmt"
)
func main() {
var n1 int = 19
var n2 float32 = 4.78
var n3 bool = true
var n4 byte = 'a'
// %d:整数转字符串格式
var s1 = fmt.Sprintf("%d", n1)
// %T:返回该变量的参数类型 %v:原样输出该变量
fmt.Printf("s1类型:%T s1 = %v\n", s1, s1)
// %f:小数转字符串格式 %.2f:保留两位小数
var s2 = fmt.Sprintf("%.2f", n2)
// %T:返回该变量的参数类型 %q:用引号引起来的字面值,必要时会采用安全的转义表示
fmt.Printf("s2类型:%T s2 = %q\n", s2, s2)
// %t:布尔值转字符串格式
var s3 = fmt.Sprintf("%t", n3)
// %T:返回该变量的参数类型 %q:用引号引起来的字面值,必要时会采用安全的转义表示
fmt.Printf("s3类型:%T s3 = %q\n", s3, s3)
// %c:字符类型转字符串格式
var s4 = fmt.Sprintf("%c", n4)
// %T:返回该变量的参数类型 %q:用引号引起来的字面值,必要时会采用安全的转义表示
fmt.Printf("s4类型:%T s4 = %q\n", s4, s4)
}
输出:
s1类型:string s1 = 19
s2类型:string s2 = "4.78"
s3类型:string s3 = "true"
s4类型:string s4 = "a"
② strconv包函数转换:
int转string
strconv.FormatInt(i int64, base int) string
- i:需要转换的变量,注意需要int64类型,不是int64类型需要强制转换
- base:转换成几进制的数据,必须在2到36之间,结果中会使用小写字母'a'到'z'表示大于10的数据
例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var n1 int32 = 192547
fmt.Println(strconv.FormatInt(int64(n1), 2)) //转换成2进制
fmt.Println(strconv.FormatInt(int64(n1), 8)) //转换成8进制
fmt.Println(strconv.FormatInt(int64(n1), 10)) //转换成10进制
fmt.Println(strconv.FormatInt(int64(n1), 16)) //转换成16进制
fmt.Println(strconv.FormatInt(int64(n1), 32)) //转换成32进制
}
输出:
101111000000100011
570043
192547
2f023
5s13
float转string
strconv.FormatFloat(f float64, fmt byte, prec, bitSize int) string
- f:需要转换的值,注意需要float64类型,不是float64类型需要强制转换
- fmt:转换成字符串的表示格式:
'f':-ddd.ddd'b':-ddddp±ddd 指数为二进制'e':-d.dddde±dd 十进制指数 e小写表示'E':-d.ddddE±dd 十进制指数 E大写表示'g':指数很大时用'e'格式,否则用'f'格式'G':指数很大时用'E'格式,否则用'f'格式
- prec:控制精度(排除指数部分):对
'f'、'e'、'E'它表示小数点后的数字个数(保留几位小数);对'g'、'G'它表示控制总的数字个数。如果prec为-1,则代表使用最少量的,但又必须得数字来表示f - bitSize:表示这个小数是float64类型还是float32类型,只填数字:(64还是32)
例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var n2 float32 = 3562.2358745896123441
fmt.Println(strconv.FormatFloat(float64(n2), 'f', 9, 64)) //输出:3562.235839844
fmt.Println(strconv.FormatFloat(float64(n2), 'f', 9, 32)) //输出:3562.235839844
fmt.Println(strconv.FormatFloat(float64(n2), 'b', 9, 64)) //输出:7833439453577216p-41
fmt.Println(strconv.FormatFloat(float64(n2), 'e', 9, 64)) //输出:3.562235840e+03
fmt.Println(strconv.FormatFloat(float64(n2), 'E', 9, 64)) //输出:3.562235840E+03
fmt.Println(strconv.FormatFloat(float64(n2), 'g', 9, 64)) //输出:3562.23584
fmt.Println(strconv.FormatFloat(float64(n2), 'G', 9, 64)) //输出:3562.23584
}
输出:
3562.235839844
3562.235839844
3562.235839844
7833439453577216p-41
3.562235840e+03
3.562235840E+03
3562.23584
3562.23584
bool转string
例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var b1 bool = true
fmt.Println(strconv.FormatBool(b1))
}
输出:
true
3.2.2 字符串转基本数据类型
使用strconv.Parse对应类型()
注意::如果转换的字符串不是对应类型可转换的值,则返回该类型的默认值
① 字符串转int
strconv.ParseInt(s string, base int, bitSize int) (i int64, err error)
参数:
- s:需要转换的字符串,接受正负号
- base:指定进制2到36,如果base为0,则会从字符串前置位置判断,"0x"是16进制,"0"是8进制,否则是10进制;
- bitSize:指定结果必须能无溢出赋值的整数类型,0、8、16、32、64分别表示int、int8、int16、int32、int64。
返回值:
- i:转换后的值,无法转换则返回默认值
- err:是
*NumErr类型的,如果语法有无,err.Error = ErrSyntax;如果超出范围:err.Error = ErrRange。 例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var s1 string = "分布式/云计算"
var s2 string = "1234"
var n1, n2 int64
n1, _ = strconv.ParseInt(s2, 10, 32)
fmt.Printf("n1 类型:%T n1 = %v\n", n1, n1)
n2, _ = strconv.ParseInt(s1, 10, 32)
fmt.Printf("n2 类型:%T n2 = %v\n", n2, n2)
}
输出:
n1 类型:int64 n1 = 1234
n2 类型:int64 n2 = 0
② 字符串转uint
strconv.ParseUint(s string, base int, bitSize int) (n uint64, err error)
ParseUint类似Parseint,但不接受正负号,用于无符号整型
参数:
- s:需要转换的字符串。
- base:指定进制2到36,如果base为0,则会从字符串前置位置判断,"0x"是16进制,"0"是8进制,否则是10进制;
- bitSize:指定结果必须能无溢出赋值的整数类型,0、8、16、32、64分别表示int、int8、int16、int32、int64;
返回值:
- n:转换后的值,无法转换则返回默认值
- err:是
*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果超出范围:err.Error = ErrRange。
例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var s1 string = "1234"
var s2 string = "-2563"
var s3 string = "分布式/云计算"
var un1, un2, un3 uint64
un1, _ = strconv.ParseUint(s1, 10, 32)
fmt.Printf("un1 类型:%T un1 = %v\n", un1, un1)
un2, _ = strconv.ParseUint(s2, 10, 32)
fmt.Printf("un2 类型:%T un2 = %v\n", un2, un2)
un3, _ = strconv.ParseUint(s3, 10, 32)
fmt.Printf("un3 类型:%T un3 = %v\n", un3, un3)
}
输出:
un1 类型:uint64 un1 = 1234
un2 类型:uint64 un2 = 0
un3 类型:uint64 un3 = 0
③ 字符串转float
strconv.ParseFloat(s string, bitSize int) (f float64, err error)
参数:
- s:需要转换的字符串。
- bitSize:如果s合乎语法规则,函数会返回最为接近s表示值的一个浮点数(使用IEEE754规范舍入)。bitSize指定了期望的接收类型,
32是float32(返回值可以不改变精确值的赋值给float32),64是float64;返回值err
返回值:
- f:转换后的值,无法转换则返回默认值
- err:是
*NumErr类型的,语法有误的,err.Error=ErrSyntax;结果超出表示范围的,返回值f为±Inf,err.Error= ErrRange。
例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var s1 string = "分布式/云计算"
var s3 string = "2563.25"
var f1, f2 float64
f1, _ = strconv.ParseFloat(s3, 64)
fmt.Printf("f1 类型 %T f1 = %v\n", f1, f1)
f2, _ = strconv.ParseFloat(s1, 64)
fmt.Printf("f2 类型 %T f2 = %v\n", f2, f2)
}
输出:
f1 类型 float64 f1 = 2563.25
f2 类型 float64 f2 = 0
④ 字符串转bool
strconv.ParseBool(str string) (value bol, err error)
参数:
- str:需要转换的字符串
返回值:
- value:转换后的值,无法转换则返回默认值。它接受:
1、0、t、f、T、F、true、false、True、False、TRUE、FALSE;否则返回错误。 - err:错误
例如:
package main
import (
"fmt"
"strconv"
)
func main(){
var s1 string = "分布式/云计算"
var s4 string = "true"
var b1 bool
var b2 bool
b1, _ = strconv.ParseBool(s4)
fmt.Printf("b1类型:%T b1 = %v\n", b1, b1)
b2, _ = strconv.ParseBool(s1) //"分布式/云计算" 转 bool 无法转换,返回bool默认值
fmt.Printf("b2类型:%T b2 = %v\n", b2, b2)
}
输出:
b1类型:bool b1 = true
b2类型:bool b2 = false
3.3 复杂数据类型
3.3.1 指针
3.3.2 数组
3.3.3 结构体
3.3.4 管道
3.3.5 函数
3.3.6 切片
3.3.7 接口
3.3.8 map
流程
Go语言没有while和do while语句,只能用for循环实现。
流程控制语句主要包括:条件判断语句(if和switch)、循环控制语句(for、break和continue)和跳转语句(goto)。
1. if
if 表达式1 {
代码块1
} else if 表达式2 {
代码块2
} else{
代码块3
}
例如:
func main() {
a := 1
if a>1 {
fmt.Println(a,"> 1")
} else if a == 1 {
fmt.Println(a,"= 1")
} else {
fmt.Println(a,"< 1")
}
}
2. switch
switch 变量 {
case value1:
代码块1
case value2:
代码块2
case value3, value4:
代码块3
default:
代码块4
}
例如:
func main() {
switch 2 {
case 1:
fmt.Println("该数字是1")
case 2:
fmt.Println("该数字是2")
case 3, 4:
fmt.Println("该数字是3或4")
default:
fmt.Println("该数字不是1、2、3、4")
}
}
默认情况下,switch匹配成功后就不会执行其他的case,如果我们需要无条件的强制执行后面的case,需要使用fallthrough关键字。例如:
func main() {
switch {
//true 肯定执行
case true:
fmt.Println("true")
fallthrough
//false 肯定不执行,但因为上一个case语句加了fallthrough标签,所以也会执行
case false:
fmt.Println("false")
//fallthrough
default:
fmt.Println("默认")
}
}
3. for
for 初始语句;条件表达式;赋值表达式{
循环体
}
例如:
func main() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
break语句可以用来结束for循环,如果不带标签,则默认跳出最内层的for循环。例如:
func main() {
i := 1
for {
for {
if i>5 {
fmt.Println("跳出最内层for循环")
break
}
fmt.Println(i)
i++
}
fmt.Println("跳出最外层for循环")
break
}
}
break语句后面添加标签,表示退出标签对应的代码块逻辑。continue类似,例如:
func main() {
fmt.Println("使用带标签的break语句,直接跳出最外层for循环")
i := 1
OuterLoop:
for {
for {
if i > 5 {
break OuterLoop //跳出OuterLoop标签对应的循环
}
fmt.Println(i)
i++
}
}
}
4. goto 跳转语句
goto 标签
标签:
代码块
例如:
func main() {
fmt.Println("hello")
goto sign
fmt.Println("无效代码块")
sign:
fmt.Println("world")
}
函数
函数是基本的代码块,用于执行一个任务。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
Go 语言最少有个 main() 函数。
golang函数特点:
支持:
- 无需声明原型。
- 支持不定 变参。
- 支持多返回值。
- 支持命名返回参数。
- 支持匿名函数和闭包。
- 函数也是一种类型,一个函数可以赋值给变量。
不支持:
- 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
- 不支持 重载 (overload)
- 不支持 默认参数 (default parameter)。
函数的声明
func name([parameter list])[return_types]{
}
1 值传递和引用传递
案例
//值传递
func test(x, y int, s string) (int, string) {
// 类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
n := x + y
return n, fmt.Sprintf(s, n)
}
//引用传递
func swap(x *int, y *int){
var temp int
temp = *x
*x = *y
*y = temp
}
//调用
var a int = 3
var b int = 4
swap(&a, &b)
注意:
- 无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
map、slice、chan、指针、interface默认以引用的方式传递。
2 不定参数传值
不定参数传值就是函数的参数数量不固定,后面的类型是固定的。(可变参数)
Golang 可变参数本质上是 slice。该 slice 只能有一个,且必须是最后一个。
//0个或多个参数
func myfunc(args ...int) {
}
//1个或多个参数
func add(a int, args…int) int {
}
//2个或多个参数
func add(a int, b int, args…int) int {
}
注意:其中 args 是一个slice,我们可以通过 arg[index] 依次访问所有参数,通过 len(arg) 来判断传递参数的个数。
1. 逐个赋值
package main
import (
"fmt"
)
func test(s string, n ...int) string {
var x int
for _, i := range n {
x += i
}
return fmt.Sprintf(s, x)
}
func main() {
println(test("sum: %d", 1, 2, 3))
}
2. 使用切片赋值
在参数赋值时可以不用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。
使用 slice 对象做变参时,必须展开。(slice…)
package main
import (
"fmt"
)
func test(s string, n ...int) string {
var x int
for _, i := range n {
x += i
}
return fmt.Sprintf(s, x)
}
func main() {
s := []int{1, 2, 3}
res := test("sum: %d", s...) // slice... 展开slice
println(res)
}
输出结果:
sum: 6
3. interface{}传值
注意
任意类型的不定参数: 就是函数的参数和每个参数的类型都不是固定的。
用 interface{} 传递任意类型数据是Go语言的惯例用法,而且 interface{} 是类型安全的
func myfunc(args ...interface{}) {
}
3 函数返回值
-
返回值的忽略
_标识符,用来忽略函数的某个返回值。
Golang返回值不能用容器对象接收多返回值。只能用多个变量,或 “_” 忽略。 -
多返回值可直接作为其他函数调用实参。
package main
func test() (int, int) {
return 1, 2
}
func add(x, y int) int {
return x + y
}
func sum(n ...int) int {
var x int
for _, i := range n {
x += i
}
return x
}
func main() {
println(add(test()))
println(sum(test()))
}
输出结果:
3
3
3.命名返回值
Go 函数的返回值可以被命名,就像在函数体开头声明变量。
返回值的名称应当具有一定的意义,可以作为文档使用。
命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。
package main
func add(x, y int) (z int) {
z = x + y
return
}
func main() {
println(add(1, 2))
}
输出结果:
3
注意:命名返回参数可被同名局部变量遮蔽,此时需要显式返回。
func add(x, y int) (z int) {
{ // 不能在一个级别,引发 "z redeclared in this block" 错误。
var z = x + y
// return // Error: z is shadowed during return
return z // 必须显式返回。
}
}
没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。
直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。
package main
import (
"fmt"
)
func add(a, b int) (c int) {
c = a + b
return
}
func calc(a, b int) (sum int, avg int) {
sum = a + b
avg = (a + b) / 2
return
}
func main() {
var a, b int = 1, 2
c := add(a, b)
sum, avg := calc(a, b)
fmt.Println(a, b, c, sum, avg)
}
输出结果:
1 2 3 3 1
理解 Golang 的延迟调用(defer)
defer特性:
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 跳转前才被执。因此,可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行。
- defer语句中的变量,在defer声明时就决定了。
defer用途:
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
Go 语言 中的 defer 语句用于延迟函数的调用,每次 defer 都会把一个函数压入 栈 中,函数返回前再把延迟的函数取出并执行。Golang 中的 defer 可以帮助我们处理容易忽略的问题,如资源释放、连接关闭等。
go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。
Golang 官方博客里总结了 defer 的行为规则,只有三条,分别为:
- 延迟函数的参数在 defer 语句出现时就已经确定下来了。
示例:
package main
import "fmt"
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
func main() {
a()
}
输出结果:
0
defer 语句中的 fmt.Println() 参数 i 值在 defer 出现时就已经确定下来,实际上是拷贝了一份。后面对变量 i 的修改不会影响 fmt.Println() 函数的执行,仍然打印 “0”。
注意:对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer 后面的语句对变量的修改可能会影响延迟函数。