Golang学习笔记
Posted Shi Peng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang学习笔记相关的知识,希望对你有一定的参考价值。
一、Go语言的知识图谱
上图表示了go的应用领域,包括容器如k8s, 服务发现如consul,kv存储如etcd,中间件如codis, 存储如minio,分布式数据库tidb,此外还有devops、区块链、人工智能、web框架、微服务等等领域的应用。
二、Go语言概述
2.1、为什么需要Go语言
相比C++和java, Go语言更年轻,他存在的意义何在呢?
1)在计算领域,是C/C++的天下,因为执行效率高,但缺点是开发和编译效率低。
2)对于Java,虽然编译速度快、开发效率高,但执行效率不如C/C++。
3)而动态语言如Python,由于没有强类型约束,很多问题需要在运行时发现:这种低级错误由编译器来发现更好些。
综上,期待出现一种语言来解决上述语言的缺点,Go语言这时出现,它易于开发、快速编译、高效执行。
Go语言起源于2007年,由google 2009年发布。
Go编译后的代码,运行速度可媲美C/C++,且编译速度更快,因此风靡全球,被称为21世纪的C语言(Go不仅具备C的性能与安全,且提供了21世纪互联网环境下服务端开发的各种使用特性)
Go语言的设计哲学是:简单、实用。
Go语言是对类C语言的重大改进,它可以访问底层操作系统,还提供了强大的网络编程和并发编程的支持。Go语言可进行网络编程、系统编程、并发编程、分布式编程。
1)并发场景性能快
2)静态的语言:没有面向对象中的“类”
3)支持自动垃圾回收
4)编译型语言:编译速度快
Go遵循简洁编程原则:
1)它没有隐式的数值转换
2)没有构造函数和析构函数
3)没有运算符重载
4)没有默认参数
5)也没有继承
6)没有泛型
7)没有异常
8)没有宏
9)没有函数修饰
10)更没有线程局部存储
Go承诺保证向后兼容:
用之前的Go语言编写程序可以用新版本的Go语言编译器和标准库直接构建而不需要修改代码
Go语言支持交叉编译,即可以在Linux系统上开发基于Windows上运行的程序,因为这是一门完全支持UTF-8的语言,不仅体现在他可以处理使用UTF-8编码的字符串,就连他的源码稳居格式都是使用UTF-8编码。
2.2、Go是编译型语言
Go代码在运行前,需要使用编译器来编译代码,编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器会做错误检查、性能优化,然后输出可在不同平台上运行的二进制文件。
要运行Go程序,需要的步骤:
1)使用文本编辑器写Go代码
2)编译程序
3)运行编译后得到的可执行文件。
这与Python, javascript等语言不同,Go自带了编译器。
2.3、Go语言的特性
1、语法简单
Go语法与C类似,但没有C那些隐秘晦涩的规则,Go语法简单严谨,无歧义。
2、并发模型
Go引入了携程Goroutine,从根本上将一切都并发化,运行时用goroutine运行所有的一切,包括main.main入口函数。
Goroutine用类携程的方式来处理并发单元,却又在运行时层面做了更深度的优化处理。但语法上的并发编程变得极为简单,无需处理回调,无需关注现场切换。
goroutine搭配channel,实现CSP模型。将并发单元间的数据耦合拆解开来,各司其职。这样对所有纠结于内存共享、锁粒度的程序员都是一种解脱。
3、内存分配
将一起并发化虽然好,但也带来了新问题,即如何实现高并发下内存的分配和管理。Go选择了tcmalloc,他本就是为了并发儿设计的高性能内存分配组件。
去除因配合垃圾回收而修改的内容,内存分配器完整保留了tcmailoc的原始架构。使用cache为当前线程提供无锁分配,多个central在不同线程间平衡内存单元复用。在更高层次里,heap管理大块内存分配,用以切分成不同等级的复用内存块。快速分配和二级平衡机制,让内存分配器能优秀地完成高压力下的内存管理任务。
除了偶尔因性能问题而被迫采用对象池和自主内存管理外,我们基本无需参与内存管理操作。
4、垃圾回收
相比Java, Go的垃圾回收面临的困难更多,因为指针的存在。幸好,指针运算被阻止,否则要做到精确回收都难。
每次版本升级,垃圾回收期都是核心组件里修改最多的部分,从并发清理,到降低STW的时间,直到Go的1.5版本实现并发标记,逐步引入三色标记和写屏障等,都是为了能让垃圾回收在不影响用户逻辑的情况下更好地工作。但当前最新版本的垃圾回收算法也只能说堪用,距离好用还有不少距离。
5、静态链接
静态链接,只需编译后的一个可执行文件(将运行时、依赖库直接打包到可执行文件内部),无需附加任何东西,就能部署。这种简单的方式对于编写系统软件有极大好处,无需实现安装运行环境和下载各种第三方库。
6、标准库
Go的标准库算比较丰富,其中值得称道的事net/http,仅需要简单的几条语句,就能实现一个高性能的web server,大批基于此的优秀第三方Framework更将Go推到了Web/微服务 开发标准之一的位置。
7、工具链
完整的工具链对日常开发极有帮助,Go的工具链做的不错,无论是编译、格式化、错误检查、帮助文档,还是第三方包的下载、更新,都有对应的工具。虽然这些工具算不上多完善,但简单易用。
Go内置的测试框架,其中包括单元测试、性能测试、代码覆盖率、数据竞争,及用来调优的pprof,都是包子代码正确与稳定运行的必备利器。
此外,还可通过环境变量输出运行时的监控信息,尤其是垃圾回收和并发调度跟踪,可进一步帮助我们改善算法,获得最佳的运行期表现。
2.4、Go语言为并发而生
Go语言的祖先C语言的指令都是串行执行,即同一时刻仅有一个指令在使用CPU。
随着多核处理器的发展,一些语言框架(如Java Netty)也在致力于提高多核CPU使用效率,但仍需要开发人员花精力去搞懂这些框架的原理及使用方法。
Go语言是在多核和网络化时代诞生的语言,天生就从底层支持并发,无需第三方库,程序员可以在写程序时很轻松地指定怎样使用CPU资源。
Go的并发,基于goroutine,可以将goroutine理解为一种虚拟线程。Go语言运行时会参与调度goroutine,并将goroutine合理地分配到每个CPU中,最大限度地使用CPU性能。
多个goroutine之间,通过使用channel通信,channel是一种内置的数据结构,可以让用户在不同的goroutine之间同步发送具有类型的消息。这种编程模型更倾向于在goroutine之间发送消息,而不是让多个goroutine争夺同一个数据的使用权。
程序可以把并发goroutine间的通信设计为生产者/消费者模式,将要通信的数据放入channel,然后channel另一端的代码将这些数据并发消费。
【goroutine与channel的例子】:生产者每秒生成一个字符串,并放入channel,生产者使用两个goroutine并发写入channel,消费者在main()函数的goroutine中处理。
package main
import (
"fmt"
"math/rand"
"time"
)
// 数据生产者
func producer(header string, channel chan<- string)
// 无限循环, 不停地生产数据
for
// 将随机数和字符串格式化为字符串发送给通道
channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
// 等待1秒
time.Sleep(time.Second)
// 数据消费者
func customer(channel <-chan string)
// 不停地获取数据
for
// 从通道中取出数据, 此处会阻塞直到信道中返回数据
message := <-channel
// 打印数据
fmt.Println(message)
func main()
// 创建一个字符串类型的通道
channel := make(chan string)
// 创建producer()函数的并发goroutine
go producer("cat", channel)
go producer("dog", channel)
// 数据消费函数
customer(channel)
运行结果:
dog: 2019727887
cat: 1298498081
dog: 939984059
cat: 1427131847
cat: 911902081
dog: 1474941318
dog: 140954425
cat: 336122540
cat: 208240456
dog: 646203300
可以看到,在并发的生产者、消费者过程中,程序员无需创建线程,无需线程池,也无需加锁,仅通过goroutine和channel,即可实现并发的目的。
2.5、各语言性能测试
表中数据的单位为秒,数值越小表明运行性能越好:
从上表可以看出,go语言的性能接近于java, 不如C/C++。
2.6、Go语言的标准库
学习编程语言,重点不单是学语法,而是熟悉其生态圈,标准库是生态圈中的重要一环。
Go语言的标准库,提供了构建模块和公共接口,包括I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。
Go语言的编译器也是标准库中的一部分,他通过词法器扫描源码,使用语法树获得源码的逻辑分支等。
Go语言的周边工具,都是建立在标准库上,用标准库可以完成绝大部分需求。
Go语言常见的包及功能如下表:
三、Go语言环境
3.1、Go语言开发环境的安装
1、从官网下载go安装包
https://golang.google.cn/dl/
2、以mac os为例,双击goxxx.pkg文件,一路next安装完成
3、安装完成后,在终端执行go version,可以看到安装的版本号
peng.shi@H7HT0YWMTC ~ % go version
go version go1.18.1 darwin/amd64
4、设置GOPATH环境变量
默认会设置好gopath路径,可在终端执行:go env 命令
peng.shi@H7HT0YWMTC ~ % go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/peng.shi/Library/Caches/go-build"
GOENV="/Users/peng.shi/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/peng.shi/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/peng.shi/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.18.1"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/yf/28dxgytd4q3blgvrcq_l7hq00000gp/T/go-build1265247406=/tmp/go-build -gno-record-gcc-switches -fno-common"
也可以在终端执行:vi ~/.bash_profile,然后输入:
export GOPATH=$HOME/go
退出编辑器后再 source ~/.bash_profile,其中,$Home可通过echo $HOME查看:
peng.shi@H7HT0YWMTC ~ % echo $HOME
/Users/peng.shi
另外,GOROOT 也就是 Go 开发包的安装目录,默认在 /usr/local/go,如不没有,可以在~/.base_profile中设置,然后再source ~/.bash_profile
在使用Goland等开发IDE时,会提示输入gopath路径,输入相应即可。
3.2、Go语言的IDE
1、Goland
Goland 是由 JetBrains 公司开发的收费的商用IDE,号称符合人体工程学。
2、LiteIDE
轻量级开发环境,免费。
3、GoClipse
通过在Eclipse安装go插件,可以使用Eclipse开发go
3.3、Go语言工程结构
要想构建一个go工程,需要把工程的目录添加到gopath中,多个项目之间用 ; 分隔,项目的构建是靠 gopath 来实现的。
3.3.1、Go工程的目录结构
Go语言的一个项目的目录通常分下面三个子目录:
1、src目录:存项目和库的源文件
以package形式组织存放Go的源文件,这里的包与src下的每个子目录对应,例如,如果一个源文件被什么为属于log包,那他就会保存在 src/log 目录中。
src目录下,如果要放go文件,需要在文件中加名为main的package,通常都是把go文件放在某个目录(package)下,然后其他人引用该文件时,先import其package。
go跟java一样,在代码的第一行必须是 package <包名>
另外需要知道的是,Go语言会通过 go get 命令,把获取到的源文件下载到src目录下对应的文件夹中。
2、pkg目录:存编译后生成的包/库的归档文件
pkg目录,用于存放 go install 命令安装某个包后的归档文件。归档文件是指那些以 ”.a" 结尾的文件。
编译和安装项目代码的过程,一般会以代码包为单位进行,比如 log 包被编译安装后,将生成一个名为 log.a 的归档文件,并存放在当前项目的 pkg 目录下。
3、bin目录:保存由Go命令源文件生成的可执行文件
在通过 go install 命令完成安装后,保存由 Go 命令源文件生成的可执行文件。在类Unix系统下,可执行文件与源文件的文件名相同,在window系统下,可执行文件的名称是命令源文件的文件名加 “.exe" 后缀。
3)bin目录:存编译后生成的可执行文件
补充:命令源文件和库文件的区别?
1)命令源文件:如果一个Go源文件被声明为属于main包,且包含main含税,则他就是命令源文件。命令源文件属于程序的入口,可通过Go语言的go run命令运行,或者通过go build命令生成可执行文件。
2)库源文件:指存在于某个包中的普通源文件,并且库源文件中不包含main函数
3.4、Go语言的编译和运行
Go跟C语言一样,是静态语言。所以在执行签,需要先将其编译成二进制的可执行文件。
go build
可通过 go build 命令把go代码编译成二进制的可执行文件
go run
此命令会先编译,再运行,编译时会产生一个临时文件,但不会生成可执行文件。
四、Go语言语法
4.1、变量声明
Go是静态语言,因此其变量必须要有明确的类型,这样编译器才能检查变量的正确性。
声明变量语法:
var 变量名 变量类型
后面无需加分号
声明变量举例:声明a, b两个变量都为int指针类型
var a, b *int
Go语言的基本数据类型:
bool
string
int、int8、int16、int32、int64
uint、uint8、uint16、uint32、uint64、uintptr
byte // uint8 的别名
rune // int32 的别名 代表一个 Unicode 码
float32、float64
complex64、complex128
当一个变量被声明后,系统会自动为他赋初始值:
int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil
变量的命名遵循驼峰命名法,即首个单词小写,每个新单词首字母大写,如startDate。
变量声明的批量格式:
var (
a int
b string
c []float32
d func() bool
e struct
x int
)
使用关键字 var 和括号,可以将一组变量定义放在一起。
除了上述两种方式,还可以把变量定义和初始化放一起:
名字 := 表达式
但这种方式有以下限制:
1)不能提供数据类型
2)只能用在函数内部
例如:
func main()
x := 100
a,s := 1, "abc"
4.2、变量初始化
在对变量进行声明后,系统会自动对变量进行初始化,如:
整型和浮点型变量的默认值为 0 和 0.0。
字符串变量的默认值为空字符串。
布尔型变量默认为 false。
切片、函数、指针变量的默认为 nil。
变量初始化语法:
var 变量名 类型 = 表达式
举例:
var aa int = 100
也可以省略类型,采用编译器推导类型的格式:
var aa = 100
变量赋值,还有一种更精简的写法:
aa := 100
注意:由于使用了 := ,而不是 = ,因此这种写法左边的aa必须是没有精益过的变量。若定义过,会发生编译错误。
注意:在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即使其他变量名可能是重复声明的,编译也不会报错,如下面写法,不会编译报错:
conn, err := net.Dial("tcp", "127.0.0.1:8080")
conn2, err := net.Dial("tcp", "127.0.0.1:8080")
Go语言的多重赋值,用于变量值的互换:
var a int = 100
var b int = 200
b, a = a, b
fmt.Println(a, b)
4.3、匿名变量
Go语言支持没有名称的变量、类型或方法,这些统称为匿名变量。匿名变量是为了增强代码的灵活性。
匿名变量是一个下划线”_" (_被称为空白标识符), _可以想其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给他),但任何赋值给这个标识符的值都将被抛弃,因此这些值不会在后续的代码中使用。
举例说明:
func GetData() (int, int)
return 100, 200
func main()
a, _ := GetData()
_, b := GetData()
fmt.Println(a, b)
运行结果:
100 200
匿名变量,不占用内存空间,不会分配内存。
4.4、变量作用域
1)局部变量:函数内定义的变量,作用域为函数体内,在函数调用结束后,变量就会被销毁。形参的作用域同局部变量。
2)全局变量:函数外定义的变量,全局变量的声明必须以var开头,如果全局变量和局部变量同名,以局部变量为准。
4.5、数据类型
4.5.1、整型
整型分为有符号整型(包括 int8、int16、int32 和 int64 ,分别对应8、16、32、64 bit(二进制位)大小的有符号整数)和无符号整型(uint8、uint16、uint32 和 uint64)。
此外还有两种整数类型 int 和 uint,它们分别对应特定 CPU 平台的字长(机器字大小),由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。
大多数情况下,我们只需要 int 一种整型即可。
用来表示unicode的rune跟 int32 等价,用来表示unicode的码点;
byte等价于uint8, byte用于强调数值是一个原始的数据,而不是一个小的整数。
rune的用法示例:
import (
"fmt"
"unicode/utf8"
)
func main()
var str = "hello 世界"
//golang中string底层是通过byte数组实现的,直接求len 实际是在按字节长度计算 所以一个汉字占3个字节算了3个长度
fmt.Println("len(str):", len(str))
//以下两种都可以得到str的字符串长度
//golang中的unicode/utf8包提供了用utf-8获取长度的方法
fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))
//通过rune类型处理unicode字符
fmt.Println("rune:", len([]rune(str)))
输出结果:
len(str): 12
RuneCountInString: 8
rune: 8
4.5.2、浮点类型
Go语言支持float32和float64 这两种。
4.5.3、复数
复数是由两个浮点数表示,其中一个是实部,一个是虚部。
复数类型有两种:complex128(64 位实数和虚数)和 complex64(32 位实数和虚数),其中 complex128 为复数的默认类型。
复数的声明方式:
var name complex128 = complex(x, y)
例子:
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"
复数也可以用==和!=进行相等比较,只有两个复数的实部和虚部都相等的时候它们才是相等的。
4.5.4、bool 类型
布尔类型的值只有两种:true 或 false,默认是false
布尔型无法参与数值运算,也无法与其他类型进行转换。
4.5.5、字符串
Go语言中字符串的内部实现使用 UTF-8 编码,通过 rune 类型,可以方便地对每个 UTF-8 字符进行访问。当然,Go语言也支持按照传统的 ASCII 码方式逐字符进行访问。
可用反引号``来定义多行字符串:
const str = `第一行
第二行
第三行
\\r\\n
`
fmt.Println(str)
4.5.6、字符类型 byte和rune
Go语言的字符有以下两种:
1)一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
2)另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。
byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = ‘A’,字符使用单引号括起来。
Go语言同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。
在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\\u或者\\U。因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则使用\\u前缀,如果需要使用到 8 个字节,则使用\\U前缀。
var ch int = '\\u0041'
var ch2 int = '\\u03B2'
var ch3 int = '\\U00101234'
fmt.Printf("%d - %d - %d\\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
结果:
65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234
格式化说明符%c用于表示字符,当和字符配合使用时,%v或%d会输出用于表示该字符的整数,%U输出格式为 U+hhhh 的字符串。
Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):
判断是否为字母:unicode.IsLetter(ch)
判断是否为数字:unicode.IsDigit(ch)
判断是否为空白符号:unicode.IsSpace(ch)
4.5.6、数据类型转换
Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:
valueOfTypeB = typeB(valueOfTypeA)
举例:
import (
"fmt"
"math"
)
func main()
// 输出各数值范围
fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
fmt.Println("int16 range:", math.MinInt16, math.MaxInt16)
fmt.Println("int32 range:", math.MinInt32, math.MaxInt32)
fmt.Println("int64 range:", math.MinInt64, math.MaxInt64)
// 初始化一个32位整型值
var a int32 = 1047483647
// 输出变量的十六进制形式和十进制值
fmt.Printf("int32: 0x%x %d\\n", a, a)
// 将a变量数值转换为十六进制, 发生数值截断
b := int16(a)
// 输出变量的十六进制形式和十进制值
fmt.Printf("int16: 0x%x %d\\n", b, b)
// 将常量保存为float32类型
var c float32 = math.Pi
// 转换为int类型, 浮点发生精度丢失
fmt.Println(int(c))
结果:
int8 range: -128 127
int16 range: -32768 32767
int32 range: -2147483648 2147483647
int64 range: -9223372036854775808 9223372036854775807
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759
3
4.5.7、指针
指针对于性能的影响不言而喻,如果你想要做系统编程、操作系统或者网络应用,指针更是不可或缺的一部分。
指针(pointer)在Go语言中可以被拆分为两个核心概念:
1)类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
2)切片,由指向起始元素的原始指针、元素数量和容量组成。
指针需要知道几个概念:指针地址、指针类型和指针取值
指针地址和指针类型
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作):
ptr := &v // v 的类型为 T
import (
"fmt"
)
func main()
var cat int = 1
var str string = "banana"
fmt.Printf("%p %p", &cat, &str)
结果:
0xc000018080 0xc000010260
从指针获取指向的值
func main()
// 准备一个字符串类型
var house = "Malibu Point 10880, 90265"
// 对字符串取地址, ptr类型为*string
ptr := &house
// 打印ptr的类型
fmt.Printf("ptr type: %T\\n", ptr)
// 打印ptr的指针地址
fmt.Printf("address: %p\\n", ptr)
// 对指针进行取值操作
value := *ptr
// 取值后的类型
fmt.Printf("value type: %T\\n", value)
// 指针取值后就是指向变量的值
fmt.Printf("value: %s\\n", value)
结果:
ptr type: *string
address: 0xc000096220
value type: string
value: Malibu Point 10880, 90265
取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。
创建指针的另一种方法:new() 函数
new(类型)
例子:
str := new(string)
*str = "Go语言教程"
fmt.Println(*str)
结果:
Go语言教程
4.5.8、常量 和 const
用const关键字定义常量,常量是在编译时创建的,值不会改变。
例子:
const pi = 3.14159 // 相当于 math.Pi 的近似值
常量也可以批量声明:
const (
e = 2.7182818
pi = 3.1415926
)
无类型常量
Go语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如 int 或 float64,或者是类似 time.Duration 这样的基础类型,但是许多常量并没有一个明确的基础类型。
编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算,可以认为至少有 256bit 的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
例子:math.Pi 无类型的浮点数常量,可以直接用于任意需要浮点数或复数的地方:
var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi
如果 math.Pi 被确定为特定类型,比如 float64,那么结果精度可能会不一样,同时对于需要 float32 或 complex128 类型值的地方则需要一个明确的强制类型转换:
const Pi64 float64 = math.Pi
var x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)
4.5.9、类型别名 type关键字
类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。在 C/C++ 语言中,代码重构升级可以使用宏快速定义一段新的代码,Go语言中没有选择加入宏,而是解决了重构中最麻烦的类型名变更问题。
在 Go 1.9 版本之前定义内建类型的代码是这样写的:
type byte uint8
type rune int32
而在 Go 1.9 版本之后变为:
type byte = uint8
type rune = int32
定义类型别名的写法为:
以上是关于Golang学习笔记的主要内容,如果未能解决你的问题,请参考以下文章