类型

2.1 变量

标识符与关键字

标识符

在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名、常量名、函数名等等。 Go语言中标识符由字母数字和_(下划线)组成,并且只能以字母和_开头。 举几个例子:abc, _, _123, a123

关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。 关键字和保留字都不建议用作变量名。

Go语言中有25个关键字:

1    break        default      func         interface    select
2    case         defer        go           map          struct
3    chan         else         goto         package      switch
4    const        fallthrough  if           range        type
5    continue     for          import       return       var

此外,Go语言中还有37个保留字。

 1    Constants:    true  false  iota  nil
 2
 3        Types:    int  int8  int16  int32  int64  
 4                  uint  uint8  uint16  uint32  uint64  uintptr
 5                  float32  float64  complex128  complex64
 6                  bool  byte  rune  string  error
 7
 8    Functions:   make  len  cap  new  append  copy  close  delete
 9                 complex  real  imag
10                 panic  recover

go 语言中的变量必须先声明再使用。

 1package main
 2
 3// 导入语句
 4import "fmt"
 5
 6// 函数外只能放置标识符(常量/变量/函数/类型),不能放置语句
 7// fmt.Println("hello world") // 非法语句
 8
 9// 程序入口函数
10func main() {
11	var s string
12	// s = "hello"
13	fmt.Println(s)
14	fmt.Println("hello")
15	fmt.Printf("name: %s", s)
16}

运行时内存分配操作会确保变量自动化初始为二进制零值(zero value), 避免出现不可预测的行为。

 1var x int  // 自动化初始为0
 2var y = false   // 自动推断为bool类型
 3
 4var x,y int // 相同类型多个变量
 5
 6// 建议以组的方式整理多行变量定义
 7var{
 8  x,y int
 9  a,s = 100,"abc"
10}

简短模式 short variable declaration

  • 定义变量同时显式初始化
  • 不能提供数据类型
  • 只能在函数内部使用
1
2
3func main(){
4  x := 100
5  a,s := 1, "adc"
6}

简短模式并不总是重新定义变量,也可能是部分退化的赋值操作

1func main(){
2  x := 100
3  print(&x)
4  
5  x, y := 200,"abc"
6  println(&x, x)
7  println(y)
8}

退化赋值的条件是:最少有一个新的变量被定义,且必须是同一作用域

在处理函数错误返回值时,退化赋值允许我们重复使用err变量

多变量赋值

在进行多变量赋值时,首先计算出所有的右值,然后再依次完成赋值操作

go tool objdump -s "main/.main" test

未使用错误

编译器将未使用的局部变量当做错误。

全局变量没问题

2.2 命名

命名建议

  • 字母或者下划线开始
  • 区分大小写
  • 使用驼峰格式
  • 局部变量优先使用短名字
  • 专有名词大写

Go 支持用汉字等Unicode字符命名

1func main(){
2	var c int
3  for i :=0; i<10;i++{
4    c++
5  }
6}

符号名字 首字母大小写决定了其作用域

首字母大写的为导出成员,可以被包外引用,而小写仅能在包内使用。

空表示符

“_” blank identifier。 通常作为忽略占位符使用,可做表达式左值,无法读取内容

1import "strconv"
2func main(){
3  x,_:=strcow.Atoi("12")
4  println(x)
5}

空标识符可以用来临时规避编译器对未使用变量和导入包的错误检查。是预置成员,不能重新定义。

匿名变量 不占用命名空间,不会分配内存,所以匿名变量存在重复声明

函数外的每个语句逗必须以关键字开始const var func

类型推导

 1package main
 2
 3func main(){
 4  var a = 101  //  a是 int
 5  b := int8(12)  // b 是 int8
 6  
 7  f := 1.2345 // 默认Go语言中的小数都是64位
 8  // float32 转float32 要进行强制类型转换,不能直接赋值
 9  a := '啥'
10	fmt.Printf("%v\n",a)
11	fmt.Printf("%T\n",a)
12	fmt.Printf("%c\n",a)  // 字符默认竟然是 int32
13}

2.3 常量

定义常量之后,在运行期间不会改变

Const 可以放在全局的也可以放在局部的

1const x,y int = 123, 0x22
2const s = "hello"

可以在函数代码中定义常量,不曾使用的常量不会引发编译错误

常量值可以使某些编译器能计算出结果的表达式

1import "unsafe"
2const{
3  ptrSize = unsafe.Sizeof(uintptr(0))
4  strSize = len("hello, world")
5}

批量声明

1const(
2	a = 1
3	b
4	c
5)
6// a,b,c 均为100

iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

iota在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

举个例子:

1const (
2		n1 = iota //0
3		n2        //1
4		n3        //2
5		n4        //3
6	)

几个常见的iota示例:

使用_跳过某些值

1const (
2		n1 = iota //0
3		n2        //1
4		_
5		n4        //3
6	)

