GO千练——指针、struct、数组、slice 和映射

GO千练——指针、struct、数组、slice 和映射指针 什么是指针? Go 拥有指针,一个指针变量指向了一个值的内存地址。 比如:类型 *T 是指向 T 类型值的指针。其零值为 nil。

指针

什么是指针?

Go 拥有指针,一个指针变量指向了一个值的内存地址。

比如:类型 *T 是指向 T 类型值的指针。其零值为 nil。指针声明格式如下:

var var_name *var-type

var p *int

& 操作符

& 操作符会生成一个指向其操作数的指针。

var i int = 10
p = &i

* 操作符

  • 操作符表示指针指向的底层值,作用是可以操作变量具体的值,也就是通常所说的“间接引用”或“重定向”。
// 通过指针 p 读取 i
fmt.Println(*p) 

// 通过指针 p 设置 i
*p = 20         

实例

package main

import "fmt"

func main() {
	var i int = 10

	// 指针
	var p *int = &i
	fmt.Println("指针p指向的值:", *p)

	// 指针重新设值
	*p = 20
	fmt.Println("指针p重新指向的值:", *p)

	// panic: runtime error: invalid memory address or nil pointer dereference
	var pointer *string = nil
	fmt.Println("指针p指向的值为nil:", *pointer)
}

结构体

什么是结构体?

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

简单来说,一个结构体(struct)就是一组字段(field)。

比如保存图书馆的书籍记录,每本书有以下属性: Title :标题 Author : 作者 Subject:学科

定义结构体的语法:

type struct_variable_type struct {
    member definition
    member definition
    ...
    member definition
}

简单实例

package main

import "fmt"

type Book struct {
	Title   string
	Author  string
	Subject string
}

func main() {
	// 初始化
	var b Book = Book{"Go程序设计语言", "Kernighan", "计算机"}
	fmt.Println(b)
	
	// 使用点号来访问,
	fmt.Println("书的名称:", b.Title)
}

结构体指针

结构体字段可以通过结构体指针来访问。比如可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。

package main

import "fmt"

type Book struct {
	Title   string
	Author  string
	Subject string
}

func main() {
	// 初始化
	var b Book = Book{"Go程序设计语言", "Kernighan", "计算机"}

	// 结构体指针
	var p *Book = &b

	// 通过 (*p).X 来访问其字段 X
	fmt.Println((*p).Title)

	// 更方便的隐式间接引用
	fmt.Println((*p).Title)
}

结构体文法

结构体文法通过直接列出字段的值来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段(字段名的顺序无关)

package main

import "fmt"

type Book struct {
	Title   string
	Author  string
	Subject string
}

var (
	b1 = Book{"Go程序设计语言", "Kernighan", "计算机"}
	b2 = Book{Author: "Kernighan", Title: "Go程序设计语言"}
	p  = &Book{Title: "Go程序设计语言"}
)

func main() {
	fmt.Println(b1)
	fmt.Println(b2)
	fmt.Println(p)
}

数组

Go 语言提供了数组类型的数据结构。 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。

数组表达式:类型 [n]T 表示拥有 n 个 T 类型的值的数组。语法格式:

var variable_name [SIZE] variable_type


var a [10]int
var s [2]string
package main

import "fmt"

func main() {
	var s [2]string
	s[0] = "hello"
	s[1] = "go!"
	
	fmt.Println(s)
}

切片

为什么使用切片?

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用。

Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

所以在实践中,切片比数组更常用。

切片定义

类型 []T 表示一个元素类型为 T 的切片。 第一:可以声明一个未指定大小的数组来定义切片:

var identifier []type

如果是用数组的片段来创建切片,则切片通过两个下标来界定,即一个上界和一个下界(左闭右开),二者以冒号分隔:

a[low : high]
package main

import "fmt"

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	slice := a[1:3]

	fmt.Println(slice)
	// 打印输出:[2 3]
}

第二:使用 make() 函数来创建切片,这是创建动态数组的方式。

var slice1 []type = make([]type, len)

// 也可以简写为
slice1 := make([]type, len)

make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

// len(s)=5
s := make([]int, 5)  

//如果要指定它的容量,需向 make 传入第三个参数
// len(ss)=0, cap(ss)=5
ss := make([]int, 0, 5)
package main

import "fmt"

func main() {
	s := make([]string, 5)
	fmt.Printf("%s 长度=%d 容量=%d %v\n",
		s, len(s), cap(s), s)

	ss := make([]string, 5, 10)
	fmt.Printf("%s 长度=%d 容量=%d %v\n",
		ss, len(ss), cap(ss), ss)
}

