指针,揭开你的盖头来

Original URL: http://blog.csdn.net/acosoft/article/details/4512628

指针,揭开你的盖头来

指针是C/C++语言的灵魂设计,我一直这么认为,没有哪一种语言在指针功能的设计上能有C/C++这么灵活强大,甚至大多数语言根本就是避而不敢提指针。指针在应用上确实难以操作,但是它强大的功能支持和良好的运行性能犹如甘美的琼浆吸引着多少程序员为之趋之若鹜、奋不顾身。不可否认,指针曾经是多少程序员的噩梦,但同时又是多少程序员的天堂。他们为之辗转反侧、百思不得其解,同时他们又因之披荆斩棘终攀理想的殿堂。

在C/C++之后的高级语言中,基本都屏蔽了指针的支持,这是为了让程序对程序员来说更容易完成。语言发展的特点是让语言本身更接近人类思维,而不是计算机“思维”。高级语言的宗旨是让程序员更专注于高层应用的开发,解决高层的业务需求而不是与计算机“纠缠不休”。所以指针在C/C++之后的语言中都是很少应用。但这并不意味着指针是不好的东西,我们甚至在全球的任何C/C++代码中都找不到一块代码是不使用指针的。指针是如此的重要,它是让C/C++连续几十年来在语言排行榜都独领风骚的幕后功臣。指针让C/C++在程序界“大小通吃”,它可以毫不费力地与计算机交互,又可以完成高层的商业逻辑,甚至我们使用的大多数软件都是用C/C++来开发的。在语言界没有谁敢与之争锋,几十年来它如同君临天下般高卧语言界的至尊龙椅。

指针是如此的重要。在应用C/C++编程时不可避免地要使用到指针,我们要对指针寄以极高的期望,同时也要有足够的警惕。它可以让你的程序运行的如骏马奔驰般流畅,同时也可以让电话铃声将你从凌晨3点钟的美梦中惊醒。那如何才能运用好指针,我想只有一条,那就是:彻底地了解指针,彻底地掌握指针!从现在开始那我们就一起乘坐“飞飞鱼”进入探索指针的梦幻之旅!

 

从内存开始

为什么从内存开始?因为指针的本质是与内存密切相关的。了解内存则很容易了解指针。

内存,我们可以看作是一条长长的不计其数的数据的集合,这是一个形象的比喻,实际上计算机的内存是由数以亿计的位(bit)顺序地组合而成,每个位则保存一个值:0或1。计算机保存数据的原理就是这样,它是以二进制的方式存储数据。由于一个位所能表示的值的范围实在有限,所以单独的位的作用不大,通常我们将许多位组合一起作为一个单位,这样以二进制的方式就能存储较大范围的值。

我们将位的最小组合称作字节。在多数的计算机中,每个字节包含8个位,可以存储无符号值0到255或有符号值-128到127。每个字节在内存中都占有一个位置,这个位置叫做内存地址,每个字节都可以通过地址来标识和访问。

当然,用字节来保存我们需要的数据还是太小。为了存储更大的数值,我们把两个或多个字节组合在一起作为一个更大的存储单位。这个存储单位在语言中称作数据类型。在C++中short类型用2个字节共16个位来保存,int类型用4个字节共32个位来保存。

我们说一个字节在内存中都占用一个独一无二的地址,那么多字节的数据在内存中如何保存和标识呢?在我们使用的计算机中,尽管int类型包含了4个字节,但它仍只用一个地址来标识,虽然它共有4个地址,一般我们取左边第一个字节的地址作为int类型的地址标识(有些计算机会取右边第一个字节的地址,这个对于我们软件设计者不重要,那是硬件设计者的问题)。那么我们现在应该很明白了,如果我们在程序中定义一个变量为int型,那么计算机就会将它保存为4个字节,在下一次获取这个int值时,它会从这个变量的地址找起,读取4个字节共32个位的数据。如果将变量定义为short类型,那么计算机将它保存为2个字节,获取时从变量地址找起读取2个字节共16个位的数据。现在我们明白了数据类型就是这么来的,它是如此的重要!

好了,现在我们清楚了,总结一下,内存中最小的单位是位,位的最小组合是字节,每个字节在内存中都占有一个位置,这个位置用一个独一无二的地址来标识,每个地址都包含一个数值。两个或多个字节的组合叫做数据类型,每个数据类型的地址用它左边第一个字节的地址来标识,计算机根据数据类型来保存和获取数据。



理解变量

我们不妨先看看下图:

我用图来表示一段内存上5个整数的值,每个框代表一个整数,框中的值代表整数的实际值,每个框上部的值表示这个整数在内存中的地址。我们可以将上图想像为一段内存上的存储表示。这个确实很形象!

对于计算机来说,只要你告诉它这个整数的地址,它就很容易的取到地址对用的实际值,然而对于人类来说要记住这个地址恐怕是很困难的事,你可别看上面我用1001,1002等数值来表示,你也许觉得并不难记,实际中计算机对内存地址的记录用的是16进制的数据,基本上都很庞大,比如82364A7F97612等,你是不是看着它都头大?那我们人如何来记住计算机中内存的数据呢?先看看下图:

 

此图和上副图好像有些相似?是的,我们只是将上面的1001,1002等换成了a,b,c,d等。

如果让我们像计算机那样记住那些复杂的长长的十六进制数据那简直就是糟蹋人类,所以人类发明了另一种记忆内存的方法,就是通过名字而不是地址来访问内存的位置,上图中的a,b,c,d等就是内存的名字,每个名字对应一个数值。

这个名字是什么?就是我们程序中声明的变量。那么,我们明白了,这就是变量的实质,变量是什么?就是标识一块内存地址,就是记录住一块内存的数据。在程序中我们通过访问变量而得到内存数值。

既然有变量,又有地址,它们之间有什么关系呢?在编译器中一个变量在同一时刻只能对应一个唯一的内存地址,但一个内存地址可以对应多个不同的变量。也就是说一个内存地址可以用多个变量表示,但一个变量只能对应到一个内存地址。

变量是人类为了方便的记住内存位置而设计的,但对于计算机来说它并不认变量这个东西。变量和内存位置之间的关系不是硬件所保存,而是由编译器来实现。人类首先声明一个变量,并将数值赋给这个变量,编译器申请一段内存并将数据存储在这段内存,然后将这段内存的地址映射到变量;同样,人类通过变量经编译器转换成地址再获取到对应的数据。总之一句话,人类使用变量,编译器将变量转换成地址,硬件通过地址来访问内存位置。最终计算机都是通过地址来访问内存,只有人类通过变量来访问,编译器是之间的桥梁。

 

必须了解数据类型

为什么必须了解数据类型?当然,我的理由还是那样,指针和数据类型是密不可分的,我无法在假设你不懂数据类型的前提下对你大谈指针,那只能把你讲得飘飘然而我又汗流浃背。



开始了解指针

好了,我们现在开始真正的进入主题。大家也许已经焦急,但是,我还是想按部就班的介绍。首先我要介绍一下指针的定义,并对其本质做详细的说明,然后对再对每一块重点的知识进行详细的说明。

那么什么是指针?指针是一个指向一块计算机内存的指示器,指针通过保存内存地址来实现指示器的作用。指针变量和普通变量不一样,普通变量保存的是实际值,而指针保存的是一个内存地址;指针变量和普通变量又相似,每一个普通变量和指针变量都有一块内存存储,且它们都有一个内存地址和一个实际值,只是指针变量的实际值是一个地址,计算机可以通过指针所保存的地址访问到这个地址所存储的实际数据。

好了,我们大概对指针有一个抽象的认识,下面我们再举例声明一个指针,这样会对指针有具体的感性认识。

int  i  = 100 ;   //假设变量i的地址是10001

int * j = &i;

//     j = ?

//    *j = ?

我们首先声明一个普通变量i,并对i初始化赋值100;再定义一个指针变量j,给指针变量j初始化赋值为变量i的地址。那么我们知道指针变量j的值就是普通变量i的地址,即10001;指针变量j指向的值是普通变量i的值,即100。

这是一个简单的指针的声明,声明指针时在数据类型和变量之间加一个*,这个*表明了这是在声明一个指针,*后面的变量是一个指针变量。这个指针变量的类型是一个int型。在这里我们要特别注意一点:这个 *可以和数据类型挨着,也可以和变量挨着,也可以和谁都不挨,自己就站在中间。在C/C++中并没有明确的规范它应该放在什么位置,但这个我们不必关心,我们只关心一点,那就是*夹在数据类型和变量之间说明了定义的这个变量是一个指针,这些就足够了,* 在这里就这个含义,它没有其它的任何含义。

然而,我们并不建议大家随便放在哪里,而是建议将数据类型和*挨着,因为这样有助于我们更容易理解指针。在上面的声明中,我们声明了一个指针变量i,而不是声明一个*i,如果在声明时把*和i放在一起,很容易被理解为声明的这个变量是*i,这样的理解实际是错误的。我们再次对上面的声明做一个对比的理解。

