我的编程空间,编程开发者的网络收藏夹
学习永远不晚

手拉手教你如何理解c/c++中的指针

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

手拉手教你如何理解c/c++中的指针

前言

指针是c语言为什么如此流行的一个重要原因,正是有了指针的存在,才使得c/c++能够可以比使用其他语言编写出更为紧凑和有效的程序,可以说,没有掌握指针,就没有权利说自己会用c/c++.然而。然而对于大多数初学者,面对指针这个概念简直是望而生畏,如果前期指针运用的不熟练,后期编写的程序随时都有可能成为一颗定时炸弹,因此今天我就花点时间给大家解释一下我自己对c/c++中指针的理解。

一,内存和地址

我们知道,计算机内存的每个字节都有一个唯一的地址,CPU每次寻址就是通过固定的步长(这就解释了为什么需要内存对齐)来跳跃进行寻址的。举个例子,我们可以把内存看做是一条长街上的一排房屋,每个房屋都有自己固定的门牌号,每座房屋里面都可以容纳数据,为了读取到某个房屋里面的数据,我们必须知道这个房屋的门牌号,根据这个门牌号来打开这个房间,取走数据。同样,计算机也必须为每个内存字节都编上号码,就像门牌号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。

二,指针的本质就是地址

当我们在程序中声明一个变量并给这个变量赋值的时候,编译器做了什么呢?实际上,变量名代表内存中的一个存储单元,在编译器对程序编译连接的时候由系统给变量分配一个地址:


int a = 10;

上面这行代码我们定义并初始化了这个变量a,系统会为a分配一块内存单元,a只是这块内存单元的别名,在程序中从变量中取值,实际上是通过变量名找到相应的内存单元,从其中读取数据。

假如系统为变量 a 分配的内存地址为0xFF00, 那么我们可以说这个地址就是变量 a 的门牌号。一个变量的地址称为该变量的指针。所以说,指针的本质就是地址,指针变量是一种特殊的变量,它专门保存指针(也即地址),当我们说这个地址对应的内存单元的时候,我们可以说这个指针指向这块内存单元。

例如:


int a = 10;  
int* p = &a;  //定义指针变量 p
*p = 20;      //将指针p指向的值修改为 20

上面两行代码中,我们首先定义了一个整型变量 a ,然后又定义了一个指针变量 p 指向 a .第二行代码中,符号&代表取地址,相当于把变量a的地址赋值给了指针变量p(p指向a),*加在指针变量前面代表解引用,意思找到指针p指向的值,因此,第三行代码的意思就是讲p指向的值也就是a修改为20.总之一定要记住,符号&代表取值,符号*代表解引用:

符号
意义
&
取地址
*
解引用

这三行代码的内存模型如下:

我们假设系统给变量 a 分配的内存首地址为2000,我们又声明了一个指针变量p,这个p也是要占用内存空间的(32位系统占用4个字节,64位系统占用8个字节,请思考为什么),只不过这个变量p保存的内容是变量a的地址,也就是2000,当我们想通过p来操纵a的话,首先要根据p保存的地址找到它指向的内容,也就是解引用*p,当*p的内容放生改变的时候,首地址为2000的内存单元存储的值也会做出改变,因此变量当*p被重新赋值为20的时候,变量a的值也会做出改变,变为20.

由此扩展到二级指针,如果我们再定义一个指针变量q来指向p,那么q就是一个二级指针,因为它指向的对象还是一个指针,只不过比他自己低一级,是一级指针,那么二级指针如何定义呢,请看下面的代码:


int a = 10;
int* p = &a;
int** q = &p;

上面第三行代码就是定义了一个二级指针q,它指向的是一级指针p,而一级指针p又指向了变量a,它的内存模型如下图所示:

二级指针q保存的内容为一级指针p的地址而非内容,注意p地址是2008,p的内容为2000. 因此对q进行解引用也即*q得出的是p,也就是2008,再对(*q)进行解引用也即*(*q)得出的才是变量a的值,由于运算符的结合性自右向左,因此括号可以省略,也即**q才是a的值。我们可以编写代码试一下:


cout <<"a的值为:"<< **q << endl;

我们观察一下输出结果:

没错,输出的结果完全正确。

由此再扩充到多级指针,二级指针是指向一级指针的指针,那么n级指针便是指向n-1级指针的指针,以此类推。

三,常量指针与指针常量

请看下面两行代码:


int a = 10;
const int * p1 = &a;    //常量指针
int * const p2 = &a;    //指针常量

上面第二行代码中的p1是一个常量指针,就是指向常量的指针变量。意味着它指向的值不可以修改,但是指针的指向可以修改:


int a = 10;
int b = 20;
const int * p1 = &a;    //常量指针
*p1 = 100;  //错误,常量指针指向的值不可以修改
p1 = &b;   //正确