// 打印
[    ] 长度=5 容量=5 [    ]
[    ] 长度=5 容量=10 [    ]

切片可包含任何类型,甚至包括其它的切片。

package main

import (
	"fmt"
	"strings"
)

func main() {
	// 创建一个井字板(经典游戏)
	b := [][]string{
		[]string{"_", "_"},
		[]string{"_", "_"},
	}

    // 设置
	b[0][0] = "X"
	b[1][1] = "O"

    // 遍历输出
	for i := 0; i < len(b); i++ {
		fmt.Printf("%s\n", strings.Join(b[i], " "))
	}
}

向切片追加元素

为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

package main

import "fmt"

func main() {
	s := []string{}
	s = append(s, "张三")

	fmt.Printf("长度=%d 容量=%d %v\n", len(s), cap(s), s)
}

切片原理

切片和数组的引用一样,切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。

package main

import "fmt"

func main() {
	persons := [3]string{"张三", "李四", "王五"}

	fmt.Println(persons)
	fmt.Println("----------")

	// : 代表首尾全部元素
	s1 := persons[:]
	s2 := persons[1:2]

	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println("----------")

	// 改名验证
	s1[1] = "改名"
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(persons)
}


//打印结果:
[张三 李四 王五]
----------
[张三 李四 王五]
[李四]
----------
[张三 改名 王五]
[改名]
[张三 改名 王五]

切片遍历

Range 遍历 for 循环的 range 形式可遍历切片,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

package main

import "fmt"

func main() {
	persons := [3]string{"张三", "李四", "王五"}

	for i, v := range persons {
		fmt.Printf("%v = %v\n", i, v)
	}
}

注意:可以将下标或值赋予 _ 来忽略它。

package main

import "fmt"

func main() {
	persons := [3]string{"张三", "李四", "王五"}

	for _, v := range persons {
		fmt.Printf("%v\n", v)
	}
}

切片默认行为

切片下界的默认值为 0,上界则是该切片的长度。所以在进行切片时,你可以利用它的默认行为来忽略上下界。 以下切片是等价的:

var a [10]int
// 等价
slice1 = a[0:10]
slice2 = a[:10]
slice3 = a[0:]
slice4 = a[:]

长度与容量

切片拥有 长度容量

对于切片 s 来说: 切片的长度就是它目前所包含的元素个数,可以使用 len(s) 来获取。 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。可以使用 cap(s) 来获取。

package main

import "fmt"

func main() {
	s := []int{1, 4, 7, 3, 6, 9}

	slice1 := s[:]
	fmt.Printf("长度=%d 容量=%d %v\n", len(slice1), cap(slice1), slice1)

	slice2 := s[2:]
	fmt.Printf("长度=%d 容量=%d %v\n", len(slice2), cap(slice2), slice2)
}

nil 切片

切片的零值是 nil。nil 切片的长度和容量为 0 且没有底层数组。

package main

import "fmt"

func main() {
	var s []string
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

映射

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	persons := make(map[string]Person)
	persons["man"] = Person{"张三", 30}
	persons["woman"] = Person{"小花", 30}

	fmt.Println(persons)
}

映射的文法

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 结构体定义
	persons := map[string]Person{
		"man":   Person{"张三", 30},
		"woman": Person{"小花", 30},
	}
	fmt.Println(persons)
}

增删改查映射

插入或修改元素(在映射 persons 中):

persons[key] = elem

// 比如
persons["man"] = Person{"李四", 22}

获取元素:

elem = m[key]

// 比如
man = persons["man"]

还可以通过双赋值检测某个键是否存在。 如果 key 在 persons 中存在,ok 为 true ;否则 ok 为 false。 如果 key 不在映射中,那么 elem 是该映射元素类型的零值,ok 为 false。

elem, ok = m[key]

// 比如
man, ok = persons["man"]
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 结构体定义
	persons := map[string]Person{
		"man":   Person{"张三", 30},
		"woman": Person{"小花", 30},
	}
	fmt.Println(persons)

	// 修改元素
	persons["woman"] = Person{"小红", 18}
	fmt.Println(persons)

	// 获取元素
	man, ok := persons["man"]
	if ok {
		fmt.Println("man 存在:", man)
	}

	// 删除元素
	delete(persons, "man")
	// 再次获取元素
	m, isExist := persons["man"]
	if isExist {
		fmt.Println("man 元素存在:", m)
	} else {
		fmt.Println("man 元素不存在!")
	}
	fmt.Println(persons)
}



// 打印结果
map[man:{张三 30} woman:{小花 30}]
map[man:{张三 30} woman:{小红 18}]
man 存在: {张三 30}
man 元素不存在!
map[woman:{小红 18}]

