go 基础中的一些坑(2)

类型转换

go 语言中,类型转换是显式的,不会自动转换

 

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 底层由三部分组成:

  1. 指向底层数组的指针
  2. slice 的长度
  3. slice 的容量

在底层数组没有被扩展之前,slice 的长度和容量相等,当我们向 slice 中添加元素时,如果超过了 slice 的容量,那么 go 就会重新分配一个更大的底层数组,并将原始数组复制到心的数组中,这个过程被称为扩容

初始化一个 slice 有两种方式:

  1. 直接使用字面量的方式初始化一个 slice
 

go

复制代码

func main() { s := []int{1, 2, 3, 4} }

  1. 使用 make 函数创建一个 slice,它接收三个参数:
  • 第一个参数是类型
  • 第二个参数是长度
  • 第三个参数是容量
    • 第三个参数可以省略,也就是说如果不指定容量,那么容量等于长度
 

go

复制代码

func main() { s := make([]int, 3, 5) // 创建一个长度为 3,容量为 5 的切片 }

slice 的访问方式和数组一样:

  1. 通过下标访问
 

go

复制代码

arr := [4]int{1, 2, 3, 4} fmt.Println(arr[0]) // 1 s := []int{1, 2, 3, 4} fmt.Println(s[0]) // 1

  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 的长度
  1. 不能越界访问

删除 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.Aserrors.Is 函数是可以判断包装过的 error

package

在一个 package 中执行顺序:

  1. 先执行 const 常量
  2. 再执行 var 变量
  3. 然后再执行 init 函数
  4. 最后执行 main 函数

如果有引入其他的 package,那么它会先执行其他的 packageconstvarinit,然后再执行当前 packageconstvarinit,这是一个深度优先的顺序

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 对同一个变量进行读写操作时,就会出现竞态

因为写入一个变量的操作不是原子的,一般会分为三步:

  1. 读取变量的值:read counter
  2. 对变量的值进行操作:counter = counter + 1
  3. 将最新的值写入变量: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 }

如何解决这个问题呢:

  1. 使用 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()) }
  2. 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, counter+1); swaped { break } } wg.Done() }(i) } wg.Wait() fmt.Println(counter) }
  3. 使用锁
     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) }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Matlab的血管图像增强算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

设计数据库之外部模式:数据库的应用

Chapter5&#xff1a;设计数据库之外部模式&#xff1a;数据库的应用 笔记来源&#xff1a;《漫画数据库》—科学出版社 设计数据库的步骤&#xff1a; 概念模式 概念模式(conceptual schema)是指将现实世界模型化的阶段进而&#xff0c;是确定数据库理论结构的阶段。 概念模…

大模型日报2024-03-23

微软生成式AI入门课程 摘要: 微软推出18堂生成式AI基础课程&#xff0c;适合初学者。课程内容丰富&#xff0c;涵盖理论与实操&#xff0c;使用Jupyter Notebook编写&#xff0c;详见官方教程链接。 DarkGPT&#xff1a;基于GPT-4的OSINT助手 摘要: DarkGPT是一个基于GPT-4-200…

k8s笔记27--快速了解 k8s pod和cgroup的关系

k8s笔记27--快速了解 k8s pod和 cgroup 的关系 介绍pod & cgroup注意事项说明 介绍 随着云计算、云原生技术的成熟和广泛应用&#xff0c;K8S已经成为容器编排的事实标准&#xff0c;学习了解容器、K8S技术对于新时代的IT从业者显得极其重要了。 之前在文章 docker笔记13–…

node.js中常用的命令及示例

node.js中常用的命令及示例&#xff1a; 启动Node.js REPL&#xff08;Read-Eval-Print Loop&#xff09;nodec此命令将启动Node.js的交互式命令行环境&#xff0c;允许你编写并立即执行JavaScript代码。 运行JavaScript文件 node script.js运行名为script.js的JavaScript文件…

【Web APIs】事件高级

目录 1.事件对象 1.1获取事件对象 1.2事件对象常用属性 2.事件流 1.1事件流的两个阶段&#xff1a;冒泡和捕获 1.2阻止事件流动 1.3阻止默认行为 1.4两种注册事件的区别 3.事件委托 1.事件对象 1.1获取事件对象 事件对象&#xff1a;也是一个对象&#xff0c;这个对象里…

apache2.4设置完一个VirtualHost后,所有的域名和IP地址都变成访问这个VirtualHost的解决办法

apache2.4设置完一个VirtualHost&#xff08;如www.example.com&#xff09;后&#xff0c;所有的域名&#xff08;如localhost&#xff09;和IP地址都变成访问这个VirtualHost。 默认的DocumentRoot /var/www/html无效了。 解决办法&#xff1a;在/etc/httpd/conf.d/&#xf…

