Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(2)

简介: Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】

Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(1)https://developer.aliyun.com/article/1534255

2.1.6、切片的判空

       正因为切片是引用类型的,所以切片之间不能用 == 来进行比较,而且切片的判空不能使用 s == nil 来判断,而是通过 len(s) == 0 来判断。

2.1.7、切片引用

func main(){
    s1 := make([]int,2,10) // [0,0]
    s2 := s1
    s2[0] = 1
    fmt.Println(s1) // [1,0]
    fmt.Println(s2) // [1,0]
}

       可以看到,s1 把自己的内存地址赋值给了 s2,所以当 s2 对切片进行操作的时候,操作的是和 s1 共享的内存地址,所以都受影响。

func main(){
    s1 := make([]int,2,10) // [0,0]
    fmt.Println(s1) // [1,0]
    addOne(s1)
    fmt.Println(s1) // [2,1]
}
func addOne(s []int){
    for i:=0;i<len(s);i++{
        s[i] += 1
    }
}

       再比如这里,当切片作为参数传递进来时,函数中虽然操作的是形参,但是实参也发生了变化,这就是因为切片是一个引用类型,形参和实参指向同一内存地址。

2.1.8、append 函数为切片添加元素

       Go 语言的内部函数 append() 可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

func main(){
    var s1 []int
    s1 = append(s1,1)
    s1 = append(s1,2,3,4)
    fmt.Println(s1) // [1 2 3 4]
        
    s2 := []int{5,6,7}
    s1 = append(s1,s2...)
    fmt.Println(s1) // [1 2 3 4 5 6 7]
}

2.1.9、切片扩容

       切片的底层是数组,而数组的大小是固定的。这也就是为什么上面给切片添加元素时,搞那么复杂( append 函数的结果返回给原来的切片,而不是 切片.append(元素) )。

func main(){
    var s1 []int
    fmt.Printf("size(s1)=%v,cap(s1)=%v,addr=%p \n",len(s1),cap(s1),s1)
    s1 = append(s1,1,2,3,4)
    fmt.Printf("size(s1)=%v,cap(s1)=%v,addr=%p \n",len(s1),cap(s1),s1)
}

运行结果:

size(s1)=0,cap(s1)=0,addr=0x0 
size(s1)=4,cap(s1)=4,addr=0xc000072020

可以看到,每扩容一次,切片的地址就会发生变化。

注意:如果是普通值类型的话,我们要取它的地址的话得配合 & 使用,但是如果是引用类型就不需要!因为引用类型本身存储的就是内存地址,不需要再用取地址符取。

Go语言中引用类型和值类型的处理方式与内存分配有关:

  • 引用类型(如切片、映射、通道、指针以及函数等)的变量存储的是实际数据的引用(或称为指针),即它们存储的是数据所在的内存地址。当你传递一个引用类型的变量给一个函数时,实际上是传递了该变量所引用的数据的地址,因此函数内部对该地址指向的数据进行修改会影响到原始数据。这就是为什么你不需要使用 & 来获取引用类型变量的地址。
  • 值类型(如整型、浮点型、布尔型、字符串以及数组等)的变量直接存储了实际的数据值。当你传递一个值类型的变量给一个函数时,会创建该变量的一个副本并将其传递给函数。这意味着函数内部对副本进行的修改不会影响到原始数据。如果你想让函数能够修改原始数据,你需要传递该变量的地址,这就需要使用 & 来获取值类型变量的地址。

2.1.10、使用 copy 函数覆盖切片

       我们创建一个切片,希望两个切片的元素一致。如果直接赋值给另一个切片时,这个两个切片将指向同一个内存地址,一个修改另一个也会被修改。

       所以,我们可以通过 copy 函数来进行切片的复制:

func main(){
    s1 := []int{1,2,3}
    s2 := []int{4,5,6}
    copy(s2,s1)
    fmt.Println(s1) //[1,2,3]
    fmt.Println(s2) //[1,2,3]
    s2[0] = -1
    fmt.Println(s1) //[1,2,3]
    fmt.Println(s2) //[-1,2,3]
}

2.1.11、删除切片中某个元素

       Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素:

// 删除索引为 2 的元素
func main(){
    s1 := []int{0,1,2,3,4,5}
    s1 = append(s1[:2],s1[3:]...)
    fmt.Println(s1) // [0,1,3,4,5]
}

这样,我们对 append 函数有了更深刻的认识,append(p1,p2) 的意思是:在切片 p1 的基础上添加p2(p2可以是切片也可以是单个或多个元素) 中的元素。

3、指针

       指针指针是一种数据类型,用于存储一个内存地址,该地址指向存储在该内存中的对象。

       区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。要搞明白Go语言中的指针需要先知道3个概念:指针地址指针类型指针取值

3.1、指针地址和指针类型

声明指针变量(指针变量的值是地址):

    a := 10
    // 声明一个指针变量b,它的值为a的地址
    b := &a

