怎么买网站域名,震天建设集团网站,网站备案 核验单,网页设计教程书籍推荐类型转换
在 go 语言中#xff0c;类型转换是显式的#xff0c;不会自动转换 go
复制代码
func main(){ i : 100 var f float64 f float64(i) }
string 转换成 int 需要借助 strconv 包
使用 strconv.Atoi 函数将 string 转换成 int#xff0c;转换后它会输出两个值类型转换是显式的不会自动转换 go
复制代码
func main(){ i : 100 var f float64 f float64(i) }
string 转换成 int 需要借助 strconv 包
使用 strconv.Atoi 函数将 string 转换成 int转换后它会输出两个值
一个是转换后的值一个 error如果转换出错了第一个值是 0 go
复制代码
func main() { str : 100s i, err : strconv.Atoi(str) if err ! nil { fmt.Println(err) } fmt.Println(i) }
这个包还提供了其他的类型的字符串转换方式
strconv.ParseBool将字符串 bool 转换成 bool 类型strconv.ParseFloat将字符串 float 转换成 float 类型strconv.ParseInt将字符串 int 转换成 int 类型strconv.ParseBool将 bool 类型转成 string 类型
数组
go 中数组是定长的也就是说你需要指定数组的长度未初始化的项默认值是 0
数组需要注意一个越界问题也就是说不能访问数组的长度之外的元素 go
复制代码
func main() { // 10 个元素的数组 arr : [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println(arr[9]) // 10 fmt.Println(arr[10]) // 会报错 arr2 : [10]int{1, 2, 3} // 从下标 4 开始到下标 9 的元素都是 0 }
数组的长度是不可改变的 go
复制代码
func main() { arr : [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} arr [10]int{1, 2, 3} // 数组的长度是不能改变的 }
slice
slice 是一种动态数组它是由相同类型元素组成的序列
slice 底层由三部分组成
指向底层数组的指针slice 的长度slice 的容量
在底层数组没有被扩展之前slice 的长度和容量相等当我们向 slice 中添加元素时如果超过了 slice 的容量那么 go 就会重新分配一个更大的底层数组并将原始数组复制到心的数组中这个过程被称为扩容
初始化一个 slice 有两种方式
直接使用字面量的方式初始化一个 slice go
复制代码
func main() { s : []int{1, 2, 3, 4} }
使用 make 函数创建一个 slice它接收三个参数
第一个参数是类型第二个参数是长度第三个参数是容量 第三个参数可以省略也就是说如果不指定容量那么容量等于长度 go
复制代码
func main() { s : make([]int, 3, 5) // 创建一个长度为 3容量为 5 的切片 }
slice 的访问方式和数组一样
通过下标访问 go
复制代码
arr : [4]int{1, 2, 3, 4} fmt.Println(arr[0]) // 1 s : []int{1, 2, 3, 4} fmt.Println(s[0]) // 1
通过区间访问前闭后开 go
复制代码
arr : [4]int{1, 2, 3, 4} fmt.Println(arr[0:2]) // 1,2 s : []int{1, 2, 3, 4} fmt.Println(s[0:2]) // 1,2
区间访问的时候如果省略了开始的下标那么默认是 0如果省略了结束的下标那么默认是 slice 的长度
不能越界访问
删除 slice 中的某项元素可以使用 append 函数三个点是展开操作符它会将 s 中的元素展开 go
复制代码
s : []int{1, 2, 3, 4, 5, 6, 7, 8} s append(s[:1], s[2:]...)
数组和 slice 最大的区别是数组的长度是固定的而 slice 的长度是可变的 go
复制代码
arr : [8]int{1, 2, 3, 4, 5, 6, 7, 8} // 数组 s : []int{1, 2, 3, 4, 5, 6, 7, 8} // 切片
数组可以转变成切片 go
复制代码
arr : [8]int{1, 2, 3, 4, 5, 6, 7, 8} // 数组 s : arr[:] // 将数组转换成切片
slice 是引用类型
如果一个函数接收的是切片那么它接收的是切片的引用也就是说它会改变原始切片的值 go
复制代码
func main() { s : []int{1, 2, 3, 4, 5, 6, 7, 8} fmt.Println(s) // [1 2 3 4 5 6 7 8] addOne(s) fmt.Println(s) // [2 3 4 5 6 7 8 9] } // 接收的事切片的引用 func addOne(n []int) { for i : 0; i len(n); i { n[i] n[i] 1 } }
如果函数接收的是数组那么它接收的是数组的值也就是说它不会改变原始数组的值 go
复制代码
func main() { s : [8]int{1, 2, 3, 4, 5, 6, 7, 8} fmt.Println(s) // [1 2 3 4 5 6 7 8] addOne(s) fmt.Println(s) // [1 2 3 4 5 6 7 8] } func addOne(n [8]int) { for i : 0; i len(n); i { n[i] n[i] 1 } }
那么数组要实现引用传递怎么办呢可以使用指针 go
复制代码
func main() { s : [8]int{1, 2, 3, 4, 5, 6, 7, 8} fmt.Println(s) // [1 2 3 4 5 6 7 8] addOne(s) fmt.Println(s) // [2 3 4 5 6 7 8 9] } func addOne(n *[8]int) { for i : 0; i len(n); i { n[i] n[i] 1 } }
最后slice 如何传递指针在函数内部需要使用 * 来解引用然后在对 slice 进行操作 go
复制代码
func main() { s : []int{1, 2, 3, 4, 5, 6, 7, 8} fmt.Println(s) addOne(s) fmt.Println(s) } func addOne(n *[]int) { // 先对 n 解引用 _n : *n for i : 0; i len(_n); i { _n[i] _n[i] 1 } }
map
map 未初始化可以取值但赋值会报错 go
复制代码
func main() { var m1 map[string]int fmt.Println(m1[age]) // 0 m1[age] 1 // 报错 }
map 的初始化有两种方式 go
复制代码
m1 : map[string]int{age: 1} // 字面量的方式 m2 : map[string]int{} // 这种也是字面量 m3 : make(map[string]int) // 使用 make 函数
如何判断一个属性在不在 map 中呢可以使用 ok 来判断 go
复制代码
func main() { m2 : make(map[string]int) m2[age] 25 a, ok : m2[age] fmt.Println(a, ok) // 25 true a, ok m2[age2] fmt.Println(a, ok) // 0 false }
switch
go 中的 switch 如果命中某条 case 语句后就不会命中其他 case 语句了
switch 可以使用 x.(type) 的方式类判断一个变量的类型x.(type) 只能在 switch 语句中使用不能在 if 语句中使用 go
复制代码
func typeof(x interface{}) { switch x.(type) { case int: fmt.Println(int) case string: fmt.Println(string) default: fmt.Println(unknown) } }
for
在 for 循环中如果操作指针的话会有一个问题如下所示添加到 oddNumbers 中都是 7
因为 number 是一个变量它的地址是不变的所以 oddNumbers 中的元素都是 number 的地址而 number 最后的值是 7 go
复制代码
func main() { numbers : []int{1, 2, 3, 4, 5, 6, 7} var oddNumbers []*int for _, number : range numbers { // number 的地址是不变的 oddNumbers append(oddNumbers, number) } for _, oddNumber : range oddNumbers { fmt.Println(*oddNumber) } }
如何解决这个问题呢可以使用一个临时变量每一个循环进来的时候都创建一个临时变量然后将它的地址添加到 oddNumbers 中 go
复制代码
func main() { numbers : []int{1, 2, 3, 4, 5, 6, 7} var oddNumbers []*int for _, number : range numbers { // 每次循环都会创建一个新变量然后将它的地址添加到 oddNumbers 中 tmp : number oddNumbers append(oddNumbers, tmp) } for _, oddNumber : range oddNumbers { fmt.Println(*oddNumber) } }
error
任何一个实现了 Error() 方法的类型都可以作为错误类型 go
复制代码
type MyError struct { message string code int } func (e MyError) Error() string { return e.message } func add() (*int, error) { var myError MyError{ message: This is an error, code: 500, } return nil, myError // 使用自定义的错误 }
判断一个 error 是什么类型最好使用 errors.Is 函数不要用 来判断 go
复制代码
e1 : fmt.Errorf(error 1: %w, io.EOF) fmt.Println(errors.Is(e1, io.EOF)) // true fmt.Println(e1 io.EOF) // false
error 类型断言可以使用 errors.As 函数不要使用 err.(xx)
因为 errors.As 和 errors.Is 函数是可以判断包装过的 error
package
在一个 package 中执行顺序
先执行 const 常量再执行 var 变量然后再执行 init 函数最后执行 main 函数
如果有引入其他的 package那么它会先执行其他的 package 的 constvarinit然后再执行当前 package 的 constvarinit这是一个深度优先的顺序
go mod 是一个用于管理 go 的依赖模块它会将依赖的模块下载到 go 的缓存目录中然后在 go.mod 文件中记录下来一般存储在 $GOPATH/pkg/mod 目录中
go mod 的命令
go mod init初始化一个 go.mod 文件go mod tidy根据 go.mod 文件整理依赖go mod download下载 go.mod 文件中的依赖但不安装go mod verify验证依赖是否正确和完整go mod graph以图形化显示模块之间的依赖关系go mod why解释模块为什么需要特定的依赖
receiver
指针类型 receiver 使用指向该类型的指针作为接收者值类型 receiver 使用该类型的值作为接收者
定义一个 Animal 接口它有一个 Eat 方法定义 Dog 结构体它有一个 Say 方法然后实现 Animal 接口
使用指针类型作为 receiver实现接口需要使用指针类型的值 go
复制代码
type Animal interface { Eat() } type Dog struct { Name string } func (d *Dog) Say() { fmt.Printf(Name is %v, d.Name) } func main() { // 这里只能使用指针类型的值 var a Animal Dog{Name: uccs} a.Say() }
使用值类型作为 receiver实现接口可以使用值类型的值也可以使用指针类型的值 go
复制代码
type Animal interface { Say() } type Dog struct { Name string } func (d Dog) Say() { fmt.Printf(Name is %v, d.Name) } func main() { // 这里可以使用指针类型的值 var a Animal Dog{Name: uccs} a.Say() // 也可以使用值类型的值 var a1 Animal Dog{Name: astak} a1.Say() }
interface
接口类型断言 go
复制代码
func AnimalSleep(e Eater){ if s, ok : e.(Animal); ok { s.Sleep() } }
对于 interface 类型的变量不能使用指针类型的零值(指针类型的零值是 nil)你会处理 go 中的 nil 吗
抽象接口的实现 go
复制代码
type Worker interface { doWork() Start() } type BaseWorker struct { Worker } func (b *BaseWorker) Start() { fmt.Println(start) b.doWork() fmt.Println(end) } type NormalWorker struct { BaseWorker } func (n *NormalWorker) doWork() { fmt.Println(do work) } func NewNormalWorker() Worker { n : NormalWorker{BaseWorker{}} // 这边需要赋值不然会报错 n.Worker n return n } func main() { NewNormalWorker().Start() }
goroutine 中的竞态
当有多个 goroutine 对同一个变量进行读写操作时就会出现竞态
因为写入一个变量的操作不是原子的一般会分为三步
读取变量的值read counter对变量的值进行操作counter counter 1将最新的值写入变量write counter go
复制代码
var counter int32 func main() { var wg sync.WaitGroup for i : 0; i 1000; i { wg.Add(1) go func(i int) { counter counter 1 wg.Done() }(i) } wg.Wait() fmt.Println(counter) // 不一定是 1000 }
如何解决这个问题呢
使用 atomic 包中的原子操作 在 x86 架构中atomic.AddInt32 函数使用 lock xaddq 指令来实现原子加操作 go 复制代码 var counter int32 func main() { var wg sync.WaitGroup for i : 0; i 1000; i { wg.Add(1) go func(i int) { // 这个操作是原子的 atomic.AddInt32(counter, 1) wg.Done() }(i) } wg.Wait() fmt.Println(counter) } 这两种方法是一样的只是 atomic.AddInt32 的方法更加简洁 go 复制代码 var counter atomic.Int32{} func main() { var wg sync.WaitGroup for i : 0; i 1000; i { wg.Add(1) go func(i int) { counter.Add(1) wg.Done() }(i) } wg.Wait() fmt.Println(counter.Load()) } atomic.CompareAndSwapInt64 函数是使用 CPU 的原子指令来实现的它使用了 CPU 提供的 compare-and-swap 指令来保证原子性 CAS 指令可以原子的比较并交换一个内存地址中的值它有三个操作数内存地址 addr期望的旧值 old新值 new如果内存地址 addr 中的值等于 old那么将 new 的值写入 addr 中否则不做任何操作 go 复制代码 var counter int32 func main() { var wg sync.WaitGroup for i : 0; i 1000; i { wg.Add(1) go func(i int) { for { if swaped : atomic.CompareAndSwapInt32(counter, counter, counter1); swaped { break } } wg.Done() }(i) } wg.Wait() fmt.Println(counter) } 使用锁 go 复制代码 var counter int32 var lock sync.Mutex func main() { var wg sync.WaitGroup for i : 0; i 1000; i { wg.Add(1) go func(i int) { lock.Lock() counter counter 1 lock.Unlock() wg.Done() }(i) } wg.Wait() fmt.Println(counter) }