花下猫语:本文是学习群内 樱雨楼 小姐姐的投稿。之前已发布过她的一篇作品《当谈论迭代器时,我谈些什么?》,大受好评。本文依然是对比 C++ 与 Python,来探讨编程语言中极其重要的概念。祝大家读有所获,学有所成!

樱雨楼 | 原创作者

豌豆花下猫 | 编辑润色

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。

原文地址:https://mp.weixin.qq.com/s/k0VNL6uyvBHT7PWdzEPROQ

0 引言

指针(Pointer)是 C、C++ 以及 Java、Go 等语言的一个非常核心且重要的概念,而引用(Reference)是在指针的基础上构建出的一个同样重要的概念。

指针对于任何一个编程语言而言都是必须且重要的,虽然 Python 对指针这一概念进行了刻意的模糊与限制,但指针对于 Python 而言依然是一个必须进行深入讨论的话题。

本文基于 C++ 与 Python,讨论了 Python 中与指针及引用相关的一些行为。

1 什么是指针?为什么需要指针?

指针有两重含义:

(1)指代某种数据类型的指针类型,如整形指针类型、指针指针类型

(2)指代一类存放有内存地址的变量,即指针变量

指针的这两重含义是紧密联系的:作为一种变量,通过指针可以获取某个内存地址,从而为访问此地址上的值做好了准备;作为一种类型,其决定了内存地址的正确偏移长度,其应等于当前类型的单位内存大小。

如果一个指针缺少指针类型,即 void *,则显然,其虽然保存了内存地址,但这仅仅是一个起点地址,指针会因为无法获知从起点向后进行的偏移量,从而拒绝解指针操作;而如果一个指针缺少地址,即 nullptr,则其根本无法读取特定位置的内存。

指针存在的意义主要有以下几点:

  • 承载通过 malloc、new、allocator 等获取的动态内存
  • 使得 pass-by-pointer 成为可能

pass-by-pointer 的好处包括但不限于:

  • 避免对实参无意义的值拷贝,大幅提高效率
  • 使得对某个变量的修改能力不局限于变量自身的作用域
  • 使得 swap、移动构造函数、移动赋值运算等操作可以仅针对数据结构内部的指针进行操作,从而避免了对临时对象、移后源等对象的整体内存操作

由此可见,与指针相关的各操作对于编程而言都是必须的或十分重要的。

2 C++中的引用

在 C++ 中,引用具有与指针相似的性质,但更加隐形与严格。C++ 的引用分为以下两种:

2.1 左值引用

左值引用于其初始化阶段绑定到左值,且不存在重新绑定。

左值引用具有与被绑定左值几乎一样的性质,其唯一的区别在于 decltype 声明:

int numA = 0, &lrefA = numA;  // Binding an lvalue
cout << ++lrefA << endl; // Use the lvalue reference as lvalue & rvalue
decltype(lrefA) numB = 1; // Error!

左值引用常用于 pass-by-reference:

void swap(int &numA, int &numB)
{
int tmpNum = numA;
numA = numB;
numB = tmpNum;
} int main()
{
int numA = 1, numB = 2;
swap(numA, numB);
cout << numA << endl << numB << endl; // 2 1
}

2.2 右值引用

右值引用于其初始化阶段绑定到右值,其常用于移动构造函数和移动赋值操作。在这些场合中,移动构造函数和移动赋值操作通过右值引用接管被移动对象。

右值引用与本文内容无关,故这里不再详述。

3 Python中的引用

3.1 Python不存在引用

由上文讨论可知,虽然“引用”对于 Python 而言是一个非常常用的术语,但这显然是不准确的——由于 Python 不存在对左/右值的绑定操作,故不存在左值引用,更不存在右值引用。

3.2 Python的指针操作

不难发现,虽然 Python 没有引用,但其变量的行为和指针的行为具有高度的相似性,这主要体现在以下方面:

  • 在任何情况下(包括赋值、实参传递等)均不存在显式值拷贝,当此种情况发生时,只增加了一次引用计数
  • 变量可以进行重绑定(对应于一个不含顶层 const(top-level const)的指针)
  • 在某些情况下(下文将对此问题进行详细讨论),可通过函数实参修改原值

由此可见,Python 变量更类似于(某种残缺的)指针变量,而不是引用变量。

3.2.1 构造函数返回指针

对于 Python 的描述,有一句非常常见的话:“一切皆对象”。

但在这句话中,有一个很重要的事实常常被人们忽略:对象是一个值,不是一个指针或引用。

所以,这句话的准确描述应该更正为:“一切皆(某种残缺的)指针”。虽然修改后的描述很抽象,但这是更准确的。

而由于对象从构造函数而来,至此我们可知:Python 的构造函数将构造匿名对象,且返回此对象的一个指针。

这是 Python 与指针的第一个重要联系。

用代码描述,对于Python代码:

sampleNum = 0

其不类似于 C++ 代码:

int sampleNum = 0;

而更类似于:

int __tmpNum = 0, *sampleNum = &__tmpNum;

// 或者:
shared_ptr<int> sampleNum(new int(0));

3.2.2 __setitems__操作将隐式解指针

Python 与指针的另一个重要联系在于 Python 的隐式解指针行为。

虽然 Python 不存在显式解指针操作,但(有且仅有)__setitems__操作将进行隐式解指针,通过此方法对变量进行修改等同于通过解指针操作修改变量原值。

此种性质意味着:

  1. 任何不涉及__setitems__的操作都将成为指针重绑定。

对于Python代码:

numList = [None] * 10

# Rebinding
numList = [None] * 5

其相当于:

int *numList = new int[10];

// Rebinding
delete[] numList;
numList = new int[5];
delete[] numList;

由此可见,对 numList 的非__setitems__操作,导致 numList 被绑定到了一个新指针上。

  1. 任何涉及__setitems__的操作都将成为解指针操作。

由于 Python 对哈希表的高度依赖,“涉及__setitems__的操作”在 Python 中实际上是一个非常广泛的行为,这主要包括:

  • 对数组的索引操作
  • 对哈希表的查找操作
  • 涉及__setattr__的操作(由于 Python 将 attribute 存储在哈希表中,所以__setattr__操作最终将是某种__setitems__操作)

我们用一个稍复杂的例子说明这一点:

对于以下Python代码:

class Complex(object):
def __init__(self, real = 0., imag = 0.):
self.real = real
self.imag = imag def __repr__(self):
return '(%.2f, %.2f)' % (self.real, self.imag) def main():
complexObj = Complex(1., 2.)
complexObj.real += 1
complexObj.imag += 1
# (2.00, 3.00)
print(complexObj) if __name__ == '__main__':
main()

其相当于:

class Complex
{
public:
double real, imag;
Complex(double _real = 0., double _imag = 0.): real(_real), imag(_imag) {}
}; ostream &operator<<(ostream &os, const Complex &complexObj)
{
return os << "(" << complexObj.real << ", " << complexObj.imag << ")";
} int main()
{
Complex *complexObj = new Complex(1., 2.);
complexObj->real++;
complexObj->imag++;
cout << *complexObj << endl;
delete complexObj;
return 0;
}

由此可见,无论是 int、float 这种简单的 Python 类型,还是我们自定义的类,其构造行为都类似使用 new 构造对象并返回指针。

且在 Python 中任何涉及“.”和“[]”的操作,都类似于对指针的“->”或“*”解指针操作。

4 后记

本文探讨了 Python 变量与指针、引用两大概念之间的关系,主要论证了“Python 不存在引用 ”以及“Python 变量的行为类似于某种残缺的指针 ”两个论点。

所有论点均系作者个人观点,如有错误,恭迎指正。

公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。

