来自--C语言指针的小故事

C语言指针的小故事

文章来自:https://www.zybuluo.com/FadeTrack/note/160734

C与指针

楔子

初学者常闻到:指针乃C的精华。 
那么指针究竟妙在哪里?如何去理解指针这个存在有些迫在眉睫,包括学习中都经常发现很多人以为自己真的懂指针,实际上你对指针理解多少呢?

开篇 从简单的开始

指针,你是如何定义这个名词的存在呢?

指针是一个变量,什么变量,指向一个内存地址的变量。 
什么是内存地址?这是个深奥的概念,如果说硬是要扯起来,要涉及的东西比较多。

谭浩强的 《C语言程序设计》中有这样一个例子(我记得是)。

我们把计算机比作一个旅馆,内存地址就是这个旅馆中的房间的门牌号,而指针则是一把唯一的能打开相应房门的钥匙 
(这是个有Bug的比喻,请看下面的 戏说多级指针————奇怪的房间问题 解释)。

这个解释的话对于普通的指针解释似乎是行的通的。

 
 
  1. int nNum = 0;
  2. int* pNum = &nNum;

那么 nNum 就是一个房间了,而 pNum 就是对应的钥匙,我们要打开房间只需要 
简单的通过钥匙就能实现, 而在 C 语言中这种实现是 *pNum

那么我们就可以尝试一下了。

 
 
  1. int nNum = 0;
  2. int* pNum = &nNum;
  3. if (*pNum == nNum)
  4. {
  5. printf("True");
  6. }
  7. else
  8. {
  9. print("False");
  10. }

房间的大小(指针的区别到底在哪?)

不得不说的变量类型

接下来我们开始考虑一个问题,房间的大小问题,我们知道旅馆的房间有大有小,有圆有方(应该没有圆的),总而言之就是千奇百怪。

我们的变量也是如此,但是事实上并不是这样的哦。 
就目前来说,在 x86 环境下,笔者见过的变量类型实质上只有这么几种。

1 字节 ==> 简单的举例: char 
2 字节 ==> 简单的举例: short int 
4 字节 ==> 简单的举例: int,long (x86 下所有的指针都是 4 字节
8 字节 ==> 简单的举例: double

有人或许会说,C语言不是还有 bool,额,有必要解释一下。 
在 C99 中加入了 一个 _Bool,这个是目前在 VS2013 上可以直接使用的 C 语言 布尔型关键字。 
还有一点就是 byte 并不是关键字。

指针从哪里来

通常我们是这样声明一个指针变量的 
在一个基本的变量类型后面加上一个 * 
例如:

 
 
  1. char* pChar;
  2. int* pInteger;
  3. float* pFloat;

通常我们是这样取出一个变量的地址的 
在变量前面加上取地址符 &

 
 
  1. int nNum;
  2. int* pNum = &nNum;

联想到的事情---把钥匙变成房间

由于指针本身是一个 4字节的变量,那么我们是不是能用任意一个4字节变量来保存地址呢? 
也就是能不能把房间当做钥匙呢?

 
 
  1. int nNum;
  2. int npNum = &nNum; // 这里把一个 int 类型当做指针用所以变量命名为 npNum

我们写下如上的代码,测试之。发现编译器报出了一个警告,但是依旧可以编译通过。

警告 1 warning C4047: “初始化”:“int”与“int *”的间接级别不同

根据警告的提示 再修改代码如下:

 
 
  1. int nNum;
  2. int npNum = (int)&nNum; // 这里把一个 int 类型当做指针用所以变量命名为 npNum

警告赫然消失了,我们试图输出一下.

 
 
  1. printf("0x%X",npNum);

我们再试一下这把新的钥匙能不能开门:

 
 
  1. printf("%d",*npNum);

编译器给了我们一个脸色呢:

错误 1 error C2100: 非法的间接寻址

好吧我们得到了一把没办法开门的“钥匙”,接下来我们讨论一下这把“钥匙”为什么不能开门。

你知道么?房间是没办法打开房间的。

由于我们上面已经试验了,房间是打不开房间的,所以我们需要把房间变回钥匙再去开门。

我们来变一下:

 
 
  1. void Demo_02()
  2. {
  3. int nNum;
  4. int npNum = (int)&nNum;
  5. printf("%d\n", *(int*)npNum);
  6. }

结果告诉我们,成功的打开了一扇新世界的大门,但是因为我们房间里面还没清理,所以一篇乱七八糟。

 
 
  1. void Demo_02()
  2. {
  3. int nNum = 1; // 整理一下房间
  4. int npNum = (int)&nNum;
  5. printf("%d\n", *(int*)npNum);
  6. }

我们可以看到整理干净之后的房间里面住了 1 先生


请按任意键继续. . .

糟糕了,256先生不见了

有一天,旅馆的管理员(我们)不小心把房间变成钥匙的过程搞错了。

 
 
  1. void Demo_02()
  2. {
  3. int nNum = 256; // 整理一下房间
  4. int npNum = (int)&nNum;
  5. int nSir256 = *(char*)npNum; // 钥匙错了
  6. printf("%d\n", nSir256);
  7. }

粗心的管理员把 原本是 int 的房间的钥匙给铸成了 char* 了。 
打开门之后,我们发现 房间干干净净, 256 先生好像从来没有来过一样。

我们来看一张 int 房间内部的分布图[1]: 
| 厨房 | 卫生间| 厕所| 阳台 | 隔壁房间 .... 
| 0x00 | 0x01 | 0x00 | 0x00 | 老王.....

原来如此,我们因为钥匙铸错了,所以房间只剩下了一个厨房了。256 先生正在上厕所呢。 :)

