Python 之 指针(Pointers)的理解与应用

Python 之 指针(Pointers)的理解与应用

  • 1. 变量与对象的引用关系
  • 2. 可变对象与不可变对象
  • 3. 模拟指针操作
  • 4. 函数参数传递机制
  • 5. “空指针”“双指针”的详解和应用场景
  • 6. 垃圾回收与引用计数
  • 7. 应用场景

在 Python 中,虽然没有显式的指针概念(如 C/C++ 中的 int* p),但所有变量本质上都是对对象的引用(类似于指针的抽象)。理解这一点对掌握 Python 的内存管理、参数传递和可变/不可变对象的行为至关重要。

1. 变量与对象的引用关系

当你在 Python 中赋值一个变量时,变量名实际上是一个指向对象的引用(内存地址的抽象)。例如:

a = 10
b = a
  • ab 都指向同一个整数对象 10 的内存地址。

  • 使用 id(a)id(b) 可以验证它们指向同一地址。

a = 10
b = a

print("\n", hex(id(a)))
print("\n", hex(id(b)))
print("\n", id(a) == id(b))  # 输出 True

2. 可变对象与不可变对象

  • 不可变对象(如 int, str, tuple)无法在原地修改。重新赋值会创建新对象:
a = 5
a += 1  # 新对象 6 被创建,a 指向新地址

不可变对象 - 常数 int 示例

num1 = 11
num2 = num1

print("Before value is updated:")
print("num1 = ", num1, "num1内存地址", hex(id(num1)))
print("num2 = ", num2, "num2内存地址", hex(id(num2)))

num1 = 22

print("\nAfter value is updated:")
print("num1 = ", num1, "num1内存地址", hex(id(num1)))
print("num2 = ", num2, "num2内存地址", hex(id(num2)))
  • 可变对象(如 list, dict, set)可以在原地修改,所有引用它的变量都会“看到”变化:
list1 = [1, 2, 3]
list2 = list1
list2.append(4)
print(list1)  # 输出 [1, 2, 3, 4]

可变对象 - 字典 dict 示例

dict1 = { 'value': 11 }
dict2 = dict1 

print("Before value is updated:")
print("dict1 = ", dict1, "dict1内存地址", hex(id(dict1)))
print("dict2 = ", dict2, "dict2内存地址", hex(id(dict2)))

dict1 = { 'value': 22 }

print("\nAfter value is updated:")
print("dict1 = ", dict1, "dict1内存地址", hex(id(dict1)))
print("dict2 = ", dict2, "dict2内存地址", hex(id(dict2)))

3. 模拟指针操作

虽然 Python 没有显式指针,但可以通过以下方式模拟类似行为:

方法 1:使用可变容器包裹值

def modify_value(wrapper):
    wrapper[0] += 1  # 修改容器内的值

value = [10]  # 用列表包裹整数
modify_value(value)
print(value[0])  # 输出 11

方法 2:使用 ctypes 模块(低级操作,慎用)

import ctypes

# 定义一个整数指针
value = ctypes.c_int(10)
p = ctypes.pointer(value)
p.contents.value = 20
print(value.value)  # 输出 20

方法 3:自定义类封装值

class Pointer:
    def __init__(self, value):
        self.value = value

p = Pointer(10)
p.value = 20
print(p.value)  # 输出 20

4. 函数参数传递机制

Python 的函数参数传递本质上是“传递对象引用”:

  • 不可变对象作为参数时,函数内修改会创建新对象,不影响外部变量:
def func(x):
    x += 1  # 新对象,不影响外部
    
a = 10
func(a)
print(a)  # 输出 10
  • 可变对象作为参数时,函数内修改内容会影响外部变量:
def func(lst):
    lst.append(4)  # 原地修改
    
my_list = [1, 2, 3]
func(my_list)
print(my_list)  # 输出 [1, 2, 3, 4]

5. “空指针”“双指针”的详解和应用场景

  • 空指针(Null Pointer)
    在 Python 中,空指针对应的是 None,表示“没有指向任何对象”。
    所有变量在未赋值时默认指向 None,类似于 C/C++ 的 NULLnullptr

    示例:

    p = None  # 空指针
    if p is None:
        print("p 是空指针")
    
    # 对象销毁后引用变为 None
    a = [1, 2, 3]
    b = a
    del a      # 删除 a 的引用,但 b 仍指向原对象
    del b      # 删除最后一个引用,对象被回收
    
  • 双指针(Double Pointer)
    双指针通常用于需要通过间接引用修改对象的场景(例如修改指针的指向)。Python 中可以通过以下方式模拟:

    方法 1:使用可变容器(如列表)
    通过列表包裹值,间接修改内部的值:

    def change_pointer(ptr):
        ptr[0] = "new value"  # 修改指针指向的值
    
    # 用列表模拟指针
    pointer = ["old value"]
    change_pointer(pointer)
    print(pointer[0])  # 输出 "new value"
    

    方法 2:自定义类封装指针
    通过类属性模拟指针的间接操作:

    class Pointer:
        def __init__(self, value):
            self.value = value
    
    p1 = Pointer(10)
    p2 = p1           # p2 和 p1 指向同一个对象
    p2.value = 20      # 修改值
    print(p1.value)    # 输出 20
    
  • 双指针的典型应用场景
    双指针在算法和数据结构中非常常见,例如:

    (1) 链表操作
    反转链表时,通过双指针(快慢指针)修改节点指向:

    class ListNode:
        def __init__(self, val=0):
            self.val = val
            self.next = None
    
    # 反转链表(双指针法)
    def reverse_list(head):
        prev = None
        curr = head
        while curr:
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node
        return prev
    

    (2) 数组遍历
    左右指针处理有序数组(如两数之和):

    def two_sum(nums, target):
        left, right = 0, len(nums) - 1
        while left < right:
            sum_val = nums[left] + nums[right]
            if sum_val == target:
                return [left, right]
            elif sum_val < target:
                left += 1
            else:
                right -= 1
        return []
    

    (3) 树结构操作
    在二叉树中修改子树结构时,需要双指针传递父节点和子节点的引用:

    class TreeNode:
    	def __init__(self, val=0):
    	    self.val = val
    	    self.left = None
    	    self.right = None
    	
    	def delete_node(root, key):
    	    if not root:
    	        return None
    	    if root.val == key:
    	        # 通过双指针重新连接子树
    	        if not root.right:
    	            return root.left
    	        if not root.left:
    	            return root.right
    	        # 找到右子树的最小节点
    	        min_node = root.right
    	        while min_node.left:
    	            min_node = min_node.left
    	        root.val = min_node.val
    	        root.right = delete_node(root.right, min_node.val)
    	    elif root.val > key:
    	        root.left = delete_node(root.left, key)
    	    else:
    	        root.right = delete_node(root.right, key)
    	    return root
    
  • 总结

    空指针:用 None 表示未指向任何对象。
    双指针:通过可变容器或自定义类间接操作对象。
    应用场景:链表、数组、树结构操作等需要间接修改引用的场景。

6. 垃圾回收与引用计数

Python 使用引用计数和垃圾回收机制管理内存:

  • 当对象的引用计数为 0 时,内存会被运行的Garbage Collection自动回收。

  • 避免循环引用(如两个对象互相引用),否则可能导致内存泄漏。

7. 应用场景

  • 共享状态:多个对象通过引用同一可变对象共享数据。

  • 函数副作用:通过传递可变对象让函数修改外部状态。

  • 高效内存使用:避免复制大对象(如传递列表而非创建新副本)。

你可能感兴趣的:(python,java,前端)