Golang 不常被注意的特性

同步发布在我的博客:
Golang 不常被注意的特性

Golang 不常被注意的特性

阅读项目https://github.com/astaxie/build-web-application-with-golang进行的查漏补缺。

1. rune和byte类型

runebyte是 go 内置的两种类型别名。其中runeint32的别称,byteuint8的别称。

在处理中文时,使用rune可以正确计算字符串长度(截取字符串也是这样):

fmt.Println(len("Go语言编程"))  // 输出:14  
// 转换成 rune 数组后统计字符串长度
fmt.Println(len([]rune("Go语言编程")))  // 输出:6

这是因为UTF-8使用1~4个字节编码

参见 详解 Go 中的 rune 类型

2. slice的容量

每个slice 都对应一个底层数组(一个数组可以对应多个 slice),slice 可以视为一个结构体,包含了三个元素

  • 一个指针,指向数组中slice指定的开始位置
  • 长度,即slice的长度
  • 最大长度,也就是slice开始位置到数组的最后位置的长度

举一个例子:


// 声明一个数组
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个slice
var aSlice, bSlice []byte

aSlice = array[3:7]  // aSlice包含元素: d,e,f,g,len=4,cap=7(d~j)
// 从slice中获取slice
bSlice = aSlice[0:6] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h,i

最后一个操作合法是因为aSlice的 cap 默认设置为切片起始位置到数组末尾的长度。

如果在声明切片时显示指定cap,这样这个产生的新的slice就没办法访问最后的三个元素。:

aslice = array[3:7:8]  // aSlice包含元素: d,e,f,g,len=4,cap=5(d~h)
bSlice = array[0:6]  // panic: runtime error: slice bounds out of range [:6] with capacity 5

初始容量和增长率规则:

初始 cap:取决于创建切片的方式(可以指定,也可以通过字面量决定)。

增长率

  • 当容量小于 1024 时,cap 按 2 倍增长。
  • 当容量大于等于 1024 时,cap 按 25% 增长。

3. switch 优化

如果 switch 语句的条件是 连续整数值,Go 编译器可能会优化生成一个 查找表(jump table)来加速分支跳转,实现 O(1) 的跳转速度。

如果 switch 中的条件值是非连续但可以排序的,编译器可能会将 switch 语句转换为一种二分查找的形式,从而减少分支的比较次数。这种优化适用于较大的 switch 语句,尤其当分支数较多时。

如果 switch 语句中的条件是字符串或布尔值(或其他复杂类型),通常不会有查找表优化或二分查找优化,而是逐一比较每个分支,这与 if-else 的行为基本一致。

4. 区分 new、make 和 字面量初始化

在 Go 语言中,newmake 和字面量初始化(也称为字面量表达式)是用于创建和初始化变量的不同方式。它们之间有一些重要的区别,适用于不同的场景。下面将详细比较这三者:

4.1. new 函数

  • 功能new(T) 分配了类型 T 的内存,并返回一个指向该类型零值的指针(即 *T 类型)。
  • 适用类型new 可以用于任何类型(包括基本类型、结构体、数组、切片、map、channel 等)。new 的使用相对较少,主要用于在没有字面量初始化的情况下,快速获取一个类型的零值指针。在一些复杂的反射操作中,new 可以用来动态创建类型实例,特别是当你需要通过反射动态创建某个类型的实例时。在实际开发中,大部分情况下开发者更倾向于使用结构体、数组或切片字面量语法来进行初始化,而不是依赖 new
  • 返回值:返回类型 T 的零值指针。
  • 初始化行为new 不会初始化为非零值,只会分配零值(例如:int0string""struct 所有字段为零值)。

示例:

x := new(int)   // 返回一个指向 int 类型零值的指针,x 的值是 *int 类型,初始为 0
y := new([]int) // 返回一个指向空切片(零值切片)的指针
m := new(map[string]int) // 返回一个 *map[string]int 类型的指针,值是 nil

