news 2026/3/30 22:54:23

深入理解C/C++指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解C/C++指针

1.指针理解

首先我们看一段程序,如下:

#include <stdio.h> int main(void) { char ch; char* pc = &ch; *pc = 'H'; printf("%c", ch); return 0; }

我们可以看到这是指针最基本的用法,那么我们现在来深入理解一下这段程序,char ch;编译器在执行了这句话后就会在内存中开辟一段内存,而对于该段内存就会有一个地址,这个地址就像是这段内存空间的一个门牌号用来帮助编译器在后面我们要用到该段程序时知道该空间。那么对于char就是类型,通过这个类型编译器可以知道要开辟的空间的大小即1个字节大小。

那么我们现在来思考一下ch是什么呢?ch是一个变量名对吧,但是编译器怎么通过这个变量名就找到了那块空间呢,我们前面讲到地址才是编译器找到内存空间的标识,那么我们来好好理解一些这个变量ch,变量的本质是标识符与内存地址的绑定,它代表了一个可以存储数据的内存空间,在编译时变量名会被编译器自动的与内存地址映射起来,就可以找到对应的空间了。这也就是c/c++这些高级语言的一个表现,变量名给我们使用的人看,但是底层编译会自动转化为地址。

所以接下来的char*pc=&ch,就好理解了,这也是语言的通行,就是开辟了一个char*类型内存大小的空间,pc就是这块空间的一个标识符,然后这块空间存放的就是ch这变量名绑定的那块空间的地址。所有通过pc我们并不能直接找到ch变量名的那块空间,故我们又要引用到了一个操作符*来通过该操作符,让其解引用然后就指向到了ch的那块空间,复制后ch空间下的值也被修改了。

我们可以通过下图来更好的理解:

2.指针类型理解

我们看下面这段程序:

#include <stdio.h> int main(void) { int num = 512; char* p = &num; printf("%d", *p); return 0; }

num是int类型,但是我们的指针是char*类型,运行后我们看输出结果如下:

可见所得是0,这是为什么呢?我们知道int类型开辟的空间是4个字节,但是char类型开辟的是1个字节,所以char*类型的指针指向的虽然是num空间,但是却只指向一个字节空间,我们看其中的空间分布图:

所以输出的就是0了。

我们再看下面这段程序:

#include <stdio.h> int main(void) { int num = 512; char* p = &num; *p += 1; printf("%d", num); return 0; }

输出结果如下:

可见用char*类型的指针来进值得操作时,也只会对它所指向得那块空间有影响。我们可以看看内存分布如下:

所以指针类型其实本质就是指向那块空间的大小。但是要注意的是在C++中上面这段程序会报错因为c++的语法更加严格了。

3.指针运算理解

我们看下面这段程序:

#include <stdio.h> int main(void) { int arr[4] = { 10,20,30,40 }; int* p1 = &arr[3]; int* p2 = &arr[0]; printf("%d", p1 - p2); printf("%d", (unsigned int)p1 - (unsigned int)p2); return 0; }

看结果:

我们可以看到两个运算得到的结果不一样了,首先一个直接对p1和p2指针进行减法运算,得到的是两个指针之间的元素个数,这个元素个数所说的其实就是你指定的指针类型的这样字的空间有几块,比如我的指针类型是int*所以两者相减的其实就是这两个指针间有几块4字节大小空间,我们可以验证一下如下我们将程序修改为下面的程序:

#include <stdio.h> int main(void) { int arr[4] = { 10,20,30,40 }; //int* p1 = &arr[3]; //int* p2 = &arr[0]; char* p1 = &arr[3]; char* p2 = &arr[0]; printf("%d\n", p1 - p2); printf("%d\n", (unsigned int)p1 - (unsigned int)p2); return 0; }

结果如下:

也就验证了我上面所说的观点。

