go语言入门-类型
基本类型
基础数据类型 类型、长度、默认值、说明介绍
类型 | 长度 (单位Byte) | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | 同uint8 |
int | 4/8 | 0 | 默认的整数类型,具体位数取决于平台,32bit的OS长度为4,64位OS长度为8 |
uint | 4/8 | 0 | 无符号整数,具体位数取决于平台,32bit的OS长度为4,64位OS长度为8 |
int8 | 1 | 0 | -128~127 |
uint8 | 1 | 0 | 0~255 |
int16 | 2 | 0 | -32768~32767 |
uint16 | 2 | 0 | 0~65535 |
int32 | 4 | 0 | -2147483648~2147483647 |
uint32 | 4 | 0 | 0~4294967295 |
int64 | 8 | 0 | -9223372036854775808~9223372036854775807 |
uint64 | 8 | 0 | 0~18446744073709551615 |
float32 | 4 | 0.0 | 1.17549e-038~3.40282e+038 |
float64 | 8 | 0.0 | 2.22507e-308~1.79769e+308 |
complex64 | 8 | ||
complex128 | 16 | ||
rune | 4 | 0 | int32的别名 存放Unicode编码 |
uintptr | 4/8 | 0 | 存放指针的类型 uint的别名 |
string | “” | 字符串 | |
array | 数组 | ||
struct | 结构体 | ||
functionnil | nil | 函数 | |
interface | nil | 接口 | |
map | nil | 字典,引用类型 | |
slice | nil | 切片,引用类型 | |
channel | nil | 通道,引用类型 |
字面值常量介绍
整型字面值常量
package main
import "fmt"
func main() {
//var dec int = 1
//var oct int = 02
//var hex int = 0x03
//var bin int = 0x0101
var (
//十进制
dec int = 127
//八进制数值需要加前缀0
oct int = 0177
//十六进制数值需要加前缀0x
hex int = 0xDF
//二进制数值常量需要加前缀0b
bin int = 0b10000011
)
fmt.Printf("dec: [十进制[%d] 八进制[%o] 十六进制[%x] 二进制[%b]\n", dec, dec, dec, dec)
fmt.Printf("oct: [十进制[%d] 八进制[%o] 十六进制[%x] 二进制[%b]\n", oct, oct, oct, oct)
fmt.Printf("hex: [十进制[%d] 八进制[%o] 十六进制[%x] 二进制[%b]\n", hex, hex, hex, hex)
fmt.Printf("bin: [十进制[%d] 八进制[%o] 十六进制[%x] 二进制[%b]\n", bin, bin, bin, bin)
}
/**
output:
dec: [十进制[127] 八进制[177] 十六进制[7f] 二进制[1111111]
oct: [十进制[127] 八进制[177] 十六进制[7f] 二进制[1111111]
hex: [十进制[223] 八进制[337] 十六进制[df] 二进制[11011111]
bin: [十进制[131] 八进制[203] 十六进制[83] 二进制[10000011]
*/
我们可以通过字面值常量来定义数字。例如在刚才的例子中,可以使用0前缀标识八进制数值如: oct int = 0177、使用0x前缀标识十六进制数值如:hex int = 0xDF, 使用0b前缀标识二进制数值,如bin int = 0b10000011。
同时fmt 包里的print函数族中Printf支持格式化打印。
其中:
%d 表示十进制占位符
%o表示八进制占位符
%x表示十六进制占位符
%b表示二进制占位符
浮点型字面值常量
func main() {
var f32a float32 = 123.334434e-3
var f32b float32 = -123.3344e-2
////f32a: [十进制[ 0.123334] 十六进制[0x1.f92d88p-04] 二进制[0000000000000000000016553668p-27] 不能直接打印二进制和十六进制
fmt.Printf("f32a: [十进制[%10f] 十六进制[%08x] 二进制[%032b]\n", f32a, f32a, f32a)
//f32a: [十进制[ 0.123334] 十六进制[3dfc96c4] 二进制[00111101111111001001011011000100]
fmt.Printf("f32a: [十进制[%10f] 十六进制[%08x] 二进制[%032b]\n", f32a, math.Float32bits(f32a), math.Float32bits(f32a))
//f32b: [十进制[ -1.233344] 十六进制[bf9dde37] 二进制[10111111100111011101111000110111]
fmt.Printf("f32b: [十进制[%10f] 十六进制[%08x] 二进制[%032b]\n", f32b, math.Float32bits(f32b), math.Float32bits(f32b))
var f64c float64 = 123.334434e-3
fmt.Printf("f64c: [十进制[%10f] 十六进制[%016x] 二进制[%064b]\n", f64c, math.Float64bits(f64c), math.Float64bits(f64c))
}
/**
output:
f32a: [十进制[ 0.123334] 十六进制[0x1.f92d88p-04] 二进制[0000000000000000000016553668p-27]
f32a: [十进制[ 0.123334] 十六进制[3dfc96c4] 二进制[00111101111111001001011011000100]
f32b: [十进制[ -1.233344] 十六进制[bf9dde37] 二进制[10111111100111011101111000110111]
f64c: [十进制[ 0.123334] 十六进制[3fbf92d870802bf1] 二进制[0011111110111111100100101101100001110000100000000010101111110001]
*/
浮点数的字面值常量表示方式有两种,第一种是普通10进制+小数点标识如:123.01,第二种是科学计数法标识,如1.2301e+3/123010e-3。
同时fmt包中也支持对浮点数的格式打印,其中
使用%f表示浮点数占用符
如:
////f32a: [十进制[ 0.123334] 十六进制[0x1.f92d88p-04] 二进制[0000000000000000000016553668p-27] 不能直接打印二进制和十六进制
fmt.Printf("f32a: [十进制[%10f] 十六进制[%08x] 二进制[%032b]\n", f32a, f32a, f32a)
备注:fmt可以使用%032b这种方式进行 数值对齐,%32b标识右对齐长度为32位,%032b标识右对齐长度为32位,不足32位用0补全,其他使用请参考官方文档。
以上代码中不仅仅打印浮点数值,但是打印的浮点数的十六进制和二进制不是符合预期的,那应该怎么办呢?math包中提供一个一组函数:
// Float32bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position.
// Float32bits(Float32frombits(x)) == x.
func Float32bits(f float32) uint32 { return *(*uint32)(unsafe.Pointer(&f)) }
// Float64bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position,
// and Float64bits(Float64frombits(x)) == x.
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
该组函数把浮点数转换为对应的无符号的整型,并且bit位不发生变化。
上个例子演示一下:
var f32b float32 = -123.3344e-2
//f32b: [十进制[ -1.233344] 十六进制[bf9dde37] 二进制[10111111100111011101111000110111]
fmt.Printf("f32b: [十进制[%10f] 十六进制[%08x] 二进制[%032b]\n", f32b, math.Float32bits(f32b), math.Float32bits(f32b))
var f64c float64 = 123.334434e-3
//f64c: [十进制[ 0.123334] 十六进制[3fbf92d870802bf1] 二进制[0011111110111111100100101101100001110000100000000010101111110001]
fmt.Printf("f64c: [十进制[%10f] 十六进制[%016x] 二进制[%064b]\n", f64c, math.Float64bits(f64c), math.Float64bits(f64c))
我们再来分析下Float32bits到底做了什么。
func Float32bits(f float32) uint32 { return *(*uint32)(unsafe.Pointer(&f)) }
通过unsafe包中的Pointer函数获取入参的指针,然后把该指针转换为 *uint32 的指针,然后再获取该指针的内容(转换uint32的值)。
布尔值字面值常量
func main() {
var b bool = true
var c bool = false
fmt.Printf("%v\n", b)
fmt.Printf("%v\n", c)
//bool值不能转为uint值进行后去二进制,本事bool值在栈区是没有指定的内存空间的。
fmt.Printf("address:%08x value:%08x\n", (*uint64)(unsafe.Pointer(&b)), *(*uint64)(unsafe.Pointer(&b)))
fmt.Printf("address:%08x value:%08x\n", (*uint64)(unsafe.Pointer(&c)), *(*uint64)(unsafe.Pointer(&c)))
fmt.Printf("%t\n", c)
}
/**
true
false
address:c00000a0d8 value:00000001
address:c00000a0d9 value:4300000000000000
false
*/
bool值字面值常量只有true和false,fmt.Printf可以使用%t 或者 %v占用符进行打印。
注意:
- 布尔变量的默认值为false
- 布尔变量无法进行整型类型转换,即使通过unsafe包也无法转换出正确的值。
- 基于第二点,也能推导出布尔变量无法进行数值计算。
再看true和false的定义
// true and false are the two untyped boolean values.
const (
true = 0 == 0 // Untyped bool.
false = 0 != 0 // Untyped bool.
)
//自定义bool类型
myTrue := 0 == 0
//true
fmt.Println(myTrue)
实际上go语言中bool中 true和false是常量,对应的0 == 0是常量表达式。
我们也可以自己自定义布尔类型。就像上诉例子所表示的。
字符串字面值常量
Go语言中字符串编码使用UTF-8编码,字符串的值使用双引号包裹如:str := “我”。
func main() {
var str1 string = "hello世界"
var str2 string = "再见world"
var str3 string = "hello\t世界"
var str4 string = `hello\t世界`
var str5 string = `hello\t世
r
界`
//hello世界
fmt.Printf("%s\n", str1)
//再见world
fmt.Printf("%s\n", str2)
//hello 世界
fmt.Printf("%s\n", str3)
//hello\t世界
fmt.Printf("%s\n", str4)
//hello\t世
//r
//界
fmt.Printf("%s\n", str5)
}
/**
output:
hello世界
再见world
hello 世界
hello\t世界
hello\t世
r
界
*/
以上例子中可以看出定义字符串字面值常量有两种:
- 通过双引号""
- 双引号中对于特殊字符需要转义
- 双引号不能支持跨行
- 通过反引号``
- 反引号中不会发生转义
- 反引号支持跨行
字符字面值常量
字符是组成字符串的单元,通常使用单引号‘’定义字符如:
func main() {
var a rune = 'w'
var b byte = 'w'
var c rune = '我'
//var d byte = '我'
fmt.Printf("%c 0x%x\n", a, a)
fmt.Printf("%c 0x%x\n", b, b)
fmt.Printf("%c 0x%x\n", c, c)
fmt.Printf("0x%x\n", '我')
//constant 25105 overflows byte 如果把非ascill值的字符赋值给 byte则会溢出
//fmt.Printf("%c\n", d)
}
/**
output:
w 0x77
w 0x77
我 0x6211
0x6211
*/
查看byte和rune的定义
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
byte是uint8的别名
那么byte的范围就是 0-255(用于存放ASCILL码字符)
rune是uint32的别名
那么uint32的范围就是0~4294967295(用于存放Unicode字符,兼容ASCILL码)
- 探索一下rune byte string的关系
func main() {
str := "hello世界"
for _, r := range str {
//通过range遍历,此时r的类型为rune
fmt.Printf("%v(%c)", r, r)
}
fmt.Printf("\n")
for i := 0; i < len(str); i++ {
//通过下标遍历,此时str[i]为byte
fmt.Printf("%v(%c)", str[i], str[i])
}
fmt.Printf("\n")
}
/**
output:
104(h)101(e)108(l)108(l)111(o)19990(世)30028(界)
104(h)101(e)108(l)108(l)111(o)228(ä)184(¸)150(–)231(ç)149(•)140(Œ)
*/
通过以上例子可以看出来,实际字符串的存放是按照字节数组来存放,一个汉字占用了4个字节。一个普通ascill字符占用了1个字节。在使用range进行遍历的时候会把byte转为rune,同时3个字节的汉字向上转换为rune类型,长度均为4个byte。
字面值常量格式化打印
引用类型
引用类型特指 slice-切片、map-字典、channel-通道 这三种预定义类型。
同数字、数组等类型相比,引用类型的存储结构更加复杂。在完成内存分配后还需要进行相关属性初始化。
引用类型创建
初始化表达式
引用类型声明,默认值都为nil
package main
import "fmt"
func main() {
//slice表达式创建
var s []int
fmt.Printf("%#v\n", s)
//map表达式声明
var m map[int]string
fmt.Printf("%#v\n", m)
//channel表达式声明
var c chan int
fmt.Printf("%#v\n", c)
}
/**
output:
[]int(nil)
map[int]string(nil)
(chan int)(nil)
*/
其中切片类型、map类型 比较特殊。可以通过初始化表达式进行定义,如:
切片
func main() {
s := []int {1, 2, 3}
fmt.Printf("%#v\n", s)
}
/**
output:
[]int{1, 2, 3}
*/
字典
func main() {
//表达式定义初始化
m := map[string]int{
"c" : 13,
"a" : 10,
"b" : 11, //最后一个元素需要","逗号结尾
}
fmt.Println(m)
}
make函数
引用类型必须使用make函数进行内存分配、初始化一系列属性。演示:
切片:
func main() {
//创建一个长度为3,容量为10的切片。存放数据的类型为int,并初始化切片中元素的值为0
s := make([]int, 3, 10)
fmt.Printf("%#v\n", s)
}
/**
output:
[]int{0, 0, 0}
*/
字典:
func main() {
//创建一个键类型为string, 值类型为int的字典,该字典的容量
m := make(map[string]int, 10)
//创建一个键类型为string,值类型为int的字典,未指定容量
m1 := make(map[string]int)
m["1"] = 1
m["2"] = 2
m["3"] = 3
m1["1"] = 1
m1["2"] = 2
m1["3"] = 3
fmt.Printf("%#v\n", m)
fmt.Printf("%#v\n", m1)
}
/**
output:
map[string]int{"1":1, "2":2, "3":3}
map[string]int{"1":1, "2":2, "3":3}
*/
通道:
func main() {
//创建一个无缓存的通道 通道元素类型为int
ch := make(chan int)
//穿件一个有缓冲的通道, 通道元素类型为int
ch1 := make(chan int, 10)
fmt.Println(ch)
fmt.Println(ch1)
}
/**
output:
0xc000058060
0xc0000a4000
*/
new函数
函数new按照指定类型长度分配零值内存,返回指针。不关系内构造和初始化方式。
重点
new是用来创建值类型,返回的是一个指针。
func main() {
//创建一块内存,用于存放int变量, 并且返回一个int*指针变量,该指针变量指向新分配的内存,并且内存值清为0
s := new(int)
//(*int)(0xc000072090) 0
fmt.Printf("%#v %#v\n", s, *s)
//修改s指向内存的值为10
*s = 10
//(*int)(0xc000072090) 10
fmt.Printf("%#v %#v\n", s, *s)
}
/**
output:
(*int)(0xc000072090) 0
(*int)(0xc000072090) 10
*/
new返回的是指针—只能对值类型有效
new函数操作原始数据
func main() {
//创建一块内存,用于存放int数组变量,该数组长度为10,返回指针,该指针指向新分配的内存,并初始化内存为0
arr := new([10]int)
//&[10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} [10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
fmt.Printf("%#v %#v\n", arr, *arr)
// 修改下标为1的数组元素的值为10
arr[1] = 10
//&[10]int{0, 10, 0, 0, 0, 0, 0, 0, 0, 0} [10]int{0, 10, 0, 0, 0, 0, 0, 0, 0, 0}
fmt.Printf("%#v %#v\n", arr, *arr)
//通过原始数据arr创建切片s
s := arr[1:2]
fmt.Printf("%T %v\n", s, s)
}
/**
&[10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} [10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
&[10]int{0, 10, 0, 0, 0, 0, 0, 0, 0, 0} [10]int{0, 10, 0, 0, 0, 0, 0, 0, 0, 0}
[]int [10]
*/
通过上面的例子可以看出数组的类型是[N]int N表示数组的长度 切片的类是[]int,没有长度。一定不要把数组和切片混淆。
未命名类型
数组、切片、字典、通道等类型域具体元素类型或者长度等属性有关----未命名类型。可以通过type提供具体名称。可以改变命名类型。
具备相同声明的未命名类型。
– 待续
类型转换
除常量、别名类型以及未命名类型外,Go语言必须使用显示类型转换。
func main() {
var b byte = 10
//显示类型转换 小转大 数据不会溢出
var i = int(b)
fmt.Printf("%#v %T\n", b, b)
fmt.Printf("%#v %T\n", i, i)
}
/**
output:
0xa uint8
10 int
*/
向下转换,可能会导致数据截断
func main() {
var b byte = 10
//显示类型转换 小转大 数据不会溢出
var i = int(b)
fmt.Printf("%#v %T\n", b, b)
fmt.Printf("%#v %T\n", i, i)
var tmp = i + 1000
fmt.Printf("0x%0x %T\n", tmp, tmp)
//向下转换高位截断 0x3f2-->0xf2 显示转换数据丢失
var bb = byte(tmp)
fmt.Printf("%#v %T\n", bb, bb)
}
/**
0xa uint8
10 int
0x3f2 int
0xf2 uint8
*/
强制转换的类型从以上例子中可以得出:T(表达式)