Golang 学习
一些知识点的记录
- 反引号的作用:用来创建原生的字符串字面量 ,这些字符串可能由多行组成,不支持任何转义序列。原生的字符串字面量多用于书写多行消息、HTML 以及正则表达式。
[...NodeList]:用扩展运算符将NodeList转换成数组。
go的执行流程
两种方式:
- 编译后执行:可以直接拷贝到另外一台机器
- 解释型执行:需要开发环境

注释
支持C语言风格的 /* **/ 和c++风格的行注释://
一、输入与输出 IO
1. 输入
Scan:从标准输入os.Stdin中读取数据,包括Scan,Scanf,Scanln

2. 输出
Sprintf根数格式化参数生成格式化的字符串并返回该字符串
Printf根据格式化参数生成格式化字符串并写入标准输出
二、操作文件
- 打开文件并输出文件内容
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
var filename string
fmt.Println("Enter the filename:")
fmt.Scanln(&filename)
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error in opening file:", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
三、 数据类型
基本数据类型的默认值:
整形/浮点型 :0
字符串:“”
布尔型:false
类型转换
Go与java/c不同,在不同类型变量之间赋值需要显式转换(强制转换)
转换的基本语法:

一、字符串类型:string
- 编码:统一使用UTF-8
- string类型一旦赋值,字符串就不能修改,字符串类型不可变
字符串的表示方法
- 双引号:可以识别转义字符
- 反引号:以字符串原生方式输出,包括换行符和特殊字符。可以防止攻击,输入源代码等
二、浮点型
float32,也即我们常说的单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数
float64,也即我们熟悉的双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数
-
float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度
-
float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度
-
常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
-
常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
三、数组
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var arrayName [size]dataType
还可以使用初始化列表来初始化数组的元素:
var numbers = [5]int{1, 2, 3, 4, 5}
**注意:在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的,也就是说 [5]int 和 [10]int 是不同的类型。
以下定义了数组 balance 长度为 5 类型为 float32,并初始化数组的元素:
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
切割数组:arr[startIndex:endIndex] 左闭右开
四、!切片!
数组和切片的区别:
- 数组是定长的数据结构,长度被指定后就不能改变。
- 切片是不定长的,会自行扩容
初始化
var nums []int
nums := []int{1, 2, 3, 4}
nums := make([]int, 0, 0) // 建议使用
nums := new([]int) //指针
"切片的底层实现是数组,是引用类型,可以理解为指向底层数组的指针,通过var nums []int 这种方式声明的切片不会分配内存,默认为nil"
使用make初始化时建议预分配一个足够的容量,可以有效减少后续扩容的内存消耗
四、指针
取地址符:&
解引用:*
go和c的区别对比

