Golang学习笔记
安装Golang
来源
:linux 安装 golang - 知乎 (zhihu.com)
由于我用的是linux系统,所以本文采用linux的安装方式介绍,如果你使用的是Windows/Mac 也可以看下该文章,或者自己去下列地址进行操作。
Download and install - The Go Programming Language (google.cn)
该地址是golang中国官网,你可以放心的按照指示安装。
安装环境: ubuntu20.04操作系统
一、下载golang包
All releases - The Go Programming Language (google.cn)
根据不同的系统进行下载。
二、安装
2.1 删除 /usr/local/go 目录, 根据官方说法,如果之前有安装过go,那么需要将该位置的go目录删除掉
$ rm -rf /usr/local/go
2.2 解压并安装
# 切换到 golang.tar.gz 存放目录, 已经在跟目录就执行 cd Downloads/
$ cd /Download
# 解压golang到 /usr/local 下
$ sudo tar -C /usr/local -xzf go1.14.3.linux-amd64.tar.gz
2.3 设置环境变量
# 方式一:
# 修改 $HOME/.profile 或 /etc/profile 文件
# 这里可能会出现权限不足(ubuntu需要加sudo, centos需要切换成root权限)
# vi 进去后可能会出现键盘乱码,这是因为没有安装vim, 可以自行百度一下
$ sudo vi /etc/profile
# 在该文件最后一行插入(进入后,按 i键进入编辑模式)
$ export PATH=$PATH:/usr/local/go/bin
$ source /etc/profile
# 按 esc 退出编辑模式, 按 :wq 保存文件
$ go version
# 方式二:
# 修改~/.bashrc 文件
$ sudo gedit ~/.bashrc
# 在该文件最后一行插入
$ export PATH=$PATH:/usr/local/go/bin
$ source ~/.bashrc
$ go version
2.4 永远的"Hello, World!"
# 创建hello.go 并进入编辑模式, vim 命令可以自行百度或者后期我再写一篇文章
$ vi hello.go
复制以下内容(vi中的操作上面有讲,就不重复了)
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
控制台切换到 hello.go文件所在目录
$ go run ./hello.go
到此golang安装并测试完成
三、工具
3.1 代理
由于种(万)种(米)原(高)因(qiang), 我们安装golang的一些辅助工具会经常失败,这里就需要配置国内镜像
了
linux 用户按一下操作
# 控制台中输入一下命令
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
# 查看go所有相关配置
$ go env
# 修改 ~/.profile
$ echo "export GO111MODULE=on" >> ~/.profile
$ echo "export GOPROXY=https://goproxy.cn" >> ~/.profile
# 重启配置, 如果是在vscode中下载辅助工具失败后在配置的,重启vscode,再次重新下载即可
$ source ~/.profile
GO111MODULE
GO111MODULE
环境变量用于启用或禁用Go模块支持,它的值可以是以下几个之一:
off
:禁用模块支持,Go命令会使用旧的基于GOPATH的机制。on
:启用模块支持,Go命令会忽略GOPATH,只使用模块。auto
(默认值):Go命令会根据当前目录来决定是否启用模块支持。如果当前目录在GOPATH/src之外,并且包含go.mod文件,则使用模块。
GOPROXY
GOPROXY
环境变量用于指定Go命令在下载模块时使用的代理服务器。它的默认值是 https://proxy.golang.org
,这是Go官方提供的模块代理服务。在中国大陆,由于网络原因,你可能需要设置一个国内的代理服务器,例如:
-
https://goproxy.cn
:中国大陆的一个公共Go模块代理。 -
https://mirrors.aliyun.com/goproxy/
:阿里云提供的Go模块代理服务。
windows/mac用户前往该地址查看(git地址访问比较慢)
goproxy.cn/README.zh-CN.md at master · goproxy/goproxy.cn (github.com)【可靠的Go模块代理】
3.2 开发工具推荐使用免费的 vscode
Visual Studio Code - Code Editing. Redefined
最后,如果有不正确的地方欢迎大家指正。
Go语言基础
一、Go语言的优点
-
简单易用,Go语言上手非常容易,许多零基础的初学者在学习大约一周的时间后就可以使用Go语言完成些既定的任务。
-
编译度快,在Go编程语言工程结构简单,没有头文件和不允许包的交叉编译等规则,这也在很大程度上减少了编译所需的时间。
-
运行很快,虽然Go编译后的二进制文件与C编译后的二进制文件相比,执行速度要慢一些,但对于大多数应用程序来说。
-
支持并发,Go语言最主要的特性就是从语言层面原生支持并发。
-
垃圾回收,Go语言采用了垃圾回收技术,从而使程序员更加专注于业务本身,不用关心内存管理问题。
二、标识符
-
字符区分大小写:name与Name是两个不同的标识符。
-
首字符,可以是下画线()或字母,但不能是数字。
-
除首字符外其他字符,可以由下画线()、字母和数字构成。
-
关键字不能作为标识符。
三、关键字
Go语言的关键字是Go语言预定义的,具有特定意义的单词,不能用作变量名、函数名或其他标识符。
关键字 | 说明 |
---|---|
break | 用于跳出循环 |
default | 用于switch语句的默认情况 |
func | 用于定义函数 |
interface | 用于定义接口 |
select | 用于选择发送或接收操作的通道 |
case | 用于switch语句的条件分支 |
defer | 用于延迟函数的执行直到包含它的函数返回 |
go | 用于创建并发执行的goroutine |
map | 用于定义映射类型 |
struct | 用于定义结构体 |
chan | 用于定义通道类型 |
else | 用于if语句的else分支 |
goto | 用于无条件跳转到标签 |
package | 用于定义包名 |
switch | 用于多条件分支 |
const | 用于定义常量 |
fallthrough | 用于在switch语句中继续执行下一个case |
if | 用于条件语句 |
range | 用于遍历数组、切片、映射或通道 |
type | 用于定义类型 |
continue | 用于跳过当前循环的剩余代码,继续下一次循环 |
for | 用于循环语句 |
import | 用于导入包 |
return | 用于从函数返回 |
var | 用于声明变量 |
四、语句
在Go语言中,一般情况下一行代码表示一条语句,语句结束可以加分号,也可以省略分号。
多条语句会构成代码块也称复合语句,Go中的代码块是放到左右大括号({})中,语句块中可以有0~n条语句。
五、变量
在Go语言中,变量是用来存储数据值的标识符。Go语言是静态类型语言,这意味着所有变量在声明时都必须指定一个明确的数据类型
5.1标准声明
var 变量名 数据类型 = 表达式
5.2简短声明(只能在函数内部使用)
变量名:=表达式
5.3批量声明
var (
变量名1 类型1 = 表达式1
变量名2 类型2 = 表达式2
// ...
)
5.5类型推断
在声明变量时,如果初始化表达式被提供了,那么可以省略变量类型,由编译器自动推断。
var 变量名 = 表达式
Go语言的变量有以下几个特点:
- 变量必须使用后才能访问:Go语言中的变量声明后必须至少赋值一次才能使用,否则编译器会报错。
- 变量类型不可变:一旦一个变量被声明为某个类型,它的类型就不能改变。
- 零值初始化:如果变量声明时没有初始化,它们会自动初始化为其类型的零值(如
int
类型的零值为0
,string
类型的零值为空字符串)。
变量的作用域由其声明位置决定,如果在函数外部声明,则为全局变量(包级变量),作用域为整个包;如果在函数内部声明,则为局部变量,作用域为函数内部。
六、常量
const 常量名 数据类型 = 表达式
七、格式化输出
7.1格式转换符
使用格式转换符(verb)来指定输出数据的格式。可以在 fmt.Printf
、fmt.Sprintf
和其他格式化输出函数中使用,以控制输出的格式和内容。
转换符 | 描述 |
---|---|
%v | 默认格式的占位符,会自动选择变量的默认表现形式。 |
%+v | 当输出结构体时,会添加字段名。 |
%#v | Go语法格式的值,用于输出值的Go语言表示。 |
%T | 输出一个值的数据类型。 |
%% | 输出一个百分号 % 。 |
%b | 二进制表示。 |
%c | 字符(rune)(Unicode码点)。 |
%d | 十进制表示。 |
%o | 八进制表示。 |
%x , %X | 十六进制表示,%X 使用大写字母。 |
%e , %E | 科学计数法表示,%E 使用大写字母 E 。 |
%f , %F | 小数点表示,无小数部分会省略小数点。 |
%g , %G | 根据实际情况选择 %e 或 %f ,%G 使用大写字母。 |
%s | 字符串或切片的无解译字节。 |
%q | 带引号的字符串或字符,必要时会进行转义。 |
%x , %X | 十六进制,每个字节用两位表示,%X 使用大写字母。 |
%p | 十六进制表示的地址值。 |
%t | 布尔值(true 或 false )。 |
%n | 将输出到目前为止已写的字节数写入到指定的整数指针中。 |
格式化输出主要使用fmt
包中的几个函数,其中最常用的是Println
、Printf
和Print
。这些函数通过标准输出(通常是终端或控制台)显示信息。
7.2Println
Println
函数会自动添加空格分隔参数,并在末尾添加换行符。
package main
import "fmt"
func main() {
fmt.Println("Hello", "World")
}
输出:
Hello World
7.3Printf
Printf
函数允许你使用格式字符串来控制输出的格式。
package main
import "fmt"
func main() {
name := "Alice"
age := 25
fmt.Printf("Name: %s, Age: %d\n", name, age)
}
输出:
Name: Alice, Age: 25
格式占位符包括但不限于:
-
%s
:字符串 -
%d
:十进制整数 -
%f
:浮点数 -
%t
:布尔值 -
%v
:任何值的默认格式 -
%T
:值的类型 -
%%
:字面上的百分号字符
7.4Print
Print
函数类似于Println
,但它不会在参数之间添加空格,也不会在末尾添加换行符。
package main
import "fmt"
func main() {
fmt.Print("Hello ")
fmt.Print("World")
}
输出:
Hello World
7.5其他函数
fmt
包还提供了其他一些函数,如Fprint
、Fprintln
、Fprintf
等,这些函数与上述函数类似,但它们将输出写入指定的io.Writer
接口,例如文件。
使用这些函数,你可以根据需要格式化和输出信息。在实际编程中,根据不同的场景选择合适的函数可以使代码更加清晰和易于维护。
八、注释
单行注释:(//)
多行注释:(/*……*/)
在Go语言中,注释是用来解释代码和增加文档的非执行文本。Go支持两种类型的注释:
-
单行注释:以
//
开头,直到行尾的所有内容都被视为注释。单行注释只能注释紧跟其后的代码行。// 这是一个单行注释 var x int = 10 // 这里也是一个单行注释
-
多行注释:以
/*
开始,以*/
结束。多行注释可以跨越多行,但不能嵌套。/* 这是一个多行注释 它可以跨越多行 */ func main() { /* 这里也是一个多行注释 */ println("Hello, World!") }
注释对于代码的可读性和维护性非常重要。它们可以帮助其他开发者(和未来的你)理解代码的意图和功能。在Go中,注释也可以用来生成文档。如果一个包、函数、类型或变量的声明前面有一个以
//
开头的注释,那么这个注释可以用来自动生成文档。这些注释通常被称为文档注释。
文档注释的惯例是以//
后跟一个或多个空格,然后是描述性的文本。文档注释通常以被注释的元素的行为或目的开始,并且是完整的句子。// Package math provides basic constants and mathematical functions. package math // Sqrt returns the square root of x. func Sqrt(x float64) float64 { // ... }
Go的文档工具,如
godoc
,可以从这些注释中提取信息,并生成在线文档或HTML页面。
九、包
9.1声明包
声明包是每个Go源文件的第一行,使用关键字package
后面跟上包的名字。包是Go语言中代码组织和编译的单位,它将一组相关的源文件集合在一起,并提供了一种命名空间隔离的机制。
包声明的一般形式如下:
package 包名
例如,如果你正在编写一个名为math
的包,你的Go源文件将这样开始:
package math
在Go中,包名通常与其目录名匹配,但这不是强制的。包名应该使用小写字母,并且通常是单个单词。如果包名包含多个单词,通常使用驼峰命名法。
当你导入包时,你会使用包的导入路径,这是包在仓库中的目录路径。例如,标准库中的fmt
包的导入路径是fmt
,而如果你创建了一个名为math
的包,在仓库中的路径可能是github.com/yourusername/math
。
在Go项目中,通常会将相关的源文件放在同一个目录中,并共同组成一个包。这些源文件可以相互访问包内的变量、类型和函数,而不需要导出它们(即首字母大写)。如果想要在其他包中使用这些变量、类型或函数,必须导出它们,这意味着它们的名字必须以大写字母开头。
包的声明是Go语言中最基本的组织单位,它有助于保持代码的模块化和可重用性。
注意事项: 1.package语句应该放代码文件的第一行。
2.在每个代码文件中只能有一个包声明语句。
9.2导入包
在Go语言中,导入包是通过 import 语句实现的。import 语句用于在当前文件中导入其他包,以便可以使用这些包中定义的函数、变量、类型和接口。导入语句可以位于包声明(package语句)之后,在任何函数之外。
导入单个包
导入单个包时,可以直接指定包的路径:
import "fmt"
导入多个包
如果需要导入多个包,可以每行导入一个包,也可以在一行中导入多个包,用括号包裹:
import (
"fmt"
"math"
)
导入别名
如果两个或多个包有相同的名字,或者你想为导入的包指定一个更短的名称,可以使用别名:
import (
"fmt"
m "math"
)
在这个例子中,math
包被导入了,但是给它指定了一个别名 m
。这样,在当前文件中,你可以通过 m
来引用 math
包中的内容,而不是直接使用 math
。
导入特定包成员
Go 1.9 引入了导入声明的新特性,允许你只导入包中的特定成员,而不是整个包。这可以通过点(.
)操作符来实现:
import (
"fmt"
_ "math/rand" // 导入但不使用
"os"
"path/filepath"
)
func main() {
_, _ = os.Open("file.txt")
_ = filepath.Abs("file.txt")
}
在这个例子中,math/rand
包被导入了,但是使用下划线(_
)作为别名,这意味着包中的内容在当前文件中不可用。这通常用于包的初始化函数,即使不直接使用包中的其他内容,也会执行初始化。
导入本地包
如果你正在导入本地仓库中的包,需要指定包的相对路径或绝对路径:
import (
"github.com/user/repo/subpkg"
)
确保你的 Go 工作区(GOPATH)设置了正确的路径,以便 Go 编译器可以找到这些本地包。
导入标准库包
标准库中的包可以直接导入,不需要指定完整的路径,只需使用包名:
import "fmt"
标准库中的包通常都有唯一的名字,因此不需要别名。
正确导入包是编写 Go 代码的基础,它使得代码组织更加清晰,并且可以方便地重用其他包中的功能。
十、数据类型
10.1基本类型
整型
-
平台无关整型类型,他们数据占用的内存空间与平台相关,它占用空间分别是:8位、16 位、32位和64 位他们又分为:有符号的整数和无符号的整数。
-
平台相关整数类型,数据类型占用的内存空间是由系统决定的。
数据类型 | 占用空间(单位:位) |
---|---|
int8 | 8 |
int16 | 16 |
int32 | 32 |
uint | 与平台相关 |
int | 与平台相关 |
int64 | 64 |
uint8 | 8 |
uint16 | 16 |
uint32 | 32 |
uint64 | 64 |
byte | 等价于 uint8 |
uintptr | 无符号的指针 |
rune | 等价于 int32 |
整数表示方式
Go整数类型默认是为int,例如19表示的十进制整数。那么其他进制,如二进制数、八进制数和十六进制整数表示方式如下:
-
二进制数:以0b或0B为前缀,注意0是阿拉伯数字,例如0B10011表示十进制19。
-
八进制数,以0o或0O为前缀,第一个字符是阿拉伯数字0,第二个字符是英文字母o或O,例如0o23表示十进制19。
-
十六进制数:以0x或0X为前缀,注意0是阿拉伯数字,例如0X13表示十进制19。
浮点型
浮点型包括float32
, float64
。可用科学计数法表示浮点数
var float1 float32 =1213.4
var float2 = float64 = 3.12e-2 // e前e后必有数,e后必为整
复数类型
-
complex128(64位实部和虚部)
-
complex64(32位实部和虚部),其中complex128为复数的默认类型。
var complex1 = complex128 = complex(2, -3) // 声明实部为2,虚部为-3的复数
var complex2 complex64 = complex(9, 2) // 通过complex()函数创建复数
布尔类型
Go中布尔类型为bool
,表示布尔值,有两值true和flase。
字符串类型
Go中布尔类型为string
,表示字符串,是不可变的字节序列。
Go语言中字符串是用双引号(")包裹起来表示的。
字符转义
字符表示 | Unicode编码 | 说明 |
---|---|---|
\t | \u0009 | 水平制表符tab |
\n | \u000a | 换行 |
\r | \u000d | 回车 |
" | \u0022 | 双引号 |
’ | \u0027 | 单引号 |
\ | \u005c | 反斜线 |
原始字符串
-
原始字符串 (rawstring) 表示,始字符串使用反引号 ( ` ) 把字符串包裹起来。
-
原始字符串中的特殊字符不需要转义。
// 声明变量
// 采用普通字符串表示文件路径,其中的反斜杠需要转义
const filepath1 = "c:\\Users\\tony\\Documents\\readme.txt"
// 采用原始字符串表示文件路径,其中的反斜杠不需要转义
const filepath2 = `C:\Users\tony\Documents\readme.txt`
const str = `wdfcw // 支持多行字符串
efwrv erjp seeeeeeeeeeeeesf
fwe ebt`
字符串常用函数
import "strings" // 导入字符串包
-
func Contains(s,substr string) bool,判断字符串s中是否包含字符串substr。
-
func Replace(s,old,new string,nint)string,用string替换字符串s中old字符串,返回替换后的字符串,参数n是指定替换的个数。
-
func ToUpper(s string)string,将字符串s中所有字符转换为大写字符。
-
func ToLower(s string) string,将字符串s中的所有字符转换为小写字符。
-
func Split(s,sep string)[lstring,将字符串s,按照sep进行分割,返回字符切片。
Go语言是一种静态类型语言,这意味着所有变量在声明时都必须指定一个明确的数据类型。Go语言提供了丰富的数据类型,包括基本类型、复合类型和接口类型。
10.2复合类型
-
数组 (
[n]T
):具有固定长度 n 的元素类型为 T 的数组。-
一致性:数组只能保存相同数据类型元素,元素的数据类型可以是任何相同的数据类型。
-
有序性:数组中的元素是有序的,通过下标访问。
-
不可变性:数组一旦初始化,则长度“(数组中元素的个数)不可变。
// 长变量声明方式 var 数组变量名 = [length]datatype{values} // 指定数组长度,未赋值的元素赋0 var 数组变量名 = [...]datatype{values} // 数组长度根据元素个数推导出来 // 短变量声明方式 数组变量名 := [length]datatype{values} // 指定数组长度 数组变量名 := [...]datatype{values} // 数组长度根据元素个数推导出来 // Eg: var arr1 = [3]int{1,2,3} var arr2 = [...]float32{123.34,2.2334,3.0}
访问数组元素:1.数组的下标是从0开始的,事实上很多计算机语言的数组下标都是从0开始的。
2. Go数组下标访问运算符是中括号,如intArray[0],表示访问intArray数组的第一个元素,0是第一个元素的下标。 -
-
切片 (
[]T
):动态长度、可变的元素类型为 T 的序列。-
在实际编程时数组使用得并不多,这主要是因为数组是不可变的。
-
在实际编程时常常会使用可变数组数据获得数据一一切片 (Slice)。
声明切片:
-
可以将其声明为数组,只是无需指定其大小。
-
用make()函数来创建切片,make()函数语法如下:
make([] T,len,cap) import "fmt" func main() { // 声明字符串切片 strSlicel := []string{"沃尔沃","宝马","福特","奔驰"} // 声明int类型切片 intSlicel := []int{1,2, 3, 4, 5, 6} // 使用make()函数创建int切片 var intSlice2 = make([] int, 10) // 使用make()函数创建字符串切片 var strSlice2 = make([] string, 10,20) fmt.Println(intSlicel) fmt.Println(intSlice2) fmt.Println(strSlicel) fmt.Println(strSlice2) }
使用切片操作符:
切片名[startIndex:endIndex]
,startIndex默认为0。包括索引为startIdenx的元素,不包括索引为endIndex的元素。
切片名[:],切下所有元素。
添加元素:slice = append(slice,elem1,elem2,...)
;把slice2追加到slice1后slice1 = append(slice1, slice2...)
-
-
映射 (
map[T1]T2
):键类型为 T1、值类型为 T2 的键值对集合。声明映射:
-
var 映射变量名= map [key_data_type]value_data_type{key:value…}
-
映射变量名 = make(map[key_data_type]value_data_type)
import "fmt" func main () { // 1、通过map关键字声明映射 var countryCodeMap = map[string]string{"CN":"中国","RU":"俄罗斯","US":"美国","JP":"日本"} fmt.Printf("%v n", countryCodeMap) // 2、通过make()函数声明映射 var classMap = make(map[int]string, classMap[102] = "张三" classMap[105] = "李四" classMap[109] = "王五" classMap[110] = "董六" fmt.Printf("%vn",classMap) }
访问映射元素:
value,result = 映射变量名[key]
删除元素:要映射中的元素可以通过delete(映射变量,要删除的键)
函数实现,该函数是按照键删除对应的值。- 结构体 (
struct
):由多个字段组成的复合类型。
结构体是一种用户自定义的类型,结构体是不同类型数据集合,而数组是相同类型数据集合。
声明结构体
type 结构体类型名 struct{ 成员1 数据类型 成员2 数据类型 成员3 数据类型 ... }
举例
type Student struct { id int // 学号成员 name string // 姓名成员 age int // 年龄成员 city string // 所在城市 gender string // 性别成员,M男性,F女性 } // 相同数据类型的字段可以放在一起声明 type Student1 struct { id, age int name, city, gender string }
实例化结构体
func main() { // 实例化Student创建stu1实例 var stu1 Student // 实例化Student1创建stu2实例 stu2 := Student1{101, 18, "张三", "上海", "F"} // 实例化Student创建stu2实例 stu3 := Student{name: "李四", age: 18, city: "上海", gender: "F", id: 201} fmt.Println(stu1, stu2, stu3) }
结构体指针
获得结构体实例的地址方式-
通过取地址运算符(&)运算符获得结构体实例地址
-
通过new关键字获得创建实例,并返回指向实例的指针
func main() { // 结构体指针 // 通过取地址符号(&)运算符获得结构体实例地址 stu4 := &Student{name: "王五", age: 18, city: "上海", gender: "F", id: 301} // 使用“.”运算符访问成员 fmt.Println(stu4) // 使用new关键字获得实例化实例地址,返回指向实例的指针 stu5 := new(Student) stu5.id = 401 stu5.name = "老六" stu5.age = 17 stu5.city = "北京" stu5.gender = "M" fmt.Println(stu5) }
结构体嵌套
// 结构体嵌套 type Book struct { isbn string // ISBN号 title string // 书名 price float32 // 定价 authors []Author // 作者,是Author结构体数组 publisher Publisher // 出版社,是Publisher结构体 } // 定义作者结构体 type Author struct { id int name string } // 定义出版社结构体 type Publisher struct { name string email string }
为结构体添加方法
// 为结构体添加方法 type Rectangle struct { height, width int } // 声明结构体方法 func (rect Rectangle) Area() int { return rect.height * rect.width } func (rect Rectangle) DoubleArea() int { return rect.height * rect.width * 2 } func main() { // 为结构体添加方法 rect := Rectangle{20, 20} var1 := rect.Area() var2 := rect.DoubleArea() fmt.Println(var1, var2) }
-
-
通道 (
chan T
):用于在不同 goroutine 之间传递类型为 T 的值的管道。
遍历容器
数组、切片和映射都属于容器数据,他们包含了很多元素,因此一个经常的操作就遍历容器中的元素。
Go语言提供了一个range
关键字,它可以帮助迭代: 数组、切片、映射和通道(channel)等容器数据中的元素。
使用range关键字迭代不同的类型数据时,会返回不同数据,下面根据不同的容器类型分别介绍一下。
-
数组、切片。返回两个值,其中第一个值是索引,第二个值是元素。
-
映射。返回两个值,其中第一个值是键,第二个值是值。
-
字符串。由于返回两个值,第一个值是索引,第二个值是字符。
var strl = "Hello world."
for i, item := range strl {
fmt.Printf("str1[%d] = %c\n",i,item)
}
10.3接口类型
接口类型 (interface
) 是一种抽象类型,它定义了一组方法。任何实现了这些方法的类型都满足这个接口。
接口的定义
// 定义几何图形接口
type Shape interface {
area() float32 // 计算面积
permeter() float32 // 计算周长
}
接口的实现
// 定义矩形结构体
type Rectangle1 struct {
height, width float64
}
// 定义圆形结构体
type Circle struct {
radius float64
}
// 声明Rectangle结构体方法,实现Shape接口的area方法
func (r Rectangle) area() float64 {
return r.height * r.width
}
// 声明Rectangle结构体方法,实现Shape接口的permeter方法
func (r Rectangle) permeter() float64 {
return 2*r.height + 2*r.width
}
func main() {
rect := Rectangle1{20, 10}
fmt.Println(rect.area(), rect.permeter())
}
area(), rect.permeter())
}
10.4 其他类型
-
指针类型 (
*T
):指向类型 T 的值的指针。var 变量名 *变量类型 main() { // 声明变量x var x int = 100 fmt.Printf("x变量的内存地址: %xn",&x) // 声明并初始化指针变量ptr var ptr *int = &x fmt.Printf("指针变量pt值是: %\n",*ptr) }
空指针:果指针变量没有初始化,那么它所指向的内存地址为0,表示变量没有分配内存空间,这就是空指针。
func main() { var ptr *int fmt.Printf("指针ptr的值是: %xn",ptr) // 判断ptr是否为空指针 if ptr == nil { fmt.Println("ptr 是空指针”) } }
二级指针:指向指针变量的指针变量。
func main() { // 声明整数变量x var x int var ptr *int // 声明指针变量 var pptr **int // 声明二级指针变量 x = 300 // 初始化变量x ptr = &x // 获取x变量地址 pptr = &ptr // 获取ptr变量地址 fmt.Printf("x:%d\n", x) fmt.Printf("*ptr = %d\n", *ptr) fmt.Printf("**pptr =%d\n", **pptr) }
-
函数类型 (
func
):表示函数类型,可以包含参数和返回值。 -
错误类型 (
error
):表示错误值的接口类型。
10.5类型断言
类型断言用于检查接口值的实际类型是否为某个特定的类型。如果类型断言成功,表达式返回接口值的实际类型的值;如果失败,表达式会导致运行时恐慌(panic)。
聊一聊 Go 语言中的类型:断言
10.6类型转换
类型转换用于将一个类型的值转换为另一个类型的值。Go 语言中的类型转换需要显式进行,使用 T(v)
的形式,其中 T
是目标类型,v
是要转换的值。
目标数据类型(表达式)
var var1 := float32(123)
10.7类型别名
类型别名是 Go 1.9 引入的一个特性,它允许为现有的类型定义一个新的名字。类型别名与类型定义(type definition)不同,类型别名只是为现有类型提供了一个新的名字,而类型定义则创建了一个全新的类型。
// 将NewInt定义为int类型
type NewInt int
Go语言type关键字(类型别名)
十一、运算符
11.1算数运算符
-
一元算数运算符,包括++和–。
-
二元算数运算符,包括+、-、*、/ 和 % 等。
运算符 | 名称 | 例子 | 说明 |
---|---|---|---|
+ | 加 | x + y | 求x加y的和 |
- | 减 | x - y | 求x减y的差 |
* | 乘 | x * y | 求x乘以y的积 |
/ | 除 | x / y | 求x除以y的商 |
% | 取余 | x % y | 求x除以y的余数 |
++ | 自加一 | 加1后返回 | x++ |
– | 自减一 | 减1后返回 | x– |
11.2关系运算符
运算符 | 名称 | 例子 | 说明 |
---|---|---|---|
== | 等于 | x == y | x等于y 时返回 true, 否则返回 false |
!= | 不等于 | x != y | 与==相反 |
> | 大于 | x > y | x大于y 时返回 true, 否则返回 false |
< | 小于 | x < y | x小于y 时返回 true, 否则返回 false |
>= | 大于等于 | x >= y | x大于等于y 时返回 true, 否则返回 false |
<= | 小于等于 | x <= y | x小于等于y 时返回 true, 否则返回 false |
11.3逻辑运算符
运算符 | 名称 | 例子 | 说明 |
---|---|---|---|
! | 逻辑非 | !x | x 为 true 时,值为 false,a 为 false 时,值为 true |
&& | 逻辑与 | x && y | xy 全为 true 时,计算结果为 true,否则为 false |
|| | 逻辑或 | x||y | xy 全为 false 时,计算结果为 false,否则为 true |
短路特性(如果左边的表达式确定整个表达式的结果,则右边的表达式不计算):&&:左假右不看;||:左真右不看。
11.4位运算符
运算符 | 名称 | 例子 | 说明 |
---|---|---|---|
& | 位与 | x&y | x 与 y 位进行位与运算 |
| | 位或 | x|y | x与y位进行位或运算 |
^ | 位异或 | x^y | x 与 y 位进行位异或运算 |
>> | 右移 | x>>y | x 右移 y 位,高位采用 0 补位 |
<< | 左移 | x<<y | x 左移 y 位,低位用 0 补位 |
11.5赋值运算符
运算符 | 名称 | 例子 |
---|---|---|
+= | 加赋值 | a += b、a += b+3 |
-= | 减赋值 | a -= b |
*= | 乘赋值 | a *= b |
/= | 除赋值 | a /= b |
%= | 取余赋值 | a %= b |
&= | 位与赋值 | x &= y |
|= | 位或赋值 | x|=y |
^= | 位异或赋值 | x ^= y |
<<= | 左移赋值 | x <<= y |
>>= | 右移赋值 | x >>= y |
11.6其他运算符
运算符 | 名称 | 例子 | 描述 |
---|---|---|---|
& | 取地址运算符 | &a | 获得变量a 的地址 |
* | 间接寻址运算符 | *a | 声明指针变量 |
11.7运算符优先级
优先级 | 分类 | 运算符 |
---|---|---|
1 | 逗号运算符 | , |
2 | 赋值运算符 | =、+=、-=、*=、/=、%=、>=、< <=、&=、 -=、 |
3 | 逻辑或 | || |
4 | 逻辑与 | && |
5 | 按位或 | | |
6 | 按位异或 | ^ |
7 | 按位与 | & |
8 | 相等/不等 | ==、!= |
9 | 关系运算符 | <、<=、>、>= |
10 | 位移运算符 | <<、>> |
11 | 加法/减法 | +、- |
12 | 乘法/除法/取余 | *(乘号)、/、% |
13 | 一元运算符 | !、*(指针)、&、++、–、+(正号)、 -(负号) |
14 | 后缀运算符 | ()、[ ]、-> |
十二、语句
12.1条件语句
if语句
三种结构:
-
if 结构
package main import "fmt" func main() { if x := 10; x > 5 { fmt.Println("x is greater than 5") } }
-
if-else 结构
package main import "fmt" func main() { x := 10 if x > 5 { fmt.Println("x is greater than 5") } else { fmt.Println("x is not greater than 5") } }
-
if-else-if 结构
package main import "fmt" func main() { x := 10 if x > 15 { fmt.Println("x is greater than 15") } else if x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is not greater than 10") } }
switch语句
switch(表达式) {
case value1:
语句1
case valu2:
语句2
case value3:
语句3
...
case valuen:
语句n
default:
语句n+1
}
与c语言中switch的区别,这个执行case后直接退出,不会执行下面的case语句了。
使用switch语句注意如下问题
-
switch语句中“表达式”计算结果主要是布尔类型和整数类型
-
“表达式”必须与case值具有相同的数据类型
-
默认情况下每一个case语句组执行结束后,则switch语句结束
-
default语句可以省略,default语句应该置于在switch语句未尾
-
一个case可以有多个值
使用fallthrough贯穿case
Go中的switch语句每一个case分支的代码块执行完成后,就结束switch语句,但是如果想在一个case分支执行完成后,不结束switch语句,而是进入下一个case,那么可以使用fallthrough关键字实现,fallthrough会强制执行后面的case语句,fallthrough不会判断是否与下一条 case 值匹配。
package main
import "fmt"
func main() {
switch num := 1; num {
case 1:
fmt.Println("Number is 1")
fallthrough // 继续执行下一个case,即使条件不满足
case 2:
fmt.Println("Number is 2")
fallthrough // 继续执行下一个case,即使条件不满足
case 3:
fmt.Println("Number is 3")
fallthrough // 继续执行下一个case,即使条件不满足
default:
fmt.Println("Number is not 1, 2, or 3")
}
}
12.2循环语句
基本形式for循环
for 初始值;循环条件;迭代{
语句组
}
简化的for循环
-
省略初始化和迭代语句
-
省略条件部分
package main
import "fmt"
func main() {
i := 1 // 初始化语句置于for语句之前
for i < 10 {
fmt.Printf("%d x %d = %d", i, i, i*i)
//打印一个换行符,实现换行
i++ // 选代语句置于循环体中
}
// --------------
i := 1 // 初始化语句置于for语句之前
for {
fmt.Printf("%d x %d = %d", i, i, i*i)
//打印一个换行符,实现换行
fmt.Print("\n")
i++ // 选代语句置于循环体中
if i = 10{ // 条件满终止循环
break //该语句会终止循环
}
}
}
12.3跳转语句
break语句
break语句可用于for循环结构,它的作用是强行退出循环体,不再执行循环体中剩余的语句。
使用标签的break语句
break语句还可以配合标签使用,带标签的break语句使程序跳出标签所指的循环体,语法格式如下。
break label
-----------------
package main
import "fmt"
func main(){
OuterLoop:
for x:=0;x<5;x++ {
for y:=5;y>0;y-- {
if y==x {
// 跳转到OuterLoop指向的循环
break OuterLoop
}
fmt.Printf("(x,y)=(%d,%d)\n",x,y)
}
}
fmt.Println("Game Over!")
}
continue语句
continue语句用来结束本次循环,跳过循环体中尚未执行的语句,接着进行终止条件的判断,以决定是否继续循环。对于for语句,在进行终止条件的判断前,还要先执行迭代语句。
使用标签的continue语句
continue语句也可以带有标签,语法格式如下。
continue label
----------------------------
package main
import "fmt"
func main() {
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf("(%d, %d) ", i, j)
if j == 1 {
continue outer // 跳过外层循环的剩余代码,并开始下一次迭代
}
}
fmt.Println()
}
}
goto语句
goto语句是无条件跳转语句,使用goto语句可跳转到标签所指的代码行,由于goto语句使得程序的控制流难以跟踪,如果goto语句使用得不当可能会导致程序出现错误,跳转推荐使用break和continue替代goto。
package main
import "fmt"
func main() {
i := 0
label:
for {
fmt.Println("Iteration:", i)
i++
if i == 3 {
goto end // 无条件跳转到标签end
}
}
end:
fmt.Println("Exiting the loop")
}
十三、函数
13.1用户自定义函数
func 函数名(形式参数列表)(返回值列表) {
函数体
}
13.2单一返回值
func area(width int, height int) int {
ar := width * height
return ar
}
13.3多个返回值
// 自定义计算矩形面积和周长函数
func calcRect(width int, height int) (int, int) {
// 计算矩形面积
area := width * height
// 计算矩形周长
perimeter := 2 * (width + height)
return area, perimeter
}
13.4命名函数返回值
// 自定义函数计算矩形的面积
func calcRectArea(width, height int) (area int) { // 返回值变量命名为area
ar := width * height
// 给返回值变量赋值
area = ar
return // 不返回任何数据类型
}
13.5变参函数
// 定义一个求和的变参函数
func sum(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
13.6函数式编程
-
函数是”一等公民”:是指函数与其他数据类型是一样的,处于平等的地位。函数可以作为其他函数的参数传入,也可以作为其他函数的返回值返回。
-
高阶函数:函数式编程支持高阶函数,所谓高阶函数就是一个函数可以作为另外一个函数的参数或返回值。
-
无副作用:是指函数执行过程会返回一个结果,不会修改外部变量,这就是“纯函数”,同样的输入参数一定会有同样的输出结果。
13.7匿名函数
如果函数没有名字,称为“函数”,匿名函数也称lambda
函数。
package main
import "fmt"
func main() {
// 匿名函数
add := func(a, b int) int {
return a + b
}
sub := func(a, b int) int {
return a - b
}
var a, b = 10, 5
fmt.Printf("%d+%d=%d;数据类型:%T\n", a, b, add(a, b), add)
fmt.Printf("%d-%d=%d;数据类型:%T\n", a, b, sub(a, b), sub)
}
13.8函数作为返回值使用
函数类型
函数类型是函数式编程的关键
函数类型与其他的数据类型一样都可以声明变量、参数类型和返回值类型。
// 声明变量,它是函数类型
var res func(int, int) int
-----------------------------
// 函数作为返回值使用
func calculate(opr string) func(int, int) int {
// 声明变量,它是函数类型
var res func(int, int) int
if opr == "+" {
// 声明相加函数
res = func(a, b int) int {
return a + b
}
} else {
// 声明相减函数
res = func(a, b int) int {
return a - b
}
}
// 返回值res变量是函数类
型
return res
}
func main() {
// 函数作为返回值
f1 := calculate("+")
f2 := calculate("-")
fmt.Printf("%d+%d=%d,数据类型:%T\n", a, b, f1(a, b), f1)
fmt.Printf("%d-%d=%d,数据类型:%T\n", a, b, f2(a, b), f1)
}
13.9函数作为参数使用
// 函数作为参数
// 定义一个计算面积的函数
func getAreaByFunc(funcName func(float32, float32) float32, a, b float32) float32 {
return funcName(a, b)
}
13.10闭包与捕获变量
闭包 (closure) 是一种特殊的函数,它可以访问函数体之外的变量,这个变量和函数一同存在,即使已经离开了它的原始作用域也不例外。
闭包访问函数体之外的变量的称为“捕获变量”,闭包捕获变量后,这些变量被保存在一个特殊的容器中被存储起来,即便是声明这些变量的原始作用域已经不存在,闭包体中仍然可以访问这些变量。
// 闭包函数
// 定义一个增量器函数,返回值是"func() int"函数类型
func incrementor() func() int {
i := 0 // 声明局部变量
// 返回匿名函数
return func() int { // 闭包
i++
return i
}
}
func main() {
// 闭包函数的使用,自增器
next := incrementor()
fmt.Println(next()) // 打印1
fmt.Println(next()) // 打印2
fmt.Println(next()) // 打印3
}