2024-02-16
学习
00
请注意,本文编写于 483 天前,最后修改于 291 天前,其中某些信息可能已经过时。

目录

指针
可变和不可变数据
Map
结构体

指针

可变和不可变数据

Python 里面是没有指针的概念的,所以学习到 Go 的指针这个章节的时候我一直都是一知半解的,虽然看了不少资料也问了 ChatGPT 很多次,但是到现在也还没完全弄清楚到底什么时候应该用指针什么时候不能使用指针。

Go 语言拥有四类主要的数据类型:

基本类型: 涵盖了各种数字类型(如整数类型的 int、uint 等,浮点数类型的 float32、float64 等)、字符串类型(用于表示文本数据)以及布尔类型(true 和 false 两种取值,用于表示逻辑条件)。

聚合类型: 其中包括数组(具有固定长度且元素类型相同的有序集合)和结构体(将不同类型的数据组合在一起形成的自定义复合数据类型)。

引用类型: 包含了指针(用于指向其他变量的内存地址)、切片(动态大小的数组视图)、映射(键值对的数据结构)、函数(可作为参数传递和返回)以及通道(用于 goroutine 之间的通信)。

接口类型: 接口定义了一组方法的签名,任何类型只要实现了这些方法就满足该接口。

在 Python 中,对于数据类型存在可变类型和不可变类型的划分。这种可变和不可变的特性实际上与 Go 语言中的指针存在一定的关联。具体来说,当将一个变量作为参数传递给一个函数时,如果在函数内部能够直接修改这个变量本身,那么它就是可变类型;反之,如果在函数内部无法修改该变量本身,那它就是不可变类型。

例如,在 Python 中,以下被视为不可变类型:数字(如整数和浮点数)、字符串(一旦创建其内容无法更改)、元组(元素固定且不可修改)、布尔值等。而像列表、字典和集合等则属于可变类型。

关于在什么场景下应该使用指针,以下是两种常见的情况: 第一种情况,当传递的参数不允许直接被修改,但又希望在函数内部能够改变其值时,使用指针就能够实现对参数本身值的修改。这在需要对传入的参数进行原地修改以达到某种效果的场景中非常有用。

第二种情况,如果传递的参数是大规模的数据,直接传递值会导致大量的内存拷贝,从而带来显著的性能开销。此时,使用指针进行传递可以有效地避免这种值拷贝的操作,减少内存消耗和提高程序的运行效率。

场景1的例子很容易理解:

go
func modifyValue(value int) { // 修改不会影响原始值 value = 100 } func modifyWithPointer(ptr *int) { // 修改会影响原始值 *ptr = 100 } func main() { x := 42 modifyValue(x) fmt.Println(x) // 输出: 42 y := 42 modifyWithPointer(&y) fmt.Println(y) // 输出: 100 }

整数本身是一种不可变类型,但是通过指针,就是在函数中修改这个变量本身,这个在 Python 里面是无法做到的,这也是很典型的一个指针使用场景。

场景2的例子:

go
package main import "fmt" func modifyLargeData(data *[]int) { // 在函数内部修改大型切片 for i := range *data { (*data)[i] *= 2 } } func main() { // 创建一个大型切片 largeData := make([]int, 1000000) for i := range largeData { largeData[i] = i + 1 } // 通过指针传递大型切片 modifyLargeData(&largeData) // 打印修改后的切片部分内容 fmt.Println(largeData[:10]) // 输出: [2 4 6 8 10 12 14 16 18 20] }

在这个例子中,modifyLargeData 函数接收一个指向大型切片的指针,并在函数内部修改了切片的值。通过传递指针而不是切片本身,避免了将整个大型切片复制到函数内部的开销,提高了程序的性能。

需要注意的是,这样的优化一般在处理大型数据时才会显著体现。在处理小型数据或者基本类型时,Go 的值传递通常也足够高效。

哪些场景可以不用指针

1、只使用参数的值

很多时候函数只需要使用参数的值,而不需要修改参数本身,此时可以不用指针。

2、传递可修改的类型

切片、映射、结构体、通道: 这些引用类型本身已经具有引用传递的特性,传递它们时无需使用指针。函数内对切片、映射或通道的修改会影响原始数据。

go
func modifySlice(slice []int) { // 修改会影响原始切片 slice[0] = 100 } func main() { data := []int{1, 2, 3} modifySlice(data) fmt.Println(data) // 输出: [100 2 3] }

这其实跟 Python 一样,这种可变类型被当做参数传递到函数里面,在函数内部修改参数会导致参数本身被修改。

Map

Map 就等于 Python 里面的字典,只是由于 Go 语言对于类型的要求,必须在定义的时候定义好 Map 的键值对的类型,这个不如 Python 灵活(当然,我后面学习到空接口就知道了 Go 也有比较灵活的类型)。

如下就是一个值可以是任意类型的 map 的例子:

go
package main import "fmt" func main() { m1 := map[string]interface{}{} fmt.Println(m1) m2 := make(map[string]interface{}) fmt.Println(m2) }

Map 也是无序的,这个跟 Python 一样。

结构体

结构体就是 Python 里面类的概念。构造函数要自己实现,构造函数可以返回类的实例的指针,也可以直接返回类的实例。

Python 里面类的函数在 Go 里面使用方法实现,方法的接受者就是类的指针,可以理解为 Python 里面的 self 的作用。

将 Go 的代码跟 Python 代码对照着看就会很清晰:

Go
package main import "fmt" // Animal 类型 type Animal struct { Name string } // NewAnimal 初始化方法,返回指针 func NewAnimal(name string) *Animal { return &Animal{Name: name} } // makeSound 方法,用于发出声音 func (a *Animal) makeSound() { fmt.Printf("%s makes some generic animal sound\n", a.Name) } // Dog 类型,继承自 Animal type Dog struct { *Animal Speed int } // NewDog 初始化方法,返回指针 func NewDog(name string, speed int) *Dog { return &Dog{Animal: NewAnimal(name), Speed: speed} } // makeSound 方法的重写,对于 Dog 类型 func (d *Dog) makeSound() { fmt.Printf("%s says Woof! Woof!\n", d.Name) } // run 方法,用于表示狗狗跑的行为 func (d *Dog) run() { fmt.Printf("%s is running at speed %d km/h\n", d.Name, d.Speed) } func main() { // 使用例子 animal := NewAnimal("Generic Animal") animal.makeSound() // 输出: Generic Animal makes some generic animal sound dog := NewDog("Buddy", 20) dog.makeSound() // 输出: Buddy says Woof! Woof! dog.run() // 输出: Buddy is running at speed 20 km/h }
python
class Animal: def __init__(self, name): self.name = name def make_sound(self): print(f"{self.name} makes some generic animal sound") class Dog(Animal): def __init__(self, name, speed): super().__init__(name) self.speed = speed def make_sound(self): print(f"{self.name} says Woof! Woof!") def run(self): print(f"{self.name} is running at speed {self.speed} km/h") # 使用例子 animal = Animal("Generic Animal") animal.make_sound() # 输出: Generic Animal makes some generic animal sound dog = Dog("Buddy", 20) dog.make_sound() # 输出: Buddy says Woof! Woof! dog.run() # 输出: Buddy is running at speed 20 km/h

本文作者:han

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!