• Go 基础之指针、map、函数、文件操作、结构体(六)


    指针

    任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。

    比如,“永远不要高估自己”这句话是我的座右铭,我想把它写入程序中,程序一启动这句话是要加载到内存(假设内存地址0x123456),我在程序中把这段话赋值给变量A,把内存地址赋值给变量B。这时候变量B就是一个指针变量。通过变量A和变量B都能找到我的座右铭。

    Go语言中的指针不能进行偏移和运算,因此Go语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)和*(根据地址取值)。

    指针地址和指针类型

    每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int*int64*string

    取变量指针的语法如下:

    ptr := &v    // v的类型为T
    

    其中:

    • v:代表被取地址的变量,类型为T
    • ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针。

    举个例子:

    func main() {
    	a := 10
    	b := &a
    	fmt.Printf("a:%d ptr:%p
    ", a, &a) // a:10 ptr:0xc00001a078
    	fmt.Printf("b:%p type:%T
    ", b, b) // b:0xc00001a078 type:*int
    	fmt.Println(&b)                    // 0xc00000e018
    }
    

    我们来看一下b := &a的图示:

    指针取值

    在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,代码如下。

    func main() {
    	//指针取值
    	a := 10
    	b := &a // 取变量a的地址,将指针保存到b中
    	fmt.Printf("type of b:%T
    ", b)
    	c := *b // 指针取值(根据指针去内存取值)
    	fmt.Printf("type of c:%T
    ", c)
    	fmt.Printf("value of c:%v
    ", c)
    }
    

    输出如下:

    type of b:*int
    type of c:int
    value of c:10
    

    总结: 取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

    变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

    • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
    • 指针变量的值是指针地址。
    • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

    指针传值示例:

    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
    }
    

    new和make

    我们先来看一个例子:

    func main() {
    	var a *int
    	*a = 100
    	fmt.Println(*a)
    
    	var b map[string]int
    	b["沙河娜扎"] = 100
    	fmt.Println(b)
    }
    

    执行上面的代码会引发panic,为什么呢? 在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存。

    new

    new是一个内置的函数,它的函数签名如下:

    func new(Type) *Type
    

    其中,

    • Type表示类型,new函数只接受一个参数,这个参数是一个类型
    • *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。

    new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:

    func main() {
    	a := new(int)
    	b := new(bool)
    	fmt.Printf("%T
    ", a) // *int
    	fmt.Printf("%T
    ", b) // *bool
    	fmt.Println(*a)       // 0
    	fmt.Println(*b)       // false
    }	
    

    本节开始的示例代码中var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

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

    make

    make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:

    func make(t Type, size ...IntegerType) Type
    

    make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。这个我们在上一章中都有说明,关于channel我们会在后续的章节详细说明。

    本节开始的示例中var b map[string]int只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值:

    func main() {
    	var b map[string]int
    	b = make(map[string]int, 10)
    	b["沙河娜扎"] = 100
    	fmt.Println(b)
    }
    

    new与make的区别

    1. 二者都是用来做内存分配的。
    2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
    3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

    创建slice

    make([]Type, len, cap)
    

    cap可以省略。当cap省略时,默认等于len。此外cap >= len >= 0的条件必须成立。

    package main
    
    import "fmt"
    
    func main() {
    	demo := make([]int, 10)
    	fmt.Println("demo:", demo)
    }
    

    创建map

    make(map[keyType] valueType, size)
    

    keyType表示map的key类型,valueType表示map的value类型。size是一个整型参数,表示map的存储能力,该参数可省略。

    package main
    
    import "fmt"
    
    func main() {
    	demo := make(map[string]int)
    	fmt.Println("demo:", demo)
    	// output: demo: map[]
    }
    

    创建channel

    make(chan Type, size)
    

    使用make创建channel,第一个参数是channel类型。size表示缓冲槽大小,是一个可选的大于或等于0的整型参数,默认size = 0。当缓冲槽不为0时,表示通道是一个异步通道。

    package main
    
    import "fmt"
    
    func main() {
    	demo := make(chan int, 10)
        fmt.Println("demo:", demo)
    }
    

    注意事项

    make和new的区别

    new的作用是初始化一个指向类型的指针(*T)。使用new函数来分配空间,传递给new函数的是一个类型,不是一个值。返回的是指向这个新分配的零值的指针。

    make的作用是为slice、map或chan初始化并返回引用(T)。make仅仅用于创建slice、map和channel,并返回它们的实例。

    map

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

    map定义

    Go语言中 map的定义语法如下:

    map[KeyType]ValueType
    

    其中,

    • KeyType:表示键的类型。
    • ValueType:表示键对应的值的类型。

    map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:

    make(map[KeyType]ValueType, [cap])
    

    其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

    map基本使用

    map中的数据都是成对出现的,map的基本使用示例代码如下:

    func main() {
    	scoreMap := make(map[string]int, 8)
    	scoreMap["张三"] = 90
    	scoreMap["小明"] = 100
    	fmt.Println(scoreMap)
    	fmt.Println(scoreMap["小明"])
    	fmt.Printf("type of a:%T
    ", scoreMap)
    }
    

    输出:

    map[小明:100 张三:90]
    100
    type of a:map[string]int
    

    map也支持在声明的时候填充元素,例如:

    func main() {
    	userInfo := map[string]string{
    		"username": "沙河小王子",
    		"password": "123456",
    	}
    	fmt.Println(userInfo) //
    }
    

    判断某个键是否存在

    Go语言中有个判断map中键是否存在的特殊写法,格式如下:

    value, ok := map[key]
    

    举个例子:

    func main() {
    	scoreMap := make(map[string]int)
    	scoreMap["张三"] = 90
    	scoreMap["小明"] = 100
    	// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
    	v, ok := scoreMap["张三"]
    	if ok {
    		fmt.Println(v)
    	} else {
    		fmt.Println("查无此人")
    	}
    }
    

    map的遍历

    Go语言中使用for range遍历map。

    func main() {
    	scoreMap := make(map[string]int)
    	scoreMap["张三"] = 90
    	scoreMap["小明"] = 100
    	scoreMap["娜扎"] = 60
    	for k, v := range scoreMap {
    		fmt.Println(k, v)
    	}
    }
    

    但我们只想遍历key的时候,可以按下面的写法:

    func main() {
    	scoreMap := make(map[string]int)
    	scoreMap["张三"] = 90
    	scoreMap["小明"] = 100
    	scoreMap["娜扎"] = 60
    	for k := range scoreMap {
    		fmt.Println(k)
    	}
    }
    

    注意: 遍历map时的元素顺序与添加键值对的顺序无关。

    使用delete()函数删除键值对

    使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

    delete(map, key)
    

    其中,

    • map:表示要删除键值对的map
    • key:表示要删除的键值对的键

    示例代码如下:

    func main(){
    	scoreMap := make(map[string]int)
    	scoreMap["张三"] = 90
    	scoreMap["小明"] = 100
    	scoreMap["娜扎"] = 60
    	delete(scoreMap, "小明")//将小明:100从map中删除
    	for k,v := range scoreMap{
    		fmt.Println(k, v)
    	}
    }
    

    按照指定顺序遍历map

    func main() {
    	rand.Seed(time.Now().UnixNano()) //初始化随机数种子
    
    	var scoreMap = make(map[string]int, 200)
    
    	for i := 0; i < 100; i++ {
    		key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
    		value := rand.Intn(100)          //生成0~99的随机整数
    		scoreMap[key] = value
    	}
    	//取出map中的所有key存入切片keys
    	var keys = make([]string, 0, 200)
    	for key := range scoreMap {
    		keys = append(keys, key)
    	}
    	//对切片进行排序
    	sort.Strings(keys)
    	//按照排序后的key遍历map
    	for _, key := range keys {
    		fmt.Println(key, scoreMap[key])
    	}
    }
    

    元素为map类型的切片

    下面的代码演示了切片中的元素为map类型时的操作:

    func main() {
    	var mapSlice = make([]map[string]string, 3)
    	for index, value := range mapSlice {
    		fmt.Printf("index:%d value:%v
    ", index, value)
    	}
    	fmt.Println("after init")
    	// 对切片中的map元素进行初始化
    	mapSlice[0] = make(map[string]string, 10)
    	mapSlice[0]["name"] = "小王子"
    	mapSlice[0]["password"] = "123456"
    	mapSlice[0]["address"] = "沙河"
    	for index, value := range mapSlice {
    		fmt.Printf("index:%d value:%v
    ", index, value)
    	}
    }
    

    值为切片类型的map

    下面的代码演示了map中值为切片类型的操作:

    func main() {
    	var sliceMap = make(map[string][]string, 3)
    	fmt.Println(sliceMap)
    	fmt.Println("after init")
    	key := "中国"
    	value, ok := sliceMap[key]
    	if !ok {
    		value = make([]string, 0, 2)
    	}
    	value = append(value, "北京", "上海")
    	sliceMap[key] = value
    	fmt.Println(sliceMap)
    }
    

    练习题

    1. 写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1。
    2. 观察下面代码,写出最终的打印结果。
    func main() {
    	type Map map[string][]int
    	m := make(Map)
    	s := []int{1, 2}
    	s = append(s, 3)
    	fmt.Printf("%+v
    ", s)
    	m["q1mi"] = s
    	s = append(s[:1], s[2:]...)
    	fmt.Printf("%+v
    ", s)
    	fmt.Printf("%+v
    ", m["q1mi"])
    }
    

    go rand seed

    rand.Seed(time.Now().UnixNano())
    
    randInsert := rand.Intn(4) // [0, 4)
    
    
    1.time.Now().Hour() :返回当前时间的小时
    
    2.time.Now().Unix():返回unix时间戳
    
    3.time.Now().UnixNano():64位时间戳
    

    如果每次调rand.Intn()前都调了rand.Seed(x),每次的x相同的话,每次的rand.Intn()也是一样的。

    两种解决方案:

    1. 只调一次rand.Seed():在全局初始化调用一次seed,每次调rand.Intn()前都不再调rand.Seed()。

    2. 调多次rand.Seed(x),但每次x保证不一样:每次使用纳秒级别的种子。强烈不推荐这种,因为高并发的情况下纳秒也可能重复。

    Printf() 是把格式化字符串输出到标准到标准输出(一般是屏幕,可以重定向)
    
    Printf() 是和标准输出文件(stdout)关联的,Fprintf 则没有这个限制
    
    Sprintf() 是把格式化字符串输出到指定的字符串中,可以用一个变量来接受,然后在打印
    
    Fprintf() 是把格式字符串输出到指定的文件设备中,所以参数比Printf 多一个文件指针*File
    
    主要用于文件操作,Fprintf() 是格式化输出到一个 Stream ,通常是一个文件
    
    append函数的使用
    
    作用:在原切片的末尾添加元素
    

    Go语言中type的用法

    Go语言中type的用法:

    1.定义结构体类型
    2.类型别名
    3.定义接口类型
    4.定义函数类型

    1.定义结构体类型

    结构体可用于用户自定义数据类型和进行面向对象编程。

    type Person struct {
            name string
            age int
            sex bool
    }
    

    2.类型别名

    type str string
    str类型与string类型等价
    例子:

    type str string
    func main () {
        var myname str = "Ling"
        fmt.Printf("%s",myname)
    }
    

    3.定义接口

    type Shaper interface {
        Area() float64
    }
    

    接口定义了一个 方法的集合,但是这些方法不包含实现代码,它们是抽象的,接口里也不能包含变量。
    注意实现接口可以是结构体类型,也可以是函数类型

    4.定义函数类型

    type functinTyoe func(int) bool // 声明了一个函数类型
    

    Go语言的语法糖

    go 变量前三个点与后三个点

    做为形参的参数前的三个点意思是可以传0到多个参数
    变量后三个点意思是将一个切片或数组变成一个一个的元素,俗称将数组打散.

    函数

    Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。

    函数定义

    Go语言中定义函数使用func关键字,具体格式如下:

    func 函数名(参数)(返回值){
        函数体
    }
    

    其中:

    • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
    • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
    • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
    • 函数体:实现指定功能的代码块。

    我们先来定义一个求两个数之和的函数:

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

    函数的参数和返回值都是可选的,例如我们可以实现一个既不需要参数也没有返回值的函数:

    func sayHello() {
    	fmt.Println("Hello 沙河")
    }
    

    函数的调用

    定义了函数之后,我们可以通过函数名()的方式调用函数。 例如我们调用上面定义的两个函数,代码如下:

    func main() {
    	sayHello()
    	ret := intSum(10, 20)
    	fmt.Println(ret)
    }
    

    注意,调用有返回值的函数时,可以不接收其返回值。

    参数

    类型简写

    函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:

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

    上面的代码中,intSum函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。

    可变参数

    可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。

    注意:可变参数通常要作为函数的最后一个参数。

    举个例子:

    func intSum2(x ...int) int {
    	fmt.Println(x) //x是一个切片
    	sum := 0
    	for _, v := range x {
    		sum = sum + v
    	}
    	return sum
    }
    

    调用上面的函数:

    ret1 := intSum2()
    ret2 := intSum2(10)
    ret3 := intSum2(10, 20)
    ret4 := intSum2(10, 20, 30)
    fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60
    

    固定参数搭配可变参数使用时,可变参数要放在固定参数的后面,示例代码如下:

    func intSum3(x int, y ...int) int {
    	fmt.Println(x, y)
    	sum := x
    	for _, v := range y {
    		sum = sum + v
    	}
    	return sum
    }
    

    调用上述函数:

    ret5 := intSum3(100)
    ret6 := intSum3(100, 10)
    ret7 := intSum3(100, 10, 20)
    ret8 := intSum3(100, 10, 20, 30)
    fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160
    

    注意:本质上,函数的可变参数是通过切片来实现的。

    返回值

    Go语言中通过return关键字向外输出返回值。

    多返回值

    Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

    举个例子:

    func calc(x, y int) (int, int) {
    	sum := x + y
    	sub := x - y
    	return sum, sub
    }
    

    返回值命名

    函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

    例如:

    func calc(x, y int) (sum, sub int) {
    	sum = x + y
    	sub = x - y
    	return
    }
    

    返回值补充

    当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。

    func someFunc(x string) []int {
    	if x == "" {
    		return nil // 没必要返回[]int{}
    	}
    	...
    }
    
  • 相关阅读:
    自己搭建一个vue项目
    nodejs 后台开发入门
    bootstrap table入门例子
    datatable入门
    猜数字案例
    Cookie
    管理系统案例
    PHP操作数据库(以MySQL为例)
    数据库(以MySQL为例)
    案例:音乐列表
  • 原文地址:https://www.cnblogs.com/caibaotimes/p/14814938.html
Copyright © 2020-2023  润新知