关于C的指针,Java/Python的引用,形参与实参个人理解

最近稍微学习了下Go语言,Go语言真是大融合即视感,明明是静态语言,却结合了动态语言的诸多优点,写起来感觉就像个动态语言,却有着不输给静态语言的执行速度。略屌,可是现在应用还是比较窄,编程语言排行榜上前五十基本看不到他,囧。

然后Go里面使用了指针和引用这两种概念,因为之前学过C++跟Java,所以对这两者都挺有感觉的,当时指针真是整的我痛苦不堪,后来再看Java的时候,学习了引用这个概念,再回去看指针,我去,原来指针也就那么回事。。。搞懂了内存就完全搞懂了,可是看的书硬是不说这些,让人无语。所以决心自己写个入门级的指针详解,借鉴引用的概念,说说自己的理解。

先说下静态语言跟动态语言,静态语言必须先声明变量才能使用,典型代表C跟C++,其实就是int num; num = 100,动态语言代表有python,不用声明直接使用如num = 100.静态跟动态,我们常说的就是编译跟运行。把编译时就能确定的数据类型叫做静态语言,而在运行时才确定变量数据类型的叫做动态语言。这里只是先讲个题外话=_=。

 

好了,然后正式开始介绍。

搞懂内存就搞懂了指针跟引用。所以我们从内存概念入手研究指针。

 


 

一、指针初识

在C当中,是没有引用这个概念的,当使用a = b这个操作时,不管a是什么数据类型,他都默认发生了拷贝,但是我们有时,或者大多数时候,其实压根就不希望他拷贝,我们想操作原来的数据,不然一个2G的数组来排序,每次都复制一下,内存可是扛不住的,所以在C当中就加入了指针这个概念,指针的运行逻辑是这样的:

举个例子,这个例子的代码是C语言了。

int a = 10;

int b;

b = a;

b = 100;

 

int p = 10;

int *q;

q = &p;

*q = 100;

则结果a还是等于10,而p则变成100了。第一个例子就是我们刚才说的,在C当中,赋值即是拷贝,这两个已经不是同一个值了。而第二句我们都知道,因为他们指向了同一个内存地址,所以改变了其中一个,另一个也就跟着改变了。

从内存的角度去理解这些就是。内存是一个存储单元,它上面有很多很多格子,每个格子都有一个编号,我们把它叫做地址。如下:

0000

1000

0001

1001

0010

1010

0011

1011

0100

1100

0101

1101

0110

1110

0111

1111

 

每个编号对应一个地址,在这里我只是举个例子,实际的地址编号我记得是16进制的。

然后我们赋值a = 5,5保存在了0000这个编号地址里。

所以我们知道了  

0000 -> 5

当b = a的时候,发生了拷贝,我们假设是在0001这个内存地址里保存了新的5,这个5是b变量的值,则有   

0001 -> 5

所以从这里我们知道对b的操作肯定不会影响到a,毕竟这两都不是一个地址里的东西了。

而若是这里b是一个指针,有下面的语句:

int *b

b = &a      // b = 0000

*b = 100    

这里发生的内存情况是这样的,&是取地址符,也就是&a表示的是0000!!也就是b保存的是一个地址,而且是a的地址!在内存当中就是  

0001 -> 0000,

在0001这个内存地址当中保存了一个地址!而*是取值符号,也就是把地址里保存的东西返回!b的值是0000,*b就是*0000,不就是5了吗,当*b = 100的时候,我们就相当于改变了0000这个内存地址当中的值,所以a当然也改变了,因为a本身代表的就是0000这个地址里的值啊。

就是因为这个原因,出现了形参和实参的概念,就像下面这个经典的例子:

int aa = 100;

int bb = 50;

swap(aa, bb)

 

//define

void swap(int a, int b){

int temp = a;

a = b;

b = temp;

}

最后发现aa跟bb的值并没有交换,知道了内存的概念这就是很简单的事了,因为在传入函数的时候实际发生了这样一个过程:

int a = aa

int b = bb

没错,这就是一个拷贝,拷贝是分配了新地址,改变了新地址里的值,原地址里的值当然不会改变了,所以函数就应该写成swap(int *a, int *b),所以也不用理会什么实参形参了,知道了内存概念也就知道了道道。

我们可以很形象的用图来表示:

 

因为地址不够直观,所以我们才会用指向来表示这个过程,也就是指针了。

最后要知道一个地址可以保持8位,刚好是一个字节大小。

 

 

 

二、Java的引用

建议大家也看下引用的概念,这玩意在python里可是大放异彩,所谓的引用其实就是对指针的封装!

