变量
var num int;var num int = 1,var a,b int;var a,b int = 1,2;var a = 1;_,b=1,2;
1,:=这个符号直接取代了var和type,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。
2,_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。
3,Go对于已声明但未使用的变量会在编译阶段报错
常量
const Pi=3.1415926;const i =10000;const MaxThread=10;const prefix ="astaxie_"
所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型
数值
整数类型有无符号和带符号两种。Go同时支持int和uint,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go里面也有直接定义好位数的类型:rune, int8, int16, int32, int64和byte, uint8, uint16, uint32, uint64。其中rune是int32的别称,byte是uint8的别称。
注:这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。浮点数的类型有float32和float64两种(没有float类型),默认是float64。
字符串
Go中的字符串都是采用UTF-8字符集编码。字符串是用一对双引号("")或反引号(` `)括起来定义,它的类型是string。
注:在Go中字符串是不可变的,若一定要修改,可以将字符串 s 转换为 []byte 类型,再转换回 string 类型,Go中可以使用+操作符来连接两个字符串,字符串虽不能更改,但可进行切片操作,如果要声明一个多行的字符串怎么办?可以通过`来声明。
array、slice、map
array就是数组,var arr [n]type,长度也是数组类型的一部分,因此[3]int与[4]int是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的slice类型了。
“动态数组”,在Go里面这种数据结构叫slice,slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。slice可以从一个数组或一个已经存在的slice中再次声明。slice通过array[i:j]来获取,其中i是数组的开始位置,j是结束位置,但不包含array[j],它的长度是j-i,类似于python的切片或者c++的vector。
从概念上面来说slice像一个结构体,这个结构体包含了三个元素:
一个指针,指向数组中slice指定的开始位置。
长度,即slice的长度。
最大长度,也就是slice开始位置到数组的最后位置的长度。
对于slice有几个有用的内置函数:
len 获取slice的长度
cap 获取slice的最大容量
append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数
注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。
注意:slice和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用...自动计算长度,而声明slice时,方括号内没有任何字符。slice是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值。
map类似python中的字典。声明举例:var numbersmap [string]int,numbers =make(map[string]int)。
使用map过程中需要注意的几点:
map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
map的长度是不固定的,也就是和slice一样,也是一种引用类型
内置的len函数同样适用于map,返回map拥有的key的数量
map的值可以很方便的修改,通过numbers["one"]=11可以很容易的把key为one的字典值改为11
map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
map的初始化可以通过key:val的方式初始化值,同时map内置有判断是否存在key的方式
通过delete删除map的元素:
map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变
make、new操作
make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。
注:new返回指针。make返回初始化后的(非零)值。make只能创建slice、map和channel。
流程控制
Go里面if条件判断语句中不需要括号,Go的if还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,比如if x=1;x>0{执行体}。
goto类似c语言中的goto,
for,在循环里面有两个关键操作break和continue ,break操作是跳出当前循环,continue是跳过本次循环。当嵌套过深的时候,break可以配合标签使用,即跳转至标签所指定的位置,break和continue还可以跟着标号,用来跳到多重循环中的外层循环。
switch,类似与c的switch,Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch,但是可以使用fallthrough强制执行后面的case代码。
函数
注:函数可以返回多个值。Go函数支持变参,如 func myfunc(arg...int) {}。
传值,当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
传指针使得多个函数能操作同一个对象。传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
defer,Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。
函数作为值、类型。在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。函数当做值和类型在我们写一些通用接口的时候非常有用。
Panic和Recover
Panic是一个内建函数,可以中断原有的控制流程,进入一个panic状态中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数defer会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。panic可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
Recover是一个内建的函数,可以让进入panic状态的goroutine恢复过来。recover仅在延迟函数defer中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic状态,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
main函数和init函数
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
import这个命令用来导入包文件,类似于include,
import . "fmt" 点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名
import f “fmt” 别名 fmt.Printf()->f.Printf()
import _ “fmt” _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
struct类型
和C语言类似
定义:
type person struct {
name strin
gage int
}
1.按照顺序提供初始化值
P := person{"Tom", 25}
2.通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
3.当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)
匿名字段:Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型(int string等)和自定义类型都是可以作为匿名字段的。
注:结构中匿名字段和结构本身字段重名,Go里面很简单的解决了这个问题,最外层的优先访问
面向对象
通俗的说就是给struct绑定了函数,函数接收者可以时值copy,也可以是指针,
method的语法如下:
func (r ReceiverType) funcName(parameters) (results)
在使用method的时候重要注意几点
虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
method里面可以访问接收者的字段
调用method通过.访问,就像struct里面访问字段一样
method 重写,继承。
举例
package main
import "fmt"
type Human struct {
name string
age int
phone string // Human类型拥有的字段
}
type Student struct {
Human
school string
phone string
}
type Employee struct {
Human // 匿名字段Human
speciality string
phone string // 雇员的phone字段
}
func (h Human) say() {
fmt.Println(h.name, h.age, h.phone)
}+++
func (h Employee) say() {
fmt.Println(h.name, h.age, h.phone, h.speciality, h.Human.phone)
}
func main() {
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
Tom := Student{Human{"Tom", 18, "123-456-189"}, "yichuan", "666"}
Tom.say()
Bob.say()
fmt.Println(Tom.phone, Tom.Human.phone)
注:以上笔记基本来自
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md
作为业余爱好仅仅记录了一些注意点
有疑问加站长微信联系(非本文作者)