设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间21 现在时间21 缓存数据 青春之所以让人留念,是因为我们年轻时干的蠢事大都妙不可言。晚安!

青春之所以让人留念,是因为我们年轻时干的蠢事大都妙不可言。晚安!

查看: 1470|回复: 1

go语言定义零值可用的类型学习教程

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:226
  • 打卡月天数:0
  • 打卡总奖励:3588
  • 最近打卡:2025-04-10 06:55:50
等级头衔

等級:晓枫资讯-上等兵

在线时间
7 小时

积分成就
威望
0
贡献
541
主题
521
精华
1
金钱
5235
积分
1122
注册时间
2023-1-8
最后登录
2025-6-1

发表于 2023-7-21 04:51:46 | 显示全部楼层 |阅读模式
1. Go 类型的零值

作为 C 程序员出身的我,我总是喜欢用在使用 C 语言的”受过的苦“与 Go 语言中得到的”甜头“做比较,从而来证明 Go 语言设计者在当初设计 Go 语言时是做了充分考量的。
在 C99 规范中,有一段是否对栈上局部变量进行自动清零初始化的描述:
如果未显式初始化且具有自动存储持续时间的对象,则其值是不确定的。
规范的用语总是晦涩难懂的。这句话大致的意思就是:如果是在栈上分配的局部变量,且在声明时未对其进行显式初始化,那么这个变量的值是不确定的。比如:
  1. // varinit.c
  2. #include <stdio.h>
  3. static int cnt;
  4. void f() {
  5.     int n;
  6.     printf("local n = %d\n", n);
  7.     if (cnt > 5) {
  8.         return;
  9.     }
  10.     cnt++;
  11.     f();
  12. }
  13. int main() {
  14.     f();
  15.     return 0;
  16. }
复制代码
编译上面的程序并执行:
  1. // 环境 centos linux gcc 版本 4.1.2
  2. // 注意:在您的环境中执行上述代码,输出的结果很大可能与这里有所不同
  3. $ gcc varinit.c
  4. $ ./a.out
  5. local n = 0
  6. local n = 10973
  7. local n = 0
  8. local n = 52
  9. local n = 0
  10. local n = 52
  11. local n = 52
复制代码
我们看到分配在栈上的未初始化变量的值是不确定的,虽然一些编译器的较新版本也都提供一些命令行参数选项用于对栈上变量进行零值初始化,比如 GCC 就提供如下命令行选项:
  1. -finit-local-zero
  2. -finit-derived
  3. -finit-integer=n
  4. -finit-real=<zero|inf|-inf|nan|snan>
  5. -finit-logical=<true|false>
  6. -finit-character=n
复制代码
但这并不能改变 C 语言原生不支持对未显式初始化局部变量进行零值初始化的事实。资深 C 程序员是深知这个陷阱带来的问题是有多严重的。因此同样出身于 C 语言的 Go 设计者们在 Go 中彻底对这个问题进行的修复和优化。根据Go 语言规范:
当通过声明或调用new为变量分配存储空间,或者通过复合文字字面量或make调用创建新值,
并且还不提供显式初始化的情况下,Go会为变量或值提供默认值。
Go 语言的每种原生类型都有其默认值,这个默认值就是这个类型的零值。下面是 Go 规范定义的内置原生类型的默认值(零值)。
所有整型类型:0
浮点类型:0.0
布尔类型:false
字符串类型:""
指针、interface、slice、channel、map、function:nil
另外 Go 的零值初始是递归的,即诸如数组、结构体等类型的零值初始化就是对其组成元素逐一进行零值初始化。

2. 零值可用

我们现在知道了 Go 类型的零值,接下来我们来说“可用”。
Go 从诞生以来就秉承着尽量保持“零值可用”的理念,我们来看两个例子。
第一个例子是关于 slice 的:
  1. var zeroSlice []int
  2. zeroSlice = append(zeroSlice, 1)
  3. fmt.Println(zeroSlice) // 输出:[1]
复制代码
我们声明了一个 []int 类型的 slice:zeroSlice,我们并没有对其进行显式初始化,这样 zeroSlice 这个变量被 Go 编译器置为零值:nil。按传统的思维,对于值为 nil 这样的变量我们要给其赋上合理的值后才能使用。但是 Go 具备零值可用的特性,我们可以直接对其使用 append 操作,并且不会出现引用 nil 的错误。
第二个例子是通过 nil 指针调用方法的:
  1. // callmethodthroughnilpointer.go
  2. package main
  3. import (
  4.         "fmt"
  5.         "net"
  6. )
  7. func main() {
  8.         var p *net.TCPAddr
  9.         fmt.Println(p) //输出:<nil>
  10. }
