一、命名规则
- 必须以 字母 或 下划线 开头,区分大小写
- 大写字母开头的命名,可以被外部包的代码所访问(需先导入这个包),命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的。
- 关键字不可以作自定义命名,Go语言现有25个关键字,37个保留字,均不可作为自定义名
- 推荐使用 驼峰命名法
二、数据类型
Go语言中数据类型分为两类:基本数据类型 和 派生数据类型。Go语言不支持自动类型转换,在不同类型的变量之间赋值时需要显式转换。
| 基本数据类型 | 布尔型、数值型(整型&浮点型)、字符串型 |
|---|---|
| 派生数据类型 | 指针、数组、切片、映射、结构体、函数、接口、管道 |
| 值类型 | 基本数据类型、数组array、结构体struct |
|---|---|
| 初始默认值 | 整型 0、浮点型 0、字符串 “”、布尔类型 false |
| 引用类型 | 指针pointer、切片slice、映射map、函数func、接口interface、管道chan |
| 初始默认值 | nil |
三、变量与常量
1、变量的声明与初始化
Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值。
整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。
// 声明var name1 string // 标准声明 name1 = ""var test1, test2 int // 一次性声明多个同类型变量 test1 = 0, test2 = 0var ( // 批量声明age1 int // age1 = 0isOk1 bool // isOk1 = false)// 声明+初始化var name2 string = "xxy" // 标准初始化 name2 = "xxy"var age2 = 18 // 类型推导 完成 初始化 age2 = 18isOk2 := true // 短变量声明 并 初始化 isOk2 = truev1, v2, v3 := 1, 2, 3 // 多重声明 并 初始化 v1 = 1, v2 = 2, v3 = 3// _表示匿名变量,匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明s1, _, s2 := 1, 2, 3 // 匿名变量 _ s1 = 1, s2 = 3
2、常量的定义与iota
// 常量在定义的时候必须赋值const pi float64 = 3.1415 // 标准定义 pi = 3.1415 浮点型const e = 2.7182 // 类型推导 e = 2.7182 浮点型const y1, y2, y3 = 1, 3.2, true // 多重定义 y1 整型, y2 浮点型, y3 布尔型const ( // 批量定义——常量组 如果省略了值则表示和上面一行的值相同n1 = 100 // n1 = 100n2 // n2 = 100n3 // n3 = 100n4 = 3.7 // n4 = 3.7)// iota 是 Go语言的常量计数器(枚举),只能在常量的表达式中使用// iota 在 const关键字出现时将被重置为 0。// const中每新增一行常量声明将使iota计数一次(+1)。const (a0 = iota // a0 = 0a1 // a1 = 1_ // _ = 2 (匿名,丢弃)a3 // a3 = 3a4 = iota // a4 = 4a5 = 100 // a5 = 100 (插队)b1, b2 = iota, iota // b1 = 6, b2 = 6c1, c2 = iota + 1, iota + 2 // c1 = 8, c2 = 9d1, d2 // d1 = 9, d2 = 10a9 = iota // a9 = 9)const test = iota // 0// iota 使用场景const ( // 利用 iota 定义数量级_ = iotaKB = 1 << (10 * iota) // 1024MB = 1 << (10 * iota)GB = 1 << (10 * iota)TB = 1 << (10 * iota)PB = 1 << (10 * iota))
注意事项:
- 函数外的每个语句都必须以关键字开始(var、const、func等)
- := 不能使用在函数外,且只在变量声明时使用
- _ 多用于占位,表示忽略值
- 变量声明后必须使用,常量定义后可不使用
四、流程控制
1、if else (分支结构)
// 1、if条件判断基本写法:执行语句1if 条件表达式1 {分支1} else if 条件表达式2 {分支2} else {分支3}// 2、if条件判断特殊写法:if 执行语句1; 条件表达式1 {分支1} else if 条件表达式2 {分支2} else {分支3}// 区别在于 执行语句1 的生命周期不同
2、for (循环结构)
// 1、for循环基本写法for 初始语句; 条件表达式; 结束语句 {循环体语句}// 2、for循环初始语句省略,但初始语句后的分号必须要写初始语句for ; 条件表达式; 结束语句 {循环体语句}// 3、for循环的初始语句和结束语句都可以省略初始语句for 条件表达式 {循环体语句结束语句}// 4、无限循环\死循环for {循环体语句}// 5、for range(键值循环)写法:for index, value := range varName {循环体语句}
for range 可以遍历数组、切片、字符串、map及通道(channel)。通过for range遍历的返回值有以下规律:
- 数组、切片、字符串返回索引和值。
- map返回键和值。
- 通道(channel)只返回通道内的值。
3、switch case
4、循环控制
- goto —- 跳转到指定标签
- break —- 跳出循环,可以结束for、switch、select的代码块
- continue —- 跳过当前循环,继续下次循环,仅限于for循环内使用
五、数据结构
1、指针 pointer
区别于C/C++中的指针,Go语言中的指针只能进行 &(取地址) 和 (取值)。不能进行偏移和运算,是安全指针。Go语言中的值类型 int、float、bool、string、array、struct 都有对应的指针类型,如:`int、int64、string` 等。
- 取变量指针: ```go // varName 表示被取地址的变量,类型为T // ptr 用于接收地址的变量,ptr的类型就为T,称做T的指针类型。代表指针 ptr := &varName // varName的类型为T
func main() { a := 10 b := &a fmt.Printf(“a:%d ptr:%p\n”, a, &a) // a:10 ptr:0xc00001a078 fmt.Printf(“b:%p type:%T\n”, b, b) // b:0xc00001a078 type:*int fmt.Println(&b) // 0xc00000e018 }
2. 指针取值:在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用 *操作,也就是指针取值。```gofunc main() {//指针取值a := 10b := &a // 取变量a的地址,将指针保存到b中fmt.Printf("type of b:%T\n", b)c := *b // 指针取值(根据指针去内存取值)fmt.Printf("type of c:%T\n", c)fmt.Printf("value of c:%v\n", c)}/*type of b:*inttype of c:intvalue of c:10*/
总结:取地址操作符&和取值操作符是一对互补操作符,&取出地址,根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
- 指针传值: ```go func modify1(x int) { x = 100 } func modify2(x int) { x = 100 }
func main() { a := 10 modify1(a) fmt.Println(a) // 10 modify2(&a) fmt.Println(a) // 100 }
4. new和make在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存。- new 是一个内置函数,函数签名如下:```gofunc new(Type) *Type// Type表示类型,new函数只接受一个参数,这个参数是一个类型// *Type表示类型指针,new函数返回一个指向该类型内存地址的指针
new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。
func main() {a := new(int)b := new(bool)fmt.Printf("%T\n", a) // *intfmt.Printf("%T\n", b) // *boolfmt.Println(*a) // 0fmt.Println(*b) // false}
- make也是用于内存分配的,区别于new
make只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:
func make(t Type, size ...IntegerType) Typefunc main() {var b map[string]intb = make(map[string]int, 10)b["demo"] = 100fmt.Println(b)}
make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。
new 和 make 的区别:
- 二者都是用来做内存分配的。
- make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
- 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
2、数组 array
- 一维数组 ```go //数组的定义 size必须是常量 // var varName [size]varType var arr0 [3]int //值为 [0 0 0]
//数组的初始化 //方式一:初始化数组时可以使用初始化列表来设置数组元素的值 var arr1 = [3]int{} //值为 [0 0 0],注意:{}不可省略 arr2 := [3]int{1,2} //值为 [1 2 0]
//方式二:让编译器根据初始值的个数自行推断数组的长度 var arr3 = […]int{1,2} //值为 [1 2]
//方式三:指定索引值的方式来初始化数组 arr4 := […]int{1:2,3:5} //值为 [0 2 0 5]
- 多维数组```go//多维数组的定义 size必须是常量// var varName [size1][size2]varTypevar mArr0 [3][2]int //[[0 0] [0 0] [0 0]]//多维数组的初始化,与一维数组类似//需要注意的是:[...]只能用于第一维数组(最外围数组)mArr1 := [...][3]int{{1,2,3},{4,5,6}} //[[1 2 3] [4 5 6]]
注意事项
- 数组的长度必须是常量
- 内置函数 len() 和 cap() 都返回数组元素的个数
- 相同类型的数组之间可以使用 == 或 != 进行比较,因为内存总是被初始化过的。但不可以使用 < 或 > 进行比较
- 相同类型的数组可以进行相互赋值
- […]只能应用于多维数组的第一维(最外维)
- 数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
- [n]T表示指针数组,[n]T表示数组指针
3、切片 slice
切片是基于数组类型做的一层封装。支持自动扩容。切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。
// 声明切片类型var a []string //声明一个字符串切片var b = []int{} //声明一个整型切片并初始化var c = []bool{false, true} //声明一个布尔切片并初始化var d = []bool{false, true} //声明一个布尔切片并初始化fmt.Println(a) //[]fmt.Println(b) //[]fmt.Println(c) //[false true]fmt.Println(a == nil) //truefmt.Println(b == nil) //falsefmt.Println(c == nil) //false// fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较// 基于数组定义切片a := [5]int{55, 56, 57, 58, 59}b := a[1:4] //基于数组a创建切片,包括元素a[1],a[2],a[3] [56 57 58]c := a[1:] //[56 57 58 59]d := a[:4] //[55 56 57]e := a[:] //[55 56 57 58 59]fmt.Printf("type of b:%T\n", b) //type of b:[]int// 切片再切片a := [...]string{"北京", "上海", "广州", "深圳", "成都", "重庆"}fmt.Printf("a:%v type:%T len:%d cap:%d\n", a, a, len(a), cap(a))//a:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6 cap:6b := a[1:3]fmt.Printf("b:%v type:%T len:%d cap:%d\n", b, b, len(b), cap(b))//b:[上海 广州] type:[]string len:2 cap:5c := b[1:5]fmt.Printf("c:%v type:%T len:%d cap:%d\n", c, c, len(c), cap(c))//c:[广州 深圳 成都 重庆] type:[]string len:4 cap:4// 对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误//使用make()函数构造切片a := make([]int, 2, 10)// 切片的比较var s1 []int //len(s1)=0;cap(s1)=0;s1==nils2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nils3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
切片不能直接比较: 切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。 但是我们不能说一个长度和容量都是0的切片一定是nil,所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。
4、映射 map
5、管道 chan
Go相关的Tips:
一、比较操作符
- Go 会严格筛选用于比较操作符(<、<=、==、!=、>=、>)进行比较的值。这两值的必须是相同类型的。或者如果他们是接口,就必须实现了相同的接口类型。
- 如果有一个值是常量,那么他的类型必须与另一个类型相兼容。这意味着一个无类型的数值常量可以跟另一个任意数值类型的值进行比较,但是不同类型且非常量的数值不能直接进行比较,需要进行类型转换。
- == 和 != 操作符可以用于任何可比较的类型,包括数组和结构体,只要他们的元素和成员变量与 == 和 != 相兼容。
- == 和 != 可以用于比较两个指针和接口,或者将指针、接口或者引用(比如指向通道、映射、切片)与 nil 比较。别的比较操作符(<、<=、>=、>)只适用于数字和字符串。
- 比较操作符不能直接用于切片间的比较,包括(==、!=)。
