http://bbs.9ria.com/blog-164422-18039.html

初学者在学习C语言时,通常会遇到两个瓶颈,一个是“递归”,一个是“指针”。大学老师在讲述这两个知识点时通常都是照本宣科,而没有站在一个初学者的角 度来审视问题,更没有剖析其内部机理。本人在此将发表一系列技术文章,希望能将C语言中“指针”这一概念讲述清楚,希望初学者能从中收益。在此笔者也极力 推荐Kenneth A.Reek写的《Pointers On C》这本书。
 1.内存和地址
初学者面对内存一词时总是有一种既陌生又熟悉的感觉。首先,在日常生活中大家总是会讨论某某设备内存有多大,是不是该加一个内存条等等。但是,内存究竟是 什么?在此,笔者并不想深入探讨内存的本质,以及内存在不同操作系统上的结构有什么区别等等这一系列问题。这里只希望初学者知道以下三个问题即可:
 (1)内存是计算机在运行过程中存储数据的地方
 (2)内存被分割成了“无数”个小区域,每个区域的大小在不同的环境下有所不同,可能是1个字节,2个字节,或者多个字节。
 (3)每个小区域都有一个独一无二的标识,即我们后面所说的地址(指针)。
 (4)每个小区中都有包含一个值,可以是整形,浮点型,字符型等等。
我们可以形象的用下图表示内存的结构:
    100           104           108           112             116
     |                   |                 |                 |                   |
------------------------------------------------------------------------
   (542)         (3.14)         (‘A’)     (12323)        (-12)
如图所示,上方表示内存的地址,下方括号中的内容表示该地址下内存中的值。我们想访问这些内存中的值,只需要知道内存地址即可。但是,我们怎么知道我们要 访问的内容其内存地址是多少?的确,要记住这些内存地址几乎是不可能的,所以,编译器可以让我们用变量的形式访问他们,于是这张图可以变成如下所示:
 total_count      Pi            m_ch         money           number
     |                      |                  |                 |                       |
------------------------------------------------------------------------
   (542)         (3.14)         (‘A’)     (12323)        (-12)
我们将内存地址变成了我们更容易记住的变量,这样我们在编写程序的时候就可以方便的访问内存中的数据。但是,请记住,这只是编译器帮助我们进行了优化,而 真正编译后的机器码则是通过真正的内存地址来访问内存中的数据,即寻址,关于计算机的寻址过程,有兴趣的读者可以参考计算机组成原理或者汇编语言等书。

2.值和类型
我们首先来看一下下面这几个表达式:
int  total_count = 542;
float Pi = 3.14;
int *p1 = &total_count;
char *p2 = &m_ch;
前两个表达式很好理解,我们申明了一个整形和一个浮点型变量,并分别赋值为542和3.14。那后面两个表达式是什么意思呢?
我们姑且可以简单记住:在申请变量的表达式中,如果类型的后面出现了*号,那么这个变量就叫做指针,或者指针变量。指针变量分为很多种类型,例如整形指针,浮点型指针,字符型指针等等。
好了,我们知道了指针的概念,那么,指针到底是什么?
指针也是变量,即指针变量,它和其他的变量在本质上是没有区别的。但是指针变量只能保存一种值,就是地址。
也许你会问了,既然指针保存的是地址,那么为什么要将指针分为那么多的种类,整形值要有整形指针,字符型值要有字符型指针?为什么不能用一种指针就把所有的地址都包含了呢?难道不同类型变量的地址也不同吗?
这个问题,本人没有查阅过官方的解释,首先可以肯定,内存地址在理论上是没有任何区别的,无论是用来保存什么类型变量的内存,其本质都是01Bit构成的 区域,内存地址当然也不会有任何区别,从纯技术技术的角度上讲,笔者认为编译器完全可以建立一种制度,用统一的指针类型保存不同变量的地址,这完全不会影 响程序的运行。但是编译器没有这样做的原因,笔者分析主要是出于安全性的考虑。当程序员有意或无意的将两种不同类型的指针所指向的内存内容进行赋值时,如 果编译器事先不能做出检查,那么也许会在程序运行过程中出现异常,例如产生非常严重的缓冲区溢出错误。