iota声明中间插队

1const (
2		n1 = iota //0
3		n2 = 100  //100
4		n3 = iota //2
5		n4        //3
6	)
7	const n5 = iota //0

定义数量级 (这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)

1const (
2		_  = iota
3		KB = 1 << (10 * iota)
4		MB = 1 << (10 * iota)
5		GB = 1 << (10 * iota)
6		TB = 1 << (10 * iota)
7		PB = 1 << (10 * iota)
8	)

多个iota定义在一行

1const (
2		a, b = iota + 1, iota + 2 //1,2
3		c, d                      //2,3
4		e, f                      //3,4
5	)

5 条评论

枚举

1const (
2	x = iota   // 0
3  y
4  z
5)

展开

1var x = 0x100
2const y = 0x200
3func main(){
4  print(&x,x)
5  pront(&y, y ) // cannot take the address of y
6}

不同于变量在运行期分配存储内存(非优化状态), 常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。

1const y = 0x200
2
3func main(){
4  print(y)
5}
1go build && go tool objdump -s "main./main" test

数字常量不会分配存储空间, 无须像变量那样通过内存寻址来取值,因此无法获取地址。

鉴于Go当前对动态库的支持还不完善,是否存在"常量陷阱"的问题还有待观察

2.4 基本类型

占位符 类型
%s 字符串
%b 二进制
%o 八进制
%d 十进制
%x 十六进制
%T 数据的类型
%.2f 浮点型数据
%v value
 1package main
 2
 3import "fmt"
 4
 5// fmt占位符
 6func main() {
 7	var n = 100
 8	// 查看类型
 9	fmt.Printf("%T\n", n)
10	fmt.Printf("%v\n", n)
11	fmt.Printf("%b\n", n)
12	fmt.Printf("%d\n", n)
13	fmt.Printf("%o\n", n)
14	fmt.Printf("%x\n", n)
15	var s = "Hello 沙河!"
16	fmt.Printf("字符串:%s\n", s)
17	fmt.Printf("字符串:%v\n", s)
18	fmt.Printf("字符串:%#v\n", s)
19}

八进制& 十六进制

Go语言中无法直接定义二进制数

 1package main
 2
 3import (
 4	"fmt"
 5	"math"
 6)
 7
 8func main() {
 9	a, b, c := 100, 0144, 0x64
10	println(a, b, c)
11	fmt.Println(a, b, c)
12	fmt.Printf("0b%b, %#o, %#x\n", a, b, c)
13
14	fmt.Println(math.MinInt8, math.MaxInt8)
15}

strconv

 1package main
 2
 3import "strconv"
 4
 5func main() {
 6	a, _ := strconv.ParseInt("1100100", 2, 32)
 7	b, _ := strconv.ParseInt("0144", 8, 32)
 8	c, _ := strconv.ParseInt("64", 16, 32)
 9
10	println(a, b, c)
11
12	println("0b" + strconv.FormatInt(a, 2))
13	println("0" + strconv.FormatInt(b, 8))
14	println("0x" + strconv.FormatInt(c, 16))
15
16}

浮点型

Go语言支持两种浮点型数:float32float64。这两种浮点型数据格式遵循IEEE 754标准: float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64

打印浮点数时,可以使用fmt包配合动词%f,代码如下:

1package main
2import (
3        "fmt"
4        "math"
5)
6func main() {
7        fmt.Printf("%f\n", math.Pi)
8        fmt.Printf("%.2f\n", math.Pi)
9}

复数

complex64和complex128

1var c1 complex64
2c1 = 1 + 2i
3var c2 complex128
4c2 = 2 + 3i
5fmt.Println(c1)
6fmt.Println(c2)

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)false(假)两个值。

注意:

  1. 布尔类型变量的默认值为false
  2. Go 语言中不允许将整型强制转换为布尔型.
  3. 布尔型无法参与数值运算,也无法与其他类型进行转换。

