news 2026/6/10 11:16:29

深入理解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/6/10 11:09:49

【MCP MS-720认证必看】:Teams Agent开发核心技能全解析

第一章&#xff1a;MCP MS-720认证与Teams Agent开发概述Microsoft Certified Professional (MCP) MS-720 认证专注于 Teams 语音和协作解决方案的设计与实施&#xff0c;是面向现代工作场所通信架构的关键资质。该认证验证了开发者和管理员在配置 Teams 电话系统、管理紧急呼叫…

作者头像 李华
网站建设 2026/6/10 11:11:12

远程debug服务器代码

前言 相信有不少Javaer的代码在本地跑没一点问题&#xff0c;但是部署到服务器上就会出翔莫名其妙的bug&#xff0c;为了看bug的原因只能通过日志的形式来实现&#xff0c;下边为各位Javaer提供另一种方法&#xff0c;通过IDEA的功能能够直接debug服务器上的代码&#xff0c;话…

作者头像 李华
网站建设 2026/6/10 11:07:30

如何掌握UI设计重构的完整技巧:从基础到进阶

如何掌握UI设计重构的完整技巧&#xff1a;从基础到进阶 【免费下载链接】RefactoringUIv1.0.1.pdf资源介绍 《Refactoring UI v1.0.1.pdf》是一份专注于UI设计的深度资源&#xff0c;涵盖人机交互、操作逻辑与界面美观的全面探讨。它详细解析了如何通过优秀的设计提升软件的个…

作者头像 李华
网站建设 2026/6/10 11:08:04

无需 MediaCreationTool!微软官网直下 Win10 最新 ISO 镜像,超简单教程

想重装 Win10 系统&#xff0c;却被微软官网强制要求下载 MediaCreationTool 工具搞得头大&#xff1f;不少用户吐槽这个工具不仅占用额外存储空间&#xff0c;还可能出现下载失败、兼容性报错等问题&#xff0c;尤其对追求高效的用户来说&#xff0c;“多此一举” 的操作实在影…

作者头像 李华
网站建设 2026/6/9 16:57:27

AFFiNE开发环境搭建实用指南:从问题到解决方案的完整流程

AFFiNE开发环境搭建实用指南&#xff1a;从问题到解决方案的完整流程 【免费下载链接】AFFiNE AFFiNE 是一个开源、一体化的工作区和操作系统&#xff0c;适用于组装您的知识库等的所有构建块 - 维基、知识管理、演示和数字资产。它是 Notion 和 Miro 的更好替代品。 项目地址…

作者头像 李华