变量
变量声明
四种声明语句
- var
- const
- type
- func
var 变量名字 类型 = 表达式
批量格式
var (a intb stringc []float32d func() boole struct {x int})
简短变量声明名字 := 表达式
这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型
anim := gif.GIF{LoopCount: nframes}freq := rand.Float64() * 3.0t := 0.0
i := 100 // an intvar boiling float64 = 100 // a float64var names []stringvar err errorvar p Point
匿名变量
可用于占位
package mainimport "fmt"func GetData() (int, int) {return 100, 200}func main() {a, _ := GetData() // 只需要第一个返回值_, b := GetData() // 只需要第二个返回值// b := GetData() // 只用一个变量无法达到想要的效果fmt.Println(a, b)}
匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。
变量作用域
局部变量
在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。
局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。
形式参数
在定义函数时函数名后面括号中的变量叫做形式参数
常量与枚举
常量声明
const identifier [type] = value
package mainimport "fmt"func main() {const LENGTH int = 10const WIDTH int = 5var area intconst a, b, c = 1, false, "str" //多重赋值area = LENGTH * WIDTHfmt.Printf("面积为 : %d", area)println()println(a, b, c)}
特殊常量
const a int = iota // the value of a is 0const b = iota // the value of b is still 0
定义枚举
const (unknown = 0male = 1female = 2)
const (unknown = iota // the value of unknown is 0male // the value of male is 1female // the value of female is 2)const (c1 = iota // the value of c1 is 0c2 = iota // the value of c2 is 1c3 = iota // the value of c3 is 2)
数据类型
浮点型
package mainimport ("fmt""math")//全局变量 avar a int = 13func main() {var f float32 = 16777216 // 1 << 24const e = .71828 // 0.71828const g = 1. // 1fmt.Println(f == f+1) // "true"!fmt.Printf("%f\n", math.Pi) // 3.141593fmt.Printf("%.2f\n", math.Pi) // 3.14}
字符串
换行字符串
const str = `第一行第二行第三行\r\n`fmt.Println(str)
字符类型
Go语言的字符有以下两种:
- 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
- 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。
fmt.Println(str)var ch int = '\u0041'var ch2 int = '\u03B2'var ch3 int = '\U00101234'fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integerfmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // characterfmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytesfmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
派生类型
包括:
- 指针类型(Pointer)
- 数组类型
- 结构化类型(struct)
- Channel 类型
- 函数类型
- 切片类型
- 接口类型(interface)
- Map 类型
数据类型转换
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:valueOfTypeB = typeB(valueOfTypeA)
func main() {// 将常量保存为float32类型var c float32 = math.Pi// 转换为int类型, 浮点发生精度丢失fmt.Println(int(c), c)}
条件语句
Go语言的 if / if else语句 省去了条件的括号
if a < 20 {fmt.Printf("a 小于 20\n" )}
switch 语句
switch var1 {case val1:...case val2:...default:...}
select 语句,与switch类似,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行
func demo() {var c1, c2, c3 chan intvar i1, i2 intselect {case i1 = <-c1:fmt.Printf("received ", i1, " from c1\n")case c2 <- i2:fmt.Printf("sent ", i2, " to c2\n")case i3, ok := (<-c3): // same as: i3, ok := <-c3if ok {fmt.Printf("received ", i3, " from c3\n")} else {fmt.Printf("c3 is closed\n")}default:fmt.Printf("no communication\n")}}
循环语句
go 循环语句同样省略了条件括号
函数
多个返回值
func swap(x, y string) (string, string) {return y, x}
闭包
func getSequence() func() int {i := 0return func() int {i += 1return i}}func demo() {/* nextNumber 为一个函数,函数 i 为 0 */nextNumber := getSequence()/* 调用 nextNumber 函数,i 变量自增 1 并返回 */fmt.Println(nextNumber())fmt.Println(nextNumber())fmt.Println(nextNumber())/* 创建新的函数 nextNumber1,并查看结果 */nextNumber1 := getSequence()fmt.Println(nextNumber1())fmt.Println(nextNumber1())}
方法
一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针
func (variable_name variable_data_type) function_name() [return_type]{/* 函数体*/}
/* 定义结构体 */type Circle struct {radius float64}// 属于 Circle 类型对象中的方法func (c Circle) getArea() float64 {//c.radius 即为 Circle 类型对象中的属性return 3.14 * c.radius * c.radius}func demo() {var c1 Circlec1.radius = 10.00fmt.Println("圆的面积 = ", c1.getArea())}
结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
定义
type struct_variable_type struct {member definitionmember definition...member definition}
用于变量的声明
variable_name := structure_variable_type {value1, value2...valuen}或variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
访问结构体
结构体.成员名
package mainimport "fmt"type Anime struct {title stringproducer stringanime_id int}func main() {var anime1 Anime /* 声明 anime1 为 Anime 类型 */var anime2 Anime /* 声明 anime2 为 Anime 类型 *//* anime 1 描述 */anime1.title = "咒术回战"anime1.producer = "MAPPA"anime1.anime_id = 6495407/* anime 2 描述 */anime2.title = "来自深渊"anime2.producer = "KINEMA CITRUS"anime2.anime_id = 6495700/* 打印 anime1 信息 */fmt.Printf("anime 1 title : %s\n", anime1.title)fmt.Printf("anime 1 producer : %s\n", anime1.producer)fmt.Printf("anime 1 anime_id : %d\n", anime1.anime_id)/* 打印 anime2 信息 */fmt.Printf("anime 2 title : %s\n", anime2.title)fmt.Printf("anime 2 producer : %s\n", anime2.producer)fmt.Printf("anime 2 anime_id : %d\n", anime2.anime_id)}
结构体指针
如果想在函数里面改变结构体实例,需要给函数传入结构体指针
var struct_pointer *Anime
package mainimport "fmt"type Anime struct {title stringproducer stringanime_id int}func main() {var anime1 Anime /* 声明 anime1 为 Anime 类型 */var anime2 Anime /* 声明 anime2 为 Anime 类型 *//* anime 1 描述 */anime1.title = "咒术回战"anime1.producer = "MAPPA"anime1.anime_id = 6495407/* anime 2 描述 */anime2.title = "来自深渊"anime2.producer = "KINEMA CITRUS"anime2.anime_id = 6495700changeAnime(anime2)changeAnimeByPointer(&anime1)fmt.Println(anime1)fmt.Println(anime2)}func changeAnime(anime Anime) {anime.title = "黑之契约者"}func changeAnimeByPointer(anime *Anime) {anime.title = "进击的巨人"}
这样就能改变结构体实例了
{进击的巨人 MAPPA 6495407} {来自深渊 KINEMA CITRUS 6495700}
方法
见函数中的方法
Go没有class概念,但是可以在结构体外部定义方法
数组和切片
数组
// 定义方式var a [3]int // 定义长度为3的int型数组, 元素全部为0var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6var d [0]int // 定义一个长度为0的数组var e = [0]int{} // 定义一个长度为0的数组var f = [...]int{} // 定义一个长度为0的数组// 遍历数组for i := range a {fmt.Printf("a[%d]: %d\n", i, a[i])}for i, v := range b {fmt.Printf("b[%d]: %d\n", i, v)}for i := 0; i < len(c); i++ {fmt.Printf("c[%d]: %d\n", i, c[i])}
切片
- Go数组的长度在定义后是固定的,不可改变的,而切片是对数组的抽象。
- 切片的长度和容量是不固定的,可以动态增加元素,切片的容量也会根据情况自动扩容
- 切片实际是个struct结构体,有个指针array,指向存放数据的数组
len是切片的长度,cap是切片的容量type slice struct {array unsafe.Pointerlen intcap int}
切片语法
var slice_var []data_type = make([]data_type, len, cap)
func printSlice(param []int) {fmt.Printf("slice len:%d, cap:%d, value:%v\n", len(param), cap(param), param)}func main() {slice1 := []int{1} // 数组slice2 := make([]int, 3, 100) // 切片printSlice(slice1)printSlice(slice2)}slice len:1, cap:1, value:[1]slice len:3, cap:100, value:[0 0 0]
如果slice类型的变量定义后没有初始化赋值,那值就是默认值nil。对于nil切片,len和cap函数执行结果都是0
func demo() {slice3 := []int{} // 不是0值var slice4 []int // 0值fmt.Println("is nil: ", slice3 == nil, slice4 == nil)}
切片的使用
访问
func demo02() {slice := make([]int, 3, 10)/*下标访问切片*/slice[0] = 1slice[1] = 2slice[2] = 3for i := 0; i < len(slice); i++ {fmt.Printf("slice[%d]=%d\n", i, slice[i])}/*range迭代访问切片*/for index, value := range slice {fmt.Printf("slice[%d]=%d\n", index, value)}}slice[0]=1slice[1]=2slice[2]=3slice[0]=1slice[1]=2slice[2]=3
截取array[left_index:right_index]
类似python的截取,左开右闭区间
func demo03() {// 对数组做切片array := [3]int{1, 2, 3} // array是数组slice3 := array[1:3] // slice3是切片fmt.Println("slice3 type:", reflect.TypeOf(slice3))fmt.Println("slice3=", slice3) // slice3= [2 3]slice4 := slice3[1:2]fmt.Println("slice4=", slice4) // slice4= [3] 左开右闭区间/* slice5->slice4->slice3->array切片后的指针会指向原切片的数组空间,会影响原数据对slice5的修改,会影响到slice4, slice3和array*/slice5 := slice4[:]fmt.Println("slice5=", slice5) // slice5= [3]slice5[0] = 10fmt.Println("array=", array) // array= [1 2 10]fmt.Println("slice3=", slice3) // slice3= [2 10]fmt.Println("slice4=", slice4) // slice4= [10]fmt.Println("slice5=", slice5) // slice5= [10]}
常用函数
- len():获取切片的长度,也就是实际存储了多少个元素
- cap(): 获取切片的容量。如果切片的元素个数要超过当前容量,会自动扩容
- append(): 不改变原切片的值
- 只能对切片使用append()函数,不能对数组使用append()
- copy():拷贝一个切片里的数据到另一个切片
func change2(param []int) { param = append(*param, 300) // 传切片指针,通过这种方式append可以改变外部切片的值 }
func demo04() { slice := make([]int, 2, 100) fmt.Println(slice) // [0, 0]
change1(slice)fmt.Println(slice) // [100, 0]change2(&slice)fmt.Println(slice) // [100, 0, 300]
}
- slice切片如果是函数参数,函数体内对切片底层数组以访问方式的修改会影响到实参- 如果在函数体内通过append直接对切片添加新元素,不会改变外部切片的值- 如果函数使用切片指针作为参数,在函数体内可以通过append修改外部切片的值<a name="eIvSq"></a>## map集合Go语言里的map底层是通过**hash**实现的,是一种无序的基于`<key, value>`对组成的数据结构,key是唯一的<br />**语法**```gofunc main() {var dict map[string]int = map[string]int{}dict["a"] = 1fmt.Println(dict)var dict2 = map[string]int{}dict2["b"] = 2fmt.Println(dict2)dict3 := map[string]int{"test": 0}dict3["c"] = 3fmt.Println(dict2)dict4 := make(map[string]int)dict4["d"] = 4fmt.Println(dict4)}
常用api
判断key在map里是否存在,语法value, is_exist := map[key]
func demo01() {// 构造一个mapstr := "aba"dict := map[rune]int{}for _, value := range str {dict[value]++}fmt.Println(dict) // map[97:2 98:1]// 访问map里不存在的key,并不会像C++一样自动往map里插入这个新keyvalue, ok := dict['z']fmt.Println(value, ok) // 0 falsefmt.Println(dict) // map[97:2 98:1]// 访问map里已有的keyvalue2 := dict['a']fmt.Println(value2) // 2}
获取键值对数量
counter := make(map[string]int)fmt.Println(len(counter))counter["a"] = 1fmt.Println(len(counter))
删除键值对
func demo02() {dict := map[string]int{"a": 1, "b": 2}fmt.Println(dict) // map[a:1 b:2]// 删除"a"这个keydelete(dict, "a")fmt.Println(dict) // map[b:2]// 删除"c"这个不在的key,对map结果无影响delete(dict, "c")fmt.Println(dict) // map[b:2]}
注意:
- map不是并发安全的,并发读写要加锁
切片slice,函数类型function,集合map,不能用作map的key
接口interface
定义:接口是一种抽象的类型,是一组method的集合,里面只有method方法,没有数据成员。当两个或两个以上的类型都有相同的处理方法时才需要用到接口。
- 多个struct类型可以实现同一个interface
- 一个struct类型可以实现多个interface
```go
/ 定义接口 /
type interface_name01 interface {
method_name1() [return_type]
method_name2() [return_type]
method_name3() [return_type]
method_namen() [return_type] } / 定义接口 / type interface_name02 interface { interface_name01 // 可以继承自其它接口,得到这个接口的method集合 method_name1() [return_type] method_name2() [return_type] method_name3() [return_type]...
method_namen() [return_type] }...
/ 定义结构体 / type struct_name struct { / variables / }
/ 实现接口方法 / func (struct_name_variable struct_name) method_name1() [return_type] { / 方法实现 / } … func (struct_name_variable struct_name) method_namen() [return_type] { / 方法实现/ }
```gopackage mainimport "fmt"// 定义动物类接口type Animal interface {speak()}// cat结构体type Cat struct {name stringage int}// cat方法func (cat Cat) speak() {fmt.Println("cat miaomiaomiao")}// dog结构体type Dog struct {name stringage int}// Dog方法func (dog *Dog) speak() {fmt.Println("dog wangwangwang")}func main() {var animal Animal = Cat{"gaffe", 1}animal.speak() // cat miaomiaomiao/*如果用了指针接受者,那给interface变量赋值的时候要传指针*/animal = &Dog{"caiquan", 2}animal.speak() // dog wangwangwang}
- interface可以继承:一个interface里包含其它interface ```go // interface1 type Felines interface { feet() }
// interface2, 继承了interface1 type Mammal interface { Felines born() } ```