在Java当中,数据类型分基本类型和引用类型。如下:



学过其他语言的话,大家对这些类型应该都不陌生。这里为了简化,我们先把基本类型看做int,引用类型则是数组和类。

在Java当中声明一些语句的时候,如下

int a,b

a = 5

b = a

a = 10

Java跟C一样这个时候a就变成了10,但是b还是5。原因是因为发生了拷贝,而如果是下面的语句:

 

int[] a = {1,2,3,4}   //声明一个数组

int[] b

b = a

b[1] = 100

结果:

a :[1, 100, 3, 4]

b :[1, 100, 3, 4]

 

发现2者都改变了。这两个例子之所以这样,原因是因为在内存中的存储方式不同。在Java中有栈内存和堆内存的概念。

首先是int a = 5这句,5保存在了栈内存中,在Java当中,赋值的操作其实就是复制,java会额外复制一个5,将这个5给变量b,也就是这是两个5,所以改变了a的值,不会影响到b的值。而在数组里,我们前面说了这是一个引用类型。他在java当中情况是这样的,

 

如图,这是一句int[] a = {elel1, ele2, ele3, ele4, ele5}产生的结果,首先是变量a保存在了栈内存中,然后a所指的数组则是在内存中额外申请了一块内存,将数组保存在了那块内存中,我们把它叫做堆内存。

而当你使用 b = a的时候,实际的情况是不发生数组拷贝,而是b也指向了堆内存中的那块数组,如下图:

 

 

所以你通过b改变了这块内存当中的值,a当然也会改变,因为他们指向的是同一块内存当中的数据。这就是引用类型。这是书上的解释,可是看了指针之后我觉得更底层的情况应该是这样的,b = a仍然是拷贝,但是因为a本身就是地址,这个地址保存在了栈内存中,而指向堆内存中的数组,而赋值使得b拷贝了a所保持的地址!所以拷贝的是地址!!而当使用b[1]的时候,系统自动将地址转换了,所以我们不用像操作指针一样来操作他,这就是引用这个概念,对指针的封装。

之所以加入了引用类型是为了提高速度,如果每次赋值都发生拷贝的话,对于数组或对象这种比较大的数据,会造成比较多的时间和空间浪费。所以我才说Java真是一个伟大的语言T-T。指针那么底层的东西,简直就是来给初学者添堵的。。。

最后总结:在Java当中,赋值操作仍然是拷贝,不过是栈内存中的数据拷贝。其中基础类型是值保持在栈内存中,而引用保存的值是最终指向的数据的地址。

当然使用的时候就用最上面图中的指向来理解是最好的,比较不容易搞混~~

 

三、python的引用

最后说下python,因为我用的比较多,我在知道了引用这个概念后,回去使用python,慢慢的就发现了,python在数据类型的处理上,跟Java是一个概念的!!!

瞬间就理解了之前出过的很多bug,还有很多神坑,如下:

a = {‘a’:1}

b = a

b[‘b’] = 2

发现a也改变了。所以我们知道这里字典就是一个引用类型。

还有python里的==跟is

 

a = 100

b = 100

c = 300

d = 300

a == b   #True

a is b   #True

c == d   #True

c is d   #False

 

嘿嘿,,我当时看到这个震惊了,这里的道道是这样的,==比较的是值,而is比较的是地址。

所谓值就是变量所指的内存里保存的是什么值,地址则是变量所指的值的地址是多少。也就是==做了C当中的*a == *b的比较而is做了&a == &b的比较。

所以我们看到两个==输出的都是True,因为内存里保存的都是100跟300,但是is的结果却完全不同,这里还有另外一个原因,为什么100的是True,300的却变成False,照理不应该都是False吗,因为他们地址都不一样啊,其实100的地址是一样的,python默认将-5~256的数字缓存了起来(网上一说是0~255,可是我自己在解释器里实验了下是这个范围,=_=),也就是虚拟机在启动的时候内存里就保存了这些个值,当有赋值的时候,默认都让他们指向了这个预先保存了的100,而300超过了这个范围,所以300的内存地址已经不一样了

最后要说的是python如果按照Java的概念来说的话所有的变量应该是只有引用类型的。可以从以下几个例子看出

a = 300

b = a

b is a   #True,说明b跟a指向了同一个300

 

b = 400

print a   #a当然还是输出300,b是改为指向了400,这里要这么理解,如下图:

 


以上就是我自己在学习的时候发现的一些这几个语言之间的联系。若有理解有误的地方,欢迎大家拍砖!!!!!

 

注:部分图片来自于<疯狂Java讲义>,侵删。

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章