在Go语言中,变量的关键字和C语言的关键字大体相同,但在Go语言中没有关键字“double”类型。在Go语言中“float”类型分为float32和float64,两者的区别在后文展开。Go语言的变量如表1所示。
基础类型 | 关键字 | 复合类型 |
布尔类型 | bool | 指针(pointer) |
整形 | int8、byte、int16、unit、uintptr | 数组(array) |
浮点类型 | float32、float64 | 切片(slice) |
复数类型 | complex64、complex128 | 字典(map) |
字符串 | string | 通道(chan) |
字符类型 | rune | 结构体(struct) |
错误类型 | error | 接口(interface) |
1 布尔类型
bool为布尔型用作逻辑判断,零表示false,非零表示true。不接受其他类型的赋值,不支持自动或强制的类型转换。以下是bool类型的用法示例:
package main
import "fmt"
func main() {
var success bool
if 5 == 5 {
success = true
} else {
success = false
}
fmt.Println(success)
}
2 整型
整形是所有编程语言里最基础的数据类型。Go语言支持的整形类型如下表2所示。
类型 | 长度(字节) | 值范围 |
int8 | 1 | -128 ~ 127 |
uint8(即byte) | 1 | 0 ~ 225 |
int16 | 2 | -32 768 ~ 32 767 |
uint16 | 2 | 0 ~ 65 535 |
int32 | 4 | -2 147 483 648 ~ 2 147 483 647 |
uint32 | 4 | 0 ~ 4 294 967 295 |
int64 | 8 | -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
int | 平台相关 | 平台相关 |
uint | 平台相关 | 平台相关 |
uintptr | 同指针 | 在32位平台下为4字节,64位平台下为8字节 |
2.1 类型表示
int和int32在Go语言里被认为是两种不同的类型,编译器不会自动做类型转换,使用强制类型转换可以解决这个问题,但要注意数据损失和溢出的问题(在这里不做具体分析),样例如下:
package main
import "fmt"
func main() {
var v1 int32
v2 := 64
v1 = int32(v2) //编译正确
//v1 = v2 //编译错误
fmt.Println(v1)
}
2.2 数值运算
编程语言都涉及数值运算这一基本操作,在Go语言支持的数值运算如下表3所示。
符号 | 功能 | 例子 |
+ | 加法运算 | 6 + 3 = 9 |
– | 减法运算 | 6 – 3 = 3 |
* | 乘法运算 | 6 * 3 = 18 |
/ | 除法运算 | 6 / 3 = 2 |
% | 取余运算 | 6 % 3 = 0 |
2.3 比较运算
Go语言支持以下的几种比较运算符:>、<、==、>=、<=和!=。和大多数其他编程语言相同,在这里就不做具体说明了。(需要注意的是不同类型的整形数不能直接比较,但可以直接与字面常量进行比较)
2.4 位运算
Go语言支持表4所示的运算符。
运算 | 含义 | 样例 |
x << y | 左移 | 124 << 2 //结果为496 |
x >> y | 右移 | 124 >> 2 //结果为31 |
x ^ y | 异或 | 124 ^ 2 //结果为126 |
x & y | 与 | 124 & 2 //结果为0 |
x | y | 或 | 124 | 2 //结果为126 |
^x | 取反 | ^124 //结果为-125 |
3 浮点型
浮点型用于表示包含小数点的数据,比如1.234就是一个浮点型数据。Go语言中的浮点类型采用IEEE-754标准的表达方式。
3.1 浮点数表示
Go语言定义了两个类型float32和float64,分别等价于C语言的float类型和double类型,样例如下:
package main
import "fmt"
func main() {
var fvalue1 float 32
fvalue1 = 12
fvalue2 := 12.0 //如果不加小数点,fvalue2会被推导为整形
fvalue3 := float32(fvalue2) //必须使用强制类型转换,直接赋值将导致编译错误
fmt.Println(fvalue3)
}
3.2 浮点数比较
因为浮点数不是一种精确的表达方式,所以直接使用“==”来判断两个浮点数是否相等会导致不稳定的结果。样例如下:
package main
import "fmt"
func main() {
var f32value float32 = math.Pi
var f64value float64 = math.Pi
fmt.Println("float32 Pi = ", f32value, ", float64 Pi = ", f64value)
fmt.Println("float32 Pi to float64 = ", float64(f32value), ", float64 Pi = ", f64value)
fmt.Println(f64value == float64(f32value))
fmt.Println("float32 Pi = ", f32value, ", float64 Pi to float32 = ", float32(f64value))
fmt.Println(float32(f64value) == f32value)
}
/*
打印结果如下:
float32 Pi = 3.1415927 , float64 Pi = 3.141592653589793
float32 Pi to float64 = 3.1415927410125732 , float64 Pi = 3.141592653589793
false
float32 Pi = 3.1415927 , float64 Pi to float32 = 3.1415927
true
/*
在上诉的例子中,不难看出,float32转换成float64类型的数据时,出现数据丢失的现象。所以在使用“==”比较这俩中类型时,编辑器会有“无效运算: f64value == f32value(类型 float64 和 float32 不匹配)”的警告。
4 复数类型
复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示虚部(imag)。样例如下:
package main //必须有一个main包
import "fmt"
func main() {
var cvalue1 complex128 //声明
cvalue1 = 2.1 + 3.14i //赋值
fmt.Println("cvalue1 = ", cvalue1 )
//自动推导类型
cvalue2 := 3.3 + 4.4i
fmt.Printf("cvalue2 type is %T\n", cvalue2)
//通过内建函数,取实部和虚部
fmt.Println("real(cvalue2) = ", real(cvalue2), ", imag(cvalue2) = ", imag(cvalue2))
}
/*
打印结果如下:
cvalue1 = (2.1+3.14i)
cvalue2 type is complex128
real(cvalue2) = 3.3 , imag(cvalue2) = 4.4
*/
5 字符串
在Go语言中,字符串也是一种基本类型。相比之下,C/C++语言并不存在原生的字符串类型,通常使用字符数组来表示,并以字符指针来传递。字符串的声明和使用的样例如下:
package main
import "fmt"
func main() {
var str string
str = "Hello world"
fmt.Println(str)
}
字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始化后被修改。
5.1 字符串操作
平时常用的字符串操作如表5所示。
运算 | 含义 | 样例 |
x + y | 字符串连接 | “Hello” + “World” // 结果为HelloWorld |
len(str) | 字符串长度 | len(“HelloWorld”) // 结果为10 |
s[i] | 取字符 | “HelloWorld”[1] // 结果为’e’ |
5.2 字符串遍历
Go语言支持两种方式遍历字符串,样例如下表所示:
字节数组遍历:
package main
import "fmt"
func main() {
str := "Hello,世界"
n := len(str)
for i := 0 ; i < n ; i++ {
fmt.Println(i, str[i])
}
}
/* 运行结果
0 72
1 101
2 108
3 108
4 111
5 44
6 228
7 184
8 150
9 231
10 149
11 140
*/
Unicode字节遍历:
package main
import "fmt"
func main() {
str := "Hello,世界"
for i , ch := range str{
fmt.Println(i, ch)
}
}
/* 运行结果
0 72
1 101
2 108
3 108
4 111
5 44
6 19990
9 30028
*/
6 字符类型
在Go语言中支持两种字符类型,一个是byte(实际上是unit8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。
7 数组
数组是Go语言编程中最常用的数据结构之一。顾名思义,数组就是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。以下为一些常规的数组声明方法:
var b [32]byte //长度为32的数组,每个元素为一个字节
var s [32] struct{x,y int} //复杂类型数组
var f1 [32] *float64 //指针数组
var f2 [2][4][4] float64 //等同于[2]([2]([2] float64))
var i [4][8] int //二维数组
8 结构体
Go 语言中数组只能存储同一类型的数据,但在结构体中我们可以为不同属性定义不同的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
8.1 定义结构体
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
type StudentInfo struct {
Name string
ClassRoom string
}
定义好结构体后,就能用于变量的声明,语法格式如下:
variable_name := structure_variable_type {value1, value2, ..., valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2, ..., keyn: valuen}
样例如下:
import
"fmt"
type StudentInfo struct {
Name string
ClassRoom string
}
func main() {
var students map[int]StudentInfo
students = make(map[int]StudentInfo)
//添加元素
students[0] = StudentInfo{"小明", "三年级(1)班"}
students[1] = StudentInfo{"小红", "三年级(1)班"}
students[2] = StudentInfo{"小李", "三年级(2)班"}
students[3] = StudentInfo{"小王", "三年级(2)班"}
students[4] = StudentInfo{"小赵", "三年级(3)班"}
//遍历元素
fmt.Println("学生信息如下:")
for _, student := range students {
fmt.Println(student)
}
}
9 指针
一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:
var ip *int // 指向整型
var fp *float32 // 指向浮点型
指针的使用流程:
- 定义指针变量
- 为指针变量赋值
- 访问指针变量中指向地址的值
使用指针操作数组的样例如下:
package main
import (
"fmt"
"math"
"math/rand"
"time"
)
func main() {
var p *int // 定义指针变量
var num [10]int
var temp int
p = &temp // 为指针变量赋值
// 一般使用系统时间的不确定性来进行初始化
rand.Seed(time.Now().Unix())
fmt.Print("数组初始化:")
for i := 0; i < len(num); i++ {
temp = rand.Intn(100)
num[i] = *p // 访问指针变量中指向地址的值
fmt.Print(num[i], " ")
}
fmt.Println()
fmt.Print("数组中的最大值:")
temp = num[0]
for i := 1; i < len(num); i++ {
if temp < num[i] {
temp = num[i]
}
}
fmt.Println(*p)
fmt.Print("数组中的最小值:")
temp = num[0]
for i := 1; i < len(num); i++ {
if temp > num[i] {
temp = num[i]
}
}
fmt.Println(*p)
}
/*
运行的一次结果为:
数组初始化:7 97 62 77 20 79 65 19 66 83
数组中的最大值:97
数组中的最小值:7
*/
使用指针操作单链表的样例如下:
package main
import (
"fmt"
"math/rand"
"time"
)
type Node struct {
Value int
Next *Node
}
func AddNote(t *Node, v int) int {
if head == nil {
t = &Node{v, nil}
head = t
return 0
}
if v == t.Value {
fmt.Println("节点已存在:", v)
return -1
}
// 如果当前节点下一个节点为空
if t.Next == nil {
t.Next = &Node{v, nil}
return -2
}
// 如果当前节点下一个节点不为空
return AddNote(t.Next, v)
}
func DelNote(head *Node, v int) *Node {
if FindNote(head, v) == true {
if head.Value == v {
return head.Next
}
pre := head
for head.Next.Value != v {
head = head.Next
}
head.Next = head.Next.Next
return pre
}
return head
}
func FindNote(t *Node, v int) bool {
if t == nil {
return false
} else {
for t != nil {
if v == t.Value {
return true
}
t = t.Next
}
return false
}
return false
}
func UpdateNote(head *Node, src int, dst int) *Node {
if FindNote(head, src) == true {
if head.Value == src {
head.Value = dst
return head
}
temp := head
for temp != nil {
if temp.Value == src {
temp.Value = dst
}
temp = temp.Next
}
}
return head
}
func ShowNote(t *Node) {
if t == nil {
fmt.Println("空链表!")
} else {
for t != nil {
fmt.Print(" ", t.Value)
t = t.Next
}
fmt.Println()
}
}
var head = new(Node)
func main() {
head = nil
var num [10]int
rand.Seed(time.Now().Unix())
for i := 0; i < len(num); i++ {
num[i] = rand.Intn(1000)
AddNote(head, num[i])
}
fmt.Print("链表初始值:")
ShowNote(head)
fmt.Print("删除第五个:")
ShowNote(DelNote(head, num[4]))
fmt.Print("更新第三个:")
ShowNote(UpdateNote(head, num[2], 100))
}
/*
运行的一次结果为:
链表初始值: 120 549 663 704 324 236 415 434 885 747
删除第五个: 120 549 663 704 236 415 434 885 747
更新第三个: 120 549 100 704 236 415 434 885 747
*/
10 数组切片
数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。这些都是数组的特点,但这种数据结构无法完全满足开发者的真实需求。Go语言提供了数组切片(slice)这种数据类型来弥补数组的不足。
数字切片就像一个指向数组的指针,数组切片的数据结构可以抽象为以下3个变量:
- 一个指向原生数组的指针;
- 数组切片中的元素个数;
- 数组切片已分配的存储空间。
10.1 创建数组切片
基于数组创建数组切片的方法样例如下:
package main
import "fmt"
func main() {
//定义数组
var array [10]int = [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
//基于前五个数组元素创建一个切片
var slice []int = array[:5]
//从第五个数组元素创建一个切片 array[5:]
//数组转为切片 array[:]
fmt.Println("Array:")
for _, v := range array {
fmt.Print(v, " ")
}
fmt.Println("")
fmt.Println("Slice:")
for _, v := range slice {
fmt.Print(v, " ")
}
}
/*
运行结果为:
Array:
0 1 2 3 4 5 6 7 8 9
Slice:
0 1 2 3 4
*/
直接创建数组切片的方法样例如下:
package main
import "fmt"
func main() {
//使用make()直接创建数组切片
//创建一个初始元素个数为5的数组切片,元素初始值为0
slice1 := make([]int, 5)
fmt.Println(len(slice1))
//创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间
slice2 := make([]int, 5, 10)
fmt.Println(len(slice2))
//直接创建并初始化包含5个元素的数组切片
slice3 := []int{0, 1, 2, 3, 4}
fmt.Println(len(slice3))
}
/*
运行结果为:
5
5
5
*/
10.2 切片常用的方法
10.2.1 遍历
操作数组元素的所有方法都适用于数组切片,比如数组切片也可以按下标读写元素,用len()函数获取元素的个数,并支持使用range关键字来快速遍历所有元素。
传统的元素遍历如下:
slice := []int{1, 2, 3, 4, 5}
for i := 0; i < len(slice ); i++ {
fmt.Print(slice [i], " ")
}
使用range关键字可以让遍历代码显得更整洁。range表达式有两个返回值,第一个是索引,第二个是元素的值,代码如下:
slice := []int{1, 2, 3, 4, 5}
for i, v := range slice {
fmt.Println("d[", i, "]=", v)
}
10.2.2 增减元素
使用append()操作切片,代码如下:
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
fmt.Println("a =", a, "len=", len(a))
d := make([]int, 0)
//添加切片a的前2个元素,添加切片a下标从3开始之后的元素
//总结一下:冒号在前是个数;冒号在后是下标,保留的是冒号没有数字的一侧
//只有(int):和:(int)的用法。
//添加一个切片要用...展开运算符
d = append(a[:2], a[3:]...)
fmt.Println("d =", d, "len=", len(d))
}
//删除切片中的第三个元素
//打印结果如下:
//a = [1 2 3 4 5] len= 5
//d = [1 2 4 5] len= 4
10.2.3 扩容
package main
import "fmt"
func main() {
a := make([]int, 0)
fmt.Printf("当前切片的长度:%d,容量:%d\n", len(a), cap(a))
for i := 0; i < 10; i++ {
a = append(a, i)
fmt.Printf("当前切片的长度:%d,容量:%d\n", len(a), cap(a))
}
}
//打印结果如下:
//当前切片的长度:0,容量:0
//当前切片的长度:1,容量:1
//当前切片的长度:2,容量:2
//当前切片的长度:3,容量:4
//当前切片的长度:4,容量:4
//当前切片的长度:5,容量:8
//当前切片的长度:6,容量:8
//当前切片的长度:7,容量:8
//当前切片的长度:8,容量:8
//在添加元素之前判断切片是否有剩余,如果没有剩余且容量小于1024,成倍增加容量
//在没有添加元素之前的容量是8,所以扩容量为8
//当前切片的长度:9,容量:16
//当前切片的长度:10,容量:16
- 如果容量小于1024的时候,是成倍数的添加容量
- 如果容量大于1024的时候,是之前的1/4的来添加容量
11 map
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。样例如下:
package main
import "fmt"
type StudentInfo struct {
Name string
ClassRoom string
}
func main() {
var students map[int]StudentInfo
students = make(map[int]StudentInfo)
//添加元素
students[0] = StudentInfo{"小明", "三年级(1)班"}
students[1] = StudentInfo{"小红", "三年级(1)班"}
students[2] = StudentInfo{"小李", "三年级(2)班"}
students[3] = StudentInfo{"小王", "三年级(2)班"}
students[4] = StudentInfo{"小赵", "三年级(3)班"}
//遍历元素
fmt.Println("学生信息如下:")
for _, student := range students {
fmt.Println(student)
}
//查找元素
stua, ok := students[3]
if ok {
fmt.Println("下标是3的学生信息:", stua)
} else {
fmt.Println("查找失败!")
}
//删除元素
delete(students, 1)
fmt.Println("新的学生信息如下:")
for _, student := range students {
fmt.Println(student)
}
//修改元素
stub, ok := students[2]
if ok {
stub.ClassRoom = "三年级(3)班"
} else {
fmt.Println("查找失败!")
}
fmt.Println("修改后的学生信息:", stub)
}