对于程序员来说内存可以简化成这样一种东西:
<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. 【转】 Linux Shell 命令--rename

    重命名文件,经常用到mv命令,批量重命名文件rename是最好的选择,Linux的rename 命令有两个版本,一个是C语言版本的,一个是Perl语言版本的,判断方法:输入man rename 看到第 ...

  2. AC多模式匹配算法

    建议:学习ac算法最好的途径是看论文pdf_Efficient_String_Matching_An_Aid_to_Biblio 一.一般的搜索算法 keyword: { he, she, his, ...

  3. Classic Source Code Collected

    收藏一些经典的源码,持续更新!!! 1.深度学习框架(Deep Learning Framework). A:Caffe (Convolutional Architecture for Fast Fe ...

  4. Strider 持续集成(gitlab)

    Strider安装后运行: Mac: strider Ubuntu: bin/strider 本地运行时浏览器访问: http://127.0.0.1:3000 其他服务器:服务器地址 + 端口号(3 ...

  5. iOS app的webview注入JS遇到的坑

    webview使用JSContext 向网页js注入时时机要选为网页加载完成后即放在 -(void)webViewDidFinishLoad:(UIWebView *)webView 方法 : -(v ...

  6. 第三百三十九天 how can I 坚持

    脑子里老是无缘无故浮现出之前学的古文,之前只是傻学了,什么都没搞懂啊. 吾师道也,夫庸知其年之先后生于吾乎?是故无贵无贱,无长无少,道之所存,师之所存也. 是故弟子不必不如师,师不必贤于弟子,闻道有先 ...

  7. 第一百九十七-第二百天 how can I 坚持

    又是四天,how 快. 第一天,晚上要坐车回济南,没下班就躁动了.晚上高铁竟然是知道济南西,中间没有停,到济南九点半,去刘松家又吃了一顿.喝了不少酒.挺爽. 第二天,早上五点多就醒了,睡的婚床,哈哈, ...

  8. Android反射出一个类中的其他类对象并调用其对应方法

    MainActivity如下: package cn.testreflect; import java.lang.reflect.Field; import java.lang.reflect.Met ...

  9. JAVA 开发实例 一 移动的小球

    package com.java.move; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; i ...

  10. 在sql中使用了 hashbytes 函数

    在做项目的时候,在sql中使用了hashbytes函数,运用md5算法去加密一密码.代码如下 DECLARE @psw nvarchar(20) SET @psw = 'admin' SELECT h ...