4.2. make 函数

  • 功能make(T, args) 用于初始化内建类型(slicemapchannel)。make 会为这些类型分配内存,并且初始化数据结构的内部字段(例如:slice 的底层数组,map 的哈希表等)。
  • 适用类型:只能用于 slicemapchannel 类型。
  • 返回值:返回类型 T,不是指针。返回的是已经初始化的类型,而不是类型的零值。
  • 初始化行为make 会将这些类型的内部结构初始化为有效状态,使它们能够被使用。对于 slice,它会返回一个具有指定长度和容量的切片;对于 mapchannel,它会分配内存并返回一个空的、有效的 mapchannel

示例:

s := make([]int, 10)       // 创建一个长度为 10 的切片,元素为零值(0)
m := make(map[string]int)  // 创建一个空的 map
ch := make(chan int, 2)    // 创建一个缓冲区大小为 2 的 channel

4.3. 字面量初始化

  • 功能:字面量初始化是通过直接使用字面量来创建并初始化一个变量。你可以为变量的各个字段或元素指定值。
  • 适用类型:可以用于所有类型,包括基本类型、数组、结构体、切片、map 和 channel。
  • 返回值:返回已初始化的值,通常是一个变量,而不是指针(除非显式使用 & 来获取指针)。
  • 初始化行为:字面量初始化会直接赋值并初始化变量为指定值,可以是零值,也可以是非零值。

示例:

x := 42                // int 类型变量,直接赋值为 42
s := []int{1, 2, 3}    // 创建并初始化一个切片
m := map[string]int{"a": 1, "b": 2} // 创建并初始化一个 map
b := Box{width: 10, height: 5}   // 创建并初始化一个结构体

4.4. 比较总结

