diff --git a/2.2.md b/2.2.md index 30be3efa..94aa64e8 100644 --- a/2.2.md +++ b/2.2.md @@ -1,429 +1,429 @@ -#2.2 GO基础 - -这小节我们将要介绍如何定义变量、常量;GO内置类型以及一些GO设计中的技巧 - -##定义变量 - -GO语言里面定义变量有好几种方式。 - -最基本的定义变量如下,go变量定义,它的类型是跟在变量后面的,而不是像C一样放前面 - - //定义一个名称为“variable_name”、类型为"type"的变量 - var variable_name type - -定义多个变量 - - //定义三个类型都是"type"的三个变量 - var vname1,vname2,vname3 type - -定义变量并且带有初始化的值 - - //初始化“variable_name”的变量为“value”值,类型是"type" - var variable_name type = value - -同时初始化多个变量 - - /* - 定义三个类型都是"type"的三个变量,并且他们分别初始化相应的值 - vname1为v1,vname2为v2,vname3为v3 - */ - var vname1,vname2,vname3 type= v1, v2, v3 - -你是不是觉得上面这样的定义有点复杂,没关系,因为go语言的设计者也发现这样复杂了,我们来让它变得简单一点,我们可以直接忽略类型这个申明,那么上面的什么变成了如下 - - /* - 定义三个变量,他们分别初始化相应的值 - vname1为v1,vname2为v2,vname3为v3 - 然后他们的类型自动根据初始化的值来确定相应的类型,go会帮你做这件事 - */ - var vname1,vname2,vname3= v1, v2, v3 - -你还是觉得上面的复杂?好吧,我也觉得是,让我们继续简化 - - /* - 定义三个变量,他们分别初始化相应的值 - vname1为v1,vname2为v2,vname3为v3 - 编译器会根据初始化的值自动推导出相应的类型 - */ - vname1,vname2,vname3 := v1, v2, v3 - -现在是不是看上去非常的简单了,`:=`这个定义直接替代了`var`和`type`,这样的代码是不是很简洁,但是:=有一个限制,那就是它只能用在函数内部,在函数外部使用则不能编译通过。比如用以定义全局变量。 - -一个特殊的变量名是`_`(下划线)。任何赋给它的值都被丢弃。在这个例子中,将赋值35赋值给b,同时丢弃34。 - - _, b := 34, 35 - -Go的编译器对申明却未使用的变量会在编译阶段报错。下面的代码会产生一个错误:申明了i却未使用 - - package main - - func main() { - var i int - } - -##常量 - -所谓常量,也就是在编译阶段就确定下来的值,程序运行时无法改变该值,GO程序里面,常量定义可以是数字类型、Bool、字符串 - -它的语法如下 - - const constant_name = value - -下面是一些申明的例子 - - const PI = 3.1415927 - const i = 10000 - const Max_Thread = 10 - const prefix = 'astaxie_' - -当然如果需要,可以明确指定常量的类型: - - const PI float32 = 3.1415927 - - -##内置基础类型 - -###Boolean - -对于布尔值,在GO中, 它的类型是:`bool` 可以有的值是:`true`或`false`。默认为`false` - - //示例代码 - var isactive bool - var enabled, disabled = true, false //忽略类型的申明 - func test(){ - var available bool //一般的申明 - valid := false //忽略var和type的什么 - available = true //赋值操作 - } - - -###数字类型 - -对于整数类型,有无符号和有符号两种,GO同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于编译器的实现。当前的gc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位。GO里面也有直接定义好位数的类型`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。目前`rune`是`int`的别称,将来会改为`int32`的别称。`byte`是`uint8`的别称。 - ->注意一点就是这些类型之间的变量不允许相互之间赋值、操作,不然会引起编译器的错误。 -> ->如下的代码会出现错误 -> -> var a int8 -> -> var b int32 -> -> c:=a + b - -浮点类型的值有`float32`和`float64`(没有`float`类型)。 - -这就是全部吗?NO!Go支持复数。它的变量类型是`complex128`(64位实数,64位虚数)。如果需要小一些的,还有`complex64`(32位实数,32位虚数)。复数写为`re + imi`,`re`是实数部分,`im`是虚数部分,而`i`是标记。使用复数的一个例子: - - var c complex64 = 5+5i; - - fmt.Printf("Value is: %v", c) - -将会打印:(5+5i) - -###字符串 - -前面一节里面说过,字符串都是UTF-8类型的,字符串通过一对双引号(")或反引号(`)来定义,它的类型是string - - //示例代码 - var french_hello string //声明变量为字符串的一般方法 - var empty_string string = "" // 申明了一个字符串变量,初始化为空值 - func test(){ - no, yes, maybe := "no", "yes", "maybe" //忽略var和type的申明,同时申明多个变量 - japanese_hello := "Ohaiou" //同上 - french_hello = "Bonjour" //常规赋值 - } - -在Go中字符串是不可变的,例如如下的代码编译时会报错 - - var s string = "hello" - s[0] = 'c' - - -那么如果真的想要修改怎么办呢?如下的代码可以实现 - - s := "hello" - c := []byte(s) - c[0] = 'c' - s2 := string(c) - fmt.Printf("%s\n", s2) - - -GO中可以使用`+`来链接两个字符串 - - s := "hello" - m := "world" - a := s + m - fmt.Printf("%s\n", a) - -修改字符串也可写为 - - s := "hello" - s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 - fmt.Println("%s\n", s) - -如果要申明一个多行的字符串怎么办?可以通过`` ` ``来申明 - - m:=`hello - world` - -`` ` `` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,没有字符转义,换行也将原样输出。 - -###错误类型 -Go内置有一个`error`类型,专门用来处理错误信息,GO的package里面还专门有一个包errors来处理错误 - - err := errors.New("emit macho dwarf: elf header corrupted") - if err != nil { - fmt.Print(err) - } - -###GO数据底层的存储 - -下面这张图来源于 Russ Cox blog中的一篇介绍GO数据结构的文章,大家可以看到这些基础类型底层都是开辟了一块内存,然后存了相应的值 - -![](images/2.2.basic.png?raw=true) - -##一些技巧 - -###分组定义 - -Go语言里面针对多个同时申明变量、常量或者import多个包的时候可以采用分组的方式来申明 - -如下的代码 - - import "fmt" - import "os" - - const i = 100 - const pi = 3.1415 - const prefix = "go_" - - var i int - var pi float32 - var prefix string - -可以通过分组的方式写成如下 - - import( - "fmt" - "os" - ) - - const( - i = 100 - pi = 3.1415 - prefix = "go_" - ) - - var( - i int - pi float32 - prefix string - ) - ->除非被显式设置为其他值或iota, 每个const分组的第一个常量被默认设置为它的0值,第2及后续的常量被默认设置为他前面那个常量的值,如果前面那个常量的值是iota,则它也被设置为iota - -###iota枚举 - -GO里面有一个关键字`iota`,这个关键字用来申明enum的时候采用,它默认开始值是0,每调用一次加1 - - const( - x = iota //x == 0 - y = iota //y == 1 - z = iota //z == 2 - w // 常量申明省略值时,默认和之前一个值的字面相同。这里隐示的说w = iota,因此w==3, 其实上面y和z可以同样不用=iota - ) - - const v = iota // 每遇到一个const关键字,iota被重置,此时 v==0 - -##array、slice、map - -###array -array就是数组,它的定义如下`[n]type`,n表示数组的长度,type表示存储内容的类型 - -对数组的操作和其他语言类似,都是通过`[]`来进行读取和赋值 - - var arr [10]int //申明了一个int类型的数组 - arr[0] = 42 //数组下标是从0开始的 - arr[1] = 13 //赋值操作 - fmt.Printf("The first element is %d\n", arr[0]) //获取数据 - -由于数组的长度也是类型的一部分,比如`[3]int`与`[4]int`是不同的类型,所以数组是不能改变长度的,而且数组之间的赋值是值赋值,当把一个数组作为一个参数传入函数的时候,是这个数组的副本,而不是该数组的指针,如果要使用指针,那么就需要用到下面介绍的`slice`。 - -数组申明可以使用另一种`:=`来申明 - - a := [3]int{1,2,3} //申明一个长度为3的数组 - - b := [10]int{1,2,3} //申明了一个长度为10的数组,其中前面三个元素初始化为1、2、3,其他默认为0 - - c := […]int{4,5,6} //可以省略长度,采用…,go会自动计算长度 - -也许你会说,我想数组里面还是数组,能实现吗?当然咯,GO支持嵌套数组,即多维数组,如下代码申明了一个二维数组 - - //申明了一个二维数组,该数组是一个两个元素的数组,然后每个元素里面是4个int的元素 - double_array := [2][4]int {[4]int{1,2,3,4}, [4]int{5,6,7,8}} - - //如果内部的元素和外部的一样,那么上面的申明可以简化,直接忽略内部的类型 - easy_array :=[2][4]int{{1,2,3,4},{5,6,7,8}} - -数组的分配如下所示: - -![](images/2.2.array.png?raw=true) - - -###slice - -我们很多的应用场景里面,数组不能满足我们的需求,因为我们不知道刚开始申明的时候到底需要多大的数组,也就是我们需要动态数组,GO里面我们叫做`slice` - -`slice`并不是真正意义上面的动态数组,而是一个引用类型,`slice`总是指向底层的一个`array`。`slice`的申明也可以像`array`一样,只要省略size - - //和申明array一样,只是少了长度 - var fslice []int - -接下来我们可以申明一个slice,并初始化数据,如下所示 - - slice := []byte {'a', 'b', 'c', 'd'} - -slice可以从一个数组或者一个已经存在的slice里面再次申明,`slice`通过array[i:j]来获取,i是数组的开始位置,j是结束位置,但不包含array[j],他的长度是j-i - - //申明一个含有10个字符元素的数组 - var array [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - - //申明三个含有byte的slice - var a_slice,b_slice,c_slice []byte - - //a_slice指向数组的第2个元素开始,并到第五个元素结束, - a_slice = array[2:5] - //现在a_slice含有的元素: array[2], array[3] and array[4] - - // b_slice是另一个数组的slice. - b_slice = array[3:5] - // b_slice的元素是: array[3], array[4] - -注意slice和数组申明时的区别:申明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而申明slice时,方括号内没有任何字符 - -他们的数据结构如下所示 - -![](images/2.2.slice.png?raw=true) - -slice有一些简便的操作 - - - slice的默认开始位置是0,ar[:n]等价于ar[0:n] - - slice的第二个序列默认是数组的长度,ar[n:]等价于ar[n:len(ar)] - - slice如果从一个数组里面直接获取,可以这样ar[:],因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0,len(ar)] - -下面这个例子展示更多关于slice的操作 - - //申明一个数组 - var array [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - //申明两个slice - var a_slice,b_slice - - // 演示一些简便操作 - a_slice = array[:3] // 等价于a_slice = array[0:3] a_slice包含元素: a,b,c - a_slice = array[5:] // 等价于a_slice = array[5:9] a_slice包含元素: f,g,h,i,j - a_slice = array[:] // 等价于a_slice = array[0:9] 这样a_slice包含了全部的元素 - - //从slice获取slice - a_slice = array[3:7] // a_slice包含元素: d,e,f,g,len=4,cap=8 - b_slice = a_slice[1:3] // b_slice 包含a_slice[1], a_slice[2] 也就是含有: e,f - b_slice = a_slice[:3] // b_slice 包含 a_slice[0], a_slice[1], a_slice[2] 也就是含有: d,e,f - b_slice = a_slice[0:5] // 对slice的slice可以在cap范围内扩展,此时b_slice包含:c,d,e,f,g - b_slice : a_slice[:] // b_slice包含所有a_slice的元素: d,e,f,g - -slice是引用类型,所以当引用改变其中项目的值的时候,那么其他的所有引用都会改变该值,例如上面的a_slice和b_slice,如果修改了a_slice中项目的值,那么b_slice相对应的值也会改变。 - -从概念上面来说slice想一个结构体,这个结构体包含了三个元素: -- 一个指针,指向数组中slice指定的开始位置 -- 长度,slice的长度 -- 最大长度,也就是slice开始位置到数组的最后位置的长度 - - array := [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - slice := A[4:8] - -上面代码的真正存储结构如下图所示 - -![](images/2.2.slice2.png?raw=true) - -slice下面有几个有用的内置函数 - -- len 获取slice的长度 -- cap 获取slice的最大容量 -- append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice -- copy 函数copy 从源slice src 复制元素到目标dst,并且返回复制的元素的个数 - -注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 -但当slice中没有剩余空间((cap-len) == 0)时,此时将动态分配新的数组空间,返回的slice的数组指针将指向这个空间,而原数组的内容将保持不变,其它引用此数组的slice不受影响。 - -###map - -map也就是python中字典的概念,它的格式`map[keyType]valueType` - -我们看下面的代码,map的读取和设置也类似slice一样,通过key来操作,只是slice只有int的key,而map多了很多类型,可以是int,可以是string - - // 申明一个key是字符串,值为int的字典 - var numbers map[string] int - //另一种map的申明方式 - numbers := make(map[string]int) - numbers["one"] = 1 //赋值 - numbers["ten"] = 10 //赋值 - numbers["three"] = 3 - - fmt.Println("第三个数字是: ", numbers["three"]) //读取数据 - // 打印出来如下: - //第三个数字是: 3 - -这个map就像我们平常看到的表格一样,左边列是key,右边列是值 - -使用map过程中需要注意的几点 -- map是无序的,每次打印出来的map都会不一样,他不能通过index获取,而必须通过key获取 -- map的长度是不固定的,也就是和slice一样,也是一种引用类型 -- 内置的len函数同样试用于map,返回map拥有的key的数量 -- map的值可以很方便的修改,通过`numbers["one"]=11`可以很容易的把key为`one`的字典值改为11 - -map的初始化可以通过key:val的方式初始化值,同时map内置有判断是否存在key的方式 - -删除map的元素通过delete - - //初始化一个字典 - rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } - //map可以有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true - csharp_rating, ok := rating["C#"] - if ok { - fmt.Println("C# is in the map and its rating is ", csharp_rating) - } else { - fmt.Println("We have no rating associated with C# in the map") - } - - delete rating["C"] //删除key为C的元素 - - -上面说过了,map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变 - - m = make(map[string]string) - m["Hello"] = "Bonjour" - m1 = m - m1["Hello"] = "Salut" //现在m["hello"]的值已经是Salut了 - - -###make、new操作 - -`make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。 - -内建函数new 本质上说跟其他语言中的同名函数功能一样:new(T) 分配了零值填充的T 类型的内存空间,并且返回其地址,一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针。 - -内建函数make(T, args)与new(T)有着不同的功能。它只能创建slice,map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针,长度和容量的三项描述符;在这些项目被初始化之前,slice 为nil。对于slice,map 和channel,make 初始化了内部的数据结构,填充适当的值。make返回初始化后的(非零)值。 - -下面这个图详细的解释了new和make之间的区别 - -![](images/2.2.makenew.png?raw=true) - - - -## links - * [目录]() - * 上一章: [你好,GO](<2.1.md>) - * 下一节: [流程和函数](<2.3.md>) - -## LastModified - * $Id$ +#2.2 GO基础 + +这小节我们将要介绍如何定义变量、常量;GO内置类型以及一些GO设计中的技巧 + +##定义变量 + +GO语言里面定义变量有好几种方式。 + +最基本的定义变量如下,go变量定义,它的类型是跟在变量后面的,而不是像C一样放前面 + + //定义一个名称为“variable_name”、类型为"type"的变量 + var variable_name type + +定义多个变量 + + //定义三个类型都是"type"的三个变量 + var vname1,vname2,vname3 type + +定义变量并且带有初始化的值 + + //初始化“variable_name”的变量为“value”值,类型是"type" + var variable_name type = value + +同时初始化多个变量 + + /* + 定义三个类型都是"type"的三个变量,并且他们分别初始化相应的值 + vname1为v1,vname2为v2,vname3为v3 + */ + var vname1,vname2,vname3 type= v1, v2, v3 + +你是不是觉得上面这样的定义有点复杂,没关系,因为go语言的设计者也发现这样复杂了,我们来让它变得简单一点,我们可以直接忽略类型这个申明,那么上面的什么变成了如下 + + /* + 定义三个变量,他们分别初始化相应的值 + vname1为v1,vname2为v2,vname3为v3 + 然后他们的类型自动根据初始化的值来确定相应的类型,go会帮你做这件事 + */ + var vname1,vname2,vname3= v1, v2, v3 + +你还是觉得上面的复杂?好吧,我也觉得是,让我们继续简化 + + /* + 定义三个变量,他们分别初始化相应的值 + vname1为v1,vname2为v2,vname3为v3 + 编译器会根据初始化的值自动推导出相应的类型 + */ + vname1,vname2,vname3 := v1, v2, v3 + +现在是不是看上去非常的简单了,`:=`这个定义直接替代了`var`和`type`,这样的代码是不是很简洁,但是:=有一个限制,那就是它只能用在函数内部,在函数外部使用则不能编译通过。比如用以定义全局变量。 + +一个特殊的变量名是`_`(下划线)。任何赋给它的值都被丢弃。在这个例子中,将赋值35赋值给b,同时丢弃34。 + + _, b := 34, 35 + +Go的编译器对申明却未使用的变量会在编译阶段报错。下面的代码会产生一个错误:申明了i却未使用 + + package main + + func main() { + var i int + } + +##常量 + +所谓常量,也就是在编译阶段就确定下来的值,程序运行时无法改变该值,GO程序里面,常量定义可以是数字类型、Bool、字符串 + +它的语法如下 + + const constant_name = value + +下面是一些申明的例子 + + const PI = 3.1415927 + const i = 10000 + const Max_Thread = 10 + const prefix = 'astaxie_' + +当然如果需要,可以明确指定常量的类型: + + const PI float32 = 3.1415927 + + +##内置基础类型 + +###Boolean + +对于布尔值,在GO中, 它的类型是:`bool` 可以有的值是:`true`或`false`。默认为`false` + + //示例代码 + var isactive bool + var enabled, disabled = true, false //忽略类型的申明 + func test(){ + var available bool //一般的申明 + valid := false //忽略var和type的什么 + available = true //赋值操作 + } + + +###数字类型 + +对于整数类型,有无符号和有符号两种,GO同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于编译器的实现。当前的gc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位。GO里面也有直接定义好位数的类型`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。目前`rune`是`int`的别称,将来会改为`int32`的别称。`byte`是`uint8`的别称。 + +>注意一点就是这些类型之间的变量不允许相互之间赋值、操作,不然会引起编译器的错误。 +> +>如下的代码会出现错误 +> +> var a int8 +> +> var b int32 +> +> c:=a + b + +浮点类型的值有`float32`和`float64`(没有`float`类型)。 + +这就是全部吗?NO!Go支持复数。它的变量类型是`complex128`(64位实数,64位虚数)。如果需要小一些的,还有`complex64`(32位实数,32位虚数)。复数写为`re + imi`,`re`是实数部分,`im`是虚数部分,而`i`是标记。使用复数的一个例子: + + var c complex64 = 5+5i; + + fmt.Printf("Value is: %v", c) + +将会打印:(5+5i) + +###字符串 + +前面一节里面说过,字符串都是UTF-8类型的,字符串通过一对双引号(")或反引号(`)来定义,它的类型是string + + //示例代码 + var french_hello string //声明变量为字符串的一般方法 + var empty_string string = "" // 申明了一个字符串变量,初始化为空值 + func test(){ + no, yes, maybe := "no", "yes", "maybe" //忽略var和type的申明,同时申明多个变量 + japanese_hello := "Ohaiou" //同上 + french_hello = "Bonjour" //常规赋值 + } + +在Go中字符串是不可变的,例如如下的代码编译时会报错 + + var s string = "hello" + s[0] = 'c' + + +那么如果真的想要修改怎么办呢?如下的代码可以实现 + + s := "hello" + c := []byte(s) + c[0] = 'c' + s2 := string(c) + fmt.Printf("%s\n", s2) + + +GO中可以使用`+`来链接两个字符串 + + s := "hello" + m := "world" + a := s + m + fmt.Printf("%s\n", a) + +修改字符串也可写为 + + s := "hello" + s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 + fmt.Println("%s\n", s) + +如果要申明一个多行的字符串怎么办?可以通过`` ` ``来申明 + + m:=`hello + world` + +`` ` `` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,没有字符转义,换行也将原样输出。 + +###错误类型 +Go内置有一个`error`类型,专门用来处理错误信息,GO的package里面还专门有一个包errors来处理错误 + + err := errors.New("emit macho dwarf: elf header corrupted") + if err != nil { + fmt.Print(err) + } + +###GO数据底层的存储 + +下面这张图来源于 Russ Cox blog中的一篇介绍GO数据结构的文章,大家可以看到这些基础类型底层都是开辟了一块内存,然后存了相应的值 + +![](images/2.2.basic.png?raw=true) + +##一些技巧 + +###分组定义 + +Go语言里面针对多个同时申明变量、常量或者import多个包的时候可以采用分组的方式来申明 + +如下的代码 + + import "fmt" + import "os" + + const i = 100 + const pi = 3.1415 + const prefix = "go_" + + var i int + var pi float32 + var prefix string + +可以通过分组的方式写成如下 + + import( + "fmt" + "os" + ) + + const( + i = 100 + pi = 3.1415 + prefix = "go_" + ) + + var( + i int + pi float32 + prefix string + ) + +>除非被显式设置为其他值或iota, 每个const分组的第一个常量被默认设置为它的0值,第2及后续的常量被默认设置为他前面那个常量的值,如果前面那个常量的值是iota,则它也被设置为iota + +###iota枚举 + +GO里面有一个关键字`iota`,这个关键字用来申明enum的时候采用,它默认开始值是0,每调用一次加1 + + const( + x = iota //x == 0 + y = iota //y == 1 + z = iota //z == 2 + w // 常量申明省略值时,默认和之前一个值的字面相同。这里隐示的说w = iota,因此w==3, 其实上面y和z可以同样不用=iota + ) + + const v = iota // 每遇到一个const关键字,iota被重置,此时 v==0 + +##array、slice、map + +###array +array就是数组,它的定义如下`[n]type`,n表示数组的长度,type表示存储内容的类型 + +对数组的操作和其他语言类似,都是通过`[]`来进行读取和赋值 + + var arr [10]int //申明了一个int类型的数组 + arr[0] = 42 //数组下标是从0开始的 + arr[1] = 13 //赋值操作 + fmt.Printf("The first element is %d\n", arr[0]) //获取数据 + +由于数组的长度也是类型的一部分,比如`[3]int`与`[4]int`是不同的类型,所以数组是不能改变长度的,而且数组之间的赋值是值赋值,当把一个数组作为一个参数传入函数的时候,是这个数组的副本,而不是该数组的指针,如果要使用指针,那么就需要用到下面介绍的`slice`。 + +数组申明可以使用另一种`:=`来申明 + + a := [3]int{1,2,3} //申明一个长度为3的数组 + + b := [10]int{1,2,3} //申明了一个长度为10的数组,其中前面三个元素初始化为1、2、3,其他默认为0 + + c := […]int{4,5,6} //可以省略长度,采用…,go会自动计算长度 + +也许你会说,我想数组里面还是数组,能实现吗?当然咯,GO支持嵌套数组,即多维数组,如下代码申明了一个二维数组 + + //申明了一个二维数组,该数组是一个两个元素的数组,然后每个元素里面是4个int的元素 + double_array := [2][4]int {[4]int{1,2,3,4}, [4]int{5,6,7,8}} + + //如果内部的元素和外部的一样,那么上面的申明可以简化,直接忽略内部的类型 + easy_array :=[2][4]int{{1,2,3,4},{5,6,7,8}} + +数组的分配如下所示: + +![](images/2.2.array.png?raw=true) + + +###slice + +在很多的应用场景里面,数组不能满足我们的需求,因为在刚开始的时候,我们不知道到底需要多大的数组合适,所以我们需要动态数组,在go里面这种数据结构叫`slice` + +`slice`并不是真正意义上面的动态数组,而是一个引用类型,`slice`总是指向底层的一个`array`。`slice`的申明也可以像`array`一样,只要省略size + + //和申明array一样,只是少了长度 + var fslice []int + +接下来我们可以申明一个slice,并初始化数据,如下所示 + + slice := []byte {'a', 'b', 'c', 'd'} + +slice可以从一个数组或者一个已经存在的slice里面再次申明,`slice`通过array[i:j]来获取,i是数组的开始位置,j是结束位置,但不包含array[j],他的长度是j-i + + //申明一个含有十个字符元素的数组 + var ar[10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + + //申明两个含有byte的slice + var a,b[]byte + + //a指向数组的第2个元素开始,并到第五个元素结束, + a = ar[2:5] + //现在a含有的元素: ar[2], ar[3] and ar[4] + + // b是数组ar的另一个slice. + b = ar[3:5] + // b的元素是: ar[3], ar[4] + +注意slice和数组申明时的区别:申明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而申明slice时,方括号内没有任何字符 + +他们的数据结构如下所示 + +![](images/2.2.slice.png?raw=true) + +slice有一些简便的操作 + + - slice的默认开始位置是0,ar[:n]等价于ar[0:n] + - slice的第二个序列默认是数组的长度,ar[n:]等价于ar[n:len(ar)] + - slice如果从一个数组里面直接获取,可以这样ar[:],因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0,len(ar)] + +下面这个例子展示更多关于slice的操作 + + //申明一个数组 + var array [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + //申明两个slice + var a_slice,b_slice + + // 演示一些简便操作 + a_slice = array[:3] // 等价于a_slice = array[0:3] a_slice包含元素: a,b,c + a_slice = array[5:] // 等价于a_slice = array[5:9] a_slice包含元素: f,g,h,i,j + a_slice = array[:] // 等价于a_slice = array[0:9] 这样a_slice包含了全部的元素 + + //从slice获取slice + a_slice = array[3:7] // a_slice包含元素: d,e,f,g,len=4,cap=8 + b_slice = a_slice[1:3] // b_slice 包含a_slice[1], a_slice[2] 也就是含有: e,f + b_slice = a_slice[:3] // b_slice 包含 a_slice[0], a_slice[1], a_slice[2] 也就是含有: d,e,f + b_slice = a_slice[0:5] // 对slice的slice可以在cap范围内扩展,此时b_slice包含:c,d,e,f,g + b_slice : a_slice[:] // b_slice包含所有a_slice的元素: d,e,f,g + +slice是引用类型,所以当引用改变其中项目的值的时候,那么其他的所有引用都会改变该值,例如上面的a_slice和b_slice,如果修改了a_slice中项目的值,那么b_slice相对应的值也会改变。 + +从概念上面来说slice像一个结构体,这个结构体包含了三个元素: +- 一个指针,指向数组中slice指定的开始位置 +- 长度,slice的长度 +- 最大长度,也就是slice开始位置到数组的最后位置的长度 + + array := [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + slice := A[4:8] + +上面代码的真正存储结构如下图所示 + +![](images/2.2.slice2.png?raw=true) + +slice下面有几个有用的内置函数 + +- len 获取slice的长度 +- cap 获取slice的最大容量 +- append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice +- copy 函数copy 从源slice src 复制元素到目标dst,并且返回复制的元素的个数 + +注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 +但当slice中没有剩余空间((cap-len) == 0)时,此时将动态分配新的数组空间,返回的slice的数组指针将指向这个空间,而原数组的内容将保持不变,其它引用此数组的slice不受影响。 + +###map + +map也就是python中字典的概念,它的格式`map[keyType]valueType` + +我们看下面的代码,map的读取和设置也类似slice一样,通过key来操作,只是slice只有int的key,而map多了很多类型,可以是int,可以是string及所有完全定义了"=="与"!="操作的类型 + + // 申明一个key是字符串,值为int的字典 + var numbers map[string] int + //另一种map的申明方式 + numbers := make(map[string]int) + numbers["one"] = 1 //赋值 + numbers["ten"] = 10 //赋值 + numbers["three"] = 3 + + fmt.Println("第三个数字是: ", numbers["three"]) //读取数据 + // 打印出来如下: + //第三个数字是: 3 + +这个map就像我们平常看到的表格一样,左边列是key,右边列是值 + +使用map过程中需要注意的几点 +- map是无序的,每次打印出来的map都会不一样,他不能通过index获取,而必须通过key获取 +- map的长度是不固定的,也就是和slice一样,也是一种引用类型 +- 内置的len函数同样试用于map,返回map拥有的key的数量 +- map的值可以很方便的修改,通过`numbers["one"]=11`可以很容易的把key为`one`的字典值改为11 + +map的初始化可以通过key:val的方式初始化值,同时map内置有判断是否存在key的方式 + +删除map的元素通过delete + + //初始化一个字典 + rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } + //map可以有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true + csharp_rating, ok := rating["C#"] + if ok { + fmt.Println("C# is in the map and its rating is ", csharp_rating) + } else { + fmt.Println("We have no rating associated with C# in the map") + } + + delete( rating, "C") //删除key为C的元素 + + +上面说过了,map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变 + + m = make(map[string]string) + m["Hello"] = "Bonjour" + m1 = m + m1["Hello"] = "Salut" //现在m["hello"]的值已经是Salut了 + + +###make、new操作 + +`make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。 + +内建函数new 本质上说跟其他语言中的同名函数功能一样:new(T) 分配了零值填充的T 类型的内存空间,并且返回其地址,一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针。 + +内建函数make(T, args)与new(T)有着不同的功能。它只能创建slice,map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针,长度和容量的三项描述符;在这些项目被初始化之前,slice 为nil。对于slice,map 和channel,make 初始化了内部的数据结构,填充适当的值。make返回初始化后的(非零)值。 + +下面这个图详细的解释了new和make之间的区别 + +![](images/2.2.makenew.png?raw=true) + + + +## links + * [目录]() + * 上一章: [你好,GO](<2.1.md>) + * 下一节: [流程和函数](<2.3.md>) + +## LastModified + * $Id$ diff --git a/2.3.md b/2.3.md index 3e2e8591..c9c435cc 100644 --- a/2.3.md +++ b/2.3.md @@ -1,191 +1,191 @@ -#2.3 流程和函数 -这小节我们要介绍go里面的流程控制以及函数操作 -##流程控制 -流程控制是最伟大的发明了,因为有了他,你可以通过很简单的描述来表达很复杂的事情 -###if -if语法也许是所有语言里面最常见的一种语法了,他的语法概括起来就是:`如果满足条件就做某事,否则做另一件事` - -Go里面if条件语法中不需要括号,如下代码所示 - - if x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } - -Go的if还有一个强大的地方就是条件里面允许申明一个变量,这个变量的作用域只能在该条件中,出了这个条件就不起作用了,如下所示 - - // 计算获取值x,然后根据x返回的大小,判断是否大于10. - if x := computed_value(); x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } - - //这个地方如果这样调用就编译出错了,因为x是条件里面的变量 - fmt.Println(x) - -多个条件的时候如下所示 - - if integer == 3 { - fmt.Println("The integer is equal to 3") - } else if integer < 3 { - fmt.Println("The integer is less than 3") - } else { - fmt.Println("The integer is greater than 3") - } - -###goto - -Go有goto语句——明智的使用它。用goto跳转到一定是当前函数内定义的标签。例如假设这样一个循环: - - func myfunc() { - i := 0 - Here: //这行的第一个词,以分号结束作为标签 - println(i) - i++ - goto Here //跳转到Here去 - } - -标签名是大小写敏感的。 - -###for -go里面最强大的一个控制逻辑就是for,他即可以用来循环读取数据,又可以当作while来控制逻辑,还能迭代操作。它的语法如下 - - for expression1; expression2; expression3{ - ... - } - -expression1、expression2、expression3都是表达式,其中expression1和expression3是变量申明或者函数调用返回值之类的,expression2是条件判断,expression1在循环开始之前调用,expression3在循环结束之后调用 - -一个例子比上面讲那么多更有用,那么我们看看下面的例子吧 - - package main - import "fmt" - - func main(){ - sum := 0; - for index:=0; index < 10 ; index++ { - sum += index - } - fmt.Println("sum is equal to ", sum) - } - //输出:sum is equal to 45 - -有些时候有多个需要操作的赋值操作,由于Go里面没有`,`操作,那么可以使用平行赋值`i, j = i+1, j-1` - - -有些时候如果我们忽略expression1和expression3 - - sum := 1 - for ; sum < 1000; { - sum += sum - } - -其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识,对,这就是while的功能 - - sum := 1 - for sum < 1000 { - sum += sum - } - -在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳出本次循环,详细参考如下例子 - - for index := 10; index>0; index-- { - if index == 5{ - break或者continue - } - fmt.Println(index) - } - //break打印出来10、9、8、7、6 - //continue打印出来10、9、8、7、6、4、3、2、1 - -break 和 continue 还可以跟着标号,用来跳到多重循环中的外层循环 - -for可以用于读取slice和map的数据,配合range - - for k,v:=range map { - fmt.Println("map's key:",k) - fmt.Println("map's val:",v) - } - -其中还可以使用`_`来扔掉不需要的返回值 - - -###switch -有些时候你需要写很多的`if/else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候switch就能很好的解决这个问题,他的语法如下 - - switch sExpr { - case expr1: - some instructions - case expr2: - some other instructions - case expr3: - some other instructions - default: - other code - } - -sExpr和expr1、expr2、expr3的类型必须一致。Go的switch非常灵活。表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项,而如果switch没有表达式,它会匹配true。 - - i := 10 - switch i { - case 1: - fmt.Println("i is equal to 1") - case 2, 3, 4: - fmt.Println("i is equal to 2, 3 or 4") - case 10: - fmt.Println("i is equal to 10") - default: - fmt.Println("All I know is that i is an integer") - } - -我们看第六行,我们把很多值聚合在了一个case里面,同时,Go里面switch默认相当于每个case后面带有break,匹配成功后不会自动向下尝试,而是跳出整个switch了,但是可以使用fallthrough使其这样做。 - - integer := 6 - switch integer { - case 4: - fmt.Println("The integer was <= 4") - fallthrough - case 5: - fmt.Println("The integer was <= 5") - fallthrough - case 6: - fmt.Println("The integer was <= 6") - fallthrough - case 7: - fmt.Println("The integer was <= 7") - fallthrough - case 8: - fmt.Println("The integer was <= 8") - fallthrough - default: - fmt.Println("default case") - } - -上面的程序讲输出 - - The integer was <= 6 - The integer was <= 7 - The integer was <= 8 - default case - - -##函数 -函数是Go里面的核心设计,Go的函数申明通过关键字`func`来申明,他的格式如下 - - func funcname(q int) (r,s int) { return 0,0 } - -###多个返回值 -###变参 -###defer -###递归函数 -###内置函数 - -## links - * [目录]() - * 上一章: [GO基础](<2.2.md>) - * 下一节: [高级类型](<2.4.md>) - -## LastModified - * $Id$ +#2.3 流程和函数 +这小节我们要介绍go里面的流程控制以及函数操作 +##流程控制 +流程控制是最伟大的发明了,因为有了他,你可以通过很简单的描述来表达很复杂的事情 +###if +if语法也许是所有语言里面最常见的一种语法了,他的语法概括起来就是:`如果满足条件就做某事,否则做另一件事` + +Go里面if条件语法中不需要括号,如下代码所示 + + if x > 10 { + fmt.Println("x is greater than 10") + } else { + fmt.Println("x is less than 10") + } + +Go的if还有一个强大的地方就是条件里面允许申明一个变量,这个变量的作用域只能在该条件中,出了这个条件就不起作用了,如下所示 + + // 计算获取值x,然后根据x返回的大小,判断是否大于10. + if x := computed_value(); x > 10 { + fmt.Println("x is greater than 10") + } else { + fmt.Println("x is less than 10") + } + + //这个地方如果这样调用就编译出错了,因为x是条件里面的变量 + fmt.Println(x) + +多个条件的时候如下所示 + + if integer == 3 { + fmt.Println("The integer is equal to 3") + } else if integer < 3 { + fmt.Println("The integer is less than 3") + } else { + fmt.Println("The integer is greater than 3") + } + +###goto + +Go有goto语句——请明智的使用它。用goto跳转到一定是当前函数内定义的标签。例如假设这样一个循环: + + func myfunc() { + i := 0 + Here: //这行的第一个词,以分号结束作为标签 + println(i) + i++ + goto Here //跳转到Here去 + } + +标签名是大小写敏感的。 + +###for +go里面最强大的一个控制逻辑就是for,他即可以用来循环读取数据,又可以当作while来控制逻辑,还能迭代操作。它的语法如下 + + for expression1; expression2; expression3{ + ... + } + +expression1、expression2、expression3都是表达式,其中expression1和expression3是变量申明或者函数调用返回值之类的,expression2是条件判断,expression1在循环开始之前调用,expression3在循环结束之后调用 + +一个例子比上面讲那么多更有用,那么我们看看下面的例子吧 + + package main + import "fmt" + + func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index + } + fmt.Println("sum is equal to ", sum) + } + //输出:sum is equal to 45 + +有些时候有多个需要操作的赋值操作,由于Go里面没有`,`操作,那么可以使用平行赋值`i, j = i+1, j-1` + + +有些时候如果我们忽略expression1和expression3 + + sum := 1 + for ; sum < 1000; { + sum += sum + } + +其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识,对,这就是while的功能 + + sum := 1 + for sum < 1000 { + sum += sum + } + +在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳出本次循环,当嵌套过深的时候,break可以配合标签使用,即跳出标签所指定的循环,详细参考如下例子 + + for index := 10; index>0; index-- { + if index == 5{ + break或者continue + } + fmt.Println(index) + } + //break打印出来10、9、8、7、6 + //continue打印出来10、9、8、7、6、4、3、2、1 + +break 和 continue 还可以跟着标号,用来跳到多重循环中的外层循环 + +for可以用于读取slice和map的数据,配合range + + for k,v:=range map { + fmt.Println("map's key:",k) + fmt.Println("map's val:",v) + } + +其中还可以使用`_`来扔掉不需要的返回值 + + +###switch +有些时候你需要写很多的`if/else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候switch就能很好的解决这个问题,他的语法如下 + + switch sExpr { + case expr1: + some instructions + case expr2: + some other instructions + case expr3: + some other instructions + default: + other code + } + +sExpr和expr1、expr2、expr3的类型必须一致。Go的switch非常灵活。表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项,而如果switch没有表达式,它会匹配true。 + + i := 10 + switch i { + case 1: + fmt.Println("i is equal to 1") + case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") + case 10: + fmt.Println("i is equal to 10") + default: + fmt.Println("All I know is that i is an integer") + } + +我们看第六行,我们把很多值聚合在了一个case里面,同时,Go里面switch默认相当于每个case后面带有break,匹配成功后不会自动向下尝试,而是跳出整个switch了,但是可以使用fallthrough使其这样做。 + + integer := 6 + switch integer { + case 4: + fmt.Println("The integer was <= 4") + fallthrough + case 5: + fmt.Println("The integer was <= 5") + fallthrough + case 6: + fmt.Println("The integer was <= 6") + fallthrough + case 7: + fmt.Println("The integer was <= 7") + fallthrough + case 8: + fmt.Println("The integer was <= 8") + fallthrough + default: + fmt.Println("default case") + } + +上面的程序讲输出 + + The integer was <= 6 + The integer was <= 7 + The integer was <= 8 + default case + + +##函数 +函数是Go里面的核心设计,Go的函数申明通过关键字`func`来申明,他的格式如下 + + func funcname(q int) (r,s int) { return 0,0 } + +###多个返回值 +###变参 +###defer +###递归函数 +###内置函数 + +## links + * [目录]() + * 上一章: [GO基础](<2.2.md>) + * 下一节: [高级类型](<2.4.md>) + +## LastModified + * $Id$