而对于指针常量,它本质是一个常量,但是由指针修饰。意味着它指向的值可以修改,但是指针的指向不可修改,与常量指针刚刚相反:


int a = 10;
int b = 20;
int * const p1 = &a;    //指针常量
*p1 = 100;  //正确
p1 = &b;   //错误,指针的指向不可以修改

因此,我们总结下:


名称
意义
特点
const int * p
常量指针
指向可修改,指向的值不可修改
int * const p
指针常量
指向不可修改,指向的值可修改

四,指针与数组

我们知道,一维数组名本身就是一个指针,但是在使用的过程中要小心,因为这个指针分为指向数组首元素的指针与指向整个数组的指针,那么如何区分它们呢?我们来看下面几行代码:


int arr[] = {1, 2, 3, 4, 5};
int* p1 = arr;
int* p2 = &arr[0];
int* p3 = &arr;    //报错

上面三行代码中,其中p1与p2是等价的,因为数组名arr本身就是一个指针,但是这个指针不是指向整个数组,而是指向数组的首元素的地址。第四行直接报错,因为&arr指的是整个数组的指针,不能把数组指针赋值给整形指针。虽然arr与&arr在数值上是相同的,但是两者意义不同。意味着&arr它的步长为整个数组,而对于arr,步长为单个元素。

所以,我们得出结论,对于一维数组arr:

名称
意义
步长
arr
指向数组首元素
单个元素
&arr[0]
指向数组首元素 单个元素
&arr
指向整个数组
整个数组

在定义了指向数组首元素的指针变量后,我们可以通过这个指针变量来访问数组元素:


  int arr[] = { 1,2,3,4,5 };
  int* p1 = arr;
  int length = sizeof(arr) / sizeof(int);
  for (int i = 0; i < length; i++)
  {
    cout << p1[i] << endl;
    cout << *(p1 + i) << endl;
  }

上面几行代码中,p1[i]与*(p1+i)两者是等价的,所以输出的结果一样。但是要注意,当用sizeof操作符操作arr的时候,这个时候不能把arr当做一个指针来对待,因为sizeof操作数组的时候它返回的是数组的字节长度,而单个指针变量只占用四个字节。上面循环体中,我们也可以通过下面方式访问:


cout << *p1++ << endl;  
cout << *(p1++) << endl;

*p1++与*(p1++)是等价的,这是因为++的运算符优先级比*要高,因此不管你加不加括号,都会优先执行p++,然而p++是先返回p的值,再与*结合,最后p再向后移动一位。

不过在这里要特别注意,有一种情况下我们是不能通过sizeof操作符来计算数组的长度的,就是当数组名作为函数参数传递的时候:


void test(int arr[])
{
  int lenth = sizeof(arr) / sizeof(int);
}

上面这行代码语法上没有问题,但是得出的结果却不是我们想要的结果,为什么呢,这是因为数组名作为函数传递的时候,会退化成一个指针,如果是二维数组的话,会退化成指向一维数组的指针,所以sizeof(arr)计算出来的结果就不是数组的字节长度了。所以说,在c/c++中传递数组的时候,一般我们也会把数组的长度作为形参传递过去。

但是我们不能通过下面方式去访问数组元素:


cout << *arr++ << endl;    //报错

这是因为arr本身是一个指针常量,指针的指向不可更改,因此编译器直接报错。

五,数组指针与指针数组

数组指针顾名思义,本质就是一个指针,这个指针指向整个数组;指针数组本质上是一个数组,但是数组的每个元素都是指针。请看下面两行代码:


int *p1[10];    //指针数组
int (*p2)[10];  //数组指针

上面两行代码,p1是一个数组,而p2却是一个指针,它指向一个匿名数组。为什么是这样呢?这是因为[]的优先级比*要高。p1 先与[]结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里括号的优先级比[]高,*号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。

p1为数组名,每个元素都是int型指针

p2为指针变量,指向一个匿名数组

如果我们定义:


int(*p)[10] = &arr;

那么如何访问数组的元素呢?且看,由于上行代码中,p=&arr,那么对其解引用,*p就是arr,因此我们可以通过(*p)[]来进行访问数组的元素:


for(int i = 0; i < 10; i++)
{
  cout<< (*p)[i] << endl;
}

六,指针函数与函数指针

指针函数顾名思义,他是一个函数,但返回值是一个指针,例如下面这几行代码:


int* test()
{
  int a = 10;
  int* p = &a;
  return p;
}

这个test就是一个指针函数,它返回的是一个int型的指针。

函数指针本质是一个指针,这个指针指向一个函数,那么我们如何定义函数指针呢?请看下面代码:


