您的位置:首页 > 编程语言 > C语言/C++

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

2015-08-27 13:03 337 查看


C语言指针的小故事

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


C与指针



楔子

初学者常闻到:指针乃C的精华。

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


开篇 从简单的开始

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

指针是一个变量,什么变量,指向一个内存地址的变量。

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

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

我们把计算机比作一个旅馆,内存地址就是这个旅馆中的房间的门牌号,而指针则是一把唯一的能打开相应房门的钥匙

(这是个有Bug的比喻,请看下面的 戏说多级指针————奇怪的房间问题 解释)。

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

[code]    int nNum = 0;

int* pNum = &nNum;

[/code]

那么
nNum
就是一个房间了,而
pNum
就是对应的钥匙,我们要打开房间只需要

简单的通过钥匙就能实现, 而在 C 语言中这种实现是
*pNum


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

[code]    int nNum = 0;

int* pNum = &nNum;

if (*pNum == nNum)

{

printf("True");

}

else

{

print("False");

}

[/code]


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


不得不说的变量类型

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

我们的变量也是如此,但是事实上并不是这样的哦。

就目前来说,在 x86 环境下,笔者见过的变量类型实质上只有这么几种。

1 字节 ==> 简单的举例: char

2 字节 ==> 简单的举例: short int

4 字节 ==> 简单的举例: int,long (x86 下所有的指针都是 4 字节)

8 字节 ==> 简单的举例: double

有人或许会说,C语言不是还有 bool,额,有必要解释一下。

在 C99 中加入了 一个
_Bool
,这个是目前在 VS2013 上可以直接使用的 C 语言 布尔型关键字。

还有一点就是
byte
并不是关键字。


指针从哪里来

通常我们是这样声明一个指针变量的

在一个基本的变量类型后面加上一个
*


例如:

[code]    char*  pChar;

int*   pInteger;

float* pFloat;

[/code]

通常我们是这样取出一个变量的地址的

在变量前面加上取地址符
&


[code]    int     nNum;

int*    pNum = &nNum;

[/code]


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

由于指针本身是一个 4字节的变量,那么我们是不是能用任意一个4字节变量来保存地址呢?

也就是能不能把房间当做钥匙呢?

[code]int nNum;

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

[/code]

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

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

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

[code]int nNum;

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

[/code]

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

[code]    printf("0x%X",npNum);

[/code]

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

[code]    printf("%d",*npNum);

[/code]

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

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

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


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

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

我们来变一下:

[code]    void Demo_02()

{

int nNum;

int npNum = (int)&nNum;

printf("%d\n", *(int*)npNum);

}

[/code]

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

[code]    void Demo_02()

{

int nNum = 1;   // 整理一下房间

int npNum = (int)&nNum;

printf("%d\n", *(int*)npNum);

}

[/code]

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

1

请按任意键继续. . .


糟糕了,
256
先生不见了

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

[code]    void Demo_02()

{

int nNum = 256;                 // 整理一下房间

int npNum = (int)&nNum;     

int nSir256 = *(char*)npNum;    // 钥匙错了

printf("%d\n", nSir256);

}

[/code]

粗心的管理员把 原本是
int
的房间的钥匙给铸成了
char*
了。

打开门之后,我们发现 房间干干净净,
256
先生好像从来没有来过一样。

我们来看一张
int
房间内部的分布图[1]

| 厨房 | 卫生间| 厕所| 阳台 | 隔壁房间 ....

| 0x00 | 0x01 | 0x00 | 0x00 | 老王.....

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

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


新的尝试

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

[code]    void Demo_02()

{

int nNum = 256;                 // 整理一下房间

char* cpNum = (char*)&nNum;

int nSirc   = *ncNum;

printf("%d\n", nSirc);

}

[/code]

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

结论: 指针(钥匙)决定了房间的大小。明天去买一个豪宅(大房间)的钥匙吧。:)

注意: 如果你的房间很小却配了一把豪宅(大房间)钥匙的话,隔壁老王说不定会来找你麻烦。


进阶的修罗场

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