我们设想 如果有一天,我们马虎的把 一个 char 房间的钥匙给铸成了一个 int*,隔壁的老王肯定不会饶了你,给你一个大大的脸色。

新的尝试

我们试着一开始就用上一些奇怪的钥匙。

 
 
  1. void Demo_02()
  2. {
  3. int nNum = 256; // 整理一下房间
  4. char* cpNum = (char*)&nNum;
  5. int nSirc = *ncNum;
  6. printf("%d\n", nSirc);
  7. }

果然,结果和我们猜想的一样。那么指针到底是个什么呢?

结论: 指针(钥匙)决定了房间的大小。明天去买一个豪宅(大房间)的钥匙吧。:) 
注意: 如果你的房间很小却配了一把豪宅(大房间)钥匙的话,隔壁老王说不定会来找你麻烦。

进阶的修罗场

说实在的,这个东西不怎么好比喻,接下来就是修罗场了。

混乱的运算符优先级

先热热身,指出下面的指针分别代表什么[2]

 
 
  1. 1. int* nNum[10];
  2. 2. int (*nNum)[10];
  3. 3. int (*nNum)(int);
  4. 4. int (*nNum[10])(int);
  5. 5. int *nNumA, **nNumB;
  6. 6. char str[];
  7. 7. char* strA, **strB;

:) 你能得到多少分? 
:( 如果有很多做不出来,记得好好理解。

  1. 指针数组 (一个元素个数为10的数组,每一个元素都是一个 int* 的指针)
  2. 一个指向数组名的指针(实际上数组名本身就是一个指针,这里实际上就是一个 int**)
  3. 一个函数指针,指向函数名 ( 故意写的 nNum 来混淆视线)
  4. 一个函数指针数组( 函数指针数组 就是说数组的每一个元素都是 函数指针)
  5. 一个 int* 指针 和一个 二级指针 (int**)
  6. 一个 char* 指针
  7. 一个 char* 指针 和一个 二级指针(char**)

到这里 梳理一下指针运算的几个运算符优先级:

() > [] > *

那么记不住怎么办?没办法,但是如果使用中不记得话 就记得打上括号吧! :) 我一直是这么做的。

 
 
  1. int (*Func[10])(int);
  2. int (*(Func[10]))(int);

先说一下这样写的原因, 首先函数指针数组 是一个数组,其次他才是一个 什么类型的数组。 
好比说

 
 
  1. int nNum[10];
  2. int (nNum[10]);

虽然这样臃肿难看,但是如果记不住优先级的话,请一定不要吝啬你的().

混乱的指针长度

 
 
  1. void Demo_04()
  2. {
  3. int* pNum;
  4. int nNumArry[10];
  5. int* pNumC = nNumArry;
  6. printf("%d---%d---%d", sizeof(pNum), sizeof(nNumArry), sizeof(pNumC));
  7. }

结果:

4 --- 40 ---- 4

结论: 
数组名虽然也是一个地址,甚至可以直接给指针赋值。

但是 数组名不等于指针,这是一个陷阱。

