Go 基本语法 (二)



一. 切片

因为数组的长度是固定的,并且数组的长度属于类型的一部分。所以数组有很多的局限性。所以 go 语言中,提出 切片 的概念

func main() {
    
    
	var s1 []int    // 定义了一个存放 int 类型的切片
	var s2 []string //定义了一个存放 string 类型的切片
	fmt.Println(s1, s2)

	// 初始化
	s1 = []int{
    
    5, 7, 3}
	fmt.Println(s1)

	// 长度和容量
	fmt.Println(len(s1), cap(s1))
	// 结果都是3

	// 由数组得到的切片
	a1 := [...]int{
    
    1, 2, 3, 4, 5, 6}
	s3 := a1[0:4] // 从0 切到4
	fmt.Println(len(s3), cap(s3))
	// 输出结果:4 6, 长度是实际长度,容量是他底层的长度 (从切片的第一个元素到最后元素)

	// 切片再切片
	s4 := s3[0:2]
	fmt.Println(len(s4), cap(s4))
	// 切片是一个引用类型,都指向底层的一个数组。
	// 输出 (2,6)
}

在这里插入图片描述


1.1使用 make 函数创建切片

使用 make 函数,可以指定长度和容量的切片。
make 可以就是用来开辟内存空间的语言

func main() {
    
    
s1:=make([]int,5,10)
fmt.Println(s1,len(s1),cap(s1))
// 输出 5,10
}

切片是一个框,相当于框住了一个连续的内存,属于引用类型,真正的数据,是保存在底层的数组里的


判断一个数组是否为空的时候,应该使用 2,而不应该使用 1;

1.	fmt.Println(s1==nil)
2.	fmt.Println(len(s1)==0)

二.如何为切片动态的追加元素

2.1 错误写法

func main() {
    
    
	s1 := []string{
    
    "北京", "上海", "深圳"}
	s1[3] = "广州"  // 错误的写法,会发现超出范围
	fmt.Println(s1)
}

使用 append 添加

func main() {
    
    
	s1 := []string{
    
    "北京", "上海", "深圳"}

	// 调用 append 函数,必须用原来的切片变量接受返回值。
	s1 = append(s1, "广州")
	fmt.Println(s1)
	
    // 输出
	[北京 上海 深圳 广州]
}

使用 append,就是相当于把原来的底层位置换了一个位置。所以要有原来的变量重新接受一下。

func main() {
    
    
	s1 := []string{
    
    "北京", "上海", "深圳"}
	s2 := []string{
    
    "广州", "武汉", "重庆"}
	s1 = append(s1, s2...) // 。。。 在go语言里,是表示把数组拆开
	fmt.Println(s1)
}

三. Copy的用法

func main() {
    
    
	a1 := []int{
    
    1, 2, 3}
	a2 := a1
	var a3 = make([]int, 3)
	copy(a3, a1)
	fmt.Println(a1, a2, a3)
	a1[0] = 100
	fmt.Println(a1, a2, a3)

	// 输出
	[1 2 3] [1 2 3] [1 2 3]
    [100 2 3] [100 2 3] [1 2 3]
}




四. 从数组中删除元素

4.1 原本数组中是没有删除元素这一个功能的,所以要删除元素,需要使用元素自带的一些属性

func main() {
    
    
	a1 := []int{
    
    1, 2, 3}
	fmt.Println(a1)
	// 要删除掉a1 中索引值为 1 的 元素
	a1 = append(a1[:1], a1[2:]...)
	fmt.Println(a1)
}

4.2使用append 的补充

后面需要的时候再细细的研究吧,现在先记住,使用 append 和使用s1[10]=11,是一样的,只不过他是改了好多个。

func main() {
    
    
	a1 := [...]int{
    
    1, 2, 3, 7, 8, 9, 10, 11}
	s1 := a1[:]

	// 删除掉索引为1 的底层数组
	s1 = append(s1[:1], s1[2:]...)
	fmt.Println(s1,len(s1),cap(s1))
	fmt.Println(a1,len(a1),cap(a1))
}

// 输出
[1 3 7 8 9 10 11] 7 8
[1 3 7 8 9 10 11 11] 8 8



五.指针

5.1基本操作

go 语言中不需要对指针进行操作,只需要记住两个符号就可以了。

  • & :是用来取地址的
  • * :用来读地址里的值得