接着我们来理解一下(unsigned int)p1 - (unsigned int)p2这句程序,该句程序就是将地址强转为了无符号整形然后进行减法运算,所以得到的就是整形减整形的值。对其的强转其实就是改变了编译器在访问时的访问类型。为什么上面两者的结果会这样子呢,我们来看看其中的汇编会更加清楚如下:

我们注意到其中一句汇编,sar eax,2可以在我们地址相减后得到的eax值,在执行的时地址减法运算时,eax右移了2位即除以了4,而在将地址转化为整数后再相减就没有做此处理,这也说明了其二者值不一样的原因。

4.指针和数组名的关系

我们知道了指针和数组的关系,如可以通过指针来操作数组,数组每个元素取出地址也可以赋值给指针操作,特别的我们知道数组名的地址和数组首元素地址一样,我们可以通通过指针来遍历数组也可以通过数组下标来遍历数组。下面我们来深入理解一下,数组名和指针的关系,看下面这段程序:

#include <stdio.h> int main(void) { int arr[4] = { 10,20,30,40 }; int* p1 = arr; printf("%d\n", *arr); printf("%d\n", *p1); return 0; }

运行后结果如下:

可见两者的输出结果都是10,但是这两者是一样的嘛,我们再看下面这段程序:

#include <stdio.h> int main(void) { int arr[4] = { 10,20,30,40 }; int* p1 = arr; printf("%d\n", arr); printf("%d\n", *p1); int a = 100; arr = &a; p1 = &a; return 0; }

运行后如下:

可见p1指针可以改变指向而arr却不能改变指向。再看下面这段程序:

#include <stdio.h> int main(void) { int arr[4] = { 10,20,30,40 }; int* p1 = arr; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(p1)); return 0; }

运行后:

可见数组名和指针有着相似性但是又不是一样的,接下来我们就来深入理解一下这两者。首先看下图:

可见p1的类型为int*但是arr的类型为int[4],p1指向的是单个的int但是arr指向的是4个int也就是整个数组,编译器在识别p1时就只会识别到一个int,而在识别arr时识别的时整个数组,这是在早年c语言创建时为了操作数组的高效性所定义的。

5.指针与const的应用

第一种就是我们不加const,我们称为自由指针可以更换指针的指向也可以更改指针指向内容的值。如下:

#include <stdio.h> int main(void) { int a = 100; int arr[4] = { 10,20,30,40 }; int* p1 = arr; p1 = &a; *p1 = 10; return 0; }

第二种就是在指针类型前面加上const,该指针可以改变指向,但是不能改变指向内容的值。如下:

第三种就是在指针变量前加上const ,该指针不可以改变指针的指向,但是可以改变指向的内容的值如下:

第四种在指针类型和指针变量之前都加上const,该指针既不可以改变指向,也不可以改变指向内容的值,如下:

指针与const的联动常用于在给程序员定义接口时使用,防止实现接口的程序员对不需要进行修改的值不小心进行了修改,导致出现意外错误。

6.函数参数传递二维数组

对于二维数组作为参数进行传递有下面几种常用方法:

第一种:

#include <stdio.h> void add(int array[][4] , int n) { int sum = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < 4; j++) { sum += array[i][j]; } } printf("sum=%d", sum); } int main(void) { int a = 100; int arr[2][4] = { 10,20,30,40, 50,60,70,80}; add(arr, 2); return 0; }

这种写法比较明确,可以很清楚的看出传入该函数的参数是一个二维数组。在编译时会将该参数理解为int (*array)[4]。

第二种:

#include <stdio.h> void add(int (*array)[4] , int n) { int sum = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < 4; j++) { sum += array[i][j]; } } printf("sum=%d", sum); } int main(void) { int a = 100; int arr[2][4] = { 10,20,30,40, 50,60,70,80}; add(arr, 2); return 0; }

这种写法也比较明确,而且可以明确的看出该array就是一个二维数组的指针。

第三种:

#include <stdio.h> void add(int *array, int n , int m) { int sum = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { sum += array[i*m+j]; } } printf("sum=%d", sum); } int main(void) { int a = 100; int arr[2][4] = { 10,20,30,40, 50,60,70,80}; add(arr, 2,4); return 0; }