strlen 和 szieof

 
 
  1. void Demo_05()
  2. {
  3. // Hello,World 经常被大牛耻笑,虽然这种事情完全没有根据
  4. char szA[20] = "No Hello,World!" ;
  5. char* szB = "No Hello,World!";
  6. printf("A=>sizeof: %d -- strlen: %d\n", sizeof(szA), strlen(szA));
  7. printf("B=>sizeof: %d -- strlen: %d\n", sizeof(szB), strlen(szB));
  8. }

结果:

A=>sizeof: 20 -- strlen: 15 
B=>sizeof: 4 -- strlen: 15

二维数组

 
 
  1. int nNum[2][3] = {1, 2, 3, 4, 5, 6};

nNum 是什么? 
nNum + 1 是什么? 
*nNum + 1 是什么?

 
 
  1. void Demo_06()
  2. {
  3. int nNum[2][3] = { {1, 2, 3}, {4, 5, 6 }};
  4. printf("%d --- %d --- %d", **nNum, **(nNum + 1), *(*nNum + 1));
  5. }

结果:

1 --- 4 --- 2

结论:

nNum 他的类型并不是一个 int*,而是 int[3],他每加 1 就会后移 3个元素 
*nNum 才是一个 int* 
&nNum 就是一个 int[2][3] 的类型了,他每加 1 就会后移 6个元素

备注:

如果硬是要说 数组即指针的话,那二维数组请当做二级指针来理解,但是请不要搞混了指针到底是什么类型的。

戏说多级指针————奇怪的房间问题

如何这里再沿用初试的那个比喻就会出现奇怪的问题了。

 
 
  1. int** ppNum;
  2. int nNumArry[10];
  3. ppNum = &nNumArry;

于是乎,我们最初的比喻出现了问题了。

如果指针是钥匙,那么二级指针就是放在 钥匙里面的钥匙咯。:) 
真的是一个奇怪的说法。

为了消除掉这个 Bug ,我又要开始自圆其说了

想了很久,最后决定这样解释比较好一点。 
int** 是一个“假房间”的钥匙。

假房间就是说我明明是拿钥匙要打开房间的,结果打开了之后发现房间变成了一个钥匙。 
(不要关心是怎么打开了) 
反正你打开了,结果是你没有得到一个房间而是得到一把新的钥匙 int *

如果再用这把新的钥匙去打开房间 我们就能得到一个真正的房间 int

二级指针就是 挂羊头卖狗肉的假房间。 :)

多级指针的意义[指针的意义]

我觉得我说的这种意义可能有些以篇概全的味道,可能不是很好。

如果说 有一天你要去一个地方旅游,海关告诉你,要去旅游可以,但是有一个有趣的游戏规则。 
如果你带了什么过去的话,其实并不会被你真的带过去,有一个叫做系统的家伙会在你要去的地方给你弄个假的东西,和原来一模一样的。

(求求你们放过我吧,不要说什么浅拷贝逻辑不对问题了,不要在意那些细节)。

这样就有了一个问题了,有一天我要回来了,我的房间里面的东西都带不回来了,它会把你以前的房间给你。 TAT..

为了解决这个问题,于是我想到了一个好办法,我不带房间了,我带一把钥匙过去。 
这个叫系统的坏家伙就把我的钥匙给复制了一份,但是这并不影响我打开房间。 
(不要问房间怎么打开,这是任意门的钥匙,而任意门遍布世界各地)。。(我编不下去了)

于是我回来的时候,我的房间里面的都装回来了 
(房间君: 我就没动过好吧,你那是任意门的钥匙(づ ̄3 ̄)づ╭❤~) 
虽然钥匙换了几遍,但是它不影响我打开这个门。

接下来是二级指针:

有一天,我突发奇想要把我的钥匙带到另一个地方去做个美容。 
(钥匙君:我才不要什么美容)。

于是我又想到了新的方法, 
把我的钥匙变成一个假房间,然后我带着这个假房间的钥匙过去 
过去之后我打开了这个假房间,于是我得到了之前的那把钥匙。 
做完美容之后。我又把这个钥匙变成假房间。 
于是系统这个家伙再也没办法对付我的钥匙了。 
(钥匙君:我才不要变来变去)

备注:

钥匙 指针 
假房间 多级指针 
旅游 函数调用 
新地方 函数内部

最后说一下引用是什么?

引用就是 我有个叫做 &的兄弟在给系统打工,我把房间给他,他就会帮我干上面的那活。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值