3.指针的间接访问符
好了,我们将问题回到这几个表达式上来。现在的问题是p1和p2这两个变量中保存的是什么。拿p1为例,也许你可以这样理解,因为p1是指针变量,所以他 的值应该和他指向的内存中的值一样,所以p1 = 542. 这样理解看似非常符合逻辑,但却是一个大的错误。虽然p1很特殊,但是指针变量也是变量,它不会聪明到自动去完成一个非常复杂的自动间接访问操作。p1的 值实际是100,即变量total_count的地址。但是读者请注意,100并不是传统意义上整形100,例如 int *p1 = 100;这是一个错误的表达式,因为不能直接将整形值赋值给指针变量,作如下变化即可 int *p1 = (int *)100; 
也就是说,如果我们输出p1的值,打印出来将会是一串莫名其妙的数字。当然,我们通常对这些莫名其妙的地址不感兴趣,我们更感兴趣的是这些地址背后影藏这怎样的信息。所以我们可以这样来访问int count = *p1;
等等...读者读到这里也许有些糊涂了,
int *p1 = &total_count; int count = *p1; 这么多的*号和&号也许读者有些搞不清了。我们来理清一下思路。
在int *p1 = &total_count;中:
 *p1只是一个标识,代表p1是一个指针,以后访问这个指针时直接p1即可,访问得到的值是一个地址编码。
&total_count代表一个地址,在任意一个变量前(包括指针)加上&符号,都将代表这个变量所在内存的地址。&p1则代 表指针所在内存的地址,即指针的指针(稍后详细介绍)其实这么有什么可疑问的,因为指针本身也是变量,所以和其他变量本质上没有任何区别。
在 int count = *p1;中,p1和前面说的一样,访问它的值是一个地址,而*号与之前的*号有些不同,之前的*号只是一个标识,表示当前申请的变量是一个指针,而这里 的*号我们给它一个新的名字,叫指针的间接访问符。听起来有些别扭,简单地理解就是,在一个指针变量前加上*号则可以访问该指针所所表示内存的实际值。
这里可以教初学者一个小技巧,当利理解一个和指针有关的语句是,“指针”和“地址”这两个词可以互换,初学者姑且可以认为“指针”就是“地址”,“地址”就是“指针”,这样理解不确切,但却很实用。
好了,下面让我们看一个更复杂的例子:
int *p1 = &total_count;
int count2 = *(*(&p1));
请读者在10秒钟之内告诉我count2的值是多少。哈哈,好吧,让我们来一点点分析。
从括号最里面开始:
p1是一个指针,他的值是total_count的地址;
&p1是一个地址,即指针p1的地址,我们用刚才的小技巧来看看,即地址p1地址,即指针p1的指针。
*(&p1)是一个值,这个值是&p1表示的地址所在内存的值实际上就是p1的值,&对p1进行了一次引用操作,*对其再解引用,实际上没有变化。此时*(&p1)的值为total_count的地址。
*(*(&p1))为total_count的值,也就是542.
到此,该表达式的分析结束,不过在实际编程中,没有人会用如此复杂的表达式进行编码。

4.指针的指针
指针的指针,即地址的地址。第一个地址是狭义上的地址,该地址实际上已经被“值”化,第二个地址是我们传统意义上的地址。这句话理解起来有些困难,我们先来看看下面的表达式;
int *p1 = &total_count;
int **p2 = &p1;
第一个表达式的意义我们已经清楚了,我们申请了一个指针变量,或者说是地址变量,它保存的是total_count的地址。
第二个表达式我们来一步步分析,p1是一个指针,即一个地址,&为地址符,加起来就是地址的地址,或者说指针的指针。而int **p2 我们可以这样理解 int *X,我们定义了一个指针变量X ,X为*p2,即X也是一个指针变量,加起来的意思就是我们申请了一个p2指针变量,这个指针变量指向的也是一个指针变量。
同理: int ***p3 = &p2;
       int ****p4 = &p3;
      ......都是成立的表达式,不过在实际编码中,遇到的最多情况是指针和指针的指针这两种情况。

深入浅出C指针的更多相关文章

  1. 【C++深入浅出】智能指针之auto_ptr学习

    起:  C++98标准加入auto_ptr,即智能指针,C++11加入shared_ptr和weak_ptr两种智能指针,先从auto_ptr的定义学习一下auto_ptr的用法. template& ...

  2. 深入浅出c++之---this指针

    前言:C语言中的数组指针和指针数组 数组指针,是指向数组的指针的缩写:指针数组,是存放指针的数组的缩写.其实很多时候,往往因为简写和缩写带给我们很多困惑.我曾想过不用简称去学习,但在很多时候,我们查询 ...

  3. 深入浅出剖析C语言函数指针与回调函数(一)【转】

    本文转载自:http://blog.csdn.net/morixinguan/article/details/65494239 关于静态库和动态库的使用和制作方法. http://blog.csdn. ...

  4. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  5. VC++动态链接库(DLL)编程深入浅出(zz)

    VC++动态链接库(DLL)编程深入浅出(zz) 1.概论 先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用 ...

  6. 索引深入浅出(4/10):非聚集索引的B树结构在聚集表

    一个表只能有一个聚集索引,数据行以此聚集索引的顺序进行存储,一个表却能有多个非聚集索引.我们已经讨论了聚集索引的结构,这篇我们会看下非聚集索引结构. 非聚集索引的逻辑呈现 简单来说,非聚集索引是表的子 ...

  7. 索引深入浅出(5/10):非聚集索引的B树结构在堆表

    在“索引深入浅出:非聚集索引的B树结构在聚集表”里,我们讨论了在聚集表上的非聚集索引,这篇文章我们讨论下在堆表上的非聚集索引. 非聚集索引可以在聚集表或堆表上创建.当我们在聚集表上创建非聚集索引时,聚 ...

  8. 《深入浅出Windows Phone 8.1 应用开发》基于Runtime框架全新升级版

    <深入浅出Windows Phone 8.1 应用开发>使用WP8.1 Runtime框架最新的API重写了上一本<深入浅出Windows Phone 8应用开发>大部分的的内 ...

  9. [深入浅出WP8.1(Runtime)]生成图片和存储生成的图片文件

    7.2.3 使用RenderTargetBitmap类生成图片 RenderTargetBitmap类可以将可视化对象转换为位图,也就是说它可以将任意的UIElement以位图的形式呈现.那么我们在实 ...

随机推荐

  1. 《腾讯游戏人生》微信小程序开发总结

    为打通游戏人生擂台赛与线下商家的O2O衔接,同时响应时下日臻火热的微信小程序,项目团队决定也开发一款针对性的微信小程序,以此方便商家在我们平台入驻并进行擂台赛事的创建和奖励的核销,进一步推广擂台赛的玩 ...

  2. js中判断对象是否存在

    s中判断对象是否存在,写法有很多种: 第一种:if (!myObj) { var myObj = { }; }第二种:var global = this;  if (!global.myObj) {  ...

  3. Vue2.0以后,有哪些变化

    最近移动端项目版本升级,Vue由之前的1.0升级到2.3,那么,Vue2.0之后,有哪些细节的变化呢,现在总结如下: 1.在每个组件模板,不再支持片段代码 组件中模板: 之前: <templat ...

  4. Zookeeper之集群搭建(Linux)

    Zookeeper集群搭建(Linux环境) 条件准备:准备三台Linux服务器 vt-serv1.vt-serv2.vt-serv3(虚拟机/物理机均可,服务器数量一定要是单数,不要问我为什么,据说 ...

  5. jq实现发送验证码倒计时60s

    setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式.方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭. setTimeout() :在指定的毫 ...

  6. 如何用dva来构建你的应用

    dva的github地址: https://github.com/dvajs/dva-knowledgemap#%E9%80%9A%E8%BF%87-connect-%E7%BB%91%E5%AE%9 ...

  7. STROME --realtime & online parallel computing

    Data Collections ---> Stream to Channel (as source input) ----> Parallel Computing---> Resu ...

  8. list 和 iterate

    list 和 iterate 不同之处 a) list取所有: b) iterate 先取ID,等到用到的时候再根据ID来取对象: c) session 中 list 第二次发出,仍会到数据库查询: ...

  9. java面试题----String、StringBuffer、StringBudder区别

    面试题1 - 什么情况下用+运算符进行字符串连接比调用StringBuffer/StringBuilder对象的append方法连接字符串性能更好? 面试题2 - 请说出下面程序的输出. class ...

  10. 增强for循环 java.util.ConcurrentModificationException

    Java中的Iterator功能比较简单,并且只能单向移动: (1) 使用方法iterator()要求容器返回一个Iterator.第一次调用Iterator的next()方法时,它返回序列的第一个元 ...