int     i  =  100 ;  

int*    j  =  &i;

首先我们声明一个普通变量 i,然后再声明一个指针变量j,我们应该把*和int看作是一体的,是这样的(int*),指针变量和普通变量的声明唯一的区别就是在数据类型的后面加一个*,而不是在变量前面加一个*。理解到这一点很重要。

在给指针赋值时我们用到一个符号&,这里的&叫做地址操作符,在一个变量的前面加&表明是取这个变量的地址。那么我们上面就是给指针变量j赋值为变量i的地址。

另外我们还要特别注意一点,*在指针的内容里还有另一个用途,它单独在变量前面表明了是取这个指针变量指向的实际值。这时*被称作引用操作符,在C/C++中属于单目操作符的一种。接着上面的例子:

j  =  10001 ;

*j  =  100  ;

指针变量j的值是10001,而指针变量指向的内存地址的值 *j 是100。在这里我们要注意到这个*和变量前面没有数据类型,只有在声明指针变量时才会有数据类型。前面我们在建议声明指针时将数据类型和*放在一起的另一个主要原因是能和单目操作符的*简单的区分。

好了,总结一下,*在指针内容里有两个作用:

1,  夹在数据类型和变量之间表明是声明一个指针;

2,  单独出现在变量前表明是取这个指针变量所指向的内存的实际值,这是*被称作引用操作符。

另外,我还要提示一点,*和&在C/C++里分别代表指针和引用,有关指针和引用的区别

以及*和&在使用上的区别请参考我的另一篇文章《指针和引用操作符的区别》。



指针变量与普通变量的区别

C/C++中每一个普通变量都包含两个值:1,变量所在内存的实际值;2,是标识这块内存的地址。我们通过i获得这个变量的实际值,通过&i获取这个变量的地址。

指针变量和普通变量一样,但指针变量包含了三个值:1,变量所在内存的实际值,即另一块内存的地址;2,指针变量的地址,我们可以通过&获得指针变量的地址;3,变量通过引用操作符所获得的指向另一个地址的内存实际值。

这样我们可以看出,指针变量和普通变量的有本质的相似,即他们都是同样的保存在内存中,他们都有一个内存存储值和一个地址值,只是指针变量比普通变量多一个功能,它可以通过*引用操作符获取其内存值所对应的内存地址的存储值(普通变量不可以使用*引用操作符)。

    int i = 100 ;   

    int * j = &i ;   

    cout << i << endl;  

    //cout << *i << endl;     编译器会报错,因为这不是一个指针

    cout << &i << endl;   

    cout << j << endl ;   

    cout << *j << endl ;   

    cout << &j << endl ;

输出结果:--------------------------------------

100

0x22ff74

0x22ff74

100

0x22ff70

-------------------------------------------------------

指针四要素

一个指针包含四个内容,我们简称为指针四要素,即指针类型,指针所指向的类型,指针的值,指针所指向的值。

1.         指针类型。从指针的声明我们可以看出除去指针变量所剩的部分即指针的类型。比如:int*  i ;   //指针类型为int*

char   *c ;   //指针类型为char*

int**  j ;     //指针类型为int** ,这个是指针的指针

2.   指针所指向的类型。相对来说指针类型对于内存来说没有太大意义,任何指针在内存中都保存为32位的整数。指针类型只是对C/C++编译器有意义,它表明这是一个指针,而不是一个普通类型,编译器需要按指针的方式去解析。

而指针所指向的类型对于内存来说就有很大意义,指针所指向的类型直接决定着编译器如何去读这块内存块,如果这个类型是int,那么编译器需要读32位,如果是long,那么编译器需要读64位。对于一块内存块,如果它被一个普通变量表示,这个普通变量必须有一个数据类型,如果被一个指针指向,这个指针必须有所指向的类型,只有这样,这个内存块才能被正确解析。

最后,我们得给指针所指向的类型一个定义:即指针声明语句中的指针名字和名字左边的指针声明符*去掉所剩下的部分。比如:

int*  i ;   //指针所指向的类型为int

char   *c ;   //指针所指向的类型为char

int**  j ;     //指针所指向的类型为int*

3.   指针的值。指针的值就是指针变量保存在内存中的实际值,这个值是一个内存地址,编译器根据这个内存地址可以找到指针所指向的内存块,从而获取这个指针所指向的值。在32位机器上指针的值就是一个32位的整数。

4.   指针所指向的值。指针的值保存了指针所指向的值的内存地址,编译器根据指针的值获取指针所指向值的首地址,并根据指针所指向的类型解析出指针所指向的值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值