Go 的设计(安全性优先)
- 自动垃圾回收:Go 的指针指向的内存由 GC 自动管理,减少内存泄漏风险。
- 禁止危险操作:
- 不允许指针运算(如
p+1)。 - 不能将指针转换为任意类型(需通过
unsafe.Pointer实现,但不推荐常规使用)。
- 不允许指针运算(如
- 空指针安全:零值指针为
nil,解引用nil会触发 panic(类似 C 的段错误,但更可控)。
C 的设计(性能优先)
- 手动内存管理:需手动调用
malloc/free管理内存,易出现泄漏或悬空指针。 - 无限制操作:
- 支持任意指针算术(如数组越界访问)。
- 可通过
(void*)进行任意类型转换,增加代码复杂度和风险。
- 空指针风险:解引用
NULL指针直接导致段错误(Segmentation fault)。
package main
import (
"fmt"
)
func main() {
s1 := "hello"
s2 := "world"
s3 := "vstral"
ptr := [3]*string{&s1, &s2, &s3}
for i, ptr1 := range ptr {
fmt.Printf("这是第%d句话:", i)
fmt.Println(ptr1)
}
}
五、结构体
数组中可以存储同一类型的数据,在结构体中我们可以为不同项定义不同的数据类型。
结构体是一系列具有相同或不同数据构成的集合
1. 结构体的定义
结构体的定义需要使用type和struct语句
struct语句定义一个新的数据类型,结构体中有一个或多个成员
type语句设定结构体的名称
格式如下:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
2. 结构体 - 方法
package main
import (
"fmt"
)
type Student struct {
name string
id int
}
func (stu *Student) hello(person string) string {
return fmt.Sprintf("Hello, %s, my name is %s. my id is %d",person, stu.name, stu.id)
}
func main() {
stu := &Student{
name: "vstral",
id: 2024122079,
}
msg := stu.hello("jack")
fmt.Println(msg)
}
这里的 (stu *Student) 是方法的接收者(receiver),它的作用是将这个函数绑定到 Student 类型上,使得 hello 成为 Student 类型的一个方法。
具体来说:
-
stu是接收者变量名,类似于面向对象语言中的this或self,代表调用该方法的那个Student实例。 -
*Student表示接收者是指向Student类型的指针,这样方法内部可以访问和修改调用者的字段(比如stu.name)。 -
通过这种方式,你可以用
stu.hello("Tom")来调用这个方法,其中stu是一个Student类型的变量。
总结:
-
(stu *Student)让hello成为Student类型的一个方法。 -
使用指针接收者(
*Student)可以避免值拷贝,提高效率,并且允许方法修改Student实例的内容。
这是Go语言中实现面向对象编程的一种方式。
- 使用
Student{field: value, ...}的形式创建 Student 的实例,字段不需要每个都赋值,没有显性赋值的变量将被赋予默认值 - 实现方法和实现函数的区别在于func和 函数名hello之间,加上该方法对应的实例名stu及其类型*student,可以通过实例名访问该实例的字段和其他方法
- 调用方法通过 实例名.方法名(参数) 的方式
也可以使用new实例化
func main() {
stu2 := new(Student)
fmt.Println(stu2.hello("Alice")) // hello Alice, I am , name 被赋予默认值""
}
3. 接口
接口定义了一组方法的集合,接口不能被实例化,一个类型可以实现多个接口
package main
import (
"fmt"
)
type Person interface {
getName() string
getScore() int
}
type Student struct {
name string
score int
}
func (stu *Student) getName() string {
return stu.name
}
func (stu *Student) getScore() int {
return stu.score
}
func main() {
var p Person = &Student{
name: "vstral",
score: 100,
}
fmt.Println(p.getName())
fmt.Println(p.getScore())
}
这里的就是用stu将Person接口的方法全部实现了,每一次实现方法都相当于是讲方法增加到这个结构体里面
接口需要完全实现
接口实现不完整会导致以下问题:
-
编译错误 如果你使用
var _ Person = (*Student)(nil)这种方式来做静态检查,在编译阶段就能发现Student类型没有完整实现Person接口,从而避免程序在运行时出现问题3。 -
运行时 panic 如果你没有做静态检查,而是直接将一个未完整实现接口的类型赋值给接口变量,在调用接口中未实现的方法时,会导致运行时 panic5。
-
逻辑错误 即使程序没有 panic,如果接口实现不完整,可能会导致程序行为不符合预期2。例如,本应该返回错误信息的地方没有返回,或者返回了不完整的错误信息2。
此外,还有一些与接口使用相关的常见问题:
-
接口值为 nil 并不代表动态类型为 nil 一个接口变量,如果它的 type 和 value 都是 nil 时,这个接口才等于 nil6。如果 interface 包含了 type,但是 value 为 nil,和 nil 比较仍然是 false6。
-
同名方法冲突 如果一个类型实现了多个接口,并且这些接口中有同名的方法,可能会造成问题5。
-
接口修改 如果接口发生更改,所有实现了该接口的类型都需要相应地修改,否则会导致编译错误5。
-
不确定接口值类型 由于空接口可以表示任何类型,因此如果不确定接口值的具体类型,就需要使用类型断言来判断接口值的类型5。
-
方法签名不匹配 接口未实现错误通常源于方法签名不匹配1。
检测接口是否完全实现的方法
var _ Person = (*Student)(nil)
var _ Person = (*Worker)(nil)
- 将空值 nil 转换为 *Student 类型,再转换为 Person 接口,如果转换失败,说明 Student 并没有实现 Person 接口的所有方法。
- Worker 同上。
!!!空接口!!!
map 是一种无序的键值对的集合,map最重要的一点是通过key来快速检索数据,key类似于索引,指向数据的值
因为map是无序的,所以遍历map时返回的值顺序也是不确定的
创建map,使用make函数创建map,键类型为string,值类型是空接口 interface{}
map[string]interface{} 表示这个 map 的键是字符串类型,值可以是任意类型,因为空接口 interface{} 可以存储任何类型的值。
六、 异常处理
go中的错误分为两类:
- error : 往往是能够预知的错误
- panic: 一般是不可预知的错误,可能会导致程序非正常退出
自定义错误:
error.New:
import (
"errors"
"fmt"
)
func hello(name string) error {
if len(name) == 0 {
return errors.New("error: name is null")
}
fmt.Println("Hello,", name)
return nil
}
func main() {
if err := hello(""); err != nil {
fmt.Println(err)
}
}
// error: name is null
go中也有类似于python和java的try catch机制,go中有defer和recover
func get(index int) (ret int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Some error happened!", r)
ret = -1
}
}()
arr := [3]int{2, 3, 4}
return arr[index]
}
func main() {
fmt.Println(get(5))
fmt.Println("finished")
}
$ go run .
Some error happened! runtime error: index out of range [5] with length 3
-1
finished
- 在 get 函数中,使用 defer 定义了异常处理的函数,在协程退出前,会执行完 defer 挂载的任务。因此如果触发了 panic,控制权就交给了 defer。
- 在 defer 的处理逻辑中,使用 recover,使程序恢复正常,并且将返回值设置为 -1,在这里也可以不处理返回值,如果不处理返回值,返回值将被置为默认值 0。
七、 网络编程
1. 互联网的分层模型 - OSI七层模型

八、 并发编程(goroutine)
协程(Coroutines):“也可以叫做纤程、绿色线程”,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做【用户空间线程】,具有对内核来说不可兼得特性

go中有sync和channel两种方式支持协程(goroutine)的并发
1. sync(同步包)
sync是Go语言标准库中的一个包,提供了多种并发同步,主要用于控制多个 goroutine 之间执行顺序
如果我们希望并发下载N个资源,多个并发协程之间不需要通信,就可以使用sync.WaitGroup,等待所有并发协程执行结束
2. channel
go中用于在Goroutine之间通信的机制
- 支持同步和数据共享,避免显式的锁机制
- 使用chan关键字创建,通过 <- 操作符发送和接收数据
本文来自博客园,作者:vstral,转载请注明原文链接:https://www.cnblogs.com/vstral/p/18979881

浙公网安备 33010602011771号