上面我们声明了一个指针变量 b,它的指针类型为 *int 。

    a := 10
    b := &a
    fmt.Printf("a=%d addr=%p \n",a,&a) // a=10 addr=0xc000014028 
    fmt.Printf("addr=%p type=%T \n",b,b) // addr=0xc000014028 type=*int 
    fmt.Println(&b) // 0xc00000e030

       通过上面的结果可以看到,因为 b 存储了 a 的地址,所以 b 的值和 a 的地址是一样的,而 b 也有自己的地址。

3.2、指针取值

    a := 10
    b := &a
    fmt.Printf("type of b: %T \n",b) // type of b: *int 
    c := *b
    fmt.Printf("type of c: %T \n",c) // type of c: int 
    fmt.Printf("value of c = %v ",c) // value of c = 10 

可以看到,变量 c 是数值类型,它把指针变量 b 的值( a 的内存地址)对应的值了取出来。

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

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

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

3.3、指针传值

       之前我们知道值类型的变量传递给函数后无法被操作,因为函数中的形参和实参的地址是不同的。所以,学了指针之后,我们可以通过给函数传递指针地址来保证形参和实参操作的是同一个地址对应的值。

func main(){
    a := 10
    fmt.Println(a) // 10
    addOne(&a)
    fmt.Println(a) // 11
}
func addOne(num *int){
    *num += 1
}

3.4、new 和 make

    var b map[string]int
    b["李大喜"] = 22
    fmt.Println(b)

       对于上面的代码,运行会直接异常:panic: assignment to entry in nil map。这是因为 b 是引用类型(map),我们还没有给它分配内存空间就直接使用了。而值类型是不需要分配内存,因为我们在声明的时候会有默认值。

       而要分配内存,就引出来 Go 语言中的new和make,它俩都是 Go 内建的两个函数,主要用来分配内存。

3.4.1、new

new 函数在源码中是这样的:

func new(Type) *Type
  • Type 代表类型,new 函数的参数是一个类型,而一般的函数参数是值。
  • *Type 代表类型指针,new 函数返回一个指向该类型的内存地址的指针。

       new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的初始值:

func main(){
    // 声明一个int类型指针 该指针并没有初始值,因为指针的值是其它变量的内存地址
    var a *int
    // 为指针a开辟内存空间,默认指向0的地址值
    a = new(int)
    // 取出指针的值
    fmt.Println(*a) // 0
    *a = 10
    fmt.Println(*a) // 10
}

3.4.2、make

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

func make(t Type, size ...IntegerType) Type
  • t Type:表示传入一个 Type 类型的变量 t
  • size... IntegerType:表示传入一个或多个整型的值

       我们之前在动态创建切片(相对的是通过数组创建切片)的时候就是使用的 make。make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。

    var score map[string]int
    score = make(map[string]int,10)
    score["小明"] = 98
    fmt.Println(len(score)) // 1
    fmt.Println(score) // map[小明:98]

这里,我们通过 make 函数为 map 开辟了 10 个内存空间,并使用了一个内存空间。

3.4.3、new 和 make 的区别

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

4、map

4.1、 map 定义

map 的定义:

map[KeyType]ValueType

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

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

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

比如:

users := make(map[string]int)

Go 语言基础之指针、复合类型【数组、切片、指针、map、struct】(3)https://developer.aliyun.com/article/1534260

相关文章
|
18小时前
数组方法中的`forEach()`方法和`map()`方法有什么区别?
数组方法中的`forEach()`方法和`map()`方法有什么区别?
|
18小时前
|
索引
ES5常见的数组方法:forEach ,map ,filter ,some ,every ,reduce (除了forEach,其他都有回调,都有return)
ES5常见的数组方法:forEach ,map ,filter ,some ,every ,reduce (除了forEach,其他都有回调,都有return)
|
23小时前
|
JavaScript 前端开发
JavaScript 中 五种迭代数组的方法 every some map filter forEach
本文介绍了JavaScript中五种常用数组迭代方法:every、some、filter、map和forEach,并通过示例代码展示了它们的基本用法和区别。
|
1天前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
30天前
|
JavaScript 前端开发 索引
JS中常用的数组迭代方法(filter,forEach,map,every,some,find,findIndex)
这段代码和说明介绍了JavaScript中数组的一些常用方法。函数接收三个参数:`item`(数组项的值)、`index`(项的位置,可选)和`array`(数组本身,可选)。示例展示了如何使用`filter()`过滤非空项、`forEach()`遍历数组、`map()`处理并返回新数组、`every()`检查所有元素是否满足条件、`some()`检查是否存在满足条件的元素、`find()`获取首个符合条件的元素值以及`findIndex()`获取其索引位置。这些方法都不会修改原数组。
JS中常用的数组迭代方法(filter,forEach,map,every,some,find,findIndex)
|
24天前
|
Go
Golang语言高级数据类型之指针篇
这篇文章详细讲解了Golang语言中的指针概念、指针地址和类型、定义指针变量、指针的细节操作、指针传值,以及内置函数new和make的用法和它们之间的区别。
15 0
|
27天前
|
安全 Go
|
27天前
|
存储 安全 程序员
|
27天前
|
编译器 Go 开发者
Go 在编译时评估隐式类型的优点详解
【8月更文挑战第31天】
24 0
|
27天前
|
存储 编译器 Go