func main() {
    
    
	a := 10
	p := &a
	fmt.Println(p)
	fmt.Printf("%T\n", p) // 打印一下P的类型

	b:=*p
	fmt.Println(b)
}

// 输出
0xc000014088
*int  (表示的是 int 类型的指针)
10

5.2 指针的错误写法

func main() {
    
    
	var a *int
	*a = 100
	fmt.Println(*a)
}

这是因为,声明 var a 的时候,他会默认吧 var 内的指针地址,声明为 nil,因此他里面根本就没有存地址,所以也就无法往里面存值了。

5.3 使用 new 关键字,声明一块地址

func main() {
    
    
	var a =new(int)
	fmt.Println(a)
	fmt.Println(*a)
	*a=100
	fmt.Println(*a)
}

// 输出
0xc000014088
0
100


5.4 使用 make 关键字,声明一块地址

new 和 make 的区别:

  • make 和 new 都是用来申请内存的。
  • new 很少用,new 一般是用来给基本的类型赋值的,比如 string int 返回的是 对应类型的指针(*int,*string)。
  • make 一般是用来给 slice map chan 申请地址的,make 函数返回的是对应类型本身。


六. map


map 是一种无序的,基于 key - value 的数据结构,Go语言中的 map 是引用类型,必须要 初始化才能使用,

6.1 声明的方法:

map[keyType] valueType

6.2 基本的使用

func main() {
    
    
	var m1 map[string]int
	fmt.Println(m1 == nil)
	m1 = make(map[string]int, 18) // 应该预估一下他的内存大小,防止二次分配,影响效率
	m1["zhao"] = 18
	fmt.Println(m1["zhao"])
	value, ok := m1["peng"]      // 如果要确定是否存在这个人,可以使用该方法 
	if !ok {
    
    
		fmt.Println("查无此人")
	} else {
    
    
		fmt.Println(value)
	}
}

6.3 遍历 map

func main() {
    
    
	var m1 map[string]int
	fmt.Println(m1 == nil)
	m1 = make(map[string]int, 18) // 应该预估一下他的内存大小,防止二次分配,影响效率
	m1["zhao"] = 18
	m1["zhang"] = 20

	// 使用 range 直接遍历
	for k, v := range m1 {
    
    
		fmt.Println(k, v)
	}

	// 只想得到 key
	for k := range m1 {
    
    
		fmt.Println(k)
	}
	//  只想得到 value
	for _, value := range m1 {
    
    
		fmt.Println(value)
	}

	// 删除
	delete(m1,"zhang")
	fmt.Println(m1)
}

// 输出结果
true
zhang 20
zhao 18
zhao
zhang
18
20
map[zhao:18]



七. 函数

7.1 函数的声明

func Add(x int, y int) (n int) {
    
    
	return x + y
}

func main() {
    
    
	r := Add(1, 5)
	fmt.Println(r)
}

7.2 函数的一些其他的变形体

// 普通函数
func f1(x int, y int) (n int) {
    
    
	return x + y
}

// 没有返回值
func f2() {
    
    
	fmt.Println("f2")
}

// 没有参数,但是有返回值
// 例如返回值,参数可以命名,也可以不命名
func f3() int {
    
    
	return 5
}

// 命名的返回值
func f7(x, y int) (sum int) {
    
    
	sum = x + y
	return sum
}

// 多个返回值
func f4() (string, int) {
    
    
	return "青岛", 8
}

// 参数类型简写,当连续多个参数的类型一样的时候,可以简写
func f5(x, y int) {
    
    
	fmt.Println(x, y)
}

// 可变长参数
// 可变参数一定要写在后边
func f6(x string, y ...int) {
    
    
	fmt.Println(x)
	fmt.Println(y)
}

// go 语言中没有默认参数整个概念

func main() {
    
    
	r := f1(1, 5)
	fmt.Println(r)
	f2()
	f3()
	f4()
	f5(5, 8)
	f6("zhao", 8, 9, 6, 10, 14, 15)
	a := f7(8, 9)
	fmt.Println(a)
}

// 输出
6
f2
5 8
zhao
[8 9 6 10 14 15]
17



八. defer 语句

1.defer 语句的一般定义

defer 语句是用于延迟到函数即将返回的时候再执行,一般用于释放某些资源。

