大家好,又见面了,我是你们的朋友全栈君。
目录
在风起云涌的编程世界中,C/C++作为编程界的扛把子,以霸主地位而屹立不倒,究其原因,它有其他语言无法相媲美的“底牌”而存在,那就是——指针。指针被称为是C/C++中的精髓,也有人说由于指针的存在让C/C++变得更加难学,难懂,难消化。果真是这样吗?本篇文章让我们一起来揭开指针的真正面纱。
一.变量的内存实质到
1.1变量的实质
要理解指针,首先就要理解“变量”的存储实质,如果你已经深入理解“变量”的存储实质,直接跳过本小节……
首先我们来看一下内存空间图:
如图所示,内存只不过是一个存放数据的空间,可以理解为装鸡蛋的篮子,装水果的箱子,或是装RMB的钱包,随便啦,反正就是这么一个很随意的玩意……现在我们把它想象成电影院的座位,电影院中的每个座位都要编号,而我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧!所以内存也要象座位一样进行编号了,这就是我们所说的内存编址(为内存进行地址编码)。座位可以是遵循“一个座位对应一个号码”的原则,从“第 1 号”开始编号。而内存则是按一个字节接着一个字节的次序进行编址,如上图所示。每个字节都有个编号,我们称之为内存地址。
内存编址:
当我们在程序中写下了这样的语言声明:
int i;
char a;
时,它其实是内存中申请了一个名为 i 的整型变量宽度空间(DOS 下的 16 位编程中其宽度为 2 个字节),和一个名为 a 的字符型变量宽度的空间(占 1 个字节)。
内存中的映象如下图所示:
图中可看出,i 在内存起始地址为 6 上申请了两个字节的空间(我这里假设
了 int 的宽度为 16 位,不同系统中 int 的宽度可能是不一样的,最常用的win32环境下为4个字节),并命名为 i。
a 在内存地址为 8 上申请了一字节的空间,并命名为 a。阿欧……这样我们就有两个不同类型的变量了。变量有了接下来我们考虑的就是如何给变量进行赋啦。
1.2 赋值给变量
再看下面赋值:
i = 30;
a = ’t’;
你当然知道个两个语句是将 30 存入 i 变量的内存空间中,将“t”字符存入 a 变量的内存空间中。我们可以利用这样来形象理解:
我们将30存在了以地址6(真正的地址可不是这样子哦,真正的地址如:0016FD14)为起始地址的两个字节空间里,a 在内存地址为 8 上申请了一字节的空间存入了‘t’,那么变量i和a在哪呢???
1.3 变量在哪里?
接下来我们来看看&i 是什么意思?是取 i 变量所在的地址编号嘛!我们可以这样大声读出来:返回 i 变量的地址编号。你记住了吗?如果没有,在读一遍:返回 i 变量的地址编号。
以一小段简单代码来弱弱看下:
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int i = 30;
std::cout << "&i = "<< &i << std::endl;
std::cout << "i = " << i << std::endl;
system("pause");
return 0;
}
输出结果为:
输出的 &i 的值 0016FD14就是我们图示中内存空间编码为6的内存地址。接下来就进入我们真正的主题——指针。
二. 指针是个什么东西?
指针,想说弄懂你不容易啊!我常常在思索它,为什么呢?其实生活中处处都有指针,我们也处处在使用它。有了它
我们的生活才更加方便了。没有指针,那生活才不方便。不信?你看下面的例子。
比如有天你说你要学习C++,要借我的这本 C++ Primer Plus,我把书给你送过去发现你已经跑出去打篮球了,于是我把书放在了你桌子上书架的第三层四号的位置。并写了一张纸条:你要的书在第 三 层 四号的书架上。贴在你门上。当你回来时,看到这张纸条,你就知道了我借与你的书放在哪了。你想想看,这张纸条的作用,纸条本身不是书,它上面也没有放着书。那么你又如何知道书的位置呢?因为纸条上写着书的位置嘛!聪明!!!其实这张纸条就是一个指针了。它上面的内容不是书本身,而是
书的地址,你通过纸条这个指针找到了我借给你的这本书。
那么我们 C/C++中的指针又戴上了啥面具呢?让我们拭目以待。下面看一条声明一个指向整型变量的指针的语句:
int *pi;
pi 是一个指针,当然我们知道啦,但是这样说,你就以为 pi 一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。好了,这就是指针。仅此而已,就这么简单。不信你看下图:
(说明:这里我假设了指针只占 2 个字节宽度,实际上在 32 位系统中,指针的宽度是 4 个字节宽的,即 32 位。)
由图示中可以看出,我们使用“int *pi”声明指针变量 —— 其实是在内存的某处声明一个一定宽度的内存空间,并把它命名为 pi。你能在图中看出pi 与前面的 i、a 变量有什么本质区别吗?没有,当然没有!肯定没有!!真的没有!!!pi 也只不过是一个变量而已嘛!那么它又为什么会被称为“指针”?关键是我们要让这个变量所存储的内容是什么。现在我要让 pi 成为具有真正“指针”意义的变量。请接着看下面语句:
pi = &i;
你应该知道 &i 是什么意思吧!刚刚大声读的那句话,如果忘了,回头去在大声读,记住了!!!那这句代码怎么读呢?这样大声读:把 i 地址的编号赋值给 pi。并记下来。也就是你在 pi 里面写上 i 的地址编号。结果如下图所示:
你看,执行完 pi=&i 后,在图示中的内存中,pi 的值是 6。这个 6 就是i 变量的地址编号,这样 pi 就指向了变量 i 了。你看,pi 与那张纸条有什么区别?pi 不就是那张纸条嘛!上面写着 i 的地址,而 i 就是那本厚书C++ Primer Plus。你现在看懂了吗?因此,我们就把 pi 称为指针。所以你要牢牢记住:指针变量所存的内容就是内存的地址编号 ! 本篇文章看完啥都可以没记住,这18个红字切记,切记,切记要牢牢刻在脑子里,也许可能或许大概在将来某个面试中人家问你指针是啥?这18个字足以得满分。也会随着你不断的学习对这句话会理解的越来越深。切记记住这18个红字!!好了废话太多了,现在我们就可以通过这个指针 pi 来访问到 i 这个变量了,请看代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int i = 30;
std::cout << "&i = "<< &i << std::endl;
std::cout << "i = " << i << std::endl;
int *pi = &i;
std::cout << "*pi = " << *pi << std::endl;
system("pause");
return 0;
}
输出结果如下:
那么程序中*pi 什么意思呢?你只要这样大声读它:pi 内容所指的地址的内容(读上去好像在绕口令了),就是 pi 这张“纸条”上所写的位置上的那本 “书”—— i 。你看,Pi 的内容是 6,也就是说 pi 指向内存编号为 6 的地址。*pi嘛,就是它所指地址的内容,即地址编号 6 上的内容了,当然就是 30 这个“值”了。所以这条语句会在屏幕上显示 30。请结合上图好好体会吧!由于本人水平有限,对“指针是个什么东西?”的理解仅限于此,有问题我们在随时探讨喽。真想说:指针是个什么东西,这么难理解,脑仁疼疼的。
到此为止,你已经能大声读出类似&i、*pi 写法的含义了。也知道了具体的相关操作。总结一句话:我们的纸条就是我们的指针,同样我们的 pi 也就是我们的纸条!剩下的就是我们如何应用这张纸条了。如何用?大声读出下面的代码并正确理解含义。
char a,*pa;
a = 10;
pa = &a;
*pa = 20;
假设你已经完全掌握在此之前的所有内容,我们接着看看“指针的指针”它是个什么东西?即对int **ppa;中 ppa 的理解。
三. 二级指针(指针的指针)
二级指针,是一种指向指针的指针。我们可以通过它实现间接访问数据,和改变一级指针的指向问题。
3.1 定义与初始化
以一张简图说明问题:(看不懂看第二节)
例子代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int i = 30;
std::cout << "&i = "<< &i << std::endl;
std::cout << "i = " << i << std::endl;
int *pi = &i;
std::cout << "*pi = " << *pi << std::endl;
int **ppi = π
std::cout << "**ppi = " << **ppi << std::endl;
system("pause");
return 0;
}
输出结果:
思考:**pi 怎么读?代表的含义是什么?
3.2 间接数据访问
3.2.1 .改变一级指针指向
例子代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int i = 30;
int *pi = &i;
std::cout << "一级指针*pi = " << *pi << std::endl; //一级指针
int **ppi = π
std::cout << "二级指针**ppi = " << **ppi << std::endl; //二级指针
*pi = 20;
std::cout << "改变一级指针内容: *pi = " << *pi << std::endl; //改变一级指针值
std::cout << "一级指针*pi = " << *pi << std::endl; //二级指针
int b = 10;
*ppi = &b;
std::cout << "改变一级指针指向*pi = " << *pi << std::endl; //改变一级指针的指向
std::cout << "二级指针**ppi = " << **ppi << std::endl;
system("pause");
return 0;
}
输出结果:
3.2.2 改变 N-1 级指针的指向
- 可以通过一级指针,修改 0 级指针(变量)的内容。
- 可以通过二级指针,修改一级指针的指向。
- 可以通过三级指针,修改二级指针的指向。
- ·····
- 可以通过 n 级指针,修改 n-1
3.2.3 二级指针的步长
所有类型的二级指针,由于均指向一级指针类型,一级指针类型大小是 4,所以二级指针的步长也是 4,这个信息很重要。
四. 指针与数组
4.1 指针与数组名
4.1.1 通过数组名访问数组元素
看下面代码:
int i, a[] = {3,4,5,6,7,3,7,4,4,6};
for (i = 0; i <= 9; i++)
{
std::cout << a[i] std::endl;
}
很显然,它是显示 a 数组的各元素值。
我们还可以这样访问元素,如下:
int i, a[] = {3,4,5,6,7,3,7,4,4,6};
for (i = 0; i <= 9; i++)
{
std::cout << *(a+i) << std<<endl;;
}
它的结果和作用完全一样。
4.1.2 通过指针访问数组元素
int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
pa = a; /*请注意数组名 a 直接赋值给指针 pa*/
for (i = 0; i <= 9; i++)
{
std::cout << pa[i] << std::endl;
}
很显然,它也是显示 a 数组的各元素值。
另外与数组名一样也可如下:
int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
pa = a;
for (i = 0; i <= 9; i++)
{
std::cout << *(pa+i) << std::endl;
}
4.1.3 数组名与指针变量的区别
请看下面的代码:
int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
pa = a;
for (i = 0; i <= 9; i++)
{
printf(“%d\n”, *pa);
pa++; /*注意这里,指针值被修改*/
}
可以看出,这段代码也是将数组各元素值输出。不过,你把循环体{}中的 pa改成 a 试试。你会发现程序编译出错,不能成功。看来指针和数组名还是不同的。其实上面的指针是指针变量,而 数组名只是一个指针常量。
4.2 指针数组( 字符指针数组 )
4.2.1 定义
指针数组的本质是数组,数组中每一个成员是一个指针。定义形式如下:
char * pArray[10];
语法解析:pArray 先与“[ ]”结合,构成一个数组的定义,char *修饰的是数组的内容,即数组的每个元素。
图示:
4.2.2 代码实例
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char * pArray[] ={"apple","pear","banana","orange","pineApple"};
for(int i=0; i<sizeof(pArray)/sizeof(*pArray); i++)
{
std::cout << pArray[i] << std::endl;
}
system("pause");
return 0;
}
输出结果:
4.3 二级指针与指针数组
4.3.1 .指针数组名赋给二级指针的合理性
二级指针与指针数组名等价的原因:
char **p 是二级指针;
char* array[N]; array = &array[0]; array[0] 本身是 char*型;
char **p = array;
例子代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char * pArray[] ={"apple","pear","banana","orange","pineApple"};
std::cout << "**********pArray[i]************" << std::endl;
for(int i=0; i<sizeof(pArray)/ sizeof(*pArray); i++)
{
std::cout << pArray[i] << std::endl;
}
char **pArr = pArray;
std::cout << "**********pArr[i]************" << std::endl;
for(int i=0; i<sizeof(pArray)/ sizeof(*pArray); i++)
{
std::cout << pArr[i] << std::endl;
}
system("pause");
return 0;
}
输出结果:
4.3.2 完美匹配的前提(小尾巴 NULL)
数组名,赋给指针以后,就少了维度这个概念,所以用二级指针访问指针数组,需要维度,当然了,也可以不用需要。
实例代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
//演绎 1
std::cout << "******演绎 1*****" << std::endl;
int arr[10] = {1};
for(int i=0; i<10; i++)
{
std::cout << arr[i] << std::endl;
}
int *parr = arr;
for(int i=0; i<10; i++)
{
std::cout << *parr++ << std::endl;
}
//演绎 2
std::cout << "*****演绎 2*****"<<std::endl;
char *str = "china";
while(*str)
{
std::cout << *str++ << std::endl;
}
char * pArray[] ={"apple","pear","banana","orange","pineApple",NULL};
char **pa = pArray;
while(*pa != NULL)
{
std::cout << *pa++ << std::endl;
}
system("pause");
return 0;
}
输出结果:
【下一篇:】C/C++指针详解之提高篇(史上最全最易懂指针学习指南!!!!)
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143274.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...