根据自己的理解整理的笔记
请不要转载
Golang 基础语法
主要内容
- 项目结构
- 变量
- 指针和引用
- 定义函数
- 定义结构/接口
- 闭包
- 关键字和控制语句
- 标准库
项目结构
基本概念
-
命名空间:
package提供了一个命名空间,用于避免不同package中的标识符冲突。通过使用不同的package,可以将代码组织成独立的模块,并避免命名冲突。 -
可见性:Go语言中的标识符(变量、函数、结构体等)的可见性受限于其所属的
package。标识符以小写字母开头的,只能在所属的package内部访问,而以大写字母开头的标识符可以被其他package引用和访问。 -
导入:通过
import语句,可以在一个package中引入其他package,以便使用其提供的功能。导入的package可以通过其包名来访问其中的标识符,注意 import 的包不能为循环依赖。 -
可执行程序:一个特殊的
package是main,它定义了一个可执行程序的入口点。一个Go程序必须包含一个main包,并且在该包中的main函数将作为程序的入口点。 通过使用package,我们可以将代码组织成模块化的单元,并通过导入和可见性规则来管理代码的访问和复用。这有助于提高代码的可维护性、可读性和可测试性。 -
go.mod 文件:
go.mod文件是Go语言模块的管理文件,用于定义和管理项目的依赖关系和版本信息。它是Go语言在版本1.11及以上引入的模块化开发的一部分。其作用包括:-
依赖管理:
go.mod文件记录了项目所依赖的外部模块和版本信息。它列出了项目所需的依赖项,并指定了允许使用的版本范围。当构建项目时,Go工具会根据go.mod文件自动下载和管理所需的依赖项。 -
版本控制:
go.mod文件指定了每个依赖项所允许的版本范围。这使得可以精确控制项目所使用的依赖项的版本,以确保项目的稳定性和一致性。 -
模块标识:
go.mod文件定义了项目的模块标识,即模块的名称和版本。模块标识在构建和发布模块时起到唯一标识的作用。 -
自动化构建:
go.mod文件使得构建和发布项目变得更加简单和自动化。通过运行go build、go test等命令,Go工具会自动处理依赖项的下载和版本管理,以及构建和测试项目。
-
项目结构示例
Go语言的项目结构通常遵循一定的约定,尽管没有强制要求。常见的Go项目结构包括将源代码文件放置在一个目录中,以及使用go.mod文件来管理依赖关系。典型的Go项目结构如下:
myproject/
├── cmd/
│ └── main.go
├── pkg/
│ └── mypackage/
│ └── mypackage.go
├── internal/
├── api/
├── web/
├── go.mod
└── README.md
cmd/目录通常包含可执行程序的入口点,每个子目录对应一个可执行程序。pkg/目录包含可导入的包,用于封装和复用代码。internal/目录包含项目内部使用的私有包,不可被其他项目导入。api/和web/目录可能包含与API和Web相关的代码。go.mod是Go模块文件,用于管理项目的依赖关系。
变量:
声明
- 在Go语言中,可以使用
var关键字来声明变量。变量的声明可以包含类型和初始值,也可以使用 (:=) 省略类型而由编译器自动推断。
var name string = "Alice"
age := 30
const length = 16 // 常量
-
Go 中如果定义一个没有使用到的变量或依赖,编译器会报错。可以使用 _ 表示占位
-
变量可以定义在函数外部作为全局变量,在包内均可使用
-
any表示任意类型,等同于interface{}
变量类型
-
基本类型(Primitive Types):
- 整数类型:
int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr。 - 浮点数类型:
float32、float64。 - 复数类型:
complex64、complex128。 - 布尔类型:
bool。 - 字符串类型:
string。 - 字符类型:
byte(uint8的别名)和rune(int32的别名)。
- 整数类型:
-
复合类型(Composite Types):
- 数组类型:
[n]T,表示具有固定长度的元素序列。 - 切片类型:
[]T,表示可变长度的元素序列。 - 映射类型:
map[K]V,表示键值对的集合。 - 结构体类型:
struct,表示字段的集合。 - 接口类型:
interface{},表示方法的集合。 - 函数类型:
func,表示函数(闭包)类型。
- 数组类型:
-
指针类型(Pointer Types):使用
*T表示,指向类型T的指针。 -
通道类型(Channel Types):用于在 Goroutine 之间进行通信和同步。
这些是Go语言中常见的变量类型。通过使用这些类型,可以声明和操作各种不同类型的变量。
指针和引用
- Go语言支持指针类型,可以使用
&操作符获取变量的地址,使用*操作符获取指针指向的值(解引用。例如:
var x int = 10
var ptr *int = &x
fmt.Println(*ptr) // 输出:10
nil表示空指针
定义函数:
- 在Go语言中,可以使用
func关键字来定义函数。函数可以有参数和返回值。例如:
func add(a, b int) int {
return a + b
}
- 定义递归函数
func getHeight(t *Tree) int {
if t == nil {
return 0
} else {
return max(getHeight(t.Left), getHeight(t.Right)) + 1
}
}
- 定义多返回值函数
func multiResult(a, b int) (c, d int) {
return a, b
}
-
Golang 不支持方法重载
-
在Go语言中,只有以大写字母开头的标识符(包括方法,字段)才可以从包外部访问,也被称为导出标识符。
定义结构/接口:
结构
- 在Go语言中,可以使用
type关键字来定义自定义的结构体和接口。结构体用于定义自定义的数据类型。例如:
//定义新结构
type Tree struct {
Val int
Left *Tree
Right *Tree
}
// 取别名
type Array []*int
- Go语言支持面向对象编程(OOP),但与传统的面向对象语言不同,Go语言没有类的概念。它通过结构体和方法来实现面向对象的特性。可以将方法与结构体关联,从而实现方法的调用。 即类似成员方法,可以使用 obj.method(..) 调用函数
type List struct {
Value int
next *List
}
func (l *List) InsertOne(node *List) {
node.next = l.next
l.next = node
}
func (l *List) HasNext() bool {
return l.next == nil
}
func (l *List) PrintList() {
dummy := l
for dummy.HasNext() {
fmt.Printf("%d ", dummy.Value)
}
}
- 标签:
Go语言中没有直接的注解(Annotation)机制,但可以使用标签(Tag)来为结构体的字段添加元数据。标签是以反引号包围的字符串,可以在运行时通过反射来读取。例如:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
接口
- 接口用于定义方法集合
type Animal interface {
Sound() string
}
- 在Go语言中,接口的实现是隐式的,即不需要在类型声明中显式声明它实现了某个接口。只要类型的方法集包含了接口中定义的所有方法,它就被认为是实现了该接口。
- 在实现了某接口的所有方法后,便可以通过接口接收此结构体的实例(多态)。或者通过类型转换将接口类型转为结构体实例
闭包:
- 在Go语言中,闭包是指一个函数捕获并引用了其外部作用域中的变量。闭包可以在函数内部定义,并可以访问外部函数的变量。例如:
func increment() func() int {
count := 0
return func() int {
count++
return count
}
}
在上述示例中,我们定义了一个名为increment的函数,它返回一个闭包函数。闭包函数捕获了外部函数中的count变量,并在每次调用时递增并返回它。
- 使用闭包
func closure(a []int) {
// 将整数转为字符串
var function = func(a []int) []string {
var res []string
for i := 0; i < len(a); i++ {
res = append(res, strconv.Itoa(a[i]))
}
return res
}
// 使用闭包
var tmp = function(a)
for index, value := range tmp {
fmt.Printf("%s in index %d \n", value, index)
}
}
关键字
所有关键字
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
包括代码结构,控制语句,goroutine, 数据类型的基本功能
循环
在 Go 语言中,有多种形式的循环语句可供使用。以下是 Go 语言中常用的循环语句形式:
-
for循环:for initialization; condition; post { // 循环体 } -
for循环的简化形式:for condition { // 循环体 } -
for循环的无限循环形式:for { // 循环体 } -
range循环:for index, value := range collection { // 循环体 } -
range循环的简化形式(忽略索引或值):for _, value := range collection { // 循环体 } -
goto:跳转 (不能跳转到其他函数或方法中的标签, 只能在同一个代码块内跳转到标签。)func main() { i := 0 loop: for i < 5 { fmt.Println(i) i++ if i == 3 { goto loop } } }
-
for循环是最常见的循环形式,用于在给定条件下重复执行一段代码。range循环用于遍历集合(如数组、切片、映射、通道等)中的元素。 -
可以使用
break和continue语句来控制循环的执行流程。break用于终止循环,continue用于跳过当前循环迭代并进入下一次迭代。
分支语句
在 Go 语言中,有多种形式的分支语句可供使用。以下是 Go 语言中常用的分支语句形式:
-
if语句:if condition { // 条件为真时执行的代码块 } else { // 条件为假时执行的代码块 } -
if语句的简化形式(只有条件部分):if condition { // 条件为真时执行的代码块 } -
if语句的初始化部分:if initialization; condition { // 条件为真时执行的代码块 } -
switch语句:switch expression { case value1: // expression 等于 value1 时执行的代码块 case value2: // expression 等于 value2 时执行的代码块 default: // expression 不等于任何 case 时执行的代码块 } -
switch语句的初始化部分:switch initialization; expression { case value1: // expression 等于 value1 时执行的代码块 case value2: // expression 等于 value2 时执行的代码块 default: // expression 不等于任何 case 时执行的代码块 } -
switch语句的简化形式(只有表达式部分):switch expression { case value1, value2: // expression 等于 value1 或 value2 时执行的代码块 default: // expression 不等于任何 case 时执行的代码块 } -
fallthrough:case 穿透switch i { case 1: fmt.Println("One") fallthrough case 2: fmt.Println("Two") fallthrough case 3: fmt.Println("Three") }
标准库
预定义软件包 builtin
builtin软件包中的函数和类型是Go语言的内置功能,无需导入即可直接使用。它们提供了一些基本的操作和功能,用于支持Go语言的核心特性和语法。
以下是对Go语言builtin包中几个常用函数的使用示例:
-
类型转换函数:
num := 10 str := string(num) -
内存分配函数:
ptr := new(int) *ptr = 42 slice := make([]int, 0, 10) -
类型信息函数:
// 获取容量和长度,追加元素 str := "Hello, Go!" length := len(str) nums := []int{1, 2, 3, 4, 5} capacity := cap(nums) nums = append(nums, 6) -
类型断言函数:
// 定义了一个空接口类型的变量 val,并将其赋值为字符串 "Hello, Go!"。空接口类型可以表示任意类型的值。 var val interface{} = "Hello, Go!" // 类型断言, 它尝试将 val 的值断言为 string 类型,并将结果赋值给变量 str。如果断言成功,str 将包含断言后的字符串值,同时 ok 的值将为 true。如果断言失败,str 将为零值(空字符串),同时 ok 的值将为 false。 str, ok := val.(string) if ok { fmt.Println(str) }
其他常用标准库包
-
fmt 包:用于格式化输入和输出,包括打印、格式化字符串、读取输入等。
fmt.Println(): 打印输出到标准输出。fmt.Printf(): 格式化输出到标准输出。fmt.Sprintf(): 格式化字符串并返回结果。
-
strings 包:用于处理字符串的函数。
strings.Contains(): 判断字符串是否包含子串。strings.Split(): 将字符串分割为子串切片。strings.Join(): 将字符串切片连接为一个字符串。
-
strconv 包:用于字符串和基本数据类型之间的转换。
strconv.Atoi(): 将字符串转换为整数。strconv.ParseFloat(): 将字符串转换为浮点数。strconv.Itoa(): 将整数转换为字符串。
-
os 包:提供与操作系统交互的功能。
os.Args: 获取命令行参数。os.Exit(): 终止程序并退出。os.Open(): 打开文件。
-
time 包:用于处理时间和日期。
time.Now(): 获取当前时间。time.Parse(): 解析字符串为时间。time.Sleep(): 暂停一段时间
-
math 包:提供了数学运算相关的函数和常量。
math.Abs(): 返回一个数的绝对值。math.Sin(): 返回一个角度的正弦值。math.Sqrt(): 返回一个数的平方根。
-
sort 包:提供了对切片和用户自定义数据类型进行排序的函数。
sort.Ints(): 对整数切片进行升序排序。sort.Strings(): 对字符串切片进行升序排序。sort.Slice(): 对切片进行自定义排序。
Golang 基本IO操作
主要内容
- 标准 IO
- 文件 IO
标准 IO
builtin 中的 IO
builtin 中定义了两个打印函数,可用于控制台打印字符串和数字.
func TestBuiltinIO() {
// 获取环境变量
println(os.Getenv("paas_url"))
// 单个字符按 ascii 码打印
print(1, 2, 3, 4, 5, '6', '7', "8", "\n")
println("Done", "builtin IO")
}
fmt 包中的 IO
fmt 包中实现了完善的IO函数,默认使用标准IO文件对象(os.Stdin,os.Stdout)进行输入输出
func TestStandardIO() {
fmt.Print("Enter a string: ")
var name string
var num int
var ch int
var f float64
fmt.Scanln(&name)
fmt.Print("Enter with %d %c %f: ")
_, err := fmt.Fscanf(os.Stdin, "%d %c %f\n", &num, &ch, &f)
if err != nil {
fmt.Println("error", err)
return
}
cnt, _ := fmt.Fprintf(os.Stdout, "%s,%.2f\n", name, f)
fmt.Printf("parameter count: %d\n", cnt)
fmt.Println("Done", "standard IO")
}
重定向
可以通过赋值标准IO文件进行重定向
func TestRedirectStandardIO() {
// 获取工作目录output
dir, _ := os.Getwd()
// 将文件设置为标准输入
file1, _ := os.Open(dir + "/files/standardIO/input.txt")
os.Stdin = file1
// 从标准输入读取内容
input, _ := bufio.NewReader(os.Stdin).ReadString('\n')
fmt.Println(input)
// 将文件设置为标准输出
file2, _ := os.Open(dir + "/files/standardIO/output.txt")
os.Stdout = file2
_, err := fmt.Println(input + " output")
if err != nil {
println(err.Error())
}
}
异常处理
- func Println(a ...any) (n int, err error), func Scanln(a ...any) (n int, err error)
输入输出函数都有返回值 (n int, err error) 用于返回解析的参数个数和处理IO异常
_, err := fmt.Fscanf(os.Stdin, "%d %c %f\n", &num, &ch, &f)
if err != nil {
fmt.Println("error", err)
return
}
cnt, _ := fmt.Fprintf(os.Stdout, "%s,%.2f\n", name, f)
fmt.Printf("parameter count: %d\n", cnt)
文件操作
defer 关键字
defer 是Go语言中的一个关键字,用于延迟(defer)函数或方法的执行。通过使用 defer 关键字,可以确保在函数执行完毕前,某个函数或方法会被调用。常用于资源释放、文件关闭、解锁互斥锁等操作,以确保在函数返回之前进行必要的清理工作。以下是 defer 关键字的一些特点和用法:
-
延迟执行:使用 defer 关键字可以将函数或方法的执行推迟到当前函数返回之前。无论函数是正常返回还是发生异常,被延迟的函数都会被执行。
-
堆栈机制:被延迟执行的函数或方法会被添加到一个堆栈中。当函数返回时,延迟执行的函数会按照后进先出(LIFO)的顺序执行。
-
参数求值:被延迟执行的函数的参数会在 defer 语句出现时进行求值,而不是在实际执行时求值。这意味着函数参数的值会被保存并在延迟执行时使用。
打开文件
func readFileByChar(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
_ = fmt.Errorf(err.Error())
}
}(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
}
}
异常处理
file, err := os.Create(getPath(filename))
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
_ = fmt.Errorf(err.Error())
}
}(file)
读写字符流
- 按行读写文本
func ReadFileByChar(filename string) {
file, err := os.Open(getPath(filename))
// 异常处理
if err != nil {
fmt.Println("Error opening file:", err)
return
}
// 关闭文件
defer func(file *os.File) {
err := file.Close()
if err != nil {
_ = fmt.Errorf(err.Error())
}
}(file)
// 读取文件
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
}
}
func WriteFileByChar(filename string, text string) {
file, err := os.Create(getPath(filename))
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
_ = fmt.Errorf(err.Error())
}
}(file)
// 写文件
cnt, err := fmt.Fprintln(file, text)
fmt.Printf("%d bytes are written\n", cnt)
if err != nil {
println(err.Error())
}
}
访问权限
-
可以使用 file.chmod 查看和修改文件的权限。
-
可以使用
os.Stat()函数来获取文件信息,然后通过Mode()方法获取文件的权限信息。