混乱的运算符优先级

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

[code]    1. int* nNum[10];

2. int (*nNum)[10];

3. int (*nNum)(int);

4. int (*nNum[10])(int);

5. int *nNumA, **nNumB;

6. char str[];

7. char* strA, **strB;

[/code]

:) 你能得到多少分?

:( 如果有很多做不出来,记得好好理解。

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

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

() > [] > *

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

[code]int (*Func[10])(int);


int (*(Func[10]))(int);

[/code]

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

好比说

[code]int nNum[10];


int (nNum[10]);

[/code]

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


混乱的指针长度

[code]    void Demo_04()

{

int* pNum;

int  nNumArry[10];

int* pNumC = nNumArry;

printf("%d---%d---%d", sizeof(pNum), sizeof(nNumArry), sizeof(pNumC));

}

[/code]

结果:

4 --- 40 ---- 4

结论:

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

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


strlen 和 szieof

[code]    void Demo_05()

{

// Hello,World 经常被大牛耻笑,虽然这种事情完全没有根据

char  szA[20] = "No Hello,World!" ;

char* szB = "No Hello,World!";

printf("A=>sizeof: %d -- strlen: %d\n", sizeof(szA), strlen(szA));

printf("B=>sizeof: %d -- strlen: %d\n", sizeof(szB), strlen(szB));

}

[/code]

结果:

A=>sizeof: 20 -- strlen: 15

B=>sizeof: 4 -- strlen: 15


二维数组

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

[/code]

nNum 是什么?

nNum + 1 是什么?

*nNum + 1 是什么?

[code]    void Demo_06()

{

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

printf("%d --- %d --- %d", **nNum, **(nNum + 1), *(*nNum + 1));

}

[/code]

结果:

1 --- 4 --- 2

结论:

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

*nNum 才是一个 int*

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

备注:

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


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

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

[code]    int** ppNum;

int nNumArry[10];

ppNum = &nNumArry;

[/code]

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

如果指针是钥匙,那么二级指针就是放在 钥匙里面的钥匙咯。:)

真的是一个奇怪的说法。

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

想了很久,最后决定这样解释比较好一点。

int**
是一个“假房间”的钥匙。

假房间就是说我明明是拿钥匙要打开房间的,结果打开了之后发现房间变成了一个钥匙。

(不要关心是怎么打开了)

反正你打开了,结果是你没有得到一个房间而是得到一把新的钥匙
int *


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


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


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

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

如果说 有一天你要去一个地方旅游,海关告诉你,要去旅游可以,但是有一个有趣的游戏规则。

如果你带了什么过去的话,其实并不会被你真的带过去,有一个叫做系统的家伙会在你要去的地方给你弄个假的东西,和原来一模一样的。

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

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

为了解决这个问题,于是我想到了一个好办法,我不带房间了,我带一把钥匙过去。

这个叫系统的坏家伙就把我的钥匙给复制了一份,但是这并不影响我打开房间。

(不要问房间怎么打开,这是任意门的钥匙,而任意门遍布世界各地)。。(我编不下去了)

于是我回来的时候,我的房间里面的都装回来了

(房间君: 我就没动过好吧,你那是任意门的钥匙(づ ̄3 ̄)づ╭❤~)

虽然钥匙换了几遍,但是它不影响我打开这个门。

接下来是二级指针:

有一天,我突发奇想要把我的钥匙带到另一个地方去做个美容。

(钥匙君:我才不要什么美容)。

于是我又想到了新的方法,

把我的钥匙变成一个假房间,然后我带着这个假房间的钥匙过去

过去之后我打开了这个假房间,于是我得到了之前的那把钥匙。

做完美容之后。我又把这个钥匙变成假房间。

于是系统这个家伙再也没办法对付我的钥匙了。

(钥匙君:我才不要变来变去)

备注:

钥匙 指针

假房间 多级指针

旅游 函数调用

新地方 函数内部

最后说一下引用是什么?

引用就是 我有个叫做 &的兄弟在给系统打工,我把房间给他,他就会帮我干上面的那活。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: