本文中 我对指针总结的维度 用四个字来概括 就是:"两己三他"!是不是读起来一点都不顺口 一点都不押韵啊 什么个玩意儿。这"两己三他" 展开来说 就是:己址、己值、他值、他址、他型。

我觉得可以从这5个维度再来聊聊指针。不过在聊之前 我写了个程序 把指针的"两己三他"维度都包含进来 然后再来一个一个解释每个维度的意思 你看看是不是这回事儿。

在大部分的使用指针的场景下 这5个维度应该足够帮你去理解了。不过在一些使用指针特殊的场景下 可能5维度法帮助不了你。

前方长文预警 若看的不耐烦了 可以收藏本文 有时间了接着看。

一、程序代码

1.1. 代码

实例

#include <stdio.h>int main(void){int *pInt = NULL;printf("指针变量pInt自己的地址是: 0X%xn", &pInt);//printf("指针变量pInt自己的值是: 0X%xn", pInt);int para = 1;printf("变量para自己的地址是: 0X%xn", &para);printf("变量para自己的值是: 0X%xn", para);pInt = &para;printf("指针变量pInt自己的值是: 0X%xn", pInt);printf("指针变量pInt的他值是: 0X%xn", *pInt);int arr_int[2] = {1, 2};pInt = arr_int;printf("arr_int第一个元素arr_int[0]的地址是: 0X%xn", pInt);printf("arr_int第二个元素arr_int[1]的地址是: 0X%xn", pInt + 1);double *pDouble = NULL;double arr_double[2] = {1.1, 2.2};pDouble = arr_double;printf("arr_double第一个元素arr_double[0]的地址是: 0X%xn", pDouble);printf("arr_double第二个元素arr_double[1]的地址是: 0X%xn", pDouble + 1);return 0;}

运行的结果如下:

v2-25383b95ca2689820e0f080f0a5.jpg

我在自己的文章里讲我自己理解的一些东西时 我很喜欢用非常简单的程序来阐明。所以那些认为要用天书般的程序来阐明观点的 或者鄙视低水平程序的大牛们 请忽略我^_^。

2.2. int型变量para

程序中:

int para = 1;printf("变量para自己的地址是: 0X%xn", ¶);printf("变量para自己的值是: 0X%xn", para);

定义了变量para 它有自己的数据值 也有自己的存储地址 这些个都很好理解。从运行结果来看 变量para自己的数据值是16进制的"0X1" 地址是16进制的"0X22feb4"。换句话说 在内存中 地址为"0X22feb4"开始的存储空间 有4个字节存储了一个数据值"0X1"。在我的机器上 一个int型变量占用4个字节 在你的机器上可能与我不一样。

关于int型变量para大家都好理解。下面 我来画一个示意图 来指明变量para在内存中存储的情况 如下:

v2-dc257a1ea4804322ea4e42f04cc.jpg

下面开始说说"两己三他"的概念。

二、两己三他

"两己三他" 展开来说 就是:己址、己值、他值、他址、他型。

2.1. 己址

2.1.1 "己址"的概念

"己址" 就是"自己的地址"的简称。指针pInt作为一个变量 与int变量para一样 也需要存储在内存中的一段存储空间 这段存储空间也会有一个开始地址 也就是说 指针变量pInt也会有自己的地址。上面说了 变量para的地址是 " 0X22feb4" 那么 指针变量pInt的地址是啥呢?

2.1.2 "己址"的获取

都学过 "&"是一个取地址的运算符 在程序中:

printf("指针变量pInt自己的地址是: 0X%xn", &pInt);

就是通过"&"来获取指针变量pInt的地址。从运行结果来看 指针变量pInt的地址是"0X22feb8"。在我的机器上 指针变量pInt也是占用4个字节 因此 指针变量pInt存储在开始地址是"0X22feb8"开始的4个字节空间。

2.1.3 "己址"的代码写法

在代码中 表示指针变量pInt的"己址"的代码写法 常见的是:

&pInt;

现在 来完善那个示意图 图中加入指针变量pInt的"己址" 指出指针变量pInt在内存中是怎么个存储形式。

v2-e4884b45870809fe5177f31c378.jpg

"己址" 就是这个意思。你看 没什么特别难的吧!

2.2 己值

2.2.1 "己值"的概念

