C以及Python中的引用,指针的区别

引用:引用是一个变量的另一个名字,又称别名。定义方式:
int a=10;
int &b=a;在这里,意思就是给a变量起了一个新名字b,因此b不可再次被重新定义。
引用必须初始化,无空引用,并且引用不分等级。
引用与指针的异同点:
相同点:在引用中 int &b=a;这一句代码可被编译器看做int * const b=&a;//加上const的作用是表明指针b的自身的值(a的地址)不能改变,而指向的值(a的值)可以改变。也就是说引用相当于指针,引用与指针在编译时的方法一样,指向的都是变量的内存地址
不同点:1.在定义方式中引用储存的是值,而指针是一个变量的拷贝,存储的是地址。
2.引用只能对已经存在的变量或对象实现引用,而指针则不需要,可以定义为空。
3.在函数的传参中,如果传递的是一个引用,意味着这个变量或对象已经存在了;如果传递的是一个指针,则不能判断这个指针是不是有效的,是不是空的,因此在函数体 中大多数都得进行指针是否为空的判断。但是虽然引用较为安全,但是利用指针来传参效率较快。
4.引用是直接访问变量,不用分配自己的内存空间,而指针是间接访问,需要定义,需要有自己的内存空间。
例:交换函数swap()

void swap(int &a,int &b)
{
   int temp=a;
   a=b;
   b=temp;
}
void main()
{
   int x=10,y=20;
   swap(x,y);
}

等价指针为:

void swap(int *const a,int *const b)
{
   int tmp=*a;
   *a=*b;
   *b=temp;
}
void main()
{
int x=10,y=20;
swap(&x,&y);
}

##################################################################################################

Python传值方式

Python不支持显式支持引用或者指针的使用,它的函数参数传递可以看做是传值与传引用的一种综合形式,传递对象的引用,或者认为是传递地址。Python中的等号可以看做是引用或者绑定一个对象的过程,其可以不断换绑,且只有当换绑之后,它才不会对原引用对象造成影响,而并不是C中的数据赋值。而且,Python中对象还可以分为可变对象与不可变对象,关于这点可以参考我的github:https://github.com/edwardzcl/my_collection
以及我的另一篇博客:https://blog.csdn.net/edward_zcl/article/details/88382319

以下内容参考自:https://mp.weixin.qq.com/s/eE8u4mpkRz_FOY5CxVfWog

前言

Python的函数参数是传值 (pass by value) 还是传引用 (pass by reference) 呢? 这是我们在学习的过程中会纠结的一点,网上的总结基本有以下三点:

  • 传引用

  • 传值

  • 可变对象 (mutable object) 传引用,不可变对象 (immutable object) 传值

在下结论之前,我们需要了解引用传递和传值的概念:

  • 值传递: 是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数

  • 引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数

事实上,我认为造成大家对Python传参方式困惑的一个很重要原因是:试图将已建立的术语与其它编程语言相匹配。对于重复性的事物和概念,我们往往会使用简化的术语来表示,这样可以做到统一,降低沟通成本。就如线程 thread 之于C++,Java和Python。但不同的地方是Python中的"引用"概念与在C++和Java中是有区别的。

引用在Python和C++中的一些区别

C++:引用就是变量的别名,对引用的操作与对变量的直接操作一致。定义一个引用时,必须被初始化,绑定到一个变量对象上。绑定之后,无法令这个引用重新绑定到另外的变量对象上。

例一:

int a = 1024, &ra = a;
printf("a = %d ra = %d\r\n", a, ra);
printf("address of a=0x%08x address of ra=0x%08x\r\n", &a, &ra);

// cmd output
// a = 1024 ra = 1024
// address of a=0x00f9f760 address of ra=0x00f9f760

例一中,整形变量 a 的地址和引用 ra 的地址相同。引用是对象的别名,指向已经存在的对象,编译器一般不会为引用分配内存。使用 sizeof(ra) 获取到的也只是引用指向对象 a 的内存大小。

与C++不同,Python中的引用是通过赋值 = (assignment) 隐式实现的。Python中的引用还可以重新指定引用的对象,改变其中任何一个变量的引用,都不会影响其它变量对原对象的引用。

例二:

>>> a = 1024
>>> ra = a
>>> print("a = %d ra = %d\r\n" % (a, ra))
a = 1024 ra = 1024
>>> print("id(a)=%d id(ra)=%d" % (id(a), id(ra)))
id(a)=67777200 id(ra)=67777200
>>> b = 3
>>> ra = b
>>> print("id(a)=%d id(ra)=%d" % (id(a), id(ra)))
id(a)=67777200 id(ra)=255645904

例二中, ara 指向同一个对象 1024,两者都是对同一个对象 1024 的引用。ra 引用新的对象 ba 仍然引用对象1024。

在C++中对引用重新赋值,相当于对原对象赋值,而在Python中则是更改引用的对象。

# C
int a = 1024, &ra = a;
ra = 1; # equals "a = 1"

#
 Python
a = 1
ra = a
ra = 2 # equals "a = 1, ra = 2"

变量不是对象

哈姆雷特不是莎士比亚写的,是被人写的。这句话听起来是不是很奇怪? 让我们来尝试使用Python的方式理解这句话的含义,它是被一个人写的,人的名字叫做莎士比亚,当然我们也可以将这个人称为 Shakespeare。不知道你是否理解了这个含义,哈姆雷特是被人写的,但是我们可以通过多个名字指向这个人。

Python中的变量在我再看来就是姓名,人就是这个对象。我们通过名字找到这个人,与他沟通和交互。所有的话都是表达一个观点,变量不是对象

lst = []

[] 是一个空列表,lst 是一个指向空列表的变量,变量本身并不是空列表。我使用方框表示变量,椭圆表示对象,不论它是否形象,让人能够理解区别才是重点。

在这里插入图片描述

Python 是传值的

在传值的方式下,函数 function 接收到的是调用者 caller 传给它的参数对象的拷贝副本, 这个副本占有新的内存空间。

在这里插入图片描述

函数接收到了原对象的副本,在函数内部对变量的任何操作,都不会影响到外部。两者只是拥有相同的值,除此之外没有任何关系,是相互独立的。让我们尝试重新赋值:

在这里插入图片描述

在函数内外,变量之间应该完全没有任何影响,类似的还有:

在这里插入图片描述

Python 是传引用的

在传引用的方式下中,变量直接传递到函数中,它的内容(由变量表示的对象)隐式地随函数一起传递。在函数上下文中,参数本质上是调用者传入的变量的完整别名。它们指向内存中完全相同的对象。

在这里插入图片描述

因此,函数 function 对它传递进来的变量或对象所做的任何操作都将对调用者 caller 可见。例如,函数可以完全改变变量 lst 的内容,并将其指向一个新的对象。调用者也会知道自己的 lst 指向了新的对象。

在这里插入图片描述

函数还可以通过不重新分配对象的方式修改它,就像下面这样:

在这里插入图片描述

Python 不传值也不传引用

不幸的是,上面的两个结论都是错误的。如果你实际运行了代码,你会很快发现这一点。比如在传值假设的代码中,lst.append[1] 反作用到了函数外的对象。这与我们之前说的(形参是实参的副本,两者完全独立)不符。

def list_append(lst):
    lst.append(1)

lst = [0]
list_append(lst)
print(lst)

# shell out
# [0, 1]

按照C++中引用的说法,操作对象的引用与直接操作对象的效果是相同的的。下面的代码中,重新指定函数内部的 lst 指向的对象,外部对象却毫无变化。这体现了引用概念在Python中和C++中的不同。

def reassign(lst):
    lst = [01]

lst = [0]
reassign(lst)
print(lst)

# shell out
# [0]

重新理解 Python 的赋值引用

Fredrik Lundh 的文章,帮助我重新理解了Python中对象和引用的关系。下面,我将简略的叙述文章的部分内容,详细了解则直接点击文章链接。从 a = 10 中我们可以获得三个概念,对象(Object)、名称(Name)和赋值(assignment), 其中 a 是对象 10 的名称。

我们都知道Python一切皆对象(Object),这里就不再多加叙述了。

名称(Name)不是对象的属性,对象本身也不知道它的存在。一个对象可以有任意数量的名称,也可以没有名称。名称存在于命名空间(namespace)中(例如模块命名空间、实例命名空间、函数的本地命名空间)。命名空间是(名称、对象引用)对的集合(使用字典实现)。当你调用一个函数或一个方法时,函数的命名空间使用你传递给它的参数初始化(名称取自函数的参数列表,对象是您传入的对象)。

