1. 基本类型
- bool
- string
- int、int8、int16、int32、int64
- uint、uint8、uint16、uint32、uint64、uintptr
- byte // uint8 的别名
- rune // int32 的别名 代表一个 Unicode 码
- float32、float64
- complex64、complex128
2. 三种声明变量
2.1 标准格式
var name type
其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型
2.2 批量格式
var (
a int
b string
c []float32
d func() bool
e struct {
x int
}
)
2.3 简短格式
名字 := 表达式
i, j := 0, 1
需要注意的是,简短模式(short variable declaration)有以下限制:
定义变量,同时显式初始化。 //相当于赋值,可以不用再声明获取到的变量的类型
不能提供数据类型。 // 因为赋值直接会有类型了,所以不用提供数据类型
只能用在函数内部。 //全局定义还是得用var
示例:
func main() {
x:=100
a,s:=1, "abc"
}
3. 变量初始化
3.1 标准格式
var 变量名 类型 = 表达式
var hp int = 100
上面代码中,100 和 int 同为 int 类型,int 可以认为是冗余信息,因此可以进一步简化初始化的写法
3.2 编译器推导类型的格式
var defence =
var damageRate float32 = 0.17
var damage = float32(attack-defence) * damageRate
fmt.Println(damage)
代码说明:
- 第 1 和 2 行,右值为整型,attack 和 defence 变量的类型为 int。
- 第 3 行,表达式的右值中使用了 0.17。由于Go语言和C语言一样,编译器会尽量提高精确度,以避免计算中的精度损失。所以这里如果不指定 damageRate 变量的类型,Go语言编译器会将 damageRate 类型推导为 float64,我们这里不需要 float64 的精度,所以需要强制指定类型为 float32。
- 第 4 行,将 attack 和 defence 相减后的数值结果依然为整型,使用 float32() 将结果转换为 float32 类型,再与 float32 类型的 damageRate 相乘后,damage 类型也是 float32 类型
3.3 短变量声明并初始化
如果 hp 已经被声明过,但依然使用:=时编译器会报错
// 声明 hp 变量
var hp int
// 再次声明并赋值
hp := 10
示例:
conn, err := net.Dial(“tcp”,“127.0.0.1:8080”) net.Dial
提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(err)
4. 多变量同时赋值
var b = 200
b, a = a, b
fmt.Println(a, b)
5. 变量作用域
5.1 局部变量
import (
"fmt"
)
func main() {
//声明局部变量 a 和 b 并赋值
var a int = 3
var b int = 4
//声明局部变量 c 并计算 a 和 b 的和
c := a + b
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
5.2 全局变量
import "fmt"
//声明全局变量
var c int
func main() {
//声明局部变量
var a, b int
//初始化参数
a = 3
b = 4
c = a + b
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
5.3 形式参数
import (
"fmt"
)
//全局变量 a
var a int = 13
func main() {
//局部变量 a 和 b
var a int = 3
var b int = 4
fmt.Printf("main() 函数中 a = %d\n", a)
fmt.Printf("main() 函数中 b = %d\n", b)
c := sum(a, b)
fmt.Printf("main() 函数中 c = %d\n", c)
}
func sum(a, b int) int {
fmt.Printf("sum() 函数中 a = %d\n", a)
fmt.Printf("sum() 函数中 b = %d\n", b)
num := a + b
return num
}
6. 格式化输出
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi)
fmt.Printf("%.2f\n", math.Pi)
}
通用占位符:
- %v 默认使用,可通用
- %T输出类型
- %%输出百分号
布尔型:
- %t
宽度标识符具体见下图:
n := 12.34
fmt.Printf("%f\n", n)
fmt.Printf("%9f\n", n)
fmt.Printf("%.2f\n", n)
fmt.Printf("%9.2f\n", n)
fmt.Printf("%9.f\n", n)
输出:
12.340000
12.340000
12.34
12.34
12
7. 整形、浮点、布尔值结合
package main
import "fmt"
func main() {
i := 0
var c float32 = 0.2
b := float32(sum(3, 4))
if b > 7 {
i = 1
}
fmt.Printf("当前c的值为:%.2f\n", c)
fmt.Printf("当前i的值为:%d\n", i)
b2 := isBool(2)
fmt.Printf("b2的值为: %t\n", b2)
}
func sum(a, b int) int {
return a + b
}
func isBool(i int) bool { return i != 0 }
8. 字符串
8.1 常用符号
\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\:反斜杠自身
8.2 定义多行字符串
使用 `符号来定义
内部字符串以原始文字输出,转义符号失效
package main
import "fmt"
func main() {
const str1 = `第一行
第二行
第三行
\r\n
`
fmt.Printf("str1:%v", str1)
}
8.3 字符串方法
8.3.1 字符串长度
- 以字节长度计算
package main
import "fmt"
func main() {
tip1 := "gen ji is a ninja"
fmt.Println(len(tip1))
tip2 := "忍者"
fmt.Println(len(tip2))
}
输出:
17 // 纯英文与空格只占用一个长度
6 //一个汉字占用三个长度
- 按照字符个数计算(计算UTF8中的字符个数,需要用到RuneCountInString() 函数)
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
fmt.Println(utf8.RuneCountInString("龙龟冲!"))
fmt.Println(utf8.RuneCountInString("龙龟, Fight!"))
}
8.4 获取字符串元素
ASCii码输出
theme := "狙击 start"
for i := 0; i < len(theme); i++ {
fmt.Printf("ascii: %c %d\n", theme[i], theme[i])
}
Unicode编码输出
package main
import "fmt"
func main() {
theme := "狙击 start"
for i, s := range theme {
fmt.Printf("%d Ascii: %c %d\n", i, s, s)
}
}
输出:
0 Ascii: 狙 29401
3 Ascii: 击 20987
6 Ascii: 32
7 Ascii: s 115
8 Ascii: t 116
9 Ascii: a 97
10 Ascii: r 114
11 Ascii: t 116
8.5 字符串值获取
tracer := "死神来了, 死神bye bye"
comma := strings.Index(tracer, ", ")
pos := strings.Index(tracer[comma:], "死神") //等价于从", 死神bye bye"字符串开始查找
fmt.Println(comma, pos, tracer[comma+pos:])
输出:
12 3 死神bye bye
8.6 字符串的拼接
package main
import (
"bytes"
"fmt"
)
func main() {
a := "今天"
b := "不错"
var stringBuffer bytes.Buffer
stringBuffer.WriteString(a)
stringBuffer.WriteString(b) //这种拼接方式比+号高效
fmt.Println(stringBuffer.String())
}
9. 类型转换
精度丢失问题
package main
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))
}
10. 指针
10.1 指针取值
当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值,代码如下
package main
import (
"fmt"
)
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)
}
取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值
package main
import "fmt"
func swap(a, b *int) {
fmt.Println(a)
fmt.Println(b)
*b, *a = *a, *b
}
func main() {
x, y := 1, 2
swap(&x, &y)
fmt.Println(x, y)
}
输出:
2,1
package main
import "fmt"
func swap(a, b *int) {
b, a = a, b
}
func main() {
x, y := 1, 2
swap(&x, &y)
fmt.Println(x, y)
}
输出1,2
10.2 接收启动参数
Go语言内置的 flag 包实现了对命令行参数的解析,flag 包使得开发命令行工具更为简单
下面的代码通过提前定义一些命令行指令和对应的变量,并在运行时输入对应的参数,经过 flag 包的解析后即可获取命令行的数据
package main
// 导入系统包
import (
"flag"
"fmt"
)
// 定义命令行参数
var mode = flag.String("mode", "", "process mode")
func main() {
// 解析命令行参数
fmt.Printf("%T\n", mode)
flag.Parse()
// 输出命令行参数
fmt.Println(*mode)
}
将这段代码命名为 main.go,然后使用如下命令行运行: go run main.go --mode=fast 命令行输出结果如下:
fast
代码说明如下:
flag定义的都是指针类型的变量
第 10 行,通过 flag.String,定义一个 mode 变量,这个变量的类型是 *string。后面 3 个参数分别如下:
参数名称:在命令行输入参数时,使用这个名称。
参数值的默认值:与 flag 所使用的函数创建变量类型对应,String 对应字符串、Int 对应整型、Bool 对应布尔型等。
参数说明:使用 -help 时,会出现在说明中。
第 15 行,解析命令行参数,并将结果写入到变量 mode 中。
第 18 行,打印 mode 指针所指向的变量。
10.3 创建指针方法
new(类型)
package main
import "fmt"
func main() {
a := new(string)
fmt.Printf("%T\n", a) //创建了一个指针类型
*a = "今天天气不错"
fmt.Println(*a)
}
输出:
*string
今天天气不错
11. Go变量的生命周期
11.1 变量种类
全局变量:它的生命周期和整个程序的运行周期是一致的;
局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。
堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);
栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }中定义的局部变量。
11.2 变量逃逸
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
- 上述代码中,函数 f 里的变量 x 必须在堆上分配,因为它在函数退出后依然可以通过包一级的 global
变量找到,虽然它是在函数内部定义的。用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了。 - 相反,当函数 g 返回时,变量 *y 不再被使用,也就是说可以马上被回收的。因此,*y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配
*y 的存储空间,也可以选择在堆上分配,然后由Go语言的 GC(垃圾回收机制)回收这个变量的内存空间。 - 简而言之就是f函数中的x变量虽然是在局部被定义的,但是因为他的值以指针的形式被另一个变量接收了,所以在外部全局中其实可以使用到他的指针地址,所以在Go术语中这种现象称之为逃逸
- 然而在g函数中,并没有外部变量去接收y的指针地址,所以当函数结束时就可以把变量的内存空间通过GC加以回收
- 在实际的开发中,并不需要刻意的实现变量的逃逸行为,因为逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响
12. 常量
Go语言中的常量使用关键字 const
定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式
在Go语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
- 显式类型定义: const b string = “abc”
- 隐式类型定义: const b = “abc”
常量的值必须是能够在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。
- 正确的做法:const c1 = 2/3
- 错误的做法:const c2 = getNumber() // 引发构建错误: getNumber() 用做值
如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
常量生成器
常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量
package main
import "fmt"
func main() {
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
fmt.Println(Sunday)
fmt.Println(Monday)
fmt.Println(Tuesday)
fmt.Println(Wednesday)
fmt.Println(Thursday)
fmt.Println(Friday)
fmt.Println(Saturday)
}
输出:
0
1
2
3
4
5
6
13. 关键字与标识符
Go语言的词法元素包括 5
种,分别是标识符(identifier)、关键字(keyword)、操作符(operator)、分隔符(delimiter)、字面量(literal),它们是组成Go语言代码和程序的最基本单位。
13.1 关键字
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
PS:坑爹的表格真难用,该优化了啊!
13.2 标识符
标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列,标识符由若干个字母、下划线_
、和数字组成,且第一个字符必须是字母
标识符的命名需要遵守以下规则:
- 由 26 个英文字母、0~9、_组成;
- 不能以数字开头,例如 var 1num int 是错误的;
- Go语言中严格区分大小写;
- 标识符不能包含空格;
- 不能以系统保留关键字作为标识符,比如 break,if 等等。
命名标识符时还需要注意以下几点:
- 标识符的命名要尽量采取简短且有意义;
- 不能和标准库中的包名重复;
- 为变量、函数、常量命名时采用驼峰命名法,例如 stuName、getVal;
在Go语言中还存在着一些特殊的标识符,叫做预定义标识符,如下表所示:
预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用。
14 运算符优先级
Go语言运算符优先级和结合性一览表**
15. Go的字符串与数值类型的转换**
package main
import (
"fmt"
"strconv"
)
func main() {
value1, err1 := strconv.Atoi("哈哈") // Ascii To Int
fmt.Println(value1)
fmt.Println(err1)
var x = 4
value2 := strconv.Itoa(x) // 数字转字符串是不会出err
fmt.Println(value2)
}