电子电器架构 —— 诊断数据DTC具体故障篇

电子电器架构 —— 诊断数据DTC起始篇 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师 (Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎…

算法---前缀和练习-2(和为k的子数组)

和为k的子数组 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 创建一个无序映射&#xff08;哈希表&#xff09; hash&#xff0c;用于统计前缀和的出现次数。初始时&#xff0c;将前缀和为 0 的次数设为 1&#xff0c;表示…

Spark重温笔记(三):Spark在企业中为什么能这么强?——持久化、Checkpoint机制、共享变量与内核调度原理全攻略“

Spark学习笔记 前言&#xff1a;今天是温习 Spark 的第 3 天啦&#xff01;主要梳理了 Spark 核心数据结构&#xff1a;RDD(弹性分布式数据集)&#xff0c;包括RDD持久化&#xff0c;checkpoint机制&#xff0c;spark两种共享变量以及spark内核调度原理&#xff0c;希望对大家有…

牛客题霸-SQL篇(刷题记录二)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 以下内容是…

HarmonyOS应用开发实战 - Api9 拍照、拍视频、选择图片、选择视频、选择文件工具类

鸿蒙开发过程中&#xff0c;经常会进行系统调用&#xff0c;拍照、拍视频、选择图库图片、选择图库视频、选择文件。今天就给大家分享一个工具类。 1.话不多说&#xff0c;先展示样式 2.设计思路 根据官方提供的指南开发工具类&#xff0c;基础的拍照、拍视频、图库选照片、选…

使用Python进行自动化测试Selenium与PyTest的结合【第150篇—自动化测试】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行自动化测试&#xff1a;Selenium与PyTest的结合 在软件开发中&#xff0c;自…

线程与进程的爱恨情仇???

线程与进程的爱恨情仇&#xff1f;&#xff1f;&#xff1f; 一&#xff1a;有了进程&#xff0c;为什么还需要线程&#xff1f;&#xff1f;&#xff1f;二:线程三:线程和进程的区别与联系四:创建线程or创建进程 一&#xff1a;有了进程&#xff0c;为什么还需要线程&#xff…

css盒子模型及浮动

内容(content)、内边距(padding)、边框(border)、外边距(margin) oder:1px solid red; 边框的粗细 边框的样式&#xff08;虚线还是实线&#xff09; 边框的颜色 border中也有一些属性可以直接调某一个方向上的边框的粗细&#xff0c;样式&#xff0c;颜色 border-left\bord…

2024/3/24 LED点阵屏

显示原理&#xff1a; 类似矩阵键盘&#xff0c;逐行or逐列扫描 74HC595是串行 寄存器 感觉就是三转八寄存器 并行&#xff1a;同时输出&#xff1b;串行&#xff1a;一位一位输出 先配置74HC595&#xff0c;重新进行位声明 sbit RCKP3^5; //RCLK sbit SCKP3^6; …

芒果YOLOv8改进116:即插即用:集中特征金字塔Centralized Feature Pyramid 高效涨点改进

💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv8 按步骤操作运行改进后的代码即可 该专栏完整目录链接: 芒果YOLOv8深度改进教程 🚀🚀🚀 文章目录 一、Centralized Feature Pyramid论文理论部分 + YOLOv8代码改进论文创新论文贡献论文网络部分实验对比2. YOLOv8 …

淘宝|天猫|京东|1688主流电商平台的实时数据返回接口|附Python实例

导读&#xff1a;随着淘宝/天猫直通车功能升级&#xff0c;很多功能越来越白盒化&#xff0c;越来越简化&#xff0c;更方便用户的操作&#xff0c;只需一键即可看出淘宝/天猫直通车存在的问题。淘宝/天猫直通车千人千面后有了实时数据工具&#xff0c;下面通过一个案例告诉大家…

23. UE5 RPG制作属性面板(一)

随着角色的属性越来越多&#xff0c;我们不能每次都进行showdebug abilitysystem进行查看&#xff0c;而且玩家也需要查看角色属性&#xff0c;所以需要一个查看玩家角色属性的面板。 在前面&#xff0c;我们创建三种类型的属性 Primary Attributes&#xff08;主要属性&#…

Spring-Cloud原理详解

Spring Cloud 是一套基于Spring Boot实现的云应用开发工具集&#xff0c;它为快速构建分布式系统提供了全面的解决方案&#xff0c;大大简化了在分布式系统中常见的诸如服务注册与发现、配置中心、熔断器、服务路由、负载均衡、全链路监控、服务追踪等问题的解决过程。下面是对…