特性newmake字面量初始化
适用类型所有类型(包括基本类型、结构体、数组等)仅限内建类型(slicemapchannel所有类型
返回值返回类型的零值指针(*T 类型)返回初始化后的值(T 类型)返回初始化后的值(T 类型)
初始化行为初始化为零值初始化为有效的非零值根据字面量的定义初始化,可能是零值或非零值
是否需要指针返回一个指针返回一个值,非指针返回一个值,非指针(除非使用 &
用途主要用于获取指向零值的指针用于初始化切片、映射和通道用于直接创建和初始化变量,最常用

4. 无类型常量和类型自动匹配

Golang 不支持变量的隐式转换。

在 Go 中,常量可以是无类型常量(untyped constant)。这意味着常量在声明时并不直接指定其类型,而是由使用该常量的上下文决定其类型。无类型常量的特性使得它们在赋值或作为表达式的部分时,可以自动地根据目标类型进行转换或匹配。

但是在常量和特定情况下,Go 会在编译时进行类型自动匹配(例如,将 int 类型的常量转为 bytefloat64 类型)。

看下面的例子:

package main

import "fmt"

const(
	WHITE = iota  // 无类型常量
	BLACK
	BLUE
	RED
	YELLOW
)

type Color byte

type Box struct {
	width, height, depth float64
	color Color
}

func (b *Box) SetColor(c Color) { 
	b.color = c 
}

func (bl BoxList) BiggestColor() Color {
	v := 0.00
	k := Color(WHITE)  // 进行显示转换
	for _, b := range bl {
		if bv := b.Volume(); bv > v {
			v = bv
			k = b.color
		}
	}
	return k
}

func (bl BoxList) PaintItBlack() {
	for i := range bl {
		bl[i].SetColor(BLACK)  // 没有进行显式转换
	}
}

注意到BiggestColorPaintItBlack方法中有对于常量WHITE的赋值有两个不同的写法。

  • k := Color(WHITE)强制进行了类型转换,这是因为**变量k**会自动推断为等号右侧的类型,如果不进行类型转换,k会被视为iota的默认类型int,使得k = b.color这一行出现编译器报错:无法将 ‘b. color’ (类型 Color) 用作类型 int
  • bl[i].SetColor(BLACK) 没有参数进行类型转换,BLACK传入SetColor 函数后自动变为了Color类型(可以通过DEBUG证实)。这是因为Colorbyte类型的别名,而int类型能够安全转化为byte类型,因此编译器自动进行了转换。

这里如果将WHITE = iota语句改为WHITE Color = iota,即可省去BiggestColor方法中的显示转换。

更详细可见博客 小心golang中的无类型常量"

5. 指针作为receiver

Go 中 method 可以作用于任何自定义类型中(不只是 struct,可以是type 声明的任何类型),此时这个类型称为方法的 receiver。

用Rob Pike的话来说就是:

“A method is a function with an implicit first argument, called a receiver.”

也就是说,可以把 receiver 当作方法的第一个参数(如 Python 的 self),因此,想要区分在普通类型和指针上方法的特性,就可以使用函数的值传递和引用传递的视角来解读:如果一个方法想要修改结构体内的成员,那么这个方法需要定义在指针上;否则,方法的接收者实际上只是结构体的一个 copy。

此外,Go能智能地识别调用的是指针的方法还是非指针的方法

  • 如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method;
  • 如果一个method的receiver是T,你可以在一个*T类型的变量P上面调用这个method,而不需要 *P去调用这个method;

回顾 4 的例子:

package main

import "fmt"

const(
	WHITE = iota  // 无类型常量
	BLACK
	BLUE
	RED
	YELLOW
)

type Color byte

type Box struct {
	width, height, depth float64
	color Color
}

// 因为要修改结构体成员,所以要使用指针作为方法的接收者。
func (b *Box) SetColor(c Color) { 
	b.color = c  // 不需要写作`*b.color = c`或`b->color = c`,Go会自动识别这是个指针。
}

func (bl BoxList) PaintItBlack() {
	for i := range bl {
        bl[i].SetColor(BLACK)  // 调用指针上的方法。这里不需要写作`&(bl[i]).SetColor(BLACK)`,同样,Go会自动识别。
	}
}

6. 结构体匿名字段成员重载和method重写

我们知道,Go 结构体允许存在匿名字段:

type Human struct {
	name string
	age int
	weight int
}

type Student struct {
	Human  // 匿名字段,那么默认Student就包含了Human的所有字段
    int    // 任意的内置类型或自定义类型都可以作为匿名字段
}

func main() {
	// 初始化学生Jane
    jane := Student{Human:Human{"Jane", 35, 100}, int: 100}
	// 可以直接通过 Human 的实例访问匿名字段的方法
	fmt.Println("Her name is ", jane.name)  // "Jane"
    fmt.Println("Her preferred number is", jane.int)  // 100

可以很方便地在 Student 重载 Human 的字段:

type Human struct {
	name string
	age int
	weight int 
}

type Student struct {
	Human
    int
    weight int  // 重载Human的相同字段
}

func main() {
	// 初始化学生Jane
    jane := Student{Human:Human{"Jane", 35, 100}, int: 100, weight: 70}
	// 最外层的优先访问
	fmt.Println("Her weight is ", jane.weight)  // 70
    // 如果我们要访问Human的weight字段
    fmt.Println("Human's weight is", jane.Human.weight)  // 100
}

如果匿名字段拥有方法,那么包含这个匿名字段的结构体也能调用这个方法:

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
	Human //匿名字段
	school string
}

//在human上面定义了一个method
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	mark.SayHi()  // 直接调用匿名字段的方法
}

同样,可以对这个方法进行重写:

type Human struct {
	name  string
	age   int
	phone string
}

type Student struct {
	Human  //匿名字段
	school string
}

// Human定义method
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Student的method重写Human的method
func (s *Student) SayHi() {
	fmt.Printf("Hi, I am %s, I study in %s. Call me on %s\n", s.name,
		s.school, s.phone)
}

func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	mark.SayHi()  // 调用重载后的方法
	mark.Human.SayHi()  // 重载前的方法
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF-控件的属性值的类型转化

控件的属性值需要转成int、double进行运算的&#xff0c;可以使用一下方法 页面代码 <StackPanel Margin"4,0,0,0" Style"{StaticResource Form-StackPanel}"> <Label Content"替换后材料增加金额&#xff…

【从零开始的LeetCode-算法】3270. 求出数字答案

给你三个 正 整数 num1 &#xff0c;num2 和 num3 。 数字 num1 &#xff0c;num2 和 num3 的数字答案 key 是一个四位数&#xff0c;定义如下&#xff1a; 一开始&#xff0c;如果有数字 少于 四位数&#xff0c;给它补 前导 0 。答案 key 的第 i 个数位&#xff08;1 < …

iMetaOmics | 刘永鑫/陈同-用于食物微生物组成和时间序列研究的微生物组数据库FoodMicroDB...

点击蓝字 关注我们 FoodMicroDB&#xff1a;用于食物微生物组成和时间序列研究的微生物组数据库 iMeta主页&#xff1a;http://www.imeta.science 研究论文 ● 原文链接DOI: https://doi.org/10.1002/imo2.40 ● 2024年11月1日&#xff0c;中国农业科学院深圳农业基因组研究所刘…

视觉slam十四讲 ch8 光流法和直接法

之前的都是单层光流 转载至Blibli 视觉SLAM十四讲_7视觉里程计1_计算相机运动_哔哩哔哩_bilibili

QSS 设置bug

问题描述&#xff1a; 在QWidget上add 一个QLabel&#xff0c;但是死活不生效 原因&#xff1a; c 主程序如下&#xff1a; QWidget* LOGO new QWidget(logo_wnd);LOGO->setFixedSize(logo_width, 41);LOGO->setObjectName("TittltLogo");QVBoxLayout* tit…

Linux运维篇-iscsi存储搭建

目录 概念实验介绍环境准备存储端软件安装使用targetcli来管理iSCSI共享存储 客户端软件安装连接存储 概念 iSCSI是一种在Internet协议上&#xff0c;特别是以太网上进行数据块传输的标准&#xff0c;它是一种基于IP Storage理论的存储技术&#xff0c;该技术是将存储行业广泛…

WSL--无需安装虚拟机和docker可以直接在Windows操作系统上使用Linux操作系统

安装WSL命令 管理员打开PowerShell或Windows命令提示符&#xff0c;输入wsl --install&#xff0c;然后回车 注意&#xff1a;此命令将启用运行 WSL 和安装 Linux 的 Ubuntu 发行版所需的功能。 注意&#xff1a;默认安装最新的Ubuntu发行版。 注意&#xff1a;默认安装路径是…

【学习心得】算力云平台上的大模型部署并实现远程调用

以AutoDL算力云平台为例&#xff0c;部署国产开源ChatGLM3b模型。 一、准备工作 &#xff08;1&#xff09;准备一台算力服务器 首先&#xff0c;进入AutoDL官网的算力时长选择算力服务器资源。 创建好后会自动跳转控制台的“容器实例”界面&#xff0c;稍等片刻后选择“快捷…

Vue 中的透传,插槽,依赖注入

1. 透传attributes 在组件上使用透传attribute&#xff1a; 当你在父组件中使用子组件时&#xff0c;你可以添加一些attribute到子组件上&#xff0c;即使这些attribute没有在子组件的props中声明。 父组件&#xff1a; <!-- 父组件&#xff0c;例如 ParentComponent.vue…

97.【C语言】数据结构之栈

目录 栈 1.基本概念 2.提炼要点 3.概念选择题 4.栈的实现 栈初始化函数 入栈函数 出栈函数和栈顶函数 栈顶函数 栈销毁函数 栈 基本概念参见王爽老师的《汇编语言 第四版》第56和57页 节选一部分 1.基本概念 注意:这里提到的数据结构中的栈有别于操作系统的栈,后者是…

Spring-boot 后端java配置接口返回jsp页面

Spring-boot 后端java配置接口返回jsp页面 spring boot 基于spring MVC的基础上进行了改进&#xff0c; 将Controller 与ResponseBody 进行了合并成一个新的注解 RestController。 当用户请求时&#xff0c;需要有视图渲染的&#xff0c;与请求数据的请求分别使用 1.在appli…

【操作系统实验课】Makefile与编译

1. 创建项目结构 my_project 使用mkdir命令在根目录下创建项目my_project sudo mkdir /my_project 进入my_project目录 cd my_project src 在my_project目录下创建src子目录 sudo mkdir src 进入src目录 cd src root(根用户) 切换用户身份为root(根用户) root用户…

冠层四流近似模型的发展历史

1. Kunbelka-Munk theory This is the earlist model using a two-stream approximation d I d z − ( k s ) I s J d J d z ( k s ) J − s I \begin{aligned} &\frac{dI}{dz} -(ks)IsJ\\ &\frac{dJ}{dz} (ks)J - sI \end{aligned} ​dzdI​−(ks)IsJdzdJ​(…

Linux从0——1之shell编程4

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

2024.5 AAAiGLaM:通过邻域分区和生成子图编码对领域知识图谱对齐的大型语言模型进行微调

GLaM: Fine-Tuning Large Language Models for Domain Knowledge Graph Alignment via Neighborhood Partitioning and Generative Subgraph Encoding 问题 如何将特定领域知识图谱直接整合进大语言模型&#xff08;LLM&#xff09;的表示中&#xff0c;以提高其在图数据上自…

【大语言模型】ACL2024论文-15 大型语言模型中的最佳解释推断

【大语言模型】ACL2024论文-15 大型语言模型中的最佳解释推断 目录 文章目录 【大语言模型】ACL2024论文-15 大型语言模型中的最佳解释推断目录摘要研究背景问题与挑战如何解决创新点算法模型实验效果推荐阅读指数&#xff1a;★★★★☆后记 大型语言模型中的最佳解释推断 摘…

【最新鸿蒙开发之性能优化——动态加载和延迟加载】

大家好&#xff0c;我是学徒小z&#xff0c;在经历了一段时间项目开发中&#xff0c;我也渐渐意识到了性能的重要性&#xff0c;今天就分享一篇优化应用运行性能的文章&#xff0c;话不多说&#xff0c;开干&#xff01; 引言 延时触发操作与延迟加载的简介 动态加载&#x…

云计算研究实训室建设方案

一、引言 随着云计算技术的迅速发展和广泛应用&#xff0c;职业院校面临着培养云计算领域专业人才的迫切需求。本方案旨在构建一个先进的云计算研究实训室&#xff0c;为学生提供一个集理论学习、实践操作、技术研发与创新于一体的综合性学习平台&#xff0c;以促进云计算技术…

信号保存和信号处理

目录 信号保存中重要的概念 内核中信号的保存 对sigset_t操作的函数 对block&#xff0c;pendding&#xff0c;handler三张表的操作 sigpromask ​编辑 sigpending 是否有sighandler函数呢&#xff1f; 案例 信号处理 操作系统是如何运行的&#xff1f; 硬件中断 …

用vscode编写verilog时,如何有信号定义提示、信号定义跳转(go to definition)、模块跳转(跨文件跳转)这些功能

&#xff08;一&#xff09;方法一&#xff1a;安装插件SystemVerilog - Language Support 安装一个vscode插件即可&#xff0c;插件叫SystemVerilog - Language Support。虽然说另一个插件“Verilog-HDL/SystemVerilog/Bluespec SystemVerilog”也有信号提示及定义跳转功能&am…