int myAdd(int a, int b)
{
  return a + b;
}
void test()
{
  int(*pFun)(int, int) = myAdd;    //定义一个函数指针
  cout << (*pFun)(2, 5) << endl;    //用函数指针调用函数
  cout << pFun(2, 5) << endl;      //用函数指针调用函数
}

上面test函数代码中,我们定义了一个函数指针,在最后进行调用函数的时候,有两种方法,一种是用*pFun来调用,一种是直接用pFun来调用,可见两种方法结果都一样。

最后,我们来看个比较混合指针复杂的案例:


char *(* c[10])(int **p);

乍一看,让人眼花缭乱,不知道是什么东西,在这里请大家记住一个规则:C语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。注意是从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键。

有了上面的规则,我们来逐步剖析上面哪行代码的意义:

首先从*c[10]开始,由于[]的优先级比*高,因此,*c[10]代表一个指针数组,每个元素都是指针,但类型还不知道。再看右边的(int** p),它是一个函数,参数为一个二级指针。最左边char* 代表这个函数的返回类型。因此,整行代码的含义就是:c 是一个拥有 10 个元素的指针数组,数组每个元素指向一个原型为char *(int **p)的函数。

总结

到此这篇关于c/c++中指针的文章就介绍到这了,更多相关c/c++中的指针内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

手拉手教你如何理解c/c++中的指针

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何理解C语言中的指针

这期内容当中小编将会给大家带来有关如何理解C语言中的指针,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1指针是什么指针是汇编语言中的一个对象,利用地址,指向存在电脑存储器中的另一个地方的值。由于通过地址能
2023-06-25

如何理解C语言函数传参:指针的指针

这篇文章主要介绍“如何理解C语言函数传参:指针的指针”,在日常操作中,相信很多人在如何理解C语言函数传参:指针的指针问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何理解C语言函数传参:指针的指针”的疑惑有所
2023-06-15

c语言的指针数组如何理解

本篇文章给大家分享的是有关c语言的指针数组如何理解,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。指针如何指向数组,并读取数组中的元素:#include int
2023-06-22

如何应对 C++ 中的指针管理挑战?

指针管理挑战C语言中的指针管理是一项复杂的任务,涉及分配、使用和释放指向其他数据的指针变量。不当的指针管理会导致内存泄漏、段错误和难以调试的行为。本文探讨了指针管理的挑战以及应对策略,包括:谨慎分配和初始化指针避免野指针妥善处理悬空指针使用智能指针进行仔细的内存管理利用现代编译器功能持续学习和最佳实践通过遵循这些策略,开发人员可以有效应对指针管理挑战,编写健壮、高效和无错误的代码。
如何应对 C++ 中的指针管理挑战?
2024-04-03

C++ 函数调试详解:如何调试包含指针的函数中的问题?

调试包含指针的 c++++ 函数时,需要理解指针基础并应用调试技巧:设置断点以暂停执行和检查变量。检查指针值是否与预期一致。验证指针是否为 null。检查指针所指向的内存。使用可视化工具来检查指针和内存布局。C++ 函数调试详解:如何调试包
C++ 函数调试详解:如何调试包含指针的函数中的问题?
2024-05-03

如何理解C++的转换手段并与Explicit关键词配合使用

这篇文章主要介绍“如何理解C++的转换手段并与Explicit关键词配合使用”,在日常操作中,相信很多人在如何理解C++的转换手段并与Explicit关键词配合使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答
2023-06-15

C++ 技术中的异常处理:如何通过基类指针来捕获派生类异常?

c++++ 中可通过基类指针捕获派生类异常。利用虚函数和 try-catch 块,我们可以:抛出派生类异常使用基类指针捕获它通过删除基类指针自动释放派生类对象C++ 中异常处理:通过基类指针捕获派生类异常在 C++ 中,异常处理是一种处理
C++ 技术中的异常处理:如何通过基类指针来捕获派生类异常?
2024-05-09

C++ 函数调试详解:如何调试预处理器指令中的问题?

调试预处理器指令问题的方法包括:查看预处理后的代码使用宏扩展定义调试宏使用预处理器分析器C++ 函数调试详解:如何调试预处理器指令中的问题预处理器指令是 C++ 中强大但容易出错的功能。它们允许在编译之前处理代码,例如定义宏或导入文件。调
C++ 函数调试详解:如何调试预处理器指令中的问题?
2024-05-03

C语言如何规定当解析器在 XML 文档中找到处理指令时所调用的函数

本文介绍了C语言中如何使用函数处理XML文档中的处理指令。当解析器遇到处理指令时,会调用XML处理指令函数规范中的函数。这些函数处理指令的开始和结束,以及指令的内容。步骤包括注册函数、解析XML文档和处理指令。示例代码展示了如何使用这些函数。应用程序包括自定义XML验证、处理DTD和XML转换。
C语言如何规定当解析器在 XML 文档中找到处理指令时所调用的函数
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录