字符串

Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为双引号(")中的内容,可以在Go语言的源码中直接添加非ASCII码字符,例如:

1s1 := "hello"
2s2 := "你好"

字符串转义符

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。

转义符 含义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠

举个例子,我们要打印一个Windows平台下的一个文件路径:

1package main
2import (
3    "fmt"
4)
5func main() {
6    fmt.Println("str := \"c:\\Code\\lesson1\\go.exe\"")
7}

多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符:

1s1 := `第一行
2第二行
3第三行
4`
5fmt.Println(s1)

反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出

字符串的常用操作

方法 介绍
len(str) 求长度
+或fmt.Sprintf 拼接字符串
strings.Split 分割
strings.contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置
strings.Join(a[]string, sep string) join操作
 1package main
 2
 3import (
 4	"fmt"
 5	"strings"
 6)
 7
 8// 字符串
 9
10func main() {
11	// \ 本来是具有特殊含义的,我应该告诉程序我写的\就是一个单纯的\
12	path := "'D:\\Go\\src\\code.oldboyedu.com\\studygo\\day01'"
13	fmt.Println(path)
14
15	s := "I'm ok"
16	fmt.Println(s)
17	// 多行的字符串
18	s2 := `
19世情薄
20				人情恶
21		雨送黄昏花易落
22	`
23	fmt.Println(s2)
24	s3 := `D:\Go\src\code.oldboyedu.com\studygo\day01`
25	fmt.Println(s3)
26
27	// 字符串相关操作
28	fmt.Println(len(s3)) // ?
29
30	// 字符串拼接
31	name := "理想"
32	world := "大帅比"
33
34	ss := name + world
35	fmt.Println(ss)
36	ss1 := fmt.Sprintf("%s%s", name, world)
37	// fmt.Printf("%s%s", name, world)
38	fmt.Println(ss1)
39	// 分隔
40	ret := strings.Split(s3, "\\")
41	fmt.Println(ret)
42	// 包含
43	fmt.Println(strings.Contains(ss, "理性"))
44	fmt.Println(strings.Contains(ss, "理想"))
45	// 前缀
46	fmt.Println(strings.HasPrefix(ss, "理想"))
47	// 后缀
48	fmt.Println(strings.HasSuffix(ss, "理想"))
49
50	s4 := "abcdeb"
51	fmt.Println(strings.Index(s4, "c"))
52	fmt.Println(strings.LastIndex(s4, "b"))
53	// 拼接
54	fmt.Println(strings.Join(ret, "+"))
55}
56
57/*
58'D:\Go\src\code.oldboyedu.com\studygo\day01'
59I'm ok
60
61世情薄
62                                人情恶
63                雨送黄昏花易落
64
65D:\Go\src\code.oldboyedu.com\studygo\day01
6642
67理想大帅比
68理想大帅比
69[D: Go src code.oldboyedu.com studygo day01]
70false
71true
72true
73false
742
755
76D:+Go+src+code.oldboyedu.com+studygo+day01
77*/

别名

在官方语言规范中专门提到两个别名

1byte alias for unit8
2rune alias for int32
 1package main
 2
 3import "fmt"
 4
 5// byte和rune类型
 6
 7// Go语言中为了处理非ASCII码类型的字符 定义了新的rune类型
 8
 9func main() {
10	s := "Hello沙河사샤"
11	// len()求得是byte字节的数量
12	n := len(s) // 求字符串s的长度,把长度保存到变量n中
13	fmt.Println(n)
14
15	// for i := 0; i < len(s); i++ {
16	// 	// fmt.Println(s[i])
17	// 	fmt.Printf("%c\n", s[i]) // %c:字符
18	// }
19
20	for _, c := range s { // 从字符串中拿出具体的字符
21		fmt.Printf("%c\n", c) // %c:字符
22	}
23
24	// "Hello" => 'H' 'e' 'l' 'l' 'o'
25	// 字符串修改
26	s2 := "白萝卜"      // => '白' '萝' '卜'
27	s3 := []rune(s2) // 把字符串强制转换成了一个rune切片
28	s3[0] = '红'
29	fmt.Println(string(s3)) // 把rune切片强制转换成字符串
30
31	c1 := "红"
32	c2 := '红' // rune(int32)
33	fmt.Printf("c1:%T c2:%T\n", c1, c2)
34	c3 := "H"       // string
35	c4 := byte('H') // byte(uint8)
36	fmt.Printf("c3:%T c4:%T\n", c3, c4)
37	fmt.Printf("%d\n", c4)
38
39	// 类型转换
40	n1 := 10 // int
41	var f float64
42	f = float64(n1)
43	fmt.Println(f)
44	fmt.Printf("%T\n", f)
45}

修改字符串

要修改字符串,需要先将其转换成[]rune[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

 1func changeString() {
 2	s1 := "big"
 3	// 强制类型转换
 4	byteS1 := []byte(s1)
 5	byteS1[0] = 'p'
 6	fmt.Println(string(byteS1))
 7
 8	s2 := "白萝卜"
 9	runeS2 := []rune(s2)
10	runeS2[0] = '红'
11	fmt.Println(string(runeS2))
12}

类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。

强制类型转换的基本语法如下:

1T(表达式)

其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.

比如计算直角三角形的斜边长时使用math包的Sqrt()函数,该函数接收的是float64类型的参数,而变量a和b都是int类型的,这个时候就需要将a和b强制类型转换为float64类型。

1func sqrtDemo() {
2	var a, b = 3, 4
3	var c int
4	// math.Sqrt()接收的参数是float64类型,需要强制转换
5	c = int(math.Sqrt(float64(a*a + b*b)))
6	fmt.Println(c)
7}

2.5 引用类型