目录
之前是学习了C/C++,现在开始学go,感觉go和C语言有很多相似的地方。

相比于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
先来试一下正常定义变量
- //用%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)

再来试一下简短声明

一维数组,其实就和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}}
- //数组的遍历
- //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)
- }
- }

- 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]
- }
数组不指定大小也不推导大小,则它会是切片类型,切片实际上是一个结构体类型,通过一个指针指向底层的数组,然后通过len和cap两个变量记录数组中数据的长度和数组的大小,有点类似于C++中的vector。
切片(slice)是对底层数组一个连续片段的引用,所以切片是一个引用类型。

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)
-
- }
注意:append会返回新的切片,也就是说并不会改变原来的切片,所以一般需要将返回的切片赋值给原来的切片。

- var a []int
- a = append(a, 1) // 追加1个元素
- a = append(a, 1, 2, 3) // 追加多个元素
- a = append(a, []int{1,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])
- }
运行一下,观察结果:

这里的map就相当于C++中的map,底层都通过哈希表实现。






管道是无法扩容的。



channel支持for-range的方式进行遍历,请注意几个细节:
1、在遍历的时候,如果channel没有关闭,则会出现deadlock的错误。

2、在遍历的时候,如果channel已经关闭,则会正常遍历数据,遍历完后会退出遍历。

3、遍历管道相当于从管道之中读取数据,也就是说,如果遍历完成,管道将会为空。
4、管道关闭以后不能够再打开,如果想接着使用管道,可以再创建一个。
5、当管道长度满了以后,如果没有人取走数据,则无法继续往管道中写,会报死锁错误(因为需要阻塞住,等管道中的数据被读走才能继续写)
6、当管道空了以后,如果不关闭管道,继续读会报死锁错误(因为管道空了以后,继续读会被阻塞住)。如果关闭管道,为空时继续读则会读取默认值(比如int类型的管道,读取0)。
7、管道关闭以后,可以继续从管道中读取数据,但是不能写入数据。


关于结构体类名以及成员变量,第一个字母是否大写,关乎到能否跨包访问,如果结构体类名首字母大写,则可以在其他包内使用该结构体,成员变量首字母大写,则可以在其他包内通过该结构体访问到该成员变量。

一般函数的定义方式为:
- func 函数名(变量名 变量类型)返回值类型{
- //函数体
- }
而成员方法,则只需要在func和函数名中间加上结构体的名字和类型即可
- func (对象名 结构体)函数名(变量名 变量类型)返回值类型{
- //函数体
- }
代码展示:




go语言中没有构造函数和析构函数,因为gc能够自动帮我们回收不需要的内存空间,但为了和其他语言相符合,我们可以模拟实现一个构造函数。
构造函数的名字可以随便起:


这个和C语言相同,就是传值和传指针的区别。