赋值语句修改的是名称,而不是对象

a = 10

意味着将名称 a 添加到本地命名空间,并使它引用到数值为10的整形对象上。

如果名称已经存在,赋值语句则会替换原来的名称:

a = 10
a = 20

a 的两次赋值意味着你首先将名称添加到本地命名空间,并使其引用到值为10的整数对象。然后替换名称,使其指向值为20的整数对象。原始的10对象则不受这个操作的影响。

调用对象的自带方法修改自身则会带来不同的效果:

name = []
name.append(1)

name名称被添加到本地命名空间,使其引用到一个空list对象。然后在该对象上调用一个方法,告诉它为自己追加一个整数对象。这将修改list对象的内容,但它不会触及命名空间,也不会触及integer对象。

个人理解,变量不是对象,是让我们在命名空间内找到对象的名称

换个说法-传递对象引用

按值传递和按引用传递都不能准确的表示Python的传参方式,通过查找资料,发现Python使用一个新的术语来表示参数传递方式,传递对象引用(pass by object reference)或者对象引用按值传递(Object references are passed by value)

函数接收内存中与调用者使用的相同对象的引用,但是函数接收的引用和调用者的引用不是同一个。就跟传值(pass by value)一样, 函数创建了一个属于自己的指向对象的引用。

前面的解释比较难以理解,换成命名空间的讲法可能更简单点。在调用者的命名空间,我们使用 lst 访问对象 [0],在函数的命名空间,我们也使用 lst 访问同一个对象 [0], 但是这两者是不同的,访问的却是同一个对象。或许将函数的形参 lst 改为 xxx 之类的名称可能更方便理解。

在这里插入图片描述

函数和调用者都引用内存中的相同对象,因此当 append 函数向列表中添加额外的元素时,我们在调用者中也看到了列表的变化。 它们是同一个内存对象的不同名称; 这就是按值传递对象引用的含义——函数和调用者在内存中使用相同的对象,但通过不同的变量进行访问。

还记得传值的概念吗,函数获取到的是参数的拷贝。Python获取到的是引用的拷贝,传值又传引用,称为按值传递引用(pass object reference by value)

补遗

前面的内容遗漏了最开始讲的三个结论中的——可变对象传引用,不可变对象传值。如果看过上面的内容,现在我们应该知道它是错误的结论。

Python中,函数接收不可变对象,在函数内部的操作(赋值、调用方法等)不会影响到外部调用者的对象。函数接收可变对象,我们却可以使用对象方法,影响外部的对象。通过这个现象得出了上面的结论。

def change(num, lst):
    num = 0
    lst.append(1)

n = 1
lst = [0]
change(n, lst)
print(n, lst)

# 0 [0, 1]

参考文章

1、 how-do-i-pass-a-variable-by-reference

2、 python-objects

3、 pythons-pass-by-object-reference

引用:引用是一个变量的另一个名字,又称别名。定义方式:
int a=10;
int &b=a;在这里,意思就是给a变量起了一个新名字b,因此b不可再次被重新定义。
引用必须初始化,无空引用,并且引用不分等级。
引用与指针的异同点:
相同点:在引用中 int &b=a;这一句代码可被编译器看做int * const b=&a;//加上const的作用是表明指针b的自身的值(a的地址)不能改变,而指向的值(a的值)可以改变。也就是说引用相当于指针,引用与指针在编译时的方法一样,指向的都是变量的内存地址
不同点:1.在定义方式中引用储存的是值,而指针是一个变量的拷贝,存储的是地址。
2.引用只能对已经存在的变量或对象实现引用,而指针则不需要,可以定义为空。
3.在函数的传参中,如果传递的是一个引用,意味着这个变量或对象已经存在了;如果传递的是一个指针,则不能判断这个指针是不是有效的,是不是空的,因此在函数体 中大多数都得进行指针是否为空的判断。但是虽然引用较为安全,但是利用指针来传参效率较快。
4.引用是直接访问变量,不用分配自己的内存空间,而指针是间接访问,需要定义,需要有自己的内存空间。
例:交换函数swap()

void swap(int &a,int &b)
{
   int temp=a;
   a=b;
   b=temp;
}
void main()
{
   int x=10,y=20;
   swap(x,y);
}

猜你喜欢

转载自blog.csdn.net/edward_zcl/article/details/88697763
今日推荐