"己值" 就是"自己的数据值"的简称。指针pInt作为一个变量 跟变量para一样 也有着自己的数据值。

2.2.2 "己值"的获取

上面提到 变量para自己的数据值是"1" 那么指针变量pInt自己的数据值是多少。在程序中:

pInt = ¶printf("指针变量pInt自己的值是: 0X%xn", pInt);

我通过"&"运算符 将变量para的地址值赋给了指针变量pInt 通过printf来输出指针变量pInt的数据值。从运行结果中来看 指针变量pInt自己的数据值是"0X22feb4"。再看 变量para的地址也是"0X22feb4" 所以

pInt = ¶

这个语句的本质 就是将变量para的地址 给了指针变量pInt的己值 这样就将指针变量pInt与变量para绑定在一起了。

在"己址"中提到了 指针pInt的数据值存储在地址为"0X22feb8"开始的4个字节的内存上 那么也就是说 地址为"0X22feb8"开始的内存 后面的4个字节都用来存储着一个数据值"0X22feb4"。

2.2.3 "己值"的代码写法

在代码中 表示指针变量pInt的"己值"的代码写法 常见的有

pInt;

也有的代码写法是:

pInt + N;pInt - N;

这种写法的意思是用pInt的"己值"加上一个数字N或者减去一个数字N 这个等讲到"他型"这个属性时会提到。也有的写法是:

pIntA - pIntB;

这种写法表示的是两个指针变量用"己值"做减法。

2.2.4 示意图

现在 继续来完善上面的示意图 加入指针变量pInt的己值。

v2-0b727b28d0b0b1057a5568fefa4.jpg

所以 一般而言 "己值"对于指针变量pInt来讲 是自己的数据值;对其它的int类型的变量来讲 就是它们的地址。

2.3 他址

2.3.1 "他址"的概念

"他址"的概念就是"他人的地址"的意思。其实在上面提到己值时 就已经不那么明显地提到了"他址"的概念。

2.3.2 "他址"的获取

整型变量para存储在内存地址为"0X22feb4"开始的4个字节。在程序中 我通过

pInt = ¶

将变量para的地址给了指针变量pInt 这样就将指针变量pInt与变量para绑定在一起了。更为本质的说 是把"他人的地址"赋值给了指针变量pInt的"己值" 这里 "他人的地址"的"他" 指的就是变量para "他人地址的址"的"址" 指的就是变量para的地址。注意 你看 "他址"和"己值"在数据值上是一样的 所以 你领悟出了什么东西来了没?

很多教材所谓的"指针是一个地址变量 存储的是其它变量的地址" 说白了 就是在说"他址"这个维度的数据值等于"己值"这个维度的数据值 只是教材没说的那么明白。

2.3.3 示意图

再来完善那个示意图 这次加入"他址"的概念。

v2-6259b67a8df9b9bfc3ee3c5e9d0.jpg

2.4 他值

2.4.1 "他值"的概念

"他值" 就是"他人的数据值"的意思。

2.4.2 "他值"的获取

在程序中 我通过

pInt = ¶

将变量para的地址给了指针变量pInt的"己值" 这样就将指针变量pInt与变量para绑定在一起了。这个时候 "他人的数据值"的"他" 指的就是变量para "他人的数据值"的"数据值" 指的就是变量para的数据值"1"。在程序中 我通过

printf("指针变量pInt的他值是: 0X%xn", *pInt);

也就是指针变量pInt前面加上" * " 来输出指针变量的"他值" 从运行结果来看 是"0X1"。 注意 你看 指针变量pInt的"他值" 与变量para的数据值是一样的 你又领悟到了什么?想不出来吗?继续看!

2.4.3 "他值"的代码写法

你经常在代码中看到的那些个代码写法 比如什么*pInt写法 是在表达什么意思啊 其实就是在计算指针变量pInt的"他值"啊!

这些个写法呢:*(pInt + 1)、*pInt + 1、pInt[1]?

*(pInt + 1):如果把pInt + 1 看成是另外一个指针 比如

int *pTemp = pInt + 1;

那么*(pInt + 1)计算的本质上就是指针变量pTemp的"他值";

*pInt + 1:这个就是用pInt的"他值"加1;

pInt[1]:这个呢?其实就是*(pInt + 1)。

2.4.4 示意图

继续完善上面的示意图 这次加入"他值"的概念:

v2-6a638da59da288b60cba6d16a9f.jpg

2.5 他型

2.5.1 "他型"的概念

"他型" 就是"他人的类型"的简称。在程序中 看到 声明指针变量pInt时这样写的:

int *pInt = NULL;

指针变量pInt前面的" int "并不是说指针变量pInt的"己值"是一个int类型的数据值;而是说 指针变量pInt的"他值"是一个" int "类型的数据值 此处指针变量pInt的"他值"是变量para的数据值"0X1" 因此 指针变量pInt前面的"int"指的就是数据值"0X1"是一个"int"型。

总之一句话 声明指针时的类型是用来修饰"他值"的 而不是"己值"。

你再看 在声明变量para的时候:

int para = 1;

变量para前面的"int"就是指变量para的类型是一个整型 此时的"int"对para来说是一个"自型" 也就是"自己的类型"的意思 只有指针在声明时的类型是"他型" 是"他人的类型"。

既然"他型"是来修饰"他值" 那么在声明指针时还要加上这个"他型"有什么意义呢?继续看!

在程序中 如下代码片段:

int arr_int[2] = {1, 2};    pInt = arr_int;printf("arr_int第一个元素arr_int[0]的地址是: 0X%xn", pInt);printf("arr_int第二个元素arr_int[1]的地址是: 0X%xn", pInt + 1);

我把一个整型数组arr_int的地址赋给了指针变量pInt 那么pInt的"己址"没有变化 还是 0X22feb8 但是"己值"却变了。

刚才指针变量pInt的"己值"还是" 0X22feb4" 也就是变量para的地址 现在变成了"0X22feac" 这个可是数组arr_int第一个元素的地址。也就是说 指针变量pInt的"己址"不会改变 但是"己值"是可以被改变的。

现在来看看"pInt"与"pInt + 1"的区别 这是在用pInt的"己值"在做运算。从运行结果来看 pInt的"己值"此时是"0X22feac" 而pInt + 1的"己值"是" 0X22feb0" 你发现了吗 两者正好相差4个字节 而一个"int"类型的数据也正好占用了4个字节。

你可能会认为 既然pInt + 1是用"己值"加1 那么应该是"0X22feac + 1" = "0X22fead"才对啊 为什么不是这样呢?这就是指针变量pInt的"他型"搞的鬼。

"他型"的意思 用大白话来说 就是:"我说 pInt 大兄弟啊 你的他值是个int型的数据值 你今后要是用你的己值 +1 +2 或者 -1 -2 可千万别傻乎乎的就真的加1个字节 加2个字节 或者就真的减1个字节、减2个字节。人家int类型占4个字节 你就得按照4个字节为一个单位 去加 1* 4个字节 、2 * 4个字节 或者减去1 * 4个字节、2 * 4个字节 知道不?哦 顺便说下 pInt + N的N 可以为正数也可以为负数"。

当然啦 如果你的机器上"int"型数据占8个字节 那么pInt + 1就是在"己值"上加8个字节 pInt + 2就是在"己值"上加 8 *2 = 16个字节 就这么个意思。

我在程序中又举了个例子来说明这个"他型"。程序如下:

double *pDouble = NULL;double arr_double[2] = {1.1, 2.2};pDouble = arr_double;printf("arr_double第一个元素arr_double[0]的地址是: 0X%xn", pDouble);printf("arr_double第二个元素arr_double[1]的地址是: 0X%xn", pDouble + 1);

这次声明一个指针变量pDouble 它的"他型"是个"double"型 它的"己值"是数组arr_double的地址 它的"他值"是数组arr_double[0]这个元素的数据值" 1.1 "。在我的机器上 一个double型占用8个字节 那么pDouble + 1就是用pDouble 的"己值"加 1 * 8个字节 pDouble + 2就是用pDouble 的"己值" 加 2 * 8= 16个字节 pDouble - 1就是pDouble 的"己值"减去 1 * 8个字节 pDouble - 2就是pDouble 的"己值"减去 2 * 8 = 16个字节 我滴个乖乖!朋友们可以对着运行结果自己计算对不对!

3. 总结

是时候来总结下了。

我声明一个指针变量:

type *pType = NULL;

pType有5个维度 分别是:

pType = (己址 己值 他址 他值 他型);

3.1 己址:即"自己的地址"

