在我不得不用 C/C++ 编写的一些简单应用程序中,我看到使用指针解决某些任务很容易。现在,对另一种语言更感兴趣:Python,我注意到没有这个概念。这次缺席是因为什么?Python 是一种非常强大且使用过的语言,那么,什么概念可以取代它呢?它是否隐含在数据类型、赋值、类的实例化中?
一个非常简单的例子是,在 C 中我们可以编写如下代码:
#include <stdio.h>
int main(void) {
// your code goes here
int a = 5;
int *b = &a;
printf("a = %d; b = %d\n", a, *b); // (1)
a = 6;
printf("a = %d; b = %d\n", a, *b); // (2)
return 0;
}
(1): a = 5; b = 5
(2): a = 6; b = 6
b
指向的内存地址a
,任何修改a
都可以通过取消引用来观察b
。任何间接赋值都会*b = <valor>;
修改a
.
但是,在 Python 中:
a = 5
b = a
print "a = {0:d}; b = {1:d}".format(a,b) # (1)
b is a # resultado: True
a = 6
print "a = {0:d}; b = {1:d}".format(a,b) # (2)
b is a # resultado: False
(1): a = 5; b = 5
(2): a = 6; b = 5
开头a
和b
指代的是同一个对象。然后当a
它被修改时,会创建一个新对象;因此,两者都指代不同的对象和不同的值。
没有办法用这种数据类型来做 C 在 Python 中所做的事情,但是可以用可变数据类型做类似的事情;但是,只有当我们对可变数据进行内部修改时才有可能,例如:更改列表元素的值。
a = [1, 2]
b = a
print b is a # resultado: True
b.append(3)
print b is a # resultado: True
a = [4, 5]
print b is a # resultado: False
它的缺失是因为指针的显式使用是 C 等低级语言的一个特征。Python 等高级语言避免使用它,以使其使用更容易、更灵活,如并且不必知道数据模型的细节。
仅仅因为 Python 程序员不必处理指针并不意味着解释器不使用它们。事实上,他隐含地大量使用它们。
在 Python 中,一切都是在动态(自动维护)内存中创建的对象。当你调用一个函数时,参数通过它们的指针传递。这就是所谓的对象调用约定。同样,如果你分配
a = b
,a
它保存的是 的指针b
。所以所有的变量都是指向对象的指针,它们被隐式处理。我们必须区分的是不可变对象和可变对象。
x = 2015
将创建一个完整的对象并x
指向它,但该对象的内容不能被修改。如果您随后分配x = 2016
,它将在内部执行的是使用新内容创建一个新对象。v = [1]
然后调用v.append(2)
,v
它仍然会指向同一个对象,但它的内容会改变。简而言之,在运行此代码时:
结果将是:
在 C 中,指针通常满足三个需求:引用动态保留的结构、通过引用将参数传递给函数或遍历集合。
在 Python 和一般自记忆对象语言的情况下,变量的作用是引用动态创建的结构:可以随时创建对象的实例。
一般来说,对象存储在进程的动态内存中,变量是对它们的引用:几乎几乎引用都是指针的抽象,还有更多的属性。
出于这个原因,参数传递总是通过引用完成的,因此不需要指针。
最后,在对象语言中还有迭代器对象,它暴露了一个更高级别的接口,用于遍历数据集合。
从进程内存的细节中抽象是语言所追求的东西,这就是为什么不需要指针的原因:设计使然。
@GuillermoRuiz 的回答对我来说似乎很棒,但我想深入研究一些关于可变性和不变性的细节,这些细节一开始常常令人困惑,但如果你记住它们都是指针,它们就很清楚了。
更改列表中的项目
列表是“可变的”这一事实不仅意味着我们可以向其中添加元素,还意味着我们可以更改存储的值。
实际上,作为纯粹主义者,该列表仅包含指向相关数据的“指针”。也就是说,这样的列表:
它实际上包含三个指针,每个指针指向一个整数,各自的值
1
,2
和3
。如果我们现在改变第一个元素:打印列表时,我们将看到:
这并不意味着列表的第一个元素已被替换为 100(这在 C 数组中是正确的),而是已经创建了一个整数类型的新对象,并且位于第一个元素中的指针指向 a 的列表 list
1
现在指向100
。前1
一个变为“取消引用”,稍后将由垃圾收集器从内存中删除。清单副本
另一方面,变量实际上是指针(或者如果您更喜欢,引用)这一事实意味着以下赋值:
不复制 的元素
a
,而只是分配给b
指针的副本。a
. 也就是说,a
实际上b
指向同一个列表。所以如果我们这样做:就像我们做的一样
a[0] = 50
。为了检查两个变量是否“指向”相同的数据,Python 提供了比较器
is
:如果我们不希望它们指向同一个,而是作为一个副本(在内存中的不同位置),我们可以这样实现:
或者这样:
在这两种情况中的任何一种情况下:
运算符
==
将 的元素a
与的元素进行比较b
,同时is
将的元素与a
的元素进行比较b
,这实际上包括比较每个内存地址所指的内存地址。列表作为参数
上面还解释了为什么一个函数可以修改它作为参数接收的列表元素:
在不可变的元组中,这是无法做到的。
不变性,但不是那么多
但要注意,元组是不可变的这一事实仅意味着 的值不能更改
tupla[i]
为另一个值,但如果它tupla[i]
是一个列表,则该列表的值可以更改:这有点 hack,但如果你真的需要 1 个变量和一个“指针”,你可以这样做:
我认为这是不必要的,无论如何,如果有人知道更好的方法,请告诉我!:D