Go语言学习

121 阅读24分钟

第一章 Goland介绍

一、Goland文档

1、Golang官网

go官网地址是:go.dev/

image.png 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中文网

studygolang.com/

3、Golang中文文档

studygolang.com/pkgdoc

二、go特点

Go特点:

  • 既能达到静态语言的安全和性能,又到了动态语言开发维护的高效率。
  • Go语言的每一个文件都要归属一个包,而不能单独存在。
  • 从语言层面支持高并发,实现简单。
  • 轻量级线程,可实现大并发处理,高效利用多核。
  • 基于CPS并发模型实现。
  • 吸收了管道通信机制,形成了Go语言特有的管道Channel。
  • 函数可以返回多个值。
  • 新的创新,比如切片slice(类似动态数组)、延时执行defer等。

Go应用方向:

  • 区块链
  • Go服务器端
  • 游戏软件
  • 分布式/云计算

三、Go语言开发环境搭建

golang官网:go.dev/dl/

golang稳定版:go1.20.x

image.png

1.Windows下安装

下载:go.dev/dl/go1.21.0…

go文件目录:

image.png

说明:

  • api:api接口
  • bingo.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.开发工具

开发工具推荐使用JetbrainsGoLand 也可以使用vscode

www.jetbrains.com.cn/go/download…

image.png

四、go项目结构:

当前go社区比较推荐的结构化目录结构是 github.com/golang-stan…。虽然它并不是官方和社区的规范,但因为组织方式比较合理,被很多go开发人员接受。

image.png

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.输出格式

  1. 输出数字
num = 30
fmt.Println("%d", num)
  1. 输出浮点数
fl = 2.36952
fmt.Println("%.f", fl)

//输出
//2.369520

//保留两位
fmt.Println("%.2f", fl)

//输出
//2.36
  1. 输出字符
name = "张三"
fmt.Println("%s", name)
  1. 输出类型
num = 30
fmt.Println("%T", num)

3、数据类型

go 有两种大类型:基本数据类型派生数据类型/复合数据类型

基本数据类型有:

  • 整型(有符号int?、无符号uint?
  • 浮点型(float32float64
  • 布尔型(bool)
  • 字符串(string)
  • 复数

派生数据类型/复杂数据类型:

  • 指针
  • 数组
  • 结构体
  • 管道
  • 函数
  • 切片
  • 接口
  • map

3.1 基本数据类型

基本数据类型分为:整型(有符号、无符号)浮点型复数布尔型(bool)字符串(string)

3.1.1 整型

整型分为有符号和无符号,有符号最左边的一位符号位0表示正数,1表示负数,默认值为0。

① 有符号
类型占用字节取值范围取值范围
int81-2^7 ~ (2^7)-1-128 ~ 127
int162-2^15 ~ (2^15)-1-32768 ~ 32767
int324-2^31 ~ (2^31)-1-2147483648 ~ 2147483647
int648-2^63 ~ (2^63)-1-9223372036854775808 ~ 9223372036854775807
② 无符号
类型占用字节取值范围取值范围
uint810 ~ (2^8)-10 ~ 255
uint1620 ~ (2^16)-10 ~ 65535
uint3240 ~ (2^32)-10 ~ 4294967295
uint6480 ~ (2^64)-10 ~ 18446744073709551615
③ 其他整数类型
类型占用字节有无符号取值范围取值范围
int 在32位系统中4-2^31 ~ (2^31)-1-2147483648 ~ 2147483647
int 在64位系统中8-2^63 ~ (2^63)-1-9223372036854775808 ~ 9223372036854775807
uint 在32位系统中40 ~ (2^32)-10 ~ 4294967295
uint 在64位系统中80 ~ (2^64)-10 ~ 18446744073709551615
rune4等价于int320 ~ 4294967295
byte1等价于uint80 ~ 255
3.1.2 浮点型

float32 精确到小数点后7位,float64精确到小数点后15位,默认值为0。

浮点型 IEEE-754标准:

类型占用字节取值范围精确位数
float3241.4E-45 ~ 3.4E38精确到小数点后7位
float6484.9E-324 ~ 1.8E308精确到小数点后15位

注意: 由于精度的缘故,在使用==或者!=比较浮点数时应当非常小心。

3.1.3 布尔型
类型占用字节取值范围默认值
bool1true或者falsefalse
  • 在一些判断的应用中,例如 ==><<=>=&&|| 等都会产生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 基本数据类型转字符串

基本数据类型转字符串有两种方式:

  1. fmt.sprintf("%参数", 表达式/变量) 返回 string类型
  2. 使用strconv包函数

转换格式:

完整文档地址:Go语言标准库中文文档 fmt 包

通用:

%v	值的默认格式表示
%+v	类似%v,但输出结构体时会添加字段名
%#v	值的Go语法表示
%T	值的类型的Go语法表示
%%	百分号

布尔值:

%t	单词truefalse

整数:

%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指定了期望的接收类型,32float32(返回值可以不改变精确值的赋值给float32),64float64;返回值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:转换后的值,无法转换则返回默认值。它接受:10tfTFtruefalseTrueFalseTRUEFALSE;否则返回错误。
  • 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)

注意:

  1. 无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
  2. mapslicechan指针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 函数返回值

  1. 返回值的忽略
    _标识符,用来忽略函数的某个返回值。
    Golang返回值不能用容器对象接收多返回值。只能用多个变量,或 “_” 忽略。

  2. 多返回值可直接作为其他函数调用实参。

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特性:

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

defer用途:

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

Go 语言 中的 defer 语句用于延迟函数的调用,每次 defer 都会把一个函数压入  中,函数返回前再把延迟的函数取出并执行。Golang 中的 defer 可以帮助我们处理容易忽略的问题,如资源释放、连接关闭等。

go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。

Golang 官方博客里总结了 defer 的行为规则,只有三条,分别为:

  1. 延迟函数的参数在 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 后面的语句对变量的修改可能会影响延迟函数。

数组

指针和结构体

面向对象

异常处理

常用库和包

go高级

集合框架

IO流

并发编程

网络编程

GUI编程

Go反射

测试

编译原理

内存管理

元编程