01 数组
package main
import "fmt"
func main() {
// 这里我们创建了一个具有5个元素的整型数组
// 元素的数据类型和数组长度都是数组的一部分
// 默认情况下,数组元素都是零值
// 对于整数,零值就是0
var a [5]int
fmt.Println("emp:", a)
// 我们可以使用索引来设置数组元素的值,就像这样
// "array[index] = value" 或者使用索引来获取元素值,
// 就像这样"array[index]"
a[4] = 100
fmt.Println("set:", a)
fmt.Println("get:", a[4])
// 内置的len函数返回数组的长度
fmt.Println("len:", len(a))
// 这种方法可以同时定义和初始化一个数组
b := [5]int{1, 2, 3, 4, 5}
fmt.Println("dcl:", b)
// 数组都是一维的,但是你可以把数组的元素定义为一个数组
// 来获取多维数组结构
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
// 数组是值类型,长度是类型的组成部分;数组作为参数时,函数内部不改变数组内部的值,除非是传入数组的指针
package main
import "fmt"
func main() {
var a = new([5]int)
test(a)
fmt.Println(a, len(a))
}
func test(a *[5]int) {
a[1] = 5
}
// new 创建数组,并返回数组的指针
package main
import "fmt"
func main() {
a := [...]User{
{0, "User0"},
{8, "User8"},
}
b := [...]*User{
{0, "User0"},
{8, "User8"},
}
fmt.Println(a, len(a))
fmt.Println(b, len(b))
}
type User struct {
Id int
Name string
}
02 切片
package main
import "fmt"
func main() {
// 和数组不同的是,切片的长度是可变的
// 我们可以使用内置函数make来创建一个长度不为零的切片
// 这里我们创建了一个长度为3,存储字符串的切片,切片元素, 默认为零值,对于字符串就是""。
s := make([]string, 3)
fmt.Println("emp:", s)
// 可以使用和数组一样的方法来设置元素值或获取元素值
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("set:", s)
fmt.Println("get:", s[2])
// 可以用内置函数len获取切片的长度
fmt.Println("len:", len(s))
// 切片还拥有一些数组所没有的功能, 使用内置函数append给切片追加值,返回一个拥有新切片元素的切片。
// 注意append函数不会改变原切片,而是生成了一个新切片,我们需要用原来的切片来接收这个新切片
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println("apd:", s)
// 可以从一个切片拷贝元素到另一个切片
// 下面的例子就是创建了一个和切片s长度相同的新切片, 然后使用内置的copy函数来拷贝s的元素到c中。
c := make([]string, len(s))
copy(c, s)
fmt.Println("cpy:", c)
// 切片还支持一个取切片的操作 "slice[low:high]"
// 获取的新切片包含元素"slice[low]",但是不包含"slice[high]"
// 下面的例子就是取一个新切片,元素包括"s[2]","s[3]","s[4]"。
l := s[2:5]
fmt.Println("sl1:", l)
// 如果省略low,默认从0开始,不包括"slice[high]"元素
l = s[:5]
fmt.Println("sl2:", l)
// 如果省略high,默认为len(slice),包括"slice[low]"元素
l = s[2:]
fmt.Println("sl3:", l)
// 我们可以同时声明和初始化一个切片
t := []string{"g", "h", "i"}
fmt.Println("dcl:", t)
// 我们也可以创建多维切片,和数组不同的是,切片元素的长度也是可变的。
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
// 数组和切片的定义方式的区别在于[]之中是否有固定长度或者推断长度标志符...
package main
import "fmt"
func main() {
s1 := make([]int, 0)
test(s1)
fmt.Println(s1)
}
func test(s []int) {
// 因为原来分配的空间不够,所以在另外一个地址又重新分配了空间,所以原始地址的数据没有变
s = append(s, 3)
}
//
package main
import "fmt"
func main() {
s1 := make([]int, 0)
s1 = test(s1)
fmt.Println(s1)
}
func test(s []int) []int {
s = append(s, 3)
return s
}
package main
import "fmt"
func main() {
s1 := make([]int, 3, 6)
fmt.Println("s1= ", s1, len(s1), cap(s1))
s2 := append(s1, 1, 2, 3)
fmt.Println("s1= ", s1, len(s1), cap(s1))
fmt.Println("s2= ", s2, len(s2), cap(s2))
s3 := append(s2, 4, 5, 6)
fmt.Println("s1= ", s1, len(s1), cap(s1))
fmt.Println("s2= ", s2, len(s2), cap(s2))
fmt.Println("s3= ", s3, len(s3), cap(s3))
}
// cap是slice的最大容量,append函数添加元素,如果超过原始slice的容量,会重新分配底层数组。
package main
import "fmt"
func main() {
s1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s2 := make([]int, 3, 20)
var n int
n = copy(s2, s1)
fmt.Println(n, s2, len(s2), cap(s2))
s3 := s1[4:6]
fmt.Println(n, s3, len(s3), cap(s3))
n = copy(s3, s1[1:5])
fmt.Println(n, s3, len(s3), cap(s3))
}
03 字典
package main
import "fmt"
func main() {
// 创建一个字典可以使用内置函数make
// "make(map[键类型]值类型)"
m := make(map[string]int)
// 使用经典的"name[key]=value"来为键设置值
m["k1"] = 7
m["k2"] = 13
// 用Println输出字典,会输出所有的键值对
fmt.Println("map:", m)
// 获取一个键的值 "name[key]".
v1 := m["k1"]
fmt.Println("v1: ", v1)
// 内置函数返回字典的元素个数
fmt.Println("len:", len(m))
// 内置函数delete从字典删除一个键对应的值
delete(m, "k2")
fmt.Println("map:", m)
// 根据键来获取值有一个可选的返回值,这个返回值表示字典中是否
// 存在该键,如果存在为true,返回对应值,否则为false,返回零值
// 有的时候需要根据这个返回值来区分返回结果到底是存在的值还是零值
// 比如字典不存在键x对应的整型值,返回零值就是0,但是恰好字典中有
// 键y对应的值为0,这个时候需要那个可选返回值来判断是否零值。
_, ok := m["k2"]
fmt.Println("ok:", ok)
// 你可以用 ":=" 同时定义和初始化一个字典
n := map[string]int{"foo": 1, "bar": 2}
fmt.Println("map:", n)
}
// 数组是索引对应数组元素,而字典是键对应值
04 结构体
package main
import "fmt"
// person结构体 => 成员 [name, age]
type person struct {
name string
age int
}
func main() {
// 创建一个新结构体变量
per := person{"Bob", 20}
fmt.Println(per)
// 使用"成员:值"的方式, 来初始化结构体变量
per = person{name: "Alice", age: 30}
fmt.Println(per)
// 未显式赋值的成员初始值为零值
per = person{name: "Fred"}
fmt.Println(per)
// 使用&来获取结构体变量的地址
perPoint := &person{name: "Ann", age: 40}
fmt.Println(perPoint)
// 使用点号(.)来访问结构体成员
s := person{name: "Sean", age: 50}
fmt.Println(s.name)
// 结构体指针也可以使用点号(.)来访问结构体成员
sp := &s
fmt.Println(sp.age)
// 结构体成员变量的值是可以改变的
sp.age = 51
fmt.Println(sp.age)
}
05 指针,传递变量的引用
package main
import "fmt"
// 我们用两个不同的例子来演示指针的用法
// zeroval函数有一个int类型参数,这个时候传递给函数的是变量的值
func zeroval(ival int) {
ival = 0
}
// zeroptr函数的参数是int类型指针,这个时候传递给函数的是变量的地址
// 在函数内部对这个地址所指向的变量的任何修改都会反映到原来的变量上
func zeroptr(iptr *int) {
*iptr = 0
}
func main() {
i := 1
fmt.Println("initial:", i)
zeroval(i)
fmt.Println("zeroval:", i)
// &操作符用来取得i变量的地址
zeroptr(&i)
fmt.Println("zeroptr:", i)
// 指针类型也可以输出
fmt.Println("pointer:", &i)
}
06 IF-ELSE 条件判断,条件两边的小括号()可以省略,也可以保留
package main
import "fmt"
func main() {
// 01 基本结构 [IF-ELSE]
if 3%2 == 0 {
fmt.Println("3 is even")
} else {
fmt.Println("3 is odd")
}
// 02 只有if条件的情况 [IF]
if 4%2 == 0 {
fmt.Println("8 is divisible by 4")
}
// 03 if条件可以包含一个初始化表达式, 这个表达式中的变量, 是这个条件判断结构的局部变量
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
// Go没有三元表达式"?:",所以只能使用条件判断语句
07 Switch 条件判断
package main
import "fmt"
import "time"
func main() {
// 01 基础 switch用法, 可以省略 break 语句
i := 2
fmt.Print("write ", i, " as ")
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
// 02 case 逗号可以中分开多个条件,
// default 语句
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("it's the weekend")
default:
fmt.Println("it's a weekday")
}
// 03 当 switch没有跟表达式的时候, 功能和 if/else相同
// case 表达式 可以不是长量
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("it's before noon")
default:
fmt.Println("it's after noon")
}
}
08 FOR 循环,唯一的循环结构,有三种基本循环类型
package main
import "fmt"
func main() {
// 01 最基本的一种, 单一条件循环
// 可以代替其他语言的while循环
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
// 02 经典循环条件 [初始化/条件判断/循环后条件变化]
for j := 7; j <= 9; j++ {
fmt.Println(j)
}
// 03 无条件的for循环是死循环, 使用break跳出循环或者return从函数返回
//
for {
fmt.Println("loop")
break
}
}
// range函数循环数组,切片和字典,或者用select函数循环channel通道
09 Range 函数,内置函数,可以用来 遍历数组,切片和字典
package main
import "fmt"
func main() {
// range 计算一个切片的所有元素和, 也适用于数组
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
// range 遍历数组和切片 => 返回 索引和元素值, 使用一个下划线(_), 忽略返回值
// 遍历数组和切片的时候,range函数返回索引和元素
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
// range 遍历字典 => 返回键值对
// 遍历字典的时候,range函数返回字典的键和值
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
// range 遍历字符串 => 返回Unicode代码点 [字符的起始字节的索引, 字符代码点]
// 字符串是由字节组成的, 多个字节组成一个rune类型字符。
for i, c := range "go" {
fmt.Println(i, c)
}
}
10 函数定义
package main
import "fmt"
// 函数定义, 计算两个int型输入数据的和, 并返回int型
func plus(a int, b int) int {
// 需要使用return语句显式地返回
return a + b
}
func main() {
// 函数调用方式 => "名称(参数列表)"
res := plus(1, 2)
fmt.Println("1+2 =", res)
}
函数,内置支持多返回值
package main
import "fmt"
// 函数的返回值为两个int
func vas() (int, int) {
return 3, 7
}
func main() {
// 获取函数的两个返回值
a, b := vas()
fmt.Println(a)
fmt.Println(b)
// 使用下划线(_)来忽略指定的返回值
_, c := vas()
fmt.Println(c)
}
函数回调,函数名称作为参数传递给另外一个函数,然后在别的地方实现这个函数
package main
import "fmt"
type Callback func(x, y int) int
func main() {
x, y := 1, 2
fmt.Println(test(x, y, add))
}
// 提供一个接口, 让外部去实现
func test(x, y int, callback Callback) int {
return callback(x, y)
}
func add(x, y int) int {
return x + y
}
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
// 函数接受参数,函数可以返回多个“结果参数”,而不仅仅是一个值。它们可以像变量那样命名和使用。
// 如果命名了返回值参数,一个没有参数的return语句,会将当前的值作为返回值返回。
匿名函数,闭包,闭包函数可以访问定义闭包的函数定义的内部变量
package main
import "fmt"
// "intSeq" 函数 => 返回另外一个在 intSeq内部定义的匿名函数
// 匿名函数, 引用了变量 i, 从而形成了一个闭包
func intSeq() func() int {
i := 0
return func() int {
i += 1
return i
}
}
func main() {
// 调用intSeq函数, 并且把结果赋值给一个函数 nextInt
// nextInt函数拥有自己的i变量, 这个变量每次调用都被更新, i的初始值是由intSeq调用的时候决定
nextInt := intSeq()
// 调用几次nextInt, 看看闭包的效果
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
// 为了确认闭包的状态是独立于intSeq函数的, 再创建一个。
newNextInt := intSeq()
fmt.Println(newNextInt())
}
package main
import "fmt"
func main() {
// 构造了[x=10]函数
add10 := addClosure(10)
fmt.Println(add10(5))
fmt.Println(add10(6))
// 构造了[x=20]函数
add20 := addClosure(20)
fmt.Println(add20(5))
}
// 调用, addClosure() 函数, 设置 x 初始值 => 返回函数 func(y int)
// 再次调用时, 是调用匿名函数, 传递的参数是 匿名函数的参数
func addClosure(x int) func(y int) int {
return func(y int) int {
fmt.Println(x, ",", y)
return x + y
}
}
package main
import "fmt"
func main() {
//
var fs []func() int
for i := 0; i < 5; i++ {
fs = append(fs, func() int {
fmt.Printf("%v,", i)
return i
})
}
//
for _, f := range fs {
fmt.Printf("%p = %v\n", f, f())
}
}
package main
import "fmt"
func add() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
// 构造函数对象 [sum = 0]
result := add()
for i := 0; i < 10; i++ {
fmt.Println(result(i))
}
}
可变长参数列表,支持可变长参数列表的函数可以支持任意个传入参数
package main
import "fmt"
// 函数可以传入任意数量的整型参数
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
// 支持可变长参数的函数调用方法和普通函数一样, 也支持只有一个参数的情况
sum(1, 2)
sum(1, 2, 3)
// 如果你需要传入的参数在一个切片中, 像下面一样 "func(slice...)" 把切片打散传入
nums := []int{1, 2, 3, 4}
sum(nums...)
}
// 可变长参数应该是函数定义的最右边的参数,即最后一个参数
递归函数,斐波拉切数列
package main
import "fmt"
// fact函数, 不断地调用自身 => 直到达到基本状态fact(0)
func fact(n int) int {
if n == 0 {
// 函数返回
return 1
}
return n * fact(n-1)
}
func main() {
fmt.Println(fact(7))
}
定义在结构体上面的函数叫做该结构体的方法,一般的函数定义叫做函数
package main
import "fmt"
type rect struct {
width, height int
}
// area方法, 有一个限定类型 *rect , 表示这个函数是定义在rect结构体上的方法
func (r *rect) area() int {
return r.width * r.height
}
// 方法的定义, 限定类型可以为结构体类型, 也可以是结构体指针类型
// 区别在于如果限定类型是结构体指针类型, 那么在该方法内部可以修改结构体成员信息
func (r rect) per() int {
return 2*r.width + 2*r.height
}
func main() {
r := rect{width: 10, height: 5}
// 调用方法
fmt.Println("area: ", r.area())
fmt.Println("per:", r.per())
// Go语言会自动识别方法调用的参数是结构体变量还是结构体指针
// 如果你要修改结构体内部成员值,那么使用结构体指针作为函数限定类型
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("per:", rp.per())
fmt.Println("area: ", rp.area())
fmt.Println("per:", rp.per())
}
package main
import (
"fmt"
)
func main() {
p := Person{2, "张三"}
p.test(1)
var f1 func(int) = p.test
f1(2)
Person.test(p, 3)
var f2 func(Person, int) = Person.test
f2(p, 4)
}
type Person struct {
Id int
Name string
}
func (this Person) test(x int) {
fmt.Println("Id:", this.Id, "Name", this.Name)
fmt.Println("x=", x)
}
// 方法是函数的“语法糖”,当函数与某个特定的类型绑定,那么它就是一个方法,
package main
import (
"fmt"
)
func main() {
p := Student{Person{2, "张三"}, 25}
p.test()
}
type Person struct {
Id int
Name string
}
type Student struct {
Person
Score int
}
func (this Person) test() {
fmt.Println("person test")
}
func (this Student) test() {
fmt.Println("student test")
}
// 匿名字段,实现模拟继承。即可直接访问匿名字段(匿名类型或匿名指针类型)的方法这种行为类似“继承”
// 访问匿名字段方法时,有隐藏规则,这样我们可以实现override效果。