目录
一、基础数据类型
1.1 变量的定义方式
1.2 用%T输出变量的类型
二、复合数据类型
2.1 数组
2.1.2、数组的遍历
2.1.3 数组传参
2.2. 切片slice
2.2.1. 初始化切片
2.2.2. append向切片中追加元素
2.2.3. 切片的截取
2.3. map
2.3.1. map初始化
2.3.2. 添加和删除
2.3.3. 遍历
2.4. 管道channel
2.4.1. 初始化
2.4.2. 放入和取出元素
2.4.2. 管道的遍历
2.5 结构体
2.5.1. 成员函数(方法)
2.5.2. 匿名结构体
2.5.3. 结构体中含有匿名成员
2.5.4. 结构体指针
2.5.5. 构造函数
2.5.6. 方法接收指针
之前是学习了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语言相同,就是传值和传指针的区别。