指针变量pType作为一个变量 也有自己的地址 常见的代码写法是"&pType "。

己址在一般的程序中不会被频繁地用到 如果要用的话 就涉及到"指针的指针" 这又是另外一个话题了 本文不讨论;

3.2 己值:即"自己的数据值"

指针变量pType 作为一个变量 也有自己的数据值 代码的写法是"pType "。

也可以在己值上做加减法运算 常见的代码写法有"pType + N"、"pType - N"、"pType2 - pType1"等。

3.3 他址:即"他人的地址"

指针变量pType的己值 意义除了表示自己的数据值外 还表示了与pType绑定在一起的"type"类型的变量的地址。一般而言 指针变量pType的"己值"与"他址"在数据值上是一样的。

将一个type类型的变量与pType绑定在一起的常见方式是:pType = &变量;

3.4 他值:即"他人的数据值"

一旦type类型的变量与pType绑定在一起 指针变量pType可以通过一些代码写法 来获取type类型变量的值 也就是"他值"。常见的代码写法有:" *pType "、" pType-> "等。

而这些代码的写法:" *(pType + N) "、" *(pType - N) "、" pType[N]"也是获取的"他值" 不过需要特别说明一下:

pType + N 你可以看成是:

type *pTemp = pType + N;

" *(pType + N) "其实计算的就是指针变量pTemp的"他值"。

" *(pType - N) " 你就好理解了吧;

" pType[N]"其实就是" *(pType + N) " 你就死记硬背吧。

3.5 他型:即"他人的类型"

声明指针变量pType时 前面的"type"不是用来修饰pType 的"己值"的 而是用来修饰"他值"的 也就是说 "type"不是说pType的"己值"是一个type类型的数据值 而是指pType 的"他值"是一个type类型的数据值。

"他型"在代码中的作用 主要是计算"pType + N"、"pType - N"时 pType要加上或者减去 ( N * sizeof(type) )个字节。

指针总是让人晕晕的 很可能就是让你晕在这5个维度里的一个或者几个上。把这5个维度好好理解透 指针啊 只是个纸老虎。

4. 习题讲解

讲完了5个维度 不来点上手习题怎么行。下面列举几个习题 都是跟指针有关的 都是让初学者晕的歇菜的。我用这5个维度来解读这些题 你看看是不是要轻松一点!

4.1 数组元素求和

4.1.1 程序

第一个例题是很常见的程序 就是求一个数组元素的和 程序如下:

实例

#include <stdio.h>int main(void){int *pArr = NULL;int sum = 0;int arr[3]= {1, 2, 3};pArr = arr;printf("数组元素是: ");for(int index = 0; index < 3; index++){printf("%d ", pArr[index]);}printf("n");for(int index = 0; index < 3; index++){sum = sum + *(pArr + index);}printf("数组元素和是: %dn", sum);return 0;}

程序很简单 先是输出数组的所有元素 然后计算出数组所有元素的和。运行结果如下:

v2-066768df2b079a2d8aba8c96871.jpg

4.1.2 "两己三他"的解读

4.1.2.1 输出数组元素

在输出数组元素时 代码如下:

pArr = arr;printf("%d ", pArr[index]);

这句代码等同于

pArr = arr;printf("%d ", *( pArr + index));

这里面使用了"己值"、"他型"做了加法运算 使用了"他值"获取数组元素。

"己值":代码先是将数组名 arr的数据值赋值给了pArr的"己值"。而数组名arr的数据值是啥啊 是arr[0]元素的地址 对吧!那么pArr的己值 也是arr[0]的地址 对吧!这样一来 pArr就和arr[0]绑定起来了。

至于( pArr + index)的意思呢 你就看成有一个间接的、临时的指针变量pTemp:

int *pTemp = pArr + index;

也就是说 pArr + index其实也是一个指针pTemp 只不过这个pTemp的己值是pArr的己值加上 index * sizeof(int) 个字节数。

"他型":pArr的他型是"int"型 pArr + index 是在pArr己值的基础上 加多少个字节呢?pArr的他型是int型 那么pArr + index 是不是意味着pArr的己值加上 index * sizeof(int) 个字节啊!

pArr:可以写成pArr + 0 就是加上 0 * 4 = 0个字节 此时pTemp的己值是arr[0]的地址;

pArr + 1:就是加上1 * 4 = 4 个字节 此时pTemp的己值是arr[1]的地址;

