鉴于全网Go语言知识点的总结分散难懂、良莠不齐,为了避免初学者少走弯路,更好更快地掌握Go知识,博主特地将自己所学的笔记分享出来。
Go语言(又称为Golang)是一种开源的编程语言,由Google于2007年启动并于2009年首次公开发布。Go语言是一门静态类型、编译型的语言,旨在提供一种简单、高效、可靠的编程方式。
现在越来越多的人开始使用Go语言进行开发,其原因有以下几点:
- Go语言设计简洁,语法清晰明了,容易上手和理解。它避免了冗余的语法和复杂的概念,使得编写和维护代码更加高效。
- Go语言天生支持并发编程,提供了轻量级的Goroutine和通道(channel)机制,使并发编程变得更加简单和安全。
- 高性能:Go语言在运行时表现出色,具有低延迟和高吞吐量。它采用了垃圾回收机制,使内存管理变得自动化且高效,同时还提供了一些优化策略,如原生的协程调度器和快速编译等。
- 内建工具:Go语言提供了丰富的标准库,覆盖了网络编程、文件处理、文本处理、加密解密等各个领域。它还有强大的构建工具,可以方便地进行代码的构建、测试和分发。
Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
对于高性能分布式系统领域而言,Go 语言比大多数语言有着更高的开发效率。它提供了海量并行的支持,十分适用于游戏服务端的开发。
安装包下载地址:https://go.dev/dl/
根据操作系统选择安装包:
运行msi文件:
一路next,并且选择安装路径:
等待安装:
安装完成后将bin目录添加到path环境变量中:
创建一个practice目录来测试安装配置是否成功:
在practice目录中新建test.go文件
package main import "fmt" func main() { fmt.Println("Hello, World!") }//Println不能写作println
在命令行输出:
若页面回显Hello,World! 则说明Go环境安装成功。
我们以下面的代码为例:
package main import "fmt" func main() { /* 输出 */ fmt.Println("Hello, World!") }
Go语言中的每个文件都属于一个包(package)。包声明用于定义当前文件所属的包名,不同的包之间可以通过包名进行引用和调用。
包的声明必须是在源文件中非注释的第一行,且每一个Go程序都包含一个名为main的包。
通过 import 关键字引入其他包,以便在当前文件中使用其他包提供的功能和类型。引入包后,就可以使用其提供的函数、变量和结构体等。本题中,fmt 包实现了格式化 IO(输入/输出)的函数
函数是实现特定功能的代码块。在Go语言中,函数由 func 关键字定义,并可以带有参数和返回值。通过定义函数,可以将代码模块化并重复使用。
在Go语言中,变量用于存储数据。使用关键字 var 来声明变量,同时指定变量的名称和类型。变量可以存储数值、字符串、布尔值等不同类型的数据。
语句是Go程序的执行单位,由一个或多个表达式组成。表达式用于计算值或执行特定操作。常见的语句包括赋值语句、条件语句(如 if 语句)、循环语句(如 for 语句)等。
注释用于向代码中添加注解和说明信息,对于其他人阅读代码时起到解释作用。在Go语言中,注释可以使用 // 开始的单行注释,或者使用 /* */ 包围的多行注释。
注意事项
func main() { // 错误,{ 不能在单独的行上 fmt.Println("Hello, World!") }
在 Go 语言中,大括号通常应该与相关的语句在同一行,并且需要有一个空格将大括号与前面的语句分隔开。
同样,函数的左括号 { 也应该与函数签名在同一行,并且右括号 } 应该独占一行。
Go 语言在大多数情况下不需要显式的分号来结束语句。编译器会根据规则自动插入分号。但是,如果一行上有多个语句,则需要使用分号将它们分隔开。
Go 语言采用驼峰命名法。变量和函数应该使用有意义且描述性的名称。公共(public)的标识符应该以大写字母开头,非公共(private)的标识符应该以小写字母开头。
也就是说,当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Qiu,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(类似于面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(类似于面向对象语言中的 protected )
在Go语言中,标记(Tokens)是源代码的最小语法单位,编译器将源代码分解为一系列标记进行解析和处理。以下是Go语言中的一些常见标记类型:
标识符(Identifiers):标识符用于表示变量、函数、类型等的名称。标识符由大小写字母、数字和下划线组成,并且不能以数字开头。
以下是无效的标识符: 1aht(以数字开头) case(Go 语言的关键字) a+3(运算符是不允许的)
关键字(Keywords):Go语言预先定义了一些关键字,它们具有特殊的含义和用途,例如if、for、func等。关键字不能作为标识符使用。
运算符(Operators):运算符用于执行各种算术、逻辑和比较操作,例如+、-、*、/等。
分隔符(Delimiters):分隔符用于将程序的不同部分分隔开来,例如括号( )、花括号{ }、方括号[ ]、逗号,、分号;等。
字面量(Literals):字面量表示直接使用的常量值,例如整型字面量123、浮点型字面量3.14、字符串字面量"QiuShuo"、布尔字面量true和false等。
注释(Comments):注释用于向代码中添加注解和说明,不会被编译器解析。单行注释以//开头,多行注释以/*开始,以*/结束。
举个例子:
fmt.Println("Hello, World!")
以上代码含有6个标记:
1. fmt 2. . 3. Println 4. ( 5. "Hello, World!" 6. )
这些标记构成了Go语言源代码的基本元素,它们按照一定的规则组合在一起形成具有意义的句子和表达式。编译器通过解析这些标记来理解和执行代码逻辑。
在 Go 程序中,一行代表一个语句结束。每个语句不需要以分号结尾。
如果将多个语句写在同一行,它们必须使用 ; 进行区分,使编译器理解代码逻辑。
例如:
package main import "fmt" func main() { var a = 10; var b = 20; fmt.Println(a + b) }
但我们并不推荐这种做法,因为它会降低代码的可读性。
Go 语言的字符串连接可以通过 + 实现:
package main import "fmt" func main() { fmt.Println("Qiu" + "Shuo") }
Go 语言中变量的声明必须使用空格隔开,例如:
var a float const Pi float64 = 3.14159265358979323846
在关键字和表达式之间要使用空格,例如:
if x<20 { // do something }
在函数调用时,函数名和左边等号之间要使用空格,参数之间也要使用空格。
例如:
result = add(2, 3)
Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
它的语法如下:
func Sprintf(format string, a ...interface{}) string
其中,format 是一个表示格式的字符串,a ...interface{} 是一个可变参数,用于替换格式字符串中的占位符。
举个例子:
package main import "fmt" func main() { var code = 1 var date = "2020" var url = "Code=%d&date=%s" var a = fmt.Sprintf(url, code, date) fmt.Println(a) }
fmt.Sprintf() 函数将 code 和 date 的值替换到 url 字符串中,并将结果存储在变量 a 中。然后使用 fmt.Println() 函数打印出结果:
举个例子:
package main import "fmt" func main() { var code = 2 var date = "1990" var url = "code=%d&date=%s" fmt.Printf(url,code,date) }
fmt.Printf() 函数会将 code 和 date 的值替换到 url 字符串中,并将结果打印出来:
在 Go 语言中,数据类型用于定义数据的存储和操作方式,关于 Go 语言中基本数据类型的详细说明如下:
1. 整数类型: - int:根据程序运行的平台,可以是 32 位或 64 位整数类型。 - int8、int16、int32、int64:固定长度的有符号整数类型。 - uint、uint8、uint16、uint32、uint64:固定长度的无符号整数类型。 2. 浮点数类型: - float32:单精度浮点数类型,占用 32 位。 - float64:双精度浮点数类型,占用 64 位。 3. 复数类型: - complex64:包含实部和虚部为 float32 类型的复数。 - complex128:包含实部和虚部为 float64 类型的复数。 4. 布尔类型: - bool:表示逻辑值,只能取 true 或 false。 5. 字符串类型: - string:表示文本数据,由一系列 Unicode 字符组成。 6. 字符类型: - rune:表示单个 Unicode 字符,类型别名为 int32,常用于处理 Unicode 字符串。 7. 字节类型: - byte:表示单个字节的数据,类型别名为 uint8,常用于处理二进制数据。 8. 指针类型: - *T:表示指向类型 T 的指针,用于间接引用变量。 9. 数组类型: - [n]T:表示具有固定长度 n 的同类型元素的数组。 10. 切片类型: - []T:表示可变长度的同类型元素序列。 - 切片可以动态增长和缩减,通常比数组更灵活和方便。 11. 映射类型: - map[K]V:表示键值对的无序集合。 - K 表示键的类型,V 表示值的类型。 - 常用于实现字典、关联数组等数据结构。 12. 结构体类型: - struct:表示用户自定义的复合数据类型。 - 可以包含不同类型的字段来组成一个结构。 13. 函数类型: - func:表示函数类型。 - 在 Go 语言中,函数是一等公民,可以作为参数、返回值等。 14. 接口类型: - interface:表示一组方法的抽象集合。 - 可以通过实现接口来达到多态的效果。
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
我们使用var关键字来声明变量,如:
var a string
也可以一次声明多个变量:
var b, c int
我们也可用 := 来声明变量:
a := 1 a, b, c := 5, 7, "abc"
例如:
var s string = "qiushuo" 等同于 s:="qiushuo"
注意:在声明变量,就不能再对该变量进行声明。
所以在声明变量之后,不能再使用:=对其赋值,而是要使用=进行赋值。
例如: a := 20 a = 24
当变量没有被初始化时,变量为系统默认设置的值。
举个例子:
package main import "fmt" func main() { // 声明一个变量并初始化 var a = "Qiushuo" //变量 a 的类型被推断为字符串类型,所以不需要显式地指定它为 string 类型。 //编译器会根据赋值的值来确定变量的类型。 fmt.Println(a) // 没有初始化就为零值 var b int fmt.Println(b) // bool 零值为 false var c bool fmt.Println(c) var d string fmt.Println(d) }
输出:
Qiushuo 0 false ""
在编程中,变量可以分为全局变量和局部变量,它们的作用域和生命周期有所不同:
示例代码:
package main import "fmt" var globalVariable int // 定义一个全局变量:globalVariable func main() { globalVariable = 10 // 在主函数中访问和修改全局变量 fmt.Println(globalVariable) someFunction() } func someFunction() { fmt.Println(globalVariable) // 在其他函数中访问全局变量 }
运行结果:
示例代码:
package main import "fmt" func main() { someFunction() } func someFunction() { localVariable := 20 // 局部变量 fmt.Println(localVariable) }
在这个示例中,localVariable 是一个局部变量,只能在 someFunction() 函数内部访问。它在函数每次被调用时创建,并且每次调用都会有自己独立的 localVariable 实例。
反例
package main import "fmt" func main() { someFunction() fmt.Println(localVariable) // 在函数外部尝试访问局部变量,会导致编译错误 } func someFunction() { localVariable := 10 // 声明并初始化局部变量 fmt.Println(localVariable) // 在函数内部可以访问局部变量 }
要注意的是:
在 声明局部变量/声明局部变量并对其赋值 后却没有使用它会发生报错
但全局变量是允许声明并不被使用的
Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
实例如下:
package main import "fmt" /* 声明全局变量 */ var a int = 20 func main() { /* 声明局部变量 */ var a int = 10 fmt.Printf ("a=%d",a) }
输出如下:
a=10
在Go语言中,常量(Constants)是指在程序编译时就确定并且不可更改的值,也就是说,常量不能被重新赋值或取地址,并且不能在运行时修改。
以下是定义常量的语法:
const identifier [type] = value
多个相同类型的声明可以简写为:
const Name1, Name2 = value1, value2
其中:
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
注意:常量的命名应该遵循Go语言的命名规范,通常使用驼峰命名法(camelCase),并且常量一般使用大写字母命名以表示其为不可变的值。
以下是一些常见的常量示例:
const Pi = 3.14 // 定义一个浮点数常量 const MaxSize int = 100 // 定义一个整数常量 const truth = true // 定义一个布尔常量
const ( Monday = 0 Tuesday = 1 Wednesday = 2 Thursday = 3 Friday = 4 Saturday = 5 Sunday = 6 )
const Greeting = "Hello, world!" // 定义一个字符串常量
举个例子:
package main import "unsafe" import "fmt" func main() { const ( strLen = len("hello") // 使用 len() 函数计算字符串长度 arrayLen = len([3]int{1, 2, 3}) // 使用 len() 函数计算数组长度 size = unsafe.Sizeof(int(0)) // 使用 unsafe.Sizeof() 函数计算整数类型的大小 ) sliceLen := len([]int{1, 2, 3}) // 使用 len() 函数计算切片长度 capacity := cap(make(chan int, 10)) // 使用 cap() 函数计算通道容量 fmt.Println(strLen, sliceLen, arrayLen, capacity, size) }
iota 用于常量的自增计数。
在常量声明中,iota 的初始值为 0,然后每次在下一个常量声明中使用时都会自动自增。它通常与常量表达式一起使用,在每个常量声明中按顺序递增。
以下是一个示例代码,演示了 iota 的使用:
package main import "fmt" const ( Red = iota // 0 Green // 1 Blue // 2 ) func main() { fmt.Println(Red, Green, Blue) }
输出结果为:
0 1 2
更复杂一点:
package main import "fmt" func main() { const ( a = iota //0 b //1 c //2 d = "qiu" //独立值,iota += 1, iota变为3 e //e是显式赋值的常量声明,它们不会影响 iota 的自增。因此 e 的值仍然是 "qiu",同时iota += 1, iota变为4 f = 100 //iota +=1,iota变为5 g //100 iota +=1,iota变为6 h = iota //7,恢复计数 i //8 ) fmt.Println(a,b,c,d,e,f,g,h,i) }
输出:
0 1 2 qiu qiu 100 100 7 8
Go语言中常用的运算符包括:
运算符 | 描述 | 示例 |
---|---|---|
+ | 相加 | a + b |
- | 相减 | a - b |
* | 相乘 | a * b |
/ | 相除 | a / b |
% | 取余 | a % b |
++ | 自增 | a++ 或 ++a |
– | 自减 | a-- 或 --a |
关系运算符:用于比较两个值之间的关系,如相等 ==,不等 !=,大于 >,小于 <,大于等于 >=,小于等于 <=。
逻辑运算符:用于进行逻辑判断,包括逻辑与 &&,逻辑或 ||,逻辑非 !。
运算符 | 描述 | 示例 |
---|---|---|
&& | 逻辑与 | a && b |
|| | 逻辑或 | a || b |
! | 逻辑非 | !a |
运算符 | 描述 | 示例 |
---|---|---|
& | 按位与 | a & b |
| | 按位或 | a | b |
^ | 按位异或 | a ^ b |
~ | 按位取反 | ~a |
<< | 左移 | a << b |
>> | 右移 | a >> b |
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
赋值运算符:用于将值赋给变量,如赋值 =,加法赋值 +=,减法赋值 -=,乘法赋值 *=,除法赋值 /=,取余赋值 %=
其他运算符:包括取地址 &,取值 *,指针运算符 ->,条件运算符 ? :,类型断言运算符 .,管道运算符 |,索引运算符 [],以及取长度 len() 等。
运算符 | 描述 | 示例 |
---|---|---|
& | 取地址运算符 | &a |
* | 取值运算符 | *ptr |
-> | 指针运算符(C语言中使用) | ptr->data |
?: | 条件运算符 | condition ? expr1 : expr2 |
. | 类型断言运算符 | value.(type) |
| | 管道运算符 | cmd1 | cmd2 |
[] | 索引运算符 | array[index] |
len() | 取长度函数 | len(array) |
除了要熟练使用这些运算符之外,我们还需要掌握运算符优先级。
下面的表格中,由上至下代表优先级由高到低:
优先级 | 运算符 |
---|---|
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
Go语言中的条件语句有两种形式:if语句和switch语句。
if语句用于根据一个条件的真假执行不同的代码块。
语法:
if condition { // 当条件为真时执行的代码块 } else { // 当条件为假时执行的代码块(可选) }
示例:
num := 10 if num%2 == 0 { fmt.Println("num是偶数") } else { fmt.Println("num是奇数") }
除了基本的if语句外,还可以使用if语句的简短语法:
语法:
if condition { // 当条件为真时执行的代码块 }
示例:
if num := 10; num > 0 { fmt.Println("num大于0") }
switch语句用于基于不同的条件执行不同的代码块。
语法:
switch expression { case value1: // 当expression等于value1时执行的代码块 case value2: // 当expression等于value2时执行的代码块 default: // 当expression不等于任何已匹配的值时执行的代码块(可选) }
示例:
grade := "C" switch grade { case "A": fmt.Println("优秀") case "B": fmt.Println("良好") case "C": fmt.Println("及格") default: fmt.Println("不及格") }
在Go语言的switch语句中,每个case后面的值和expression的类型必须相同。此外,当匹配的case执行完毕后,不会自动执行后续的case,而是跳出switch语句,除非使用fallthrough关键字。
Go语言中有三种主要的循环语句:for循环、while循环和do-while循环。
for循环用于重复执行一段代码块,可以指定循环的起始条件、循环执行前的初始化语句,以及每次循环结束后的迭代语句。
语法:
for 初始化语句; 条件表达式; 迭代语句 { // 循环体 }
示例:
for i := 0; i < 5; i++ { fmt.Println(i) }
Go语言中没有专门的while循环关键字,但可以使用for循环来实现类似的功能。
语法:
for 条件表达式 { // 循环体 }
示例:
i := 0 for i < 5 { fmt.Println(i) i++ }
Go语言中也没有专门的do-while循环关键字,但可以使用for循环结合break语句来实现类似的功能。
语法:
for { // 循环体 if !条件表达式 { break } }
示例:
i := 0 for { fmt.Println(i) i++ if i >= 5 { break } }
除了以上常用的循环语句外,Go语言还提供了range关键字用于遍历数组、切片、映射等数据结构。
示例:
arr := []int{1, 2, 3, 4, 5} for index, value := range arr { fmt.Println(index, value) }//在每次循环时,range 返回两个值:当前元素的下标 index 和对应的值 value
在循环中,常用continue和goto控制流程:
continue 是一个控制流程的关键字,用于跳过当前循环迭代中的剩余代码,直接进入下一次迭代。
示例:
for i := 0; i < 5; i++ { if i == 2 { continue } fmt.Println(i) }
上述代码的输出结果为:
0 1 3 //i==2时,跳出当前循环,进入下一次循环 4
goto 是一个控制流程的关键字,用于无条件地转移到程序中的一个标签。
示例:
package main import "fmt" func main() { i := 0 Loop: for i < 5 { fmt.Println(i) i++ if i == 3 { goto Loop } } }
上述代码的输出结果为:
0 1 2 3 4
在这个例子中,我们使用 goto 关键字和标签 Loop 实现了一个完整的循环,当 i 的值为 3 时,程序会跳转到标签 Loop 处,继续执行循环。
可以使用函数来执行需要的功能。Go 语言程序中最少有个 main() 函数。
Go 语言函数定义格式如下:
func function_name( [parameter list] ) [return_types] { 函数体 }
举个例子:
package main import "fmt" // 定义一个计算两个整数之和的函数 func add(x int, y int) int { return x + y } func main() { result := add(3, 5) fmt.Println(result) // 输出结果为 8 }
以上代码定义了一个名为 add 的函数。该函数接受两个整数类型的参数 x 和 y,并返回它们的和。在 main 函数中,我们调用 add 函数,并将参数传递为 3 和 5。然后,将返回的结果赋值给变量 result。
在Go语言中,数组是一种固定长度的数据结构,用于存储一组相同类型的元素。
语法格式如下:
var arrayName [size]dataType
其中,arrayName 是数组的名称,size 是数组的大小,dataType 是数组中元素的数据类型。
举个例子:
var a [10]float64
以上定义了数组 a 长度为 10 类型为 float64
数组的初始化:
var numbers [5]int
数组初始值为0
var numbers = [5]int{1, 2, 3, 4, 5}
分别被赋值为1,2,3,4,5
numbers := [5]int{1, 2, 3, 4, 5}
如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var a = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
// 将索引为 1 和 3 的元素初始化 a := [5]float32{1:9.9,3:7.7}
访问数组元素:
数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:
var b float32 = a[9]
以上实例读取了数组 a 第 10 个元素的值。
在Go语言中,指针是一种特殊的数据类型,用于存储变量的内存地址。指针可以用于间接访问和修改变量的值。
语法格式:
声明指针:
var ptr *Type
其中,ptr 是指针变量的名称,Type 是指针所指向的变量类型。指针变量的初始化可以选择性地进行。
取地址操作符(&):
ptr = &variable
使用 & 运算符可以获取变量 variable 的内存地址,并将其赋值给指针变量 ptr。
解引用操作符(*):
value = *ptr
使用 * 运算符可以获取指针变量 ptr 所指向的变量的值。
修改指针所指变量的值:
*ptr = newValue
使用 * 运算符,可以修改指针变量 ptr 所指向的变量的值。
下面是使用Go语言定义和操作指针的示例代码:
package main import "fmt" func main() { // 定义一个整数变量 var num int = 42 // 声明一个指向整数的指针,并将其初始化为num的内存地址 var ptr *int = &num // 输出指针的值和所指向的变量的值 fmt.Println("指针的值:", ptr) // 输出: 指针的值: 0xc000096068 fmt.Println("指针所指向的变量的值:", *ptr) // 输出: 指针所指向的变量的值: 42 // 修改所指向的变量的值 *ptr = 100 // 输出被修改后的变量的值 fmt.Println("被修改后的变量的值:", num) // 输出: 被修改后的变量的值: 100 }
在Go语言中,结构体(Struct)是一种用户定义的复合类型,用于封装不同类型的数据字段。结构体可以包含零个或多个字段,并且可以根据需要进行组合。
语法格式
定义结构体:
type StructName struct { field1 fieldType1 field2 fieldType2 // ... }
其中,StructName 是结构体的名称,field1、field2 等是字段的名称,fieldType1、fieldType2 等是字段的类型。
创建结构体对象:
var obj StructName
使用 var 声明结构体对象,并初始化为零值。
访问结构体字段:
obj.field = value
使用对象名加上.操作符来访问结构体中的字段,并进行赋值或获取值操作。
下面代码展示了结构体的定义和使用:
package main import "fmt" // 定义一个结构体类型 type Person struct { name string age int } func main() { // 创建一个结构体对象p1 var p1 Person // 访问结构体字段并赋值 p1.name = "Alice" p1.age = 20 // 输出结构体字段的值 fmt.Println("姓名:", p1.name) fmt.Println("年龄:", p1.age) // 创建结构体对象并初始化 p2 := Person{name: "Bob", age: 25} // 输出结构体字段的值 fmt.Println("姓名:", p2.name) fmt.Println("年龄:", p2.age) }
结构体参数传递
在Go语言中,可以将结构体作为函数的参数进行传递,以便在函数中对结构体进行操作或使用结构体中的字段。
结构体作为函数参数有两种传递方式:值传递和引用传递。
值传递(Pass by Value):
在值传递方式下,函数会复制传入的结构体,函数内部对结构体的修改不会影响原始结构体。
下面是一个使用值传递方式的示例代码:
package main import "fmt" type Person struct { name string age int } func updateName(p Person) { p.name = "Alice" } func main() { p := Person{name: "Bob", age: 25} fmt.Println("修改前:", p) updateName(p) fmt.Println("修改后:", p) } //输出 修改前: {Bob 25} 修改后: {Bob 25}
在该示例中,我们定义了一个 Person 结构体,并在 updateName 函数中修改了结构体的 name 字段。然而,在 main 函数中调用 updateName 函数时,输出结果仍然是原来的结构体,表明在函数内部对结构体字段的修改不会影响原始结构体。
引用传递(Pass by Reference):
在引用传递方式下,函数接收的是结构体的指针,函数内部对结构体的修改会影响原始结构体。
下面是一个使用引用传递方式的示例代码:
package main import "fmt" type Person struct { name string age int } func updateName(p *Person) { p.name = "Alice" } func main() { p := &Person{name: "Bob", age: 25} fmt.Println("修改前:", p) updateName(p) fmt.Println("修改后:", p) }
结构体指针
在Go语言中,可以使用指针来操作结构体。通过指针,可以直接修改结构体的字段值,而无需进行复制操作。
指向结构体的指针,定义格式如下:
var struct_pointer *Person
查看结构体变量地址,可以将 & 符号放置于结构体变量前:
struct_pointer = &Person1
使用结构体指针访问结构体成员,使用 “.” 操作符:
struct_pointer.title
实例如下:
package main import "fmt" type Person struct { name string age int } func main() { var Person1 Person /* 声明 Person1 为 Person 类型 */ var Person2 Person /* 声明 Person2 为 Person 类型 */ /* Person 1 描述 */ Person1.name="秋说" Person1.age="1" /* Person 2 描述 */ Person2.name="花无缺" Person2.age="2" /* 打印 Person1 信息 */ printPerson(&Person1) /* 打印 Person2 信息 */ printPerson(&Person2) } func printPerson( Person *Person ) { fmt.Printf( "Person name : %s\n", Person.name) fmt.Printf( "Person age : %d\n", Person.age) }
Go语言中的切片(Slice)是一种动态数组的抽象。切片提供了对底层数组的封装,可以方便地操作和管理数组的片段。
具体来说:
在Go语言中,使用切片的语法为[]T,其中T表示切片中元素的类型。创建切片可以通过以下方式:
slice := array[start:end]
其中,array 是一个已有的数组,start 是切片的起始索引(包含),end 是切片的结束索引(不包含)。这个语法将创建一个切片 slice,包含了从 start 索引到 end-1 索引的元素。
实例:
arr := [5]int{1, 2, 3, 4, 5} slice := arr[1:4] // 创建一个切片,包含arr索引1到索引3的元素,即[2, 3, 4]
slice := make([]T, length, capacity)
其中,T 是切片中元素的类型,length 是切片的长度,capacity 是切片的容量。通过 make 函数创建的切片具有指定的长度和容量,并初始化了相应类型的零值。
实例:
slice := make([]int, 3, 5) // 创建一个长度为3,容量为5的切片
slice := []T{element1, element2, ..., elementN}
其中,T 是切片中元素的类型,element1 到 elementN 是要添加到切片中的元素。使用切片字面量创建切片时,切片的长度会根据提供的元素个数自动确定。
实例:
slice := []int{1, 2, 3, 4, 5} // 直接创建一个切片,包含元素1到5
切片常用的操作有以下几种:
length := len(slice) // 获取切片的长度 capacity := cap(slice) // 获取切片的容量
slice = append(slice, 6) // 在切片的末尾追加元素6
for index, value := range slice { // 遍历切片的索引和对应的值 }
newSlice := slice[1:3] // 对切片进行切割,创建新的切片包含索引1到2的元素
slice = append(slice, element1, element2, ..., elementN)
其中,slice 是要追加元素的切片,element1 到 elementN 是要添加到切片中的元素。append() 函数会返回一个新的切片,如果原切片的容量不够,会自动分配更大的底层数组,并将新元素追加到其中。
示例:
slice := []int{1, 2, 3} slice = append(slice, 4, 5) // 追加元素 4 和 5
copy(destSlice, srcSlice)
其中,destSlice 是目标切片,srcSlice 是源切片。copy() 函数会将源切片中的元素复制到目标切片中,两个切片必须有相同的元素类型。
示例:
srcSlice := []int{1, 2, 3} destSlice := make([]int, len(srcSlice)) copy(destSlice, srcSlice) // 复制 srcSlice 到 destSlice
需要注意的是,append() 函数会返回一个新的切片,因此在使用时需要将其赋值给原来的切片变量;而 copy() 函数则直接在目标切片上进行复制操作。
以下是一个使用切片的示例代码:
package main import "fmt" func main() { // 创建切片 numbers := []int{1, 2, 3, 4, 5} // 获取切片长度和容量 fmt.Println("Length:", len(numbers)) // 输出:Length: 5 fmt.Println("Capacity:", cap(numbers)) // 输出:Capacity: 5 // 追加元素 numbers = append(numbers, 6) fmt.Println(numbers) // 输出:[1 2 3 4 5 6] // 遍历切片 for index, value := range numbers { fmt.Println(index, value) } // 切割切片 newSlice := numbers[1:4] fmt.Println(newSlice) // 输出:[2 3 4] }
Go语言中的范围(Range)是一种迭代数据结构(如数组、切片、映射等)的元素的方法。通过使用范围,可以遍历并访问数据结构中的每个元素,而不需要使用索引或迭代器。
范围语法如下:
for index, value := range collection { // 使用 index 和 value 来处理元素 }
其中,collection 是要迭代的数据结构(如数组、切片、映射等),index 是当前元素的索引,value 则是当前元素的值。在循环的每次迭代中,范围语句会将 index 和 value 更新为下一个元素的索引和值,直到遍历完整个集合。
范围还支持忽略索引或值,如果你只关心其中一项,可以使用 _(下划线)来忽略另一项。例如:
for _, value := range collection { // 只使用 value 处理元素,忽略索引 }
举个例子:
遍历简单的数组,2**%d 的结果为 2 对应的次方数:
package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } }
输出:
2**0 = 1 2**1 = 2 2**2 = 4 2**3 = 8 2**4 = 16 2**5 = 32 2**6 = 64 2**7 = 128
for 循环的 range 格式可以省略 key 和 value,如下实例:
package main import "fmt" func main() { map1 := make(map[int]float32) map1[1] = 1.0 map1[2] = 2.0 map1[3] = 3.0 map1[4] = 4.0 // 读取 key 和 value for key, value := range map1 { fmt.Printf("key is: %d - value is: %f\n", key, value) } // 读取 key for key := range map1 { fmt.Printf("key is: %d\n", key) } // 读取 value for _, value := range map1 { fmt.Printf("value is: %f\n", value) } }
以上实例运行输出结果为:
key is: 4 - value is: 4.000000 key is: 1 - value is: 1.000000 key is: 2 - value is: 2.000000 key is: 3 - value is: 3.000000 key is: 1 key is: 2 key is: 3 key is: 4 value is: 1.000000 value is: 2.000000 value is: 3.000000 value is: 4.000000
在Go语言中,Map(映射)是一种无序的键值对的集合。可以将其看作是一个动态的数组,其中每个元素都是一个键值对,即一个唯一的键关联一个值。您可以使用键来访问映射中的值,并可以通过添加、修改和删除元素来修改映射。
Map的定义方式如下:
// 定义一个键为string类型,值为int类型的map var m map[string]int // 初始化map m = map[string]int{"foo": 1, "bar": 2} // 或者可以使用make函数初始化map m = make(map[string]int)
可以通过make()函数来初始化一个空的Map对象,然后使用 map[key] = value 的方式向Map中添加元素。例如:
m := make(map[string]int) // 添加元素 m["foo"] = 1 m["bar"] = 2
可以使用 delete() 函数来删除Map中的元素:
delete(m, "bar") // 删除键为"bar"的元素
可以使用 len() 函数获取Map中键值对的数量。还可以使用范围(Range)语句迭代Map中的所有键值对:
for key, value := range m { fmt.Println(key, value) }
需要注意的是,Map 的遍历是无序的,因为 Map 内部实现了哈希表(Hash Table)来存储键值对,所以元素的排列顺序是不确定的。
package main import "fmt" func main() { // 定义一个string类型的键,int类型的值的map scores := make(map[string]int) // 添加学生的成绩 scores["张三"] = 90 scores["李四"] = 85 scores["王五"] = 98 // 循环遍历map中的每个元素 for name, score := range scores { fmt.Printf("%s的成绩是:%d\n", name, score) } // 删除指定的元素 delete(scores, "李四") // 输出删除后的map fmt.Println("删除李四之后的成绩:", scores) // 判断指定的键是否存在 if score, ok := scores["张三"]; ok { fmt.Printf("张三的成绩是:%d\n", score) } else { fmt.Println("找不到张三的成绩") } }
输出:
张三的成绩是:90 李四的成绩是:85 王五的成绩是:98 删除李四之后的成绩: map[王五:98 张三:90] 张三的成绩是:90
在Go语言中,递归函数是指在函数体内调用自身的函数。递归函数是一种常用的算法设计技巧,可以简化问题的解决方法,并且能够解决一些需要重复执行相同操作的问题。
语法格式如下:
func recursion() { recursion() /* 函数调用自身 */ } func main() { recursion() }
下面是一个示例,展示了如何使用递归函数来计算一个数的阶乘:
package main import "fmt" // 计算n的阶乘 func factorial(n int) int { if n <= 1 { return 1 } return n * factorial(n-1) } func main() { num := 5 result := factorial(num) fmt.Printf("%d的阶乘是:%d\n", num, result) }
在上面的代码中,factorial()函数是一个递归函数,用于计算给定数n的阶乘。当n为1或更小的值时,递归终止条件被满足,直接返回1。否则,函数会调用自身,并将n减1后的结果与n相乘,然后返回乘积作为结果。
在main()函数中,我们调用了factorial()函数来计算num(这里是5)的阶乘,并将结果打印出来。
运行该程序,输出结果为:
5的阶乘是:120
以下实例通过 Go 语言的递归函数实现斐波那契数列:
package main import "fmt" func fibonacci(n int) int { if n < 2 { return n } return fibonacci(n-2) + fibonacci(n-1) } func main() { var i int for i = 0; i < 10; i++ { fmt.Printf("%d\t", fibonacci(i)) } }
以上实例执行输出结果为:
0 1 1 2 3 5 8 13 21 34
在Go语言中,可以使用类型转换将一个类型的值转换为另一个类型。Go语言支持显式类型转换,并且只能在相互兼容的类型之间进行转换。
下面是一些常见的类型转换示例:
package main import "fmt" func main() { // 整数类型转换 var x int = 10 var y float64 = float64(x) fmt.Println(y) // 数字类型转换 var a float64 = 3.14 var b int = int(a) fmt.Println(b) // 字符串类型转换 var s string = "100" var c int = int(s) // 错误示例,无法直接将字符串转换为整数 fmt.Println(c) // 使用strconv包进行字符串转换 import "strconv" var s string = "100" c, _ := strconv.Atoi(s) // 将字符串转换为整数 fmt.Println(c) }
在上述示例中,我们展示了几种常见的类型转换。首先,将整数类型x转换为浮点数类型float64,并输出结果。然后,将浮点数类型a转换为整数类型int,并输出结果。接着,演示了错误的字符串类型转换示例,直接将字符串转换为整数会导致编译错误。为了解决这个问题,我们使用了strconv包中的Atoi()函数,将字符串转换为整数类型,并输出结果。
在进行类型转换时,如果两个类型不兼容或者转换不合法,编译器会报错。因此,在进行类型转换时,需要确保被转换的值和目标类型是兼容的。
另外,strconv包提供了更多的字符串转换函数,例如ParseInt()、ParseFloat()等,可以根据需要选择适合的方法进行类型转换。
Go语言中的接口(interface)是一种类型,它描述了一组方法的集合。接口定义了方法的签名,但是没有具体的实现代码。在Go语言中,通过实现接口的方法来实现接口的功能。
Go语言中声明接口的语法如下:
go type 接口名称 interface { 方法1() 返回类型 方法2() 返回类型 // ... }
其中,接口名称是你给接口起的名字。接口中定义了一组方法,每个方法都由方法名、参数列表和返回类型组成。你可以在接口中定义任意数量的方法。
下面是一个简单的示例:
package main import "fmt" // 定义接口 type Shape interface { Area() float64 Perimeter() float64 } // 定义结构体 Circle,并实现 Shape 接口的方法 type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } func (c Circle) Perimeter() float64 { return 2 * 3.14 * c.Radius } // 定义结构体 Rectangle,并实现 Shape 接口的方法 type Rectangle struct { Width float64 Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) } func main() { // 创建一个 Circle 实例 circle := Circle{Radius: 5} fmt.Println("Circle Area:", circle.Area()) fmt.Println("Circle Perimeter:", circle.Perimeter()) // 创建一个 Rectangle 实例 rectangle := Rectangle{Width: 3, Height: 4} fmt.Println("Rectangle Area:", rectangle.Area()) fmt.Println("Rectangle Perimeter:", rectangle.Perimeter()) }
上述代码中,我们定义了一个Shape接口,其中包含了Area()和Perimeter()两个方法。然后,我们创建了Circle和Rectangle两个结构体,并分别实现了Shape接口的方法。在main()函数中,我们创建了一个Circle实例和一个Rectangle实例,并调用它们的Area()和Perimeter()方法。
Go语言中的接口是隐式实现的,即不需要显式声明接口的实现,只需实现接口所定义的方法即可。
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error 类型是一个接口类型,这是它的定义:
type error interface { Error() string }
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现 }
在下面的例子中,我们在调用 Sqrt 的时候传递的一个负数,然后就得到了 non-nil 的 error 对象,将此对象与 nil 比较,结果为 true,所以 fmt.Println(fmt 包在处理 error 时会调用 Error 方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1) if err != nil { fmt.Println(err) }
package main import ( "fmt" ) // 定义一个 DivideError 结构 type DivideError struct { dividee int divider int } // 实现 `error` 接口 func (de *DivideError) Error() string { strFormat := ` Cannot proceed, the divider is zero. dividee: %d divider: 0 ` return fmt.Sprintf(strFormat, de.dividee) } // 定义 `int` 类型除法运算的函数 func Divide(varDividee int, varDivider int) (result int, errorMsg string) { if varDivider == 0 { dData := DivideError{ dividee: varDividee, divider: varDivider, } errorMsg = dData.Error() return } else { return varDividee / varDivider, "" } } func main() { // 正常情况 if result, errorMsg := Divide(100, 10); errorMsg == "" { fmt.Println("100/10 = ", result) } // 当除数为零的时候会返回错误信息 if _, errorMsg := Divide(100, 0); errorMsg != "" { fmt.Println("errorMsg is: ", errorMsg) } }
输出如下:
100/10 = 10 errorMsg is: Cannot proceed, the divider is zero. dividee: 100 divider: 0
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
例如:
go f(x, y, z)
开启一个新的 goroutine:
f(x, y, z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
实例如下:
package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") }
以上代码创建了两个 goroutine 分别输出 “hello” 和 “world”
- Go编译器(go):Go编译器是Go语言的官方编译器,用于将Go源代码编译成可执行文件或库。它还提供了一些命令行参数和选项,用于控制编译过程。
- Go命令(go):Go命令是Go语言的一个重要工具,用于构建、安装和管理Go项目。通过Go命令,你可以创建新的项目、构建项目、运行测试、下载依赖包等。
- Go Modules:Go Modules 是 Go 1.11 版本引入的官方依赖管理工具。它能够管理项目中的依赖关系,并支持版本控制,可以解决在 Go 语言中对于第三方库版本管理的难题。
- GoDoc:GoDoc 是Go语言官方提供的文档工具,用于生成Go代码的文档。通过GoDoc,可以将代码中的文档注释提取出来,自动生成易于阅读的HTML文档,方便其他开发人员查看和使用你的代码。
- GoLand / Visual Studio Code / Sublime Text 等集成开发环境(IDE):有许多流行的集成开发环境(IDE)对于Go语言开发提供了强大的支持。这些IDE通常具有代码补全、调试器、内置终端等功能,可以提高开发效率并简化调试过程。
- VSCode Go插件:VSCode Go 插件是 Visual Studio Code 编辑器的一个插件,它提供了很多有用的功能,如语法高亮、自动完成、代码导航、格式化等。它可以帮助你更方便地编写和调试Go代码。
- GoTest:GoTest 是Go语言官方提供的测试工具,用于编写和运行Go的单元测试和性能测试。通过编写测试用例并运行GoTest,可以确保代码的正确性和性能。
- GoLint:GoLint 是一个静态代码分析工具,用于检查Go代码的风格和潜在问题。它可以提供一些建议和警告,帮助开发人员遵循Go语言的最佳实践。
本文内容均为重点知识点,是学习Go的不二选择。学习不是一蹴而就的过程,切勿囫囵吞枣。
我是秋说,我们下次见。