目录
之前是学习了C/C++,现在开始学go,感觉go和C语言有很多相似的地方。
一、基础数据类型
1.1 变量的定义方式
相比于C++,go变量的定义就显得很灵活多变了
// 定义一个名称为 “variableName” ,类型为 "type" 的变量
var variableName type
// 定义并初始化初始化 “variableName” 的变量为 “value” 值,类型是 “type”
var variableName type = value
// 定义三个类型都是 “type” 的三个变量
var vname1, vname2, vname3 type
/*
定义并初始化三个类型都是 "type" 的三个变量 , 并且它们分别初始化相应的值
vname1 为 v1 , vname2 为 v2 , vname3 为 v3
*/
var vname1, vname2, vname3 type= v1, v2, v3
批量声明变量:
var (
a int
b string
c float32
d float64
...
)
对于变量的类型,我们也是可以直接忽略的: 让系统去给我们自动进行推导
var vname1, vname2, vname3 = v1, v2, v3
vname1, vname2, vname3 := v1, v2, v3
:= 这个符号直接取代了 var 和 type , 这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用 var 方式来定义全局变量。
_
(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。 在这个例子中,我们将值 32赋予 b ,并同时丢弃 31
_, b := 31, 32
1.2 用%T输出变量的类型
先来试一下正常定义变量
//用%T来输出变量的类型
var a int
var b byte
var f float32
var t bool
fmt.Printf("%T\n", a)
fmt.Printf("%T\n", b)
fmt.Printf("%T\n", f)
fmt.Printf("%T\n", t)
再来试一下简短声明
二、复合数据类型
2.1 数组
一维数组,其实就和C语言没多少区别的
func main() {
var arr1 [5]int = [5]int{}
var arr2 = [5]int{}
var arr3 = [5]int{3, 2} //给前两个元素赋值,没赋值的默认为0
var arr4 = [5]int{2: 15, 4: 6} //给指定位置的元素赋值
var arr5 = [...]int{6, 5, 4, 3} //根据{}里面元素的个数推导出
var arr6 = [...]struct {
name string
age int
}{{"tome", 18}, {"same", 20}}
}
二维数组:
//3行4列,只给前两行赋值,且前两行的所有列还没有赋满
var arr = [3][4]int{{1, 2}, {3, 4}}
//第一维可以用...推导,第二维不能
var arr2 = [...][3]int{{1},{2,3}}
2.1.2、数组的遍历
//数组的遍历
//1、比较简便的写法
for i, ele := range arr {
fmt.Println("index=%d, ele = %d", i, ele)
}
//2、像C语言一样遍历
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
//3、遍历二维数组
for row, array := range arr2 {//先取出一行
for col, ele := range array {//再遍历这一行
fmt.Println("arr[%d][%d]=%d", row, col, ele)
}
}
2.1.3 数组传参
package main
import "fmt"
//调用f1函数只会拷贝数组
func f1(arr [5]int) {
arr[0] += 1
}
//f2传入数组的指针,可以修改外面的数组
func f2(arr *[5]int) {
//由于go语言会省略掉指针解引用的操作,所以
//这样写也可以 arr[0] += 1
(*arr)[0] += 1
//go语言的for循环没有C++那种引用类型
//for循环中,i是arr的下标,n是arr[i]的拷贝,所以修改n不会修改arr[i]
//如果想修改数组中的内容,只能使用arr[i]的方式
for i, n := range arr {
arr[i] = n + 1
}
}
func main() {
var arr1 [5]int = [5]int{}
f1(arr1)
fmt.Println(arr1)//[0 0 0 0 0]
f2(&arr1)
fmt.Println(arr1)//[2 1 1 1 1]
}
2.2. 切片slice
数组不指定大小也不推导大小,则它会是切片类型,切片实际上是一个结构体类型,通过一个指针指向底层的数组,然后通过len和cap两个变量记录数组中数据的长度和数组的大小,有点类似于C++中的vector。
切片(slice)是对底层数组一个连续片段的引用,所以切片是一个引用类型。
2.2.1. 初始化切片
make与new类似,但make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零。
注意,初始化切片的时候不能够在[]
中赋值,否则就变成数组了。
// 定义切片
func main() {
var ss1 []int //声明一个切片,但并没有初始化
fmt.Println(ss1)
var s1 []int
s1 = []int{1, 2, 3, 4}
fmt.Println(s1)
s1 = make([]int, 3) //有点像C++的new,申请内存
//输出切片的内容
fmt.Println(s1)
fmt.Println("len = %d, cap =%d", len(s1), cap(s1))
//切片的判空
//声明但未使用的切片的默认值是 nil
//这里ss1只声明,未经过使用,s1已经使用了,被分配了内存,所以不是nil
fmt.Println(ss1 == nil)
fmt.Println(s1 == nil)
}
2.2.2. append向切片中追加元素
注意:append会返回新的切片,也就是说并不会改变原来的切片,所以一般需要将返回的切片赋值给原来的切片。
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素
a = append(a, []int{1,2,3}...) // 追加一个切片
2.2.3. 切片的截取
func sub_slice() {
arr := make([]int, 3, 5)
crr := arr[0:2] //前闭后开
crr[1] = 8
fmt.Println(arr[1]) //观察arr【1】会不会影响
crr = append(crr, 9)
fmt.Println(arr[2])
}
运行一下,观察结果:
2.3. map
这里的map就相当于C++中的map,底层都通过哈希表实现。
2.3.1. map初始化
2.3.2. 添加和删除
2.3.3. 遍历
2.4. 管道channel
2.4.1. 初始化
管道是无法扩容的。
2.4.2. 放入和取出元素
2.4.2. 管道的遍历
channel支持for-range的方式进行遍历,请注意几个细节:
1、在遍历的时候,如果channel没有关闭,则会出现deadlock的错误。
2、在遍历的时候,如果channel已经关闭,则会正常遍历数据,遍历完后会退出遍历。
3、遍历管道相当于从管道之中读取数据,也就是说,如果遍历完成,管道将会为空。
4、管道关闭以后不能够再打开,如果想接着使用管道,可以再创建一个。
5、当管道长度满了以后,如果没有人取走数据,则无法继续往管道中写,会报死锁错误(因为需要阻塞住,等管道中的数据被读走才能继续写)
6、当管道空了以后,如果不关闭管道,继续读会报死锁错误(因为管道空了以后,继续读会被阻塞住)。如果关闭管道,为空时继续读则会读取默认值(比如int类型的管道,读取0)。
7、管道关闭以后,可以继续从管道中读取数据,但是不能写入数据。
2.5 结构体
关于结构体类名以及成员变量,第一个字母是否大写,关乎到能否跨包访问,如果结构体类名首字母大写,则可以在其他包内使用该结构体,成员变量首字母大写,则可以在其他包内通过该结构体访问到该成员变量。
2.5.1. 成员函数(方法)
一般函数的定义方式为:
func 函数名(变量名 变量类型)返回值类型{
//函数体
}
而成员方法,则只需要在func和函数名中间加上结构体的名字和类型即可
func (对象名 结构体)函数名(变量名 变量类型)返回值类型{
//函数体
}
代码展示:
2.5.2. 匿名结构体
2.5.3. 结构体中含有匿名成员
2.5.4. 结构体指针
2.5.5. 构造函数
go语言中没有构造函数和析构函数,因为gc能够自动帮我们回收不需要的内存空间,但为了和其他语言相符合,我们可以模拟实现一个构造函数。
构造函数的名字可以随便起:
2.5.6. 方法接收指针
这个和C语言相同,就是传值和传指针的区别。