Go语言笔记 参考:https://www.runoob.com/go/go-tutorial.html
Go语言的优势 Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。它具有以下优势:
简单易学 :Go语言语法简洁,易于上手,减少了程序员的认知负担
高性能 :编译成机器码,执行效率高,接近C/C++
并发支持 :内置goroutine(轻量级线程)和channel,简化并发编程
内存安全 :拥有垃圾回收机制,避免内存泄漏
丰富的标准库 :提供了大量实用的标准库,减少开发工作量
工具链完善 :内置构建工具、格式化工具、测试工具等
跨平台 :支持多种操作系统和硬件架构
强类型 :静态类型系统,在编译时就能发现很多错误
C++转Go语言指南 对于C++程序员来说,Go语言既有许多熟悉的概念,也有一些需要适应的新特性。本章节将帮助C++程序员快速理解和掌握Go语言。
1. 语言哲学与主要区别 C++和Go虽然都是静态类型的编译型语言,但它们的设计哲学有很大不同:
特性
C++
Go
复杂度
复杂,提供多种编程范式
简洁,专注于实用主义
内存管理
手动内存管理(new/delete)
自动垃圾回收
并发模型
基于线程和锁
基于goroutine和channel
编译速度
较慢
非常快
错误处理
基于异常
基于返回值
模板
复杂的模板系统
简单的泛型支持(Go 1.18+)
2. 基础语法对比 程序结构 C++:
1 2 3 4 5 6 #include <iostream> int main () { std::cout << "Hello, World!" << std::endl; return 0 ; }
Go:
1 2 3 4 5 6 7 package mainimport "fmt" func main () { fmt.Println("Hello, World!" ) }
变量声明 C++:
1 2 3 int a = 10 ;const int b = 20 ;int c, d;
Go:
1 2 3 4 5 6 7 var a int = 10 const b int = 20 var c, d int var e = 30 f := 40
函数定义 C++:
1 2 3 4 5 6 7 8 int add (int a, int b) { return a + b; } double add (double a, double b) { return a + b; }
Go:
1 2 3 4 5 6 7 8 9 10 11 12 func add (a, b int ) int { return a + b } func addVariadic (args ...int ) int { sum := 0 for _, v := range args { sum += v } return sum }
3. 内存管理 C++程序员需要特别注意Go的自动内存管理机制:
堆与栈分配 C++:
1 2 3 4 5 int a = 10 ;int * p = new int (20 );delete p;
Go:
1 2 3 4 5 6 var a int = 10 p := new (int ) *p = 20
切片与数组 C++:
1 2 3 4 5 int arr[5 ] = {1 , 2 , 3 , 4 , 5 };std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; vec.push_back (6 );
Go:
1 2 3 4 5 var arr [5 ]int = [5 ]int {1 , 2 , 3 , 4 , 5 }slice := []int {1 , 2 , 3 , 4 , 5 } slice = append (slice, 6 )
4. 面向对象编程 Go没有类和继承,但可以通过结构体和接口实现类似的功能:
结构体与方法 C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Rectangle {private : double width; double height; public : Rectangle (double w, double h) : width (w), height (h) {} double area () const { return width * height; } void scale (double factor) { width *= factor; height *= factor; } }; Rectangle rect (10 , 5 ) ;std::cout << rect.area () << std::endl; rect.scale (2 );
Go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Rectangle struct { Width float64 Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r *Rectangle) Scale(factor float64 ) { r.Width *= factor r.Height *= factor } rect := Rectangle{10 , 5 } fmt.Println(rect.Area()) rect.Scale(2 )
接口实现 C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Shape {public : virtual ~Shape () = default ; virtual double area () const = 0 ; }; class Circle : public Shape {private : double radius; public : Circle (double r) : radius (r) {} double area () const override { return 3.14 * radius * radius; } };
Go:
1 2 3 4 5 6 7 8 9 10 11 12 type Shape interface { Area() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius }
5. 并发编程 这是Go语言最强大的特性之一,相比C++的线程模型有很大不同:
C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <thread> #include <vector> #include <iostream> void printHello (int id) { std::cout << "Hello from thread " << id << std::endl; } int main () { std::vector<std::thread> threads; for (int i = 0 ; i < 5 ; i++) { threads.push_back (std::thread (printHello, i)); } for (auto & t : threads) { t.join (); } return 0 ; }
Go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "fmt" "time" ) func printHello (id int ) { fmt.Printf("Hello from goroutine %d\n" , id) } func main () { for i := 0 ; i < 5 ; i++ { go printHello(i) } time.Sleep(time.Second) }
通道(Channel) Go的通道提供了一种在goroutine之间安全传递数据的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { ch := make (chan int ) go func () { ch <- 42 }() value := <-ch fmt.Println("Received:" , value) close (ch) }
6. 错误处理 Go使用返回值而不是异常来处理错误:
C++:
1 2 3 4 5 6 7 8 try { if (b == 0 ) { throw std::runtime_error ("Division by zero" ); } int result = a / b; } catch (const std::exception& e) { std::cerr << "Error: " << e.what () << std::endl; }
Go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func divide (a, b int ) (int , error ) { if b == 0 { return 0 , errors.New("division by zero" ) } return a / b, nil } func main () { result, err := divide(10 , 0 ) if err != nil { fmt.Println("Error:" , err) return } fmt.Println("Result:" , result) }
7. 实用技巧
使用Go的格式化工具 :gofmt
或go fmt
可以自动格式化代码,保持一致的代码风格
使用Go模块管理依赖 :go mod init
、go get
、go mod tidy
等命令
使用defer
进行资源清理 :替代C++中的RAII模式
使用go vet
检查代码问题 :静态代码分析工具
使用接口进行抽象 :Go的接口是隐式的,更容易实现组合而非继承
避免过度使用指针 :Go的垃圾回收器很高效,不必像C++那样频繁使用指针优化性能
学习Go的标准库 :Go的标准库非常强大,涵盖了网络、文件操作、加密等多种功能
通过理解这些关键区别和转换技巧,C++程序员可以更快地适应Go语言的开发风格,并充分利用Go的简洁性和高效的并发模型。
安装和环境配置 Go语言的安装非常简单,以下是针对不同操作系统的基本安装步骤:
Windows安装
访问Go官方下载页面 下载Windows安装包
运行安装包并按照提示完成安装
安装完成后,打开命令提示符,输入go version
验证安装是否成功
Linux/Mac安装 1 2 3 4 5 6 7 8 9 sudo apt-get updatesudo apt-get install golang-gobrew install go go version
环境变量配置 安装完成后,建议配置以下环境变量:
GOPATH : Go的工作目录,用于存放代码和依赖
GOROOT : Go的安装目录(通常由安装程序自动设置)
PATH : 包含Go的bin目录,以便在任何位置执行Go命令
Go模块管理 Go模块是Go 1.11版本引入的官方依赖管理工具,使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 go mod init myproject go get github.com/pkg/errors go mod tidy go build go run main.go
Go模块的高级用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 go mod graph go list -m -json github.com/pkg/errors go get -u github.com/pkg/errors go get github.com/pkg/errors@v0.9.1 go mod tidy go mod verify go mod vendor go build -mod=vendor
go.mod文件详解 go.mod文件是Go模块的核心,包含了模块的基本信息和依赖声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module github.com/user/myproject go 1.17 require ( github.com/pkg/errors v0.9 .1 github.com/sirupsen/logrus v1.8 .1 ) require ( github.com/davecgh/go -spew v1.1 .1 github.com/pmezard/go -difflib v1.0 .0 github.com/stretchr/testify v1.7 .0 golang.org/x/sys v0.0 .0 -20211030050608 -9e8 e0b390896 )
Go语言的常见陷阱 1. 切片的共享底层数组 1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { s := []int {1 , 2 , 3 , 4 , 5 } t := s[1 :3 ] t[0 ] = 100 fmt.Println(s) fmt.Println(t) }
2. map的并发访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "sync" ) func main () { m := make (map [string ]int ) var wg sync.WaitGroup for i := 0 ; i < 10 ; i++ { wg.Add(1 ) go func (i int ) { defer wg.Done() m[fmt.Sprintf("key%d" , i)] = i }(i) } wg.Wait() fmt.Println(m) }
解决方法:使用sync.Map或加锁保护
1 2 3 4 5 6 7 8 9 10 11 var m sync.Mapvar m = make (map [string ]int )var mu sync.Mutexmu.Lock() m["key" ] = value mu.Unlock()
3. for循环中的闭包引用变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "fmt" "time" ) func main () { for i := 0 ; i < 5 ; i++ { go func () { fmt.Println(i) }() } time.Sleep(1 * time.Second) for i := 0 ; i < 5 ; i++ { go func (i int ) { fmt.Println(i) }(i) } time.Sleep(1 * time.Second) }
4. nil接口不等于nil指针 1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var p *int = nil var i interface {} = p fmt.Println(p == nil ) fmt.Println(i == nil ) }
5. 指针作为值接收器与指针接收器的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport "fmt" type Person struct { Name string } func (p Person) SetName1(name string ) { p.Name = name } func (p *Person) SetName2(name string ) { p.Name = name } func main () { p := Person{Name: "张三" } p.SetName1("李四" ) fmt.Println(p.Name) p.SetName2("王五" ) fmt.Println(p.Name) }
空白标识符 空白标识符(_)在Go语言中有多种用途:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "os" ) func main () { _ = fmt.Println _ = os.Mkdir("test" , 0755 ) file, _ := os.Open("test.txt" ) if file != nil { defer file.Close() } numbers := []int {1 , 2 , 3 , 4 , 5 } for _, v := range numbers { fmt.Println(v) } var i interface {} = 42 if _, ok := i.(int ); ok { fmt.Println("i是int类型" ) } }
数据类型 数据类型 值类型是 int、float、string、bool、struct和array,直接存储值,分配栈的内存空间,被函数调用完之后会释放。
引用类型是 slice、map、chan和值类型对应的指针,存储是一个地址(指针),指针指向内存中真正存储数据的首地址,内存通常在堆分配,通过GC回收。
new 与 make 的区别
new 的参数要求传入一个类型,而不是一个值,它会申请该类型的内存大小空间,并初始化为对应的零值,返回该指向类型空间的一个指针。
make 也用于内存分配,但它只用于引用对象 slice、map、channel的内存创建,返回的类型是类型本身。
变量声明与初始化 Go语言有多种变量声明和初始化的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport "fmt" func main () { var a int a = 10 var b int = 20 var c = 30 d := 40 var e, f int = 50 , 60 var g, h = 70 , "hello" i, j := 80 , "world" var ( k int = 90 l string = "Go" m bool = true ) fmt.Println(a, b, c, d, e, f, g, h, i, j, k, l, m) }
流程控制 条件语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func main () { age := 18 if age >= 18 { fmt.Println("成年人" ) } else if age >= 6 { fmt.Println("未成年人" ) } else { fmt.Println("儿童" ) } if score := 95 ; score >= 90 { fmt.Println("优秀" ) } else if score >= 80 { fmt.Println("良好" ) } else { fmt.Println("一般" ) } }
循环语句 Go语言只有for一种循环结构,但可以实现多种循环方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" func main () { for i := 0 ; i < 5 ; i++ { fmt.Println(i) } j := 0 for j < 5 { fmt.Println(j) j++ } numbers := []int {1 , 2 , 3 , 4 , 5 } for index, value := range numbers { fmt.Printf("索引: %d, 值: %d\n" , index, value) } }
基础语法 基础语语法
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
第一行代码 package main 定义包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
Go 语言的字符串可以通过 + 实现
常量定义:const identifier [type] = value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { const LENGTH int = 10 const WIDTH int = 5 var area int const a, b, c = 1 , false , "str" area = LENGTH * WIDTH fmt.Printf("面积为 : %d" , area) println () println (a, b, c) }
iota,特殊常量,可以认为是一个可以被编译器修改的常量。iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
go 语言函数可以返回多个值。
Go 语言切片是对数组的抽象。Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
错误处理 Go语言使用错误值而非异常来表示错误状态。错误类型是一个内置接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 type error interface { Error() string } package mainimport ( "errors" "fmt" ) func divide (a, b int ) (int , error ) { if b == 0 { return 0 , errors.New("除数不能为零" ) } return a / b, nil } func main () { result, err := divide(10 , 0 ) if err != nil { fmt.Println("发生错误:" , err) } else { fmt.Println("结果:" , result) } }
指针 指针是存储另一个变量内存地址的变量。Go语言的指针使用非常安全,不支持指针运算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func main () { a := 10 var p *int p = &a q := &a fmt.Println("a的值:" , a) fmt.Println("a的地址:" , &a) fmt.Println("p存储的地址:" , p) fmt.Println("p指向的值:" , *p) *p = 20 fmt.Println("修改后a的值:" , a) fmt.Println("q指向的值:" , *q) }
结构体 结构体是一种复合数据类型,它组合了零个或多个任意类型的命名变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport "fmt" type Person struct { Name string Age int City string } func main () { var p1 Person p1.Name = "张三" p1.Age = 30 p1.City = "北京" p2 := Person{ Name: "李四" , Age: 25 , City: "上海" , } p3 := Person{"王五" , 35 , "广州" } p4 := &Person{"赵六" , 40 , "深圳" } fmt.Println(p1) fmt.Println(p2) fmt.Println(p3) fmt.Println(*p4) fmt.Println(p4.Name) }
结构体方法 方法是与特定类型关联的函数,可以为结构体添加方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" type Rectangle struct { Width, Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r *Rectangle) Scale(factor float64 ) { r.Width *= factor r.Height *= factor } func main () { rect := Rectangle{10 , 5 } fmt.Println("面积:" , rect.Area()) rect.Scale(2 ) fmt.Println("缩放后的矩形:" , rect) fmt.Println("缩放后的面积:" , rect.Area()) }
接口 Go语言的接口是隐式实现的,不需要显式声明实现了哪个接口。只要一个类型拥有接口要求的所有方法,它就自动实现了该接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport "fmt" type Shape interface { Area() float64 } type Rectangle struct { Width float64 Height float64 } type Circle struct { Radius float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } func PrintArea (s Shape) { fmt.Printf("面积是: %f\n" , s.Area()) } func main () { r := Rectangle{Width: 10 , Height: 5 } c := Circle{Radius: 3 } PrintArea(r) PrintArea(c) }
切片操作 切片是Go语言中非常重要的数据结构,以下是一些常用的切片操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport "fmt" func main () { var s1 []int s2 := []int {1 , 2 , 3 , 4 , 5 } s3 := make ([]int , 5 ) s4 := make ([]int , 5 , 10 ) s5 := s2[1 :3 ] s6 := s2[2 :] s7 := s2[:3 ] s8 := append (s2, 6 , 7 , 8 ) s9 := append (s8, s5...) src := []int {1 , 2 , 3 } dst := make ([]int , 3 ) copy (dst, src) fmt.Println("s1:" , s1) fmt.Println("s2:" , s2) fmt.Println("s3:" , s3) fmt.Println("s4:" , s4) fmt.Println("s5:" , s5) fmt.Println("s6:" , s6) fmt.Println("s7:" , s7) fmt.Println("s8:" , s8) fmt.Println("s9:" , s9) fmt.Println("src:" , src) fmt.Println("dst:" , dst) fmt.Println("s2长度:" , len (s2), "容量:" , cap (s2)) }
映射(Map) 映射是一种键值对数据结构,类似于其他语言中的字典或哈希表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport "fmt" func main () { var m1 map [string ]int m2 := make (map [string ]int ) m3 := map [string ]int { "apple" : 5 , "orange" : 3 , "banana" : 7 , } m2["golang" ] = 100 m2["python" ] = 80 m3["pear" ] = 2 m3["apple" ] = 10 fmt.Println("m2[" golang"]:" , m2["golang" ]) value, exists := m2["java" ] if exists { fmt.Println("java存在,值为:" , value) } else { fmt.Println("java不存在" ) } delete (m3, "orange" ) for key, value := range m3 { fmt.Printf("%s: %d\n" , key, value) } fmt.Println("m3长度:" , len (m3)) }
defer语句 defer语句用于延迟执行函数,通常用于资源清理、关闭文件等场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport "fmt" func main () { defer fmt.Println("这是最后执行的" ) fmt.Println("这是先执行的" ) defer fmt.Println("defer 1" ) defer fmt.Println("defer 2" ) defer fmt.Println("defer 3" ) file := openFile("test.txt" ) defer closeFile(file) x := 1 defer fmt.Printf("x的值: %d\n" , x) x = 100 } func openFile (filename string ) string { fmt.Printf("打开文件: %s\n" , filename) return filename } func closeFile (filename string ) { fmt.Printf("关闭文件: %s\n" , filename) }
panic和recover Go语言使用panic和recover机制来处理程序运行时的严重错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func main () { defer func () { if r := recover (); r != nil { fmt.Println("恢复程序" , r) } }() divide(10 , 0 ) fmt.Println("程序继续执行" ) } func divide (a, b int ) { if b == 0 { panic ("除数不能为零" ) } fmt.Println("结果:" , a/b) }
并发 Go语言通过goroutine实现并发,goroutine是轻量级线程,由Go运行时管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "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" ) }
通道(channel)用于在goroutine之间传递数据,确保数据同步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { ch := make (chan int ) go func () { ch <- 42 }() value := <-ch fmt.Println("收到的数值:" , value) close (ch) }
标准库的使用 Go语言有丰富的标准库,以下是一些常用标准库的使用示例:
字符串处理 (strings包) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "strings" ) func main () { s := "Hello, Go!" fmt.Println(strings.Contains(s, "Go" )) fmt.Println(strings.Count(s, "l" )) fmt.Println(strings.HasPrefix(s, "Hello" )) fmt.Println(strings.HasSuffix(s, "!" )) fmt.Println(strings.Index(s, "Go" )) fmt.Println(strings.Replace(s, "Go" , "World" , -1 )) parts := strings.Split("a,b,c,d" , "," ) fmt.Println(parts) joined := strings.Join(parts, "-" ) fmt.Println(joined) fmt.Println(strings.ToUpper(s)) fmt.Println(strings.ToLower(s)) }
文件操作 (os和io/ioutil包) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package mainimport ( "fmt" "io/ioutil" "os" ) func main () { file, err := os.Create("test.txt" ) if err != nil { fmt.Println("创建文件失败:" , err) return } defer file.Close() file.WriteString("Hello, Go!" ) file.Sync() content, err := ioutil.ReadFile("test.txt" ) if err != nil { fmt.Println("读取文件失败:" , err) return } fmt.Println("文件内容:" , string (content)) _, err = os.Stat("test.txt" ) if os.IsNotExist(err) { fmt.Println("文件不存在" ) } else { fmt.Println("文件存在" ) } err = os.Remove("test.txt" ) if err != nil { fmt.Println("删除文件失败:" , err) } }
Context包 (用于控制goroutine的生命周期) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package mainimport ( "context" "fmt" "time" ) func main () { ctx, cancel := context.WithCancel(context.Background()) defer cancel() go worker(ctx, "worker1" ) time.Sleep(3 * time.Second) fmt.Println("取消任务" ) cancel() time.Sleep(1 * time.Second) fmt.Println("程序退出" ) } func worker (ctx context.Context, name string ) { for { select { case <-ctx.Done(): fmt.Printf("%s: 收到取消信号,停止工作\n" , name) return default : fmt.Printf("%s: 正在工作...\n" , name) time.Sleep(1 * time.Second) } } }
类型断言 类型断言用于检查接口值的具体类型,或者将接口值转换为指定类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport "fmt" func main () { var i interface {} i = 42 value, ok := i.(int ) if ok { fmt.Printf("i的类型是int,值为: %d\n" , value) } else { fmt.Println("i的类型不是int" ) } i = "hello" switch v := i.(type ) { case int : fmt.Printf("这是一个整数: %d\n" , v) case string : fmt.Printf("这是一个字符串: %s\n" , v) case bool : fmt.Printf("这是一个布尔值: %t\n" , v) default : fmt.Printf("未知类型: %T\n" , v) } }
包结构
权限:所有成员在包内均可见,无论是不是在一个源码文件中。但只有首字母大写的为可导出成员,在包外可见。
源码必须使用UTF-8。
包内每个源文件都可以定义一个或多个初始化函数,但编译器不保证执行顺序。编译器先初始化全局变量,然后才开始执行初始化函数。最后才执行main包的main函数。
内部包:所有保存在internal目录下的包(包含自身)仅能被其父目录下的包(包含所有层次的子目录)访问。
反射 反射是指在程序运行时动态地获取变量的类型信息和值信息的能力。Go语言提供了reflect包来支持反射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "fmt" "reflect" ) func main () { var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("类型:" , v.Type()) fmt.Println("值:" , v.Float()) fmt.Println("可设置:" , v.CanSet()) }
测试 Go语言内置了测试框架,通过go test命令运行测试。测试文件通常以_test.go结尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package examplefunc Add (a, b int ) int { return a + b } package exampleimport "testing" func TestAdd (t *testing.T) { result := Add(1 , 2 ) if result != 3 { t.Errorf("Add(1, 2) = %d; want 3" , result) } }
运行测试命令: