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

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 lvaluecout << ++lrefA << endl;      // Use the lvalue reference as lvalue & rvaluedecltype(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# RebindingnumList = [None] * 5

其相当于:

int *numList = new int[10];// Rebindingdelete[] 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变量的行为类似于某种残缺的指针”两个论点。

文中所有论点均系作者个人观点,如有错误,恭迎指正。(猫注:本文所有赞赏,归樱雨楼小姐姐所有,鼓励优质原创,支持好文章!)


扫码赞赏她

更多优质文章,等你翻牌
1
当谈论迭代器时,我谈些什么?
2
Python对象的身份迷思:从全体公民到万物皆数
3
Python进阶:切片的误区与高级用法
4
Python 工匠:让函数返回结果的技巧

©著作权归作者所有:来自51CTO博客作者mb5fdcaee2972df的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. Python骚操作:动态定义函数
  2. 一文多图带你看看如何用「对撞指针」思想巧解数组题目
  3. 20张图!3个视频!一文带你搞定「快慢指针」在数组中的应用
  4. 假期玩得开心也不忘充电,学习Python操作JSON,网络数据交换不用愁
  5. LeetCode 题解:一顿操作猛如虎,一看击败百分五
  6. 如何设计一个支持增量操作的栈
  7. 对列和行的操作
  8. 短小精悍,双指针对撞,求解「两数之和 II」
  9. 从一道简单算法题理解快速排序的 partition 操作

随机推荐

  1. android gen文件不生成、R文件报错
  2. Android开发教程大全介绍
  3. Android的多线程限制
  4. Android,似乎没那么友好......
  5. android4.0 添加一个新的android 键值
  6. Android编译系统分析大全
  7. Android 下面的一些命令
  8. Android安装和环境搭建
  9. 2019最新Android常用开源库总结(附带githu
  10. 在android中如何在代码中设置textview的