对比 C++ 和 Python,谈谈指针与引用的更多相关文章

  1. 对比C++中的指针和引用

    指针和引用在形式上比较好区分,由于有很多相似的功能,因此在使用上容易混淆.因此有必要对指针和引用进行对比,以便于在使用时使程序正确高效. 1.引用不可以为空,而指针可以为空. 我们知道引用是对象的别名 ...

  2. PyQt(Python+Qt)学习随笔:Qt中的部分类型QString、QList和指针、引用在PyQt中的实现方式

    老猿Python博文目录 老猿Python博客地址 在我们查阅Qt的文档资料时,可以看到Qt中的链表使用的是QList,字符串使用的是QString,但老猿在测试时发现这两个类型PyQt不支持,无法找 ...

  3. python关于入参中,传入的是指针还是引用

    偶然看到别人的代码,发现有的会传入参数之后,做一次copy,试验一下,关于入参中,传入的是指针还是引用先说自己的结论:1.如果传入的是简单的类型,那么传入应该是引用的数值,2.假如传入的是df这种类型 ...

  4. 指针和引用的区别(c/c++)

      http://blog.csdn.net/thisispan/article/details/7456169 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用 ...

  5. C++指针和引用

     ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用是某块内存的别名.  ★ 区别: 1. 指针是一个实体,而引用仅是个别名: 2. 引用使用时无需解引用(*),指 ...

  6. 浅谈C++中指针和引用的区别者之间的区别和用法(转)

    指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个 ...

  7. 浅谈C++中指针和引用的区别

    指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个 ...

  8. 扩展Python模块系列(四)----引用计数问题的处理

    承接上文,发现在使用Python C/C++ API扩展Python模块时,总要在各种各样的地方考虑到引用计数问题,稍不留神可能会导致扩展的模块存在内存泄漏.引用计数问题是C语言扩展Python模块最 ...

  9. python垃圾回收机制:引用计数 VS js垃圾回收机制:标记清除

    js垃圾回收机制:标记清除 Js具有自动垃圾回收机制.垃圾收集器会按照固定的时间间隔周期性的执行. JS中最常见的垃圾回收方式是标记清除. 工作原理 当变量进入环境时,将这个变量标记为"进入 ...

随机推荐

  1. WPF中任意Object的XAML代码格式化输出

    原文:WPF中任意Object的XAML代码格式化输出 有时候,我们需要将WPF中的控件自身的XAML代码输出成文本,那么,我们可以使用System.Windows.Markup.XamlWriter ...

  2. 【WPF】SnapsToDevicePixels与UseLayoutRounding二者到底有什么区别?供参考

    原文:[WPF]SnapsToDevicePixels与UseLayoutRounding二者到底有什么区别?供参考 MSDN上解释了一大堆,二者对比来看,并不能发现什么明显的区别,微软爸爸也不知道多 ...

  3. PostMessage和SendMessage有什么区别?(有EnumChildWindowsProc的例子)

    PostMessage只是把消息放入队列,不管其他程序是否处理都返回,然后继续执行;而SendMessage必须等待其他程序处理消息后才返回,继续执行.PostMessage的返回值表示PostMes ...

  4. DLL编写与调用全解

    DLL编写与调用全解 DELPHI学习   2008-12-23 22:52   阅读8   评论0   字号: 大  中  小 第一章 为什么要使用动态链接库(DLL) top 提起DLL您一定不会 ...

  5. Win8 Metro(C#)数字图像处理--2.45图像雾化效果算法

    原文:Win8 Metro(C#)数字图像处理--2.45图像雾化效果算法 [函数名称]   图像雾化         AtomizationProcess(WriteableBitmap src,i ...

  6. SQLServer 远程服务器不存在,未被指定为有效的发布服务器,或您无权查看可用的发布服务器

    原文:SQLServer 远程服务器不存在,未被指定为有效的发布服务器,或您无权查看可用的发布服务器 创建了事务发布,在初始化时出现错误,查看相关代理信息如下: 日志读取器代理错误: 状态: 0,代码 ...

  7. windows media player 播放视频

    一.新建windows应用程序项目,添加vedioForm窗体 二.在com组件中找到windows media player,添加引用 三.代码如下: public partial class Ve ...

  8. Unity开发概览(HoloLens开发系列)

    本文翻译自:Unity development overview 要开始使用Unity创建全息应用,点此安装包含Unity HoloLens技术预览的开发工具.Unity HoloLens技术预览基于 ...

  9. Linux 桌面玩家指南:19. 深入理解 JavaScript,及其开发调试工具

    特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...

  10. 减少Qt编译时间暨简单Qt裁剪

    本站所有文章由本站和原作者保留一切权力,仅在保留本版权信息.原文链接.原文作者的情况下允许转载,转载请勿删改原文内容, 并不得用于商业用途. 谢谢合作.原文链接:减少Qt编译时间暨简单Qt裁剪 编译一 ...