Golang 学习

一些知识点的记录

  1. 反引号的作用:用来创建原生的字符串字面量 ,这些字符串可能由多行组成,不支持任何转义序列。原生的字符串字面量多用于书写多行消息、HTML 以及正则表达式。
  2. [...NodeList]:用扩展运算符将NodeList转换成数组。

go的执行流程

两种方式:

  1. 编译后执行:可以直接拷贝到另外一台机器
  2. 解释型执行:需要开发环境

image.png

注释

支持C语言风格的 /* **/ 和c++风格的行注释://

一、输入与输出 IO

1. 输入

Scan:从标准输入os.Stdin中读取数据,包括Scan,Scanf,Scanln

image.png

2. 输出

Sprintf根数格式化参数生成格式化的字符串并返回该字符串
Printf根据格式化参数生成格式化字符串并写入标准输出

二、操作文件

  1. 打开文件并输出文件内容
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不同,在不同类型变量之间赋值需要显式转换(强制转换)

转换的基本语法:

image.png

一、字符串类型:string

  1. 编码:统一使用UTF-8
  2. string类型一旦赋值,字符串就不能修改,字符串类型不可变

字符串的表示方法

  1. 双引号:可以识别转义字符
  2. 反引号:以字符串原生方式输出,包括换行符和特殊字符。可以防止攻击,输入源代码等

二、浮点型

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的区别对比

image.png

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接口的方法全部实现了,每一次实现方法都相当于是讲方法增加到这个结构体里面

接口需要完全实现

接口实现不完整会导致以下问题:

  1. 编译错误 如果你使用 var _ Person = (*Student)(nil) 这种方式来做静态检查,在编译阶段就能发现 Student 类型没有完整实现 Person 接口,从而避免程序在运行时出现问题3

  2. 运行时 panic 如果你没有做静态检查,而是直接将一个未完整实现接口的类型赋值给接口变量,在调用接口中未实现的方法时,会导致运行时 panic5

  3. 逻辑错误 即使程序没有 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七层模型

image.png

八、 并发编程(goroutine)

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

image.png

go中有sync和channel两种方式支持协程(goroutine)的并发

1. sync(同步包)

sync是Go语言标准库中的一个包,提供了多种并发同步,主要用于控制多个 goroutine 之间执行顺序

如果我们希望并发下载N个资源,多个并发协程之间不需要通信,就可以使用sync.WaitGroup,等待所有并发协程执行结束

2. channel

go中用于在Goroutine之间通信的机制

  • 支持同步和数据共享,避免显式的锁机制
  • 使用chan关键字创建,通过 <- 操作符发送和接收数据
posted @ 2025-07-11 23:00  vstral  阅读(30)  评论(0)    收藏  举报