复制代码
我们声明了一个 net.TCPAddr 的指针变量,我们并未对其显式初始化,指针变量 p 会被 Go 编译器赋值为 nil。我们在标准输出上输出该变量,fmt.Println 会调用 p.String()。我们来看看 TCPAddr 这个类型的 String 方法实现:
  1. // $GOROOT/src/net/tcpsock.go
  2. func (a *TCPAddr) String() string {
  3.         if a == nil {
  4.                 return "<nil>"
  5.         }
  6.         ip := ipEmptyString(a.IP)
  7.         if a.Zone != "" {
  8.                 return JoinHostPort(ip+"%"+a.Zone, itoa(a.Port))
  9.         }
  10.         return JoinHostPort(ip, itoa(a.Port))
  11. }
复制代码
我们看到 Go 标准库在定义 TCPAddr 类型以及其方法时充分考虑了“零值可用”的理念,使得通过值为 nil 的 TCPAddr 指针变量依然可以调用 String 方法。
在 Go 标准库和运行时代码中还有很多践行“零值可用”理念的好例子,最典型的莫过于 sync.Mutex 和 bytes.Buffer 了。
我们先来看看 sync.Mutex。在 C 语言中,如果我们要使用线程互斥锁,我们需要这么做:
  1. pthread_mutex_t mutex; // 不能直接使用
  2. // 必须先进行初始化
  3. pthread_mutex_init (&mutex, NULL);
  4. // 然后才能执行lock或unlock
  5. pthread_mutex_lock(&mutex);
  6. pthread_mutex_unlock(&mutex);
复制代码
但是在 Go 语言中,我们只需这么做:
  1. var mu sync.Mutex
  2. mu.Lock()
  3. mu.Unlock()
复制代码
Go 标准库的设计者很“贴心”地将 sync.Mutex 结构体的零值状态设计为可用状态,这样让 Mutex 的调用者可以“省略”对 Mutex 的初始化而直接使用 Mutex。
Go 标准库中的 bytes.Buffer 亦是如此:
  1. // bytesbufferwrite.go
  2. package main
  3. import (
  4.         "bytes"
  5. )
  6. func main() {
  7.         var b bytes.Buffer
  8.         b.Write([]byte("Effective Go"))
  9.         fmt.Println(b.String()) // 输出:Effective Go
  10. }
复制代码
我们看到我们无需对 bytes.Buffer 类型的变量 b 进行任何显式初始化即可直接通过 b 调用其方法进行写入操作,这源于 bytes.Buffer 底层存储数据的是同样支持零值可用策略的 slice 类型:
  1. // $GOROOT/src/bytes/buffer.go
  2. // A Buffer is a variable-sized buffer of bytes with Read and Write methods.
  3. // The zero value for Buffer is an empty buffer ready to use.
  4. type Buffer struct {
  5.         buf      []byte // contents are the bytes buf[off : len(buf)]
  6.         off      int    // read at &buf[off], write at &buf[len(buf)]
  7.         lastRead readOp // last read operation, so that Unread* can work correctly.
  8. }
复制代码
3. 小结

Go 语言零值可用的理念给内置类型、标准库的使用者带来很多便利。不过 Go 并非所有类型都是零值可用的,并且零值可用也是有一定限制的,比如:slice 的零值可用不能通过下标形式操作数据:
  1. var s []int
  2. s[0] = 12 // 报错!
  3. s = append(s, 12) // OK
复制代码
另外像 map 这样的内置类型也没有提供零值可用的支持:
  1. var m map[string]int
  2. m["tonybai"] = 1 // 报错!
  3. m1 := make(map[string]int
  4. m1["tonybai"] = 1 // OK
复制代码
另外零值可用的类型要注意尽量避免值拷贝:
  1. var mu sync.Mutex
  2. mu1 := mu // Error: 避免值拷贝
  3. foo(mu) // Error: 避免值拷贝
复制代码
我们可以通过指针方式传递类似 Mutex 这样的类型。
对于我们 Go 开发者而言,保持与 Go 一致的理念,给自定义的类型一个合理的零值,并坚持保持自定义类型是零值可用的,这样我们的 Go 代码会表现的更加符合 Go 惯用法。
以上就是go语言定义零值可用的类型的详细内容,更多关于go 零值可用类型的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:无名新人
  • 打卡总天数:1
  • 打卡月天数:0
  • 打卡总奖励:5
  • 最近打卡:2024-07-12 09:03:05
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
18
积分
6
注册时间
2024-1-24
最后登录
2024-7-12

发表于 2025-4-7 11:14:08 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表