函数值

在 GO 中函数也是值,它们可以像其它值一样传递。函数值可以用作函数的参数或返回值。

package main

import "fmt"

// f 作为函数值传递
func computer(f func(int, int) int, a int, b int) int {
	return f(a, b)
}

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

func Sub(x int, y int) int {
	return x - y
}

func main() {
	fmt.Println("传递Add函数:", computer(Add, 10, 20))
	fmt.Println("传递Sub函数:", computer(Add, 10, 20))

}

函数的闭包

Go 函数可以是一个闭包,闭包是一个函数值,如果它引用了其函数体之外的变量,该函数也可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。

例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	// 调用adder获得一个闭包函数
	pos := adder()
	for i := 0; i < 10; i++ {
		// 调用闭包
		fmt.Println(pos(i))
	}
}

练习:斐波纳契闭包 实现一个 fibonacci 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)

package main

import "fmt"

// 返回一个“返回int的函数”
func fibonacci() func() int {
	// 预设初始值,相当于绑定闭包
	back1, back2 := 0, 1
	return func() int {
		// 关键代码
		temp := back1
        // 核心:交换两值
		back1, back2 = back2, (back1 + back2)
		return temp
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

请关注公众号【Java千练】,更多干货文章等你来看!

qrcode_for_gh_e39063348296_258.jpg

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/36783.html

(0)
上一篇 2023-11-13
下一篇 2023-11-13

相关推荐

  • 一种基于Redisson实现简单的分布式定时任务执行方案

    一种基于Redisson实现简单的分布式定时任务执行方案一般在springcloud下单机执行定时任务的代码 但因为微服务下一般是多实例部署,会导致定时任务多个实例同时执行的情况。 通过对EnableScheduling进行分析,在调用定时方法时会把Met

    2023-11-15
    108
  • 温温的go编程学习笔记(1)

    温温的go编程学习笔记(1)“github.com/gogf/gf/v2/os/genv”用法总结:一般不用 new 来新建变量,凡遇到 chan、map 和 slice

    2022-12-14
    124
  • MySQL事务问题「建议收藏」

    MySQL事务问题「建议收藏」事务MySQL事务及其特征事务的概念事务的特性事务的演示事务的细节说明事务的并发问题脏读(Dirtyread)不可重复读幻读事务的隔离级别1.详细介绍+演示2.设置隔离级别3.隔离级别的作用范围4、查看隔离级别MySQL事务及其特征在正式讲解事务之前,我们先来说一下什么是事务。事务(transaction)是用来维护数据库的完整性的,它可以保证一系列的MySQL操作要么全部执行,要么全部不执行我来举几个例子,来帮助大家理解,最经典的就是银行的转帐问题,比如说张三要转账给李四,我们是不是得保证张三

    2023-04-02
    117
  • 深度神经网络压缩与加速技术

    深度神经网络压缩与加速技术随着深度神经网络使用的越来越多,相应的压缩和加速技术也孕育而生。LiveVideoStackCon 2023上海站邀请到了胡浩基教授为我们分享他们实验室的一些实践。

    2023-11-17
    130
  • 数组指针与函数指针

    数组指针与函数指针1. 前言 数组指针和函数指针都是C语言比较难的知识点,尤其是函数指针,并且函数指针在开发中有着巨大的作用。 2. 函数指针语法 定义一个函数指针,并通过函数指针间接调用函数: 通过定义一个函数指针类

    2023-11-12
    112
  • 机器视觉-文化遗产保护与修复中的卷积神经网络应用

    机器视觉-文化遗产保护与修复中的卷积神经网络应用随着科技的不断发展,人工智能技术在各个领域都取得了显著的成果。在文化遗产保护与修复领域,卷积神经网络(Convolutional Neural Networks,简称CNN)作为一种强大的图像识别工具

    2023-11-18
    146
  • IP网络技术的基础知识

    IP网络技术的基础知识在当今数字化的世界中,IP网络技术是无处不在的,它使我们能够连接到互联网、与他人通信以及访问各种在线资源。IP地址(Internet Protocol Address)是IP网络的核心元素之一,它不仅

    2023-11-12
    103
  • 基于JavaFX的贪吃蛇小游戏

    基于JavaFX的贪吃蛇小游戏游戏背景介绍 贪吃蛇游戏是一款经典的小游戏,它的玩法很简单,就是控制蛇吃食物,每吃一个食物蛇的长度就会加一,直到蛇撞到墙壁或者撞到自己时游戏结束,最终的得分是蛇的长度减一。 JavaFX 用Java开

    2023-11-14
    96

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注