pArr + 2:就是加上2 * 4 = 8 个字节 此时pTemp的己值是arr[2]的地址;

这样 pArr + index就遍历到了数组所有元素的地址了。

你会发现 pTemp的己值 一直在发生变化;pArr的己值和己址 一直未变。

"他值":既然pArr + index能够遍历到数组所有元素的地址 再使用 *(pArr + index) 也就是*pTemp 是不是就能获取到pTemp的他值了 这样也就变量到数组所有元素的值了!

4.1.2.2 数组元素求和

求数组元素的和时 使用的代码如下:

sum = sum + *(pArr + index);

根据刚才我对输出数组元素的分析 这句代码中pArr是怎么玩的 大家也清楚了吧!

pArr + index依然是在用pArr的己值做加法运算 获取到一个临时指针pTemp的己值 这个pTemp的己值是每个数组元素的地址;

再用 *(pArr + inedx) 也就是*pTemp 获取临时指针pTemp的他值 也就是每个元素的值。

最后将pTemp的每一个他值 叠加起来 算出数组元素的和。

4.1.2.3 总结

大家试着用"两己三他"的维度去理解pArr、pArr + index、*(pArr + index)、pArr[index]、*pArr + index等常见的代码写法!

4.2 指针数组

指针数组是将指针与数组结合起来的东东 对于初学者朋友而言 会比较难理解。指针数组是一个比较大的话题 相关概念请参见一般的C语言教材 这里只是用"两己三他"的概念来解释程序中的指针部分概念。

4.2.1 程序

指针数组 我举了一个例子如下:

实例

#include <stdio.h>int main(void){char *arr[3] = {"abc", "def", "ghi"};char *pArr = arr[0];printf("字符串数组arr的每个字符串元素是: ");for(int index = 0; index < 3; index++){printf("%s ", arr[index]);}printf("n");printf("字符串数组arr第一个字符串的每个元素是: ");for(int index = 0; index < 3; index++){printf("%c ", *(pArr + index) );}printf("n");return 0;}

运行结果如下:

v2-1902f5d6682294548d4eafa3c4e.jpg

4.2.2 "两己三他"的解读

4.2.2.1 输出所有的字符串

先看指针数组的定义:

char *arr[3] = {"abc", "def", "ghi"};

你看到的这个数组的每个元素好像是一个字符串 其实本质上是这样的:

char *pChar1 = "abc", *pChar2 = "def", *pChar3 = "ghi";char *arr[3] = {pChar1, pChar2, pChar3};

数组arr的每个元素其实是一个"他型"是"char"的指针。

arr[0]就是pChar1这个指针 那么pChar1的己值或者他址是啥 当然是字符串"abc"的字符'a'的地址 那么:

printf("%s ", arr[0]);

本质上是:

printf("%s ", pChar1);

使用pChar1的己值或者他址 从字符'a'的地址开始 一个一个地输出后面的'b'和'c'。

对于pChar2和pChar3也是一样地理解。

4.2.2.2 输出第一个字符串"abc"的每个字符

代码如下:

char *pArr = arr[0];printf("%c ", *(pArr + index) );

将arr[0] 也就是pChar1的己值给了pArr的己值 那么pArr的己值和他址都是字符'a'的地址。

pArr + index 是在pArr的己值上 加上 index * sizeof(char) 个字节 给了一个临时指针变量pTemp:

char *pTemp = pArr + index;

这个指针pTemp的己值或者他址 会依次为字符'a', 'b', 'c'的地址 也就是pTemp的他值也会依次为字符'a', 'b','c' 这样指针pTemp就会依次遍历到字符串"abc"的每一个字符了。

4.3 链表

链表是使用指针最为频繁的 什么插入节点、删除节点等 都会遇到如下的代码写法:

p2 = p1->next;p1->next = p3->next;...... 

这TM什么玩意儿 晕的一腿啊!这个next指针 那个next指针 跳来跳去的 我了个去 头脑已经充满浆糊了。呵呵 指针5维度分析法来了!不过 关于链表 我觉得还是另外开辟一个文章讲吧。等把链表讲完 回头再讲这些个next指针 我跟你讲 链表的本质也就那样 你懂了之后 链表比指针还要纸老虎。

作者:石家的鱼

来源:https://zhuanlan.zhihu.com/p/27974028