对于程序员来说内存可以简化成这样一种东西:
<img src="https://pic1.zhimg.com/4d060c3f67c22cd4b07273db00f64708_b.jpg" data-rawwidth="543" data-rawheight="100" class="origin_image zh-lightbox-thumb" width="543" data-original="https://pic1.zhimg.com/4d060c3f67c22cd4b07273db00f64708_r.jpg">你可以把它想象成一条无限长的纸带。纸带上边有一个个的小格子,每个小格子正好是一字节,里边能够存放一个数字。计算机的工作就是对这些小格子里的数字做处理。虽然你在电脑上能够看视频、听音乐,但这些东西本质上都是存在内存这条纸带上的数字。你可以把它想象成一条无限长的纸带。纸带上边有一个个的小格子,每个小格子正好是一字节,里边能够存放一个数字。计算机的工作就是对这些小格子里的数字做处理。虽然你在电脑上能够看视频、听音乐,但这些东西本质上都是存在内存这条纸带上的数字。

对于纸带上的每个小格子来说能够采取的操作只有两种「读取」和「写入」。每次执行操作时都必须要指明对于哪一个格子进行操作,为了方便起见人们就给这些格子编了号。如上图所示,第一个就是0号,第二个是1号以此类推(前边的0x代表是16进制的意思)。而这些编号就是我们所说的指针的内容,指针变量里边所记载的就是这些编号。但为什么我们平时看到的指针值都是类似于0xa2cf23c3d这种呢?这是因为,内存非常之大,作为编号的数字也非常大。所以看是来就像是神秘代码一样。

举个例子:

unsigned char a = 1; // unsigned char 类型占一个字节
unsigned char* b = &a; // &a 代表的就是得到变量a所存储的那个小格子的编号。
// 赋值给变量b后,b格子里存的就是a格子的编号。
printf("%d", *b); //输出1,*b代表的是看看b格子里存储的编号所代表的格子里存的数是什么。

上边代码中的内容变成纸带就是:
<img src="https://pic1.zhimg.com/26702ad72a395403ce4c7d02ccd167e4_b.jpg" data-rawwidth="544" data-rawheight="213" class="origin_image zh-lightbox-thumb" width="544" data-original="https://pic1.zhimg.com/26702ad72a395403ce4c7d02ccd167e4_r.jpg">
ok,如果你觉得这个太简单,我们现在可以深入到一些更加复杂的概念——指针的指针。例如下边的代码:

unsigned char a = 1;
unsigned char* b = &a;
unsigned char** c = &b;

上文中我们提到指针就是格子的编号,那么指针的指针是什么呢?就是指针变量所在格子的编号。如图:
<img src="https://pic3.zhimg.com/0b6ca6a620fd381b4c2395e134137ae6_b.jpg" data-rawwidth="545" data-rawheight="188" class="origin_image zh-lightbox-thumb" width="545" data-original="https://pic3.zhimg.com/0b6ca6a620fd381b4c2395e134137ae6_r.jpg">至于什么指针的指针的指针,以此类推。有 时候人们会把这种关系视作是一种指向关系。所以当人们说一个指针指向某个变量时,他们指的是这个指针变量的值是某个变量的格子编号。至于什么指针的指针的指针,以此类推。有 时候人们会把这种关系视作是一种指向关系。所以当人们说一个指针指向某个变量时,他们指的是这个指针变量的值是某个变量的格子编号。

但是这里有个问题,前文说到 ,每个格子只有一个字节。有些类型的变量需要多个字节来存储,比如int型就需要4个字节(不同平台长度不一致,这里假定是4个)。如果我有一个int型的变量a存在0x01号地址,我必须有方法告诉程序后边的三个格子也是这个变量值的一部分。

为了解决这个问题人们引入了指针类型的概念,对于int型的指针他表示的是从当前格子开始共4个格子都是该变量的值,而对于unsigned char型则只表示当前的格子。例如:

unsigned int c = 257;
unsigned int* a = &c;
unsigned char* b = (unsigned char*)&c; //必须强制转换一下 printf("%d", *a); // 输出 257
printf("%d", *b); // 输出 1

如图:
<img src="https://pic3.zhimg.com/f18c1c1a62294bc5c45682ea69776e7e_b.jpg" data-rawwidth="545" data-rawheight="188" class="origin_image zh-lightbox-thumb" width="545" data-original="https://pic3.zhimg.com/f18c1c1a62294bc5c45682ea69776e7e_r.jpg">
有的同学可能会奇怪为什么*b的值是1。要解决这个问题我们先要了解一个概念,大小端。
计算机使用二进制来表示数字。对于所有unsigned的整数类型变量来说,二进制的值本身就代表了他自己的数值,257转换成二进制是多少?「100000001」。8位是一个字节,int型就是4个字节32位。补齐前边的0后257可以写作「00000000 00000000 00000001 00000001」。8个位8个位的转换成10进制,上边的数字就是「0 0 1 1」。
OK,现在给你编号为1-4的四个格子和四个数字,让你按顺序存进去,请问有几种方法?

答案是2种,一种是1号放0,2号放0, 3号放1, 4号放1。另外一种是,4号放0,3号放0, 2号放1, 1号放1。我们把第一种方式叫小端,第二种叫大端。由于没有什么人来规定怎么在内存里存多个字节的数字,所以人们就随便来了。碰巧在我的机器上,内存里是大端存储的。所以就变成了上图中所示的样子。

此时计算机看到*a,于是找到了编号0x00的格子,因为是int型的指针,于是顺手把后边3个也读了出来,得到「1 1 0 0」。因为我的机器是大端,所以调整成人类的书写格式(低位在最右边)就是「0 0 1 1」,然后「00000000 00000000 00000001 00000001」,再然后「257」。

之后计算机又看了 *b,因为是unsigned char型的指针所以只拿0x00格子里的内容。于是就是1了。

ok,如果你还有兴趣看下去的话,我们来讨论一下指针运算的问题。
假设有一个指针a,那么a+1代表什么呢?就存储a的格子编号,加上a的类型所占的字节数所得到的的新地址。如图:
<img src="https://pic3.zhimg.com/04789934874fc4f8c8d8d742d8fa447e_b.jpg" data-rawwidth="630" data-rawheight="191" class="origin_image zh-lightbox-thumb" width="630" data-original="https://pic3.zhimg.com/04789934874fc4f8c8d8d742d8fa447e_r.jpg">
减法也是一个意思。两个指针相减就是他们之间差了几个格子。比如0x04-0x00 ==>4之类的。课本上一般会说两个指针相加没有意义,或者指针不能相加。现在知道为什么了吧,因为0x00+0x04之类的运算是没有实际含义的,算倒是可以算。

你知道为什么C语言中的数组下表从0开始吗?因为arr[n]的形式事实上等价于*(arr+n)。数组名其实就是数组的首地址,也就是数组的第一个格子的地址。作为数组中的第一个元素当然得加0啦。

关于数组我多说两句,数组就是一段连续的小格子。数组名代表的就是第一个小格所在编号。因此数组名就是指针,只不过这个指针变量的值是无法改变的。对于二维数组int [3][4] a来说,唯一的区别就是指针+1后移动过的格子数不同。感兴趣的同学可以自己查查为什么。

至于什么指针数组、数组的指针之类的等等概念,都是基于上述简单概念的组合,以此类推就可以了。

ok,理解到这一步其实基本上就算是可以了。不过为了稍微严谨一点我再多说几句。

实际中的计算机可没有这么简单。这个纸带不一定是一条,有可能一条是内存,一条是硬盘上的虚拟内存。纸带还会被分成多个区域,像什么堆栈之类的,并不是每个地址都可以访问,纸带也不一定是连续的。但是你在写程序的时候操作系统帮你把内存抽象成了一条连续的纸带。很多时候我们并不关心具体的值是什么。此外有的时候这种格子编号,不一定都指的是内存中的小格子。也有可能是某个硬件设备之类的。不过这就是另外一个话题了。

PS:上文中格子编号一律可以替换为内存地址。

作者:冯昱尧
链接:https://www.zhihu.com/question/24466000/answer/27893272
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

