写在前面
鉴于全网Go语言知识点的总结分散难懂、良莠不齐,为了避免初学者少走弯路,更好更快地掌握Go知识,博主特地将自己所学的笔记分享出来。
Go语言概念
Go语言(又称为Golang)是一种开源的编程语言,由Google于2007年启动并于2009年首次公开发布。Go语言是一门静态类型、编译型的语言,旨在提供一种简单、高效、可靠的编程方式。
语言特色
现在越来越多的人开始使用Go语言进行开发,其原因有以下几点:
- Go语言设计简洁,语法清晰明了,容易上手和理解。它避免了冗余的语法和复杂的概念,使得编写和维护代码更加高效。
- Go语言天生支持并发编程,提供了轻量级的Goroutine和通道(channel)机制,使并发编程变得更加简单和安全。
- 高性能:Go语言在运行时表现出色,具有低延迟和高吞吐量。它采用了垃圾回收机制,使内存管理变得自动化且高效,同时还提供了一些优化策略,如原生的协程调度器和快速编译等。
- 内建工具:Go语言提供了丰富的标准库,覆盖了网络编程、文件处理、文本处理、加密解密等各个领域。它还有强大的构建工具,可以方便地进行代码的构建、测试和分发。
Go 语言用途
Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
对于高性能分布式系统领域而言,Go 语言比大多数语言有着更高的开发效率。它提供了海量并行的支持,十分适用于游戏服务端的开发。
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环境安装成功。
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语言基础语法
Go标记
在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 格式化字符串并赋值给新串:
- Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
它的语法如下:
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()
函数打印出结果:
- Printf 根据格式化参数生成格式化的字符串并写入标准输出。
举个例子:
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 语言中,数据类型用于定义数据的存储和操作方式,关于 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语言变量
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 ""
局部变量和全局变量
在编程中,变量可以分为全局变量和局部变量,它们的作用域和生命周期有所不同:
- 全局变量(Global Variables):在函数外部定义的变量称为全局变量。全局变量具有全局范围,可以在整个程序、任何函数中被访问和使用。全局变量在程序启动时创建,在程序结束时销毁。
示例代码:
package main import "fmt" var globalVariable int // 定义一个全局变量:globalVariable func main() { globalVariable = 10 // 在主函数中访问和修改全局变量 fmt.Println(globalVariable) someFunction() } func someFunction() { fmt.Println(globalVariable) // 在其他函数中访问全局变量 }
运行结果:
- 局部变量(Local Variables):在函数内部定义的变量称为局部变量。局部变量只能在声明它们的函数内部被访问和使用,并且它们的作用域限定在这个函数内部。局部变量在函数每次被调用时创建,在函数结束时销毁。每个函数调用都会有自己的独立局部变量实例。
示例代码:
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语言常量
在Go语言中,常量(Constants)是指在程序编译时就确定并且不可更改的值,也就是说,常量不能被重新赋值或取地址,并且不能在运行时修改。
以下是定义常量的语法:
const identifier [type] = value
多个相同类型的声明可以简写为:
const Name1, Name2 = value1, value2
其中:
const
是关键字,用于声明常量。identifier
是常量的名称,遵循标识符命名规则。[type]
是可选的,表示常量的数据类型。如果省略类型信息,Go语言会根据所赋的值自动推断出常量的类型。
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
value
是常量的值,可以是基本数据类型(如整数、浮点数、布尔值等)或字符串。
注意:常量的命名应该遵循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!" // 定义一个字符串常量
- 常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数。
举个例子:
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
用于常量的自增计数。
在常量声明中,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语言运算符
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语言条件语句
Go语言中的条件语句有两种形式:if语句和switch语句。
- if语句:
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语句用于基于不同的条件执行不同的代码块。
语法:
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语言循环语句
Go语言中有三种主要的循环语句:for循环、while循环和do-while循环。
- for循环:
for循环用于重复执行一段代码块,可以指定循环的起始条件、循环执行前的初始化语句,以及每次循环结束后的迭代语句。
语法:
for 初始化语句; 条件表达式; 迭代语句 { // 循环体 }
示例:
for i := 0; i < 5; i++ { fmt.Println(i) }
- while循环:
Go语言中没有专门的while循环关键字,但可以使用for循环来实现类似的功能。
语法:
for 条件表达式 { // 循环体 }
示例:
i := 0 for i < 5 { fmt.Println(i) i++ }
- do-while循环:
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:
continue
是一个控制流程的关键字,用于跳过当前循环迭代中的剩余代码,直接进入下一次迭代。
示例:
for i := 0; i < 5; i++ { if i == 2 { continue } fmt.Println(i) }
上述代码的输出结果为:
0 1 3 //i==2时,跳出当前循环,进入下一次循环 4
- goto:
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语言函数
可以使用函数来执行需要的功能。Go 语言程序中最少有个 main() 函数。
Go 语言函数定义格式如下:
func function_name( [parameter list] ) [return_types] { 函数体 }
- func:函数由 func 开始声明
- function_name:函数名称
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 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语言数组
在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语言指针
在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语言结构体
在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语言切片
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]
- 使用 make 函数创建切片的模板语法:
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的元素
append()
和copy()
是在切片操作中常用的两个函数。
append()
函数用于向切片末尾追加元素或另一个切片:
slice = append(slice, element1, element2, ..., elementN)
其中,slice
是要追加元素的切片,element1
到 elementN
是要添加到切片中的元素。append()
函数会返回一个新的切片,如果原切片的容量不够,会自动分配更大的底层数组,并将新元素追加到其中。
示例:
slice := []int{1, 2, 3} slice = append(slice, 4, 5) // 追加元素 4 和 5
copy()
函数用于将一个切片的内容复制到另一个切片:
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)
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(集合)
在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 语言递归函数
在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语言中,可以使用类型转换将一个类型的值转换为另一个类型。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 语言接口
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 错误处理
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 语言支持并发,我们只需要通过 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命令,你可以创建新的项目、构建项目、运行测试、下载依赖包等。
- 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的不二选择。学习不是一蹴而就的过程,切勿囫囵吞枣。
我是秋说,我们下次见。