news 2026/5/1 14:25:18

深入理解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/4/30 0:44:15

突发!刚刚新增8本期刊被剔除!

刚刚&#xff0c;Scopus数据库再次迎来更新&#xff01;与上次更新相比&#xff0c;本次SCOPUS来源出版物列表(Scopus Sources)共有48094本期刊被收录。其中&#xff1a;● 8本期刊不再被数据库收录(Discontinued Titles)● 67本期刊被数据库收录(Accepted Titles)注&#xff1…

作者头像 李华
网站建设 2026/4/30 14:27:58

点焊机方案开发案例

功能设计 长按2秒软开关 关机状态长按触控区 ≥2 s&#xff0c;蜂鸣器“滴”一声蓝灯常亮即开机&#xff1b;再次长按 ≥2 s 直接断电&#xff0c;关机无提示&#xff0c;避免夜间扰人。 短按1次 手动/自动切换 开机默认手动6档&#xff1b;短按一下循环&#xff1a;手动→自…

作者头像 李华
网站建设 2026/4/22 18:28:23

USBIPD-WIN技术指南:Windows与WSL 2的USB设备无缝共享解决方案

USBIPD-WIN技术指南&#xff1a;Windows与WSL 2的USB设备无缝共享解决方案 【免费下载链接】usbipd-win Windows software for sharing locally connected USB devices to other machines, including Hyper-V guests and WSL 2. 项目地址: https://gitcode.com/gh_mirrors/us…

作者头像 李华
网站建设 2026/4/22 2:28:22

Xray实时协作功能终极指南:多人编程的完美解决方案

想要和团队成员一起实时编辑代码&#xff0c;享受无缝的协同编程体验吗&#xff1f;&#x1f680; Xray作为一款实验性的下一代基于Electron的文本编辑器&#xff0c;其强大的实时协作功能让多人编程变得前所未有的简单高效。本文将为你完整揭秘Xray的协同编辑机制&#xff0c;…

作者头像 李华
网站建设 2026/4/30 15:48:42

Rebel终极AppKit优化框架:告别Cocoa开发痛点

Rebel终极AppKit优化框架&#xff1a;告别Cocoa开发痛点 【免费下载链接】Rebel Cocoa framework for improving AppKit 项目地址: https://gitcode.com/gh_mirrors/reb/Rebel 在macOS应用开发中&#xff0c;AppKit框架虽然功能强大&#xff0c;但常常伴随着繁琐的API和…

作者头像 李华
网站建设 2026/4/25 7:37:09

Android bugreportz 源码分析

源码分析 Android.bp // bugreportz // package {// 许可证配置&#xff1a;继承 frameworks_native 项目的 Apache 2.0 许可证// 相关说明&#xff1a;http://go/android-license-faq// 大规模变更添加了 default_applicable_licenses 以导入所有许可证类型default_applicabl…

作者头像 李华