C 语言指针怎么理解?的更多相关文章

  1. 对C语言指针的理解

    一个小程序引发对于C语言指针的思考: #include <bits/stdc++.h> using namespace std; void my_swap (int* a,int* b) ...

  2. 说说对C语言指针的理解

    指针困扰了一些学习编程的人,或许你的老师会告诉你,指针比较难理解. 我当时被老师的话唬住所以学习指针那章的时候都没心情听课.(说得像讲别的内容时我听了似的,开玩笑) 导致了学习链表的时候各种卧槽. * ...

  3. C语言指针的理解以及指针的指针的理解

    指针指向的是内存地址编号,内存地址编号指向的是对应的内容. 我们需要一个变量,来储存内存地址编号,这个变量的值是一个内存地址编号,但是我们可以通过修改变量的值,来不断的改变内存地址编号. 但是,我们如 ...

  4. 深入理解C语言 - 指针使用的常见错误

    在C语言中,指针的重要性不言而喻,但在很多时候指针又被认为是一把双刃剑.一方面,指针是构建数据结构和操作内存的精确而高效的工具.另一方面,它们又很容易误用,从而产生不可预知的软件bug.下面总结一下指 ...

  5. C语言教学--二维数组和指针的理解

    对于初学者对二维数组和指针的理解很模糊, 或者感觉很难理解, 其实我们和生活联系起来, 这一切都会变得清晰透彻. 我们用理解一维数组的思想来理解二维数组, 对于一维数组,每个箱子里存放的是具体的苹果, ...

  6. 深入理解C语言 - 指针详解

    一.什么是指针 C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址.CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位.这里,数据对象是指存储在 ...

  7. C语言指针转换为intptr_t类型

    1.前言 今天在看代码时,发现将之一个指针赋值给一个intptr_t类型的变量.由于之前没有见过intptr_t这样数据类型,凭感觉认为intptr_t是int类型的指针.感觉很奇怪,为何要将一个指针 ...

  8. [转]C语言指针学习经验总结浅谈

    指针是C语言的难点和重点,但指针也是C语言的灵魂 . 这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较 ...

  9. 不可或缺 Windows Native (7) - C 语言: 指针

    [源码下载] 不可或缺 Windows Native (7) - C 语言: 指针 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 指针 示例cPointer.h #i ...

随机推荐

  1. MemoryMappingFile泄漏分析过程

    最近项目突然收到了一个紧急的问题报告 - 用户在进行某些关键操作的时候整个软件突然就crash掉了.幸好产品继承了自动抓取dump的功能...   收到dump之后,通过windbg打开,查看相应的c ...

  2. DOCTYPE的笔记

    平时用HTML5 所以都直接简写doctype <!DOCTYPE html> <html> 从来没考虑这个东西全文是什么 <!DOCTYPE html PUBLIC & ...

  3. Container View Controller

    有时候,我们的Controler中包含有另一个controler view的view时,可以使用这种方式. https://developer.apple.com/library/ios/featur ...

  4. 第二百三十天 how can I 坚持

    上周日去蟒山摘的松子吗?应该是松子吧,裂开了呢.为啥呢.原来博客园可以上传图片,只是上传起来好费劲啊. 今天程哥问给我分的活多不多,我竟然说了句好多,哎.其实很多问题可以用还好来回答,还好,还行,哈哈 ...

  5. 我的第一个CUDA程序

    最近在学习CUDA框架,折腾了一个多月终于把CUDA安装完毕,现在终于跑通了自己的一个CUDA的Hello world程序,值得欣喜~ 首先,关于CUDA的初始化,代码和解释如下,这部分主要参考GXW ...

  6. C++11角括号

    [C++11角括号] 标准 C++ 的剖析器一律将 ">>" 视为右移运算符. 但在样板定义式中,绝大多数的场合其实都代表两个连续右角括号. 为了避免剖析器误判,撰码时 ...

  7. 自然对数e

    上学时课本里提到过,有一种以无理数e=2.71828--为底数的对数,称为自然对数.当时老师并没有讲明白这是个啥东西.并且还有一个很奇怪的极限,也是靠记忆的,完全不理解. \[\lim_{n\to\i ...

  8. hdu 4738 Caocao's Bridges(桥的最小权值+去重)

    http://acm.hdu.edu.cn/showproblem.php?pid=4738 题目大意:曹操有一些岛屿被桥连接,每座都有士兵把守,周瑜想把这些岛屿分成两部分,但他只能炸毁一条桥,问最少 ...

  9. Python基础 练习题

    DAY .1 1.使用while循环输出 1 2 3 4 5 6     8 9 10 n = 1 while n < 11: if n == 7: pass else: print(n) n ...

  10. 手把手教你玩转SOCKET模型之重叠I/O篇(下)

    四.     实现重叠模型的步骤 作 了这么多的准备工作,费了这么多的笔墨,我们终于可以开始着手编码了.其实慢慢的你就会明白,要想透析重叠结构的内部原理也许是要费点功夫,但是只是学会 如何来使用它,却 ...