概述
第二章 类型
2.1 变量
定义
用关键字var定义变量,变量类型放在后面,运行时自动初始化为二进制零值,避免出现不可预测行为,如果显示提供初始化值,可以省略变量类型,由编译器推断
var x int // 自动初始化为 0
var y = false // 自动推断为bool类型
可以一次定义多个变量,初始化不同类型
var x,y int
var a,s = 100, "aaa"
惯例使用组方式整理多行变量
var (
x,y int
a,s = 100,"ass"
)
简短模式
除关键字var外,可以使用简短的变量定义方法
func main(){
x := 100
a,s := 1,"as"
}
注意,简短模式限制
- 定义变量,同时显式初始化
- 不能提供数据类型
- 只能用在函数内部
对于新手,可能造成意外错误,比如,原打算修改全局变量,结果变成重新定义同名局部变量
package main
var x = 100
func main(){
println(&x,x)
x := 10
println(&x,x)
}
结果
0x4b2138 100
0xc00002df80 10
简短模式并不总是重新定义变量,也可能是部分退化的赋值操作
package main
var y = 33
func main(){
var x = 100
println(&x,x)
println(&y,y)
x, y ,z:= 10,22,33
println(&x,x)
println(&y,y)
println(z)
}
结果
0xc00002df80 100
0x4b2138 33
0xc00002df80 10
0xc00002df78 22
33
可以看到x的地址相同,y的地址不同
退化赋值的前提条件是:最少有一个新变量被定义,且必须是同一作用域
在处理函数错误返回值时,退化赋值允许我们重复使用err变量,这是相当有益的。
package main
import(
"log"
"os"
)
func main(){
f,err := os.Open("/dev/random")
...
buf := make([]byte,1024)
n,err := f.Read(buf)
...
}
多变量赋值
在进行多变量赋值操作时,先计算出所有右值,再依次完成赋值操作
package main
func main() {
x, y := 1, 2
x, y = y+3, x+2
println(x, y)
}
结果
5 3
未使用错误
编译器会将未使用的变量和引用包报错
2.2 命名
对于变量、常量、函数、自定义类型,通常选用有实际含义,易于阅读和理解的字母或单词组合。
命名建议:
- 以字母或下划线开始,由多个字母、数字和下划线组合而成。
- 区分大小写
- 使用驼峰拼写格式
- 局部变量优先使用短名
- 不要使用保留关键字
- 不建议使用与预定义常量、类型、内置函数相同的名字
- 专有名词通常会全部大写
package main
func main() {
var c int // c代替成count
for i := 0; i <10 ; i++{ // i 代替 index
c++
}
println(c)
}
符号名字首字母大小决定了作用域。首字母大写的为导出成员,可被包外引用,而小写则仅能在包内使用
空标识符
和Python类似,Go也有个名为“_”的特殊成员,通常作为忽略占位符使用,可作表达式左值,无法读取内容。
package main
import "strconv"
func main() {
x,_ := strconv.Atoi("12") // _ 用来忽略 返回的err
println(x)
}
2.3 常量
常量表示运行时恒定不可改变的值,通常是一些字面量。使用常量就可用一个易于阅读理解的标识符来代替“魔法数字”,也使得在调整常量值时,无需修改所有代码。
常量值必须是编译期可确定的字符、字符串、数字或布尔类型。可指定常量类型,或由编译器通过初始值推断,不支持C/C++数字类型后缀
未使用的常量不会引发编译错误
package main
const x, y int = 123,0x22
const s string = "asd"
const c = "我"
const (
i, f = 1,0.123
b = false
)
func main() {
println(x,y)
}
如果显示指定类型,必须保证常量左右类型一致,需要时可做显示转换。右值不能超出常量类型取值范围,否则会引发溢出错误
package main
func main() {
const(
x,y int = 99,-999
b byte = byte(x)
n = uint8(y) // 错误:constant -999 overflows uint8
)
}
常量值也可以是某些编译器能计算出结果的表达式,如unsafe.Sizeof 、len、cap 等
package main
import (
"unsafe"
)
func main() {
const(
ptrSize = unsafe.Sizeof(uintptr(0))
strSize = len("hello")
)
println(ptrSize,strSize)
}
在常量组中,如果不指定类型和初始化值,则与上一行非空常量右值相同
package main
import "fmt"
func main() {
const (
x uint16 = 120
y
s = "asd"
z
)
fmt.Printf("%T,%vn", y, y)
fmt.Printf("%T,%vn", z, z)
}
结果
uint16,120
string,asd
枚举
Go 并没有明确意义上的enum定义,不过可借助itoa标识符实现一组自增常量值来实现枚举类型。
package main
func main() {
const (
x = iota
y
z
)
println(x, y, z)
}
结果
0 1 2
package main
func main() {
const (
_ = iota // 0
x = 1 << (10 * iota) // 1 << (10 * 1)
y // 1 << (10 * 2)
z
)
println(x, y, z)
}
结果
1024 1048576 1073741824
自增作用范围为常量数组。可在多常量定义中使用多个iota,它们各自单独计数,只需确保组中每行常量的列数量相同即可
package main
func main() {
const (
_,_ = iota,iota *10
x,y
z,k
)
println(x, y, z,k) //1,10,2,20
}
如中断itoa自增,必须显示恢复。且后续自增值按行序递增,而非C enum 那般按上一取值递增
package main
func main() {
const (
_ = iota
x // 1
y = 100 // 100
z // 100
k = iota // 4
)
println(x, y, z,k)
}
在实际编码中,建议使用自定义类型实现用途明确的枚举类型。但这并不能将取值范围限定在预定义的枚举内。
package main
type color byte
const(
black color = iota
red
blue
)
func test(c color){
println(c)
}
func main() {
test(red)
test(100) //100 并未超出color/byte 类型
// x := 1
//test(x) //cannot use x (type int) as type color in argument to test
}
展开
常量除了“只读”外,和变量究竟有什么不同?
不同于于变量在运行期间分配储存内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用
2.4 基本类型
数据类型:
类型 | 长度 | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | uint8 |
int,uint | 4,8 | 0 | 默认整数类型,依据目标平台,32或64位 |
int8,unit8 | 1 | 0 | -128-127,0-255 |
int16,unit16 | 2 | 0 | -32768-32767 , 0-65535 |
int32,unit32 | 4 | 0 | -21亿-21亿,0-42亿 |
int64,unit64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | 默认浮点数类型 |
complex64 | 8 | ||
complex128 | 16 | ||
rune | 4 | 0 | Unicode Code Point, int32 |
uintptr | 4,8 | 0 | 足以储存指针的uint |
string | “” | 字符串,默认空字符串,而非NULL | |
arry | 数组 | ||
struct | 结构体 | ||
function | nil | 函数 | |
interface | nil | 接口 | |
map | nil | 字典,引用类型 | |
slice | nil | 切片,引用类型 | |
channel | nil | 通道,引用类型 |
支持八进制,十六进制及科学计数法。标准库math定义了各个数字类型呢的取值范围
package main
import (
"fmt"
"math"
)
func main() {
a, b, c := 100, 0144, 0x64
println(a, b, c)
fmt.Printf("0b%b,%#o,%#xn", a, a, a)
fmt.Println(math.MaxInt64, math.MinInt64)
}
result:
100 100 100
0b1100100,0144,0x64
9223372036854775807 -9223372036854775808
标准库strconv可以在不同进制(字符串)间转换
package main
import (
"strconv"
)
func main() {
a,_ := strconv.ParseInt("1100100",2,32)
b,_ := strconv.ParseInt("0144",8,32)
c,_ := strconv.ParseInt("64",16,32)
println(a,b,c)
println("0b"+strconv.FormatInt(a,2))
println("0"+strconv.FormatInt(a,8))
println("0b"+strconv.FormatInt(a,16))
}
结果
100 100 100
0b1100100
0144
0b64
使用浮点数时,需注意小数的有效精度,相关细节参考IEEE-754标准
package main
import "fmt"
func main() {
var a float32 = 1.1234567899
var b float32 = 1.12345678
var c float32 = 1.123456781
println(a, b, c)
println(a == b, b == c)
fmt.Printf("%v, %v, %v", a, b, c)
}
结果:
+1.123457e+000 +1.123457e+000 +1.123457e+000
true true
1.1234568, 1.1234568, 1.1234568
别名
在官方语言规范中,专门提到两个别名
byte alias for unit8
rune alias for int32
别名类型无需转换,可以直接赋值
package main
func test(x byte) {
println(x)
}
func main() {
var a byte = 0x11
var b uint8 = a
var c uint8 = a + b
test(c)
}
结果
34
但这并不表示,拥有相同底层结构的就属于别名,就算在64位平台上,int 和 int64 结构完全一致,也分属于不同类型,需显示转换
package main
func add(x, y int) int {
return x + y
}
func main() {
var x int = 100
var y int64 = x //cannot use x (type int) as type int64 in assignmen
add(x, y) //cannot use y (type int64) as type int in argument to add
}
2.5 引用类型
所谓引用类型,特指slice、map、channel这三种预定义类型
相比数字、数组等类型,引用类型拥有更复杂的存储结构。除分配内存外,它们还需要初始化一系列属性,如指针,长度,甚至包括哈希分布、数据队列等。
内置函数new按指定类型长度分配零值内存,返回指针,并不关心类型内部构造和初始化方式。而引用类型必须使用make函数创建,编译器会将make转换成为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化
package main
func mkslice()[]int{
s := make([]int,0,10)
s = append(s,100)
return s
}
func mkmap()map[string]int{
m := make(map[string]int)
m["a"] = 1
return m
}
func main() {
m := mkmap()
println(m,m["a"])
s := mkslice()
println(s,s[0])
}
结果
0xc00002de88 1
[1/10]0xc00002de38 100
当然,new函数也可为引用类型分配内存,但这不是完整创建。以字典为例,它仅分配了字典类型本身(实际上就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作
package main
import "fmt"
func main() {
p := new(map[string]int)
m := *p
m["a"] = 1
fmt.Println(m) //panic: assignment to entry in nil map
}
2.6 类型转换
隐式转换造成的问题远大于它带来的好处
除常量、别名类型以及未命名类型外,Go强制要求显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义
a := 10
b := byte(a)
c := a + int(b) // 混合类型表达式必须确保类型一致
同样,不能将非bool类型当做 true/false 使用
package main
func main() {
x := 100
var b bool = x //cannot use x (type int) as type bool in assignment
if x { //non-bool x (type int) used as if condition
}
}
语法歧义
如果转换的目标是指针、单向通道或没有返回值的函数类型,那么必须使用括号,以避免造成语法歧义
package main
func main() {
x := 100
p := (*int)(&x) // 不能写成 *int(&x)
println(p,&x)
}
结果
0xc00002df80 0xc00002df80
2.7 自定义类型
使用关键字type 定义用户自定义类型,包括基于现有基础类型创建,或者是结构体、函数类型等。
package main
import "fmt"
type flags byte
const (
read flags = 1 << iota
write
exec
)
func main() {
f := read | exec
fmt.Printf("%bn", f)
}
结果
101
和var、const类似,多个type定义可合并成组,可在函数或代码块内定义局部类型
package main
import "fmt"
func main() {
type(
user struct{
name string
age uint8
}
event func(string)bool
)
u := user{"tom",20}
fmt.Println(u)
var f event = func(s string)bool{
println(s)
return s != ""
}
f("abc")
}
结果
{tom 20}
abc
即便指定了基础类型,也只能表明他们具有相同的底层数据结构,两者间不存在任何关系,属于完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视作别名,不能用于直接比较
package main
func main() {
type data int
var d data = 10
var x int = d //cannot use d (type data) as type int in assignment
println(x)
println(x == d) //invalid operation: x == d (mismatched types int and data
}
未命名类型
与有明确标识符的bool、int、string等类型相比,数组、切片、字典、通道等类型与具体元素类型或长度等属性有关,故称未命名类型。当然,可用type为其提供具体名称,将其改变为命名类型
具有相同声明的未命名类型被视为同一类型:
- 具有相同基类型的指针
- 具有相同元素类型和长度的数组(array)
- 具有相同元素类型的切片(slice)
- 具有相同类型的字典(map)
- 具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体
- 具有相同签名(参数和返回列表,不包括参数名)的函数(func)
- 具有相同方法集(方法名,方法签名,不包括顺序)的接口(interface)
容易被忽视的是struct tag,它也属于类型组成部分,而不仅仅是元数据描述
package main
import "fmt"
func main() {
var a struct {
x string `x`
y int `y`
}
var b struct {
x string
y int
}
_ = a
//b = a //cannot use a (type struct { x string "x"; y int "y" }) as type struct { x string; y int } in assignment
fmt.Println(b)
}
同样,函数的参数顺序也属于签名组成部分
package main
func main() {
var a func(int, string)
var b func(string, int)
b = a // cannot use a (type func(int, string)) as type func(string, int) in assignment
b("s", 1)
}
未命名类型转换规则
- 所属类型相同
- 基础类型相同,且其中一个是未命名类型
- 数据类型相同,将双向通道赋值给单向通道,且其中一个为未命名类型
- 将默认值nil赋值给切片、字典、通道、指针、函数或接口
- 对象实现了目标接口
package main
import "fmt"
func main() {
type data [2]int
var d data = [2]int{1, 2} //基础类型相同,右值为未命名类型
fmt.Println(d)
a := make(chan int, 2)
var b chan<- int = a //将双向通道转换为单向通道,其中b为未命名类型
b <- 2
}
最后
以上就是土豪面包为你收集整理的go语言学习(第二章,类型)(go 语言学习笔记)的全部内容,希望文章能够帮你解决go语言学习(第二章,类型)(go 语言学习笔记)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复