func deferDemo() {
    
    
	fmt.Println("start")
	// defer 把他后面的语句延迟到即将返回的时候再执行
	// defer 一般是用来释放某些资源的。
	defer fmt.Println("嘿嘿")
	fmt.Println("哈哈")
}

func main() {
    
    
	deferDemo()
}

// 输出
start
哈哈
嘿嘿


2.多个 defer 语句的时候

多个defer 语句按照先进后出的顺序延迟执行;

func deferDemo() {
    
    
	defer fmt.Println("嘿嘿")
	defer fmt.Println("哈哈")
	defer fmt.Println("呵呵")
}

func main() {
    
    
	deferDemo()
}

// 输出结果
呵呵
哈哈
嘿嘿

3.defer 执行时机

他不是原子性的执行;
在这里插入图片描述

自己写一下:

func f1() int {
    
    
	x := 5
	defer func() {
    
    
		x++ // 修改的是 x,不是返回值
	}()
	return x //5
}

// 带命名返回的函数
func f2() (x int) {
    
    
	defer func() {
    
    
		x++ // 返回值是x,而x此时=5
	}()
	return 5 //6
}

func f3() (y int) {
    
    
	x := 5
	defer func() {
    
    
		x++
	}()
	return x //5
}

func f4() (x int) {
    
    
	defer func(x int) {
    
    
		x++
	}(x)

	return 5 //5
}

func main() {
    
    
	a1 := f1()
	fmt.Println(a1)
	a2 := f2()
	fmt.Println(a2)
	a3 := f3()
	fmt.Println(a3)
	a4 := f4()
	fmt.Println(a4)
}



九.函数深究

1.函数的查找顺序(作用域)

  • 先再函数内部查找
  • 找不到就往函数的外面查找,一直找到全局
// 作用域
var x=100

func f1(){
    
    
	fmt.Println(x)
}

func main() {
    
    
	f1()
}

2.函数类型


import "fmt"

func f1() {
    
    
	fmt.Println("Hello,world")
}

func f2() int {
    
    
	return 6
}

// 函数也可以作为参数类型
func f3(x func() int) {
    
    
	ret := x()
	fmt.Println(ret)
}

// 输入和输出都是函数
func f5(x func() int)func(int,int) int {
    
    
rel:=func(a,b,int)
)
}

func main() {
    
    
	a1 := f1
	fmt.Printf("%T", a1)
	fmt.Println()
	a2 := f2
	fmt.Printf("%T", a2)
	fmt.Println()
	f3(f2)
}


// 输出是  (f5)函数没有输出
func()
func() int
6

在Python里面,函数可以作为参数输入,也可以作为输出的函数,这种叫做高阶函数


3.匿名函数

var f1 = func(x, y int) {
    
    
	rel := x + y
	fmt.Println(rel)
}

func main() {
    
    
	f1(2, 2)

	// 为了解决函数内部没有办法声明带名字的函数
	// 匿名函数
	f2 := func(x, y int) {
    
    
		rel := x + y
		fmt.Println(rel)
	}
	f2(2, 8)

	// 当作立即执行函数
	func() {
    
    
		fmt.Println("Hellow wrold")
	}() // 这个后面加个括号,表示立即执行

	func(a, b int) {
    
    
		fmt.Println(b + a)
	}(5, 4) // 后边加括号表示理解执行
}

// 输出
4
Hellow wrold
9

4. 闭包的应用场景

4.1 一些不用闭包不能解决的问题

闭包使用的场景

import "fmt"

func f1(f func()) {
    
    
	fmt.Println("this is f1")
	f()
}

func f2(x, y int) {
    
    
	fmt.Println("this is f2")
	fmt.Println(x + y)
}

func main() {
    
    
	// 不能应用,因为参数的类型不匹配
	f1(f2)  
}
4.2使用闭包解决这个问题

闭包:就是一个函数,除了使用他自己内部定义的变量以外,还能使用外边的变量。

func adder(x int) func(int) int{
    
    
	return func(y int)int{
    
    
		return x+y
	}
}

func main() {
    
    
	ret:=adder(100)
	ret2:=ret(200)
	fmt.Println(ret2)
}

// 输出 
300

猜你喜欢

转载自blog.csdn.net/zhaozhao236/article/details/114765272