当使用第三种方式来对二维数组进行参数传递时,array就是一个指向数组首元素的地址,没有了二维数组的属性,所以不能使用上面两种通过array[i][j]的形式来访问二维数组了,所以我们可以通过上面的一维数组的访问形式来访问,其实也就是指针形式来访问,以为指针也可以通过数组下标形式来进行访问。

7.函数指针

函数指针是一个指向函数入口地址的指针,通过该指针可以实现对函数的调用。那么该函数指针的声明类型应该如何写呢?如下:<数据类型> (*<函数指针名称>)(<参数说明列表>)。其中数据类型就是函数的返回值类型,函数指针名称就是你相想要给该指针取得名称,参数说明列表就是你函数得形参列表。程序如下:

#include <stdio.h> int add(int a, int b) { return a + b; } int main(void) { int m = 10; int n = 20; int (*p)(int,int) = NULL; p = add; printf("%d", (*p)(m, n)); return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 12:27:16

ImageKnife性能优化实战指南:OpenHarmony图片加载的完整解决方案

作为OpenHarmony开发者&#xff0c;你是否曾经为应用中的图片加载性能问题而烦恼&#xff1f;图片加载缓慢、内存占用过高、列表滑动卡顿&#xff0c;这些问题都直接影响用户体验。今天&#xff0c;我将为你带来ImageKnife图片加载库的终极优化指南&#xff0c;帮助你从性能瓶颈…

作者头像 李华
网站建设 2026/3/28 12:30:14

JeeLowCode新手必看:5分钟快速上手企业级低代码开发框架

JeeLowCode新手必看&#xff1a;5分钟快速上手企业级低代码开发框架 【免费下载链接】jeelowcode &#x1f525;JeeLowCode 【企业级低代码】 是一款专为企业打造的低代码开发框架《免费商用》&#xff0c;以低代码为核心&#xff0c;实现快速开发。提供可视化界面&#xff0c;…

作者头像 李华
网站建设 2026/3/15 10:16:50

Splunk Enterprise for Windows 权限配置漏洞深度研究报告

一、漏洞概述 2025年12月3日&#xff0c;Splunk官方联合Cisco PSIRT披露了一款针对Windows平台Splunk Enterprise的高危权限配置漏洞&#xff0c;漏洞编号为CVE-2025-20386&#xff0c;对应的CWE编号为CWE-732&#xff08;关键资源权限分配错误&#xff09;。该漏洞CVSS v3.1评…

作者头像 李华
网站建设 2026/3/30 21:05:49

谷歌代码规范2025:从团队痛点到高效协作的实战攻略

还在为团队代码风格混乱而苦恼吗&#xff1f;接手新项目时是否因为命名不统一而浪费大量时间&#xff1f;谷歌代码规范作为全球最权威的编码标准&#xff0c;已经帮助无数开发团队解决了这些难题。本文将带你重新认识2025年最新版规范&#xff0c;用全新的视角掌握从C到TypeScr…

作者头像 李华
网站建设 2026/3/30 19:57:47

Graphiti知识图谱构建与AI集成实战指南:从零搭建智能记忆系统

Graphiti知识图谱构建与AI集成实战指南&#xff1a;从零搭建智能记忆系统 【免费下载链接】graphiti 用于构建和查询时序感知知识图谱的框架&#xff0c;专为在动态环境中运行的 AI 代理量身定制。 项目地址: https://gitcode.com/GitHub_Trending/grap/graphiti 你是否…

作者头像 李华
网站建设 2026/3/30 19:39:57

彻底解决大型前端项目痛点:umi模块化拆分与联邦架构完全指南

彻底解决大型前端项目痛点&#xff1a;umi模块化拆分与联邦架构完全指南 【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi 你是否正面临这样的困境&#xff1a;前端项目越来越庞大&#xff0c;构建时间从…

作者头像 李华