C语言指针超全面透析(原来你一直没有搞懂C语言指针是因为没有理解其中的规律)
文章目录
- 写在前面
- 一、思考指针的基础
- 1、指针的实质
- 2、指针的层次
- 3、指针的分类
- 4、两个符号(&和*)
- 二、单指针(int *p)
- 三、指针数组(int *p[10])
- 四、行指针(int (*p)[10])
- 五、指针的指针(int **p)
- 六、指针函数(int *fun( ))
- 七、函数指针(int (*p)( ))
写在前面
指针是C/C++语言的特色之一,它允许程序员直接操纵内存。这一块内容也是比较抽象的,很多人尤其是编程小白,对C语言指针的理解都是模糊的,其实就是没有搞懂C语言指针的实质和规律。
本篇文章则是对六大类C语言指针的透析,把这六类C语言指针搞懂了基本上C语言指针就算过关了。很多有经验的编程大佬可能之前也没有这样对C语言指针进行这样规律的总结,这也算是从另外一个角度去思考C语言指针的方式。
一、思考指针的基础
1、指针的实质
指针就是指向一个地址的变量,一个指针只可以指向一个地址。很多地方可能会说成存放地址的变量,这就是理解方式的不同了,个人觉得“指针指向一个地址”好记一些。
2、指针的层次
我曾经对C语言指针也是尤为模糊的,觉得自己挺懂的,但其实还是没有理解到实质,因此通过花了一整天静下来慢慢思考过后,发现原来自己是没有将层次分清楚。
下图为我自己总结的C语言指针层次图:
这个只是层次图,只是 为了明白指针其实分为了定义和调用两种使用指针的方法。
3、指针的分类
这里就把指针的六大类分出来了,回想第二点,就可以知道每一类指针都定义和调用两种使用方法。
上面这张图中,后面则为各类指针的定义方式。
4、两个符号(&和*)
然后必须要区分并熟练理解清楚的是关于指针的两个符号的使用,即符号“&”和符号“*”;
(1)符号“&”:取地址运算符,该运算符就是用来取某个变量的地址,如果定义一个int a=2;
那么&a
就是取a的地址即2的地址。
(2)符号 “*”:间接访问运算符,这个符号用法就稍微复杂一点,主要可以分为以下三点,
符号“*”的用法中,第一个是比较好理解的,就是用于定义时用,没有别的特殊含义,就是说明在定义一个指针而不是一个普通的变量;
第二个是最容易混淆的,其原因可能就是没有与第三个区分开来,记住了第三个用法int (*p)[10];
是表示行指针的固定用法,那么其他的情况就都是用来访问指针所指向的内容的,这样就可以将符号 “*” 的三种用法比较清晰得区分开了。
二、单指针(int *p)
1、指向单个变量的地址
前面说过了,指针就是指向一个地址的变量,因此可以定义普通变量a,然后将指针指向这个变量的地址:
int a = 520;int *p;p = &a; //也可以写在一起:int *p=&a; printf("a的地址:%d, p的地址:%d\n", &a,p);printf("p的值:%d\n", *p);
那么这个指针指向的地址就是变量a的地址了,因此可以打印,打印出两个地址就可以清楚看出来。其次,这个指针便可以访问到变量a中的值了。
此时指针p指向的地址是a的地址,此时我们将p加1,则表示将p指向的地址加1,在后面加上以下代码:
p++;printf("p的地址加一:%d", p);
但是结果并不是将地址号直接加1了,而是加4了。为何?原来我们定义的是int型变量,而一个int型变量占用的是4个地址空间大小,因此地址加1则是地址号上加4。
2、指向一维数组的首地址(数值)
这一点需要强调的是,指针指向的是某个数组的首地址,并不是指向某个数组!
这里的&a就是表示获取数组a 的首地址,这里p指向的是一维数组的首地址,因此我们可以通过增加地址号来改变指针所指向的地址,从而访问到数组中的其他元素。
int a[4] = {1,3,5,7};int *p = &a; printf("%d\n", a[1]);printf("%d\n", *p);p++;printf("%d\n", *p);
3、指向字符串的首地址(字符)
这一点与第2点是一个道理的,就是指针指向了一个字符串的首地址,这里也可以看出,在C语言中,字符串也是一种特殊的一维数组,这个数组中的每个元素则是每个字符。
char a[4] = {'L','O','V','E'};char *p = &a; printf("%c\n", a[1]);printf("%c\n", *p);printf("%d\n",p);for( ; *p; p++){printf("%c", *p);}
在这里,我们用指针p指向字符数组a的首地址(即字母L),因此我们输出*p则是输出字母L;输出p,则是输出字符串a的首地址(即字母L的地址)。
同第2点一样,可以通过改变地址好来改变指针p指向的字母,因此可以遍历整个字符串。
三、指针数组(int *p[10])
1、基本概念
前面我们知道了指针可以指向某个地址,我们可以通过改变对指针的运算来指向其他的地址,然后我们是用间接访问运算符(*)来获取指针指向的值。
接下来分析的是第二类指针,即指针数组,顾名思义,它表示的就是 数组中的元素为指针变量,每个元素都可以指向一个地址(存储空间)。
例如,int *p[10];
定义一个int类型的指针数组,则p[0]
则表示第一个指针所指向的地址空间,相当于二维数组中的a[0][0]
, 这一点非常重要,没有理解清楚就很容易与行指针混淆。
2、指向字符串
因为指针数组中的每个元素都为一个指针,都可以指向不同的地址(存储空间),因此它可以指向不同大小的存储空间,例如指向不同长度的字符串。
char *p1[3];p1[0] = "Monday";p1[1] = "Tuesday";p1[2] = "Wednesday";printf("p1的首地址:%d\n",p1); //也就是p1[0],即p1的值,是p1的首地址 printf("p1[0]中M的地址:%d\n",*p1); //也就是p1[0]的值,是字符串Monday的首地址 printf("p1[0]中M的地址:%d\n",p1[0]);printf("p1[0]中M的值:%c\n",**p1); //也就是*p1[0]的值 printf("p1[0]中M的值:%c\n",*p1[0]);puts(p1[0]); //puts可以用来输出字符串
从这里也可以看出来,说字符数组指向一个字符串是不严谨的,实际上是字符数组中的元素(一个个指针)指向一个个字符串的首地址了,因此一个指针数组相当于是一个二维数组。
3、指向二维数组
理解了指向字符串,再来理解指向二维数组就容易得多了,因为字符串就相当于是一个一维数组,上面说指针数组指向字符串,实际上是指针数组中的元素(指针)指向了字符串的首地址;
那么这里的指针数组指向二维数组,则是指针数组中的元素(指针)指向了一维数组的首地址。
int a1[][3] = {{1,3,5}, {7,9,11}};printf("a1的首地址:%d,a1[0]的地址:%d\n",a1,a1[0]);int *p1[3];p1[0] = a1[0];p1[1] = &a1[0][0];printf("p1的首地址:%d,*p1的首地址:%d\n",p1,*p1); //就是p1[0]这个指针的地址;则是p1[0]这个指针指向的地址即a1[0][0]的地址 printf("p1[0]的地址:%d,p1[1]的地址:%d\n",p1[0],p1[1]); //两个都是a1[0][0]的地址 printf("p1[0]的值:%d,p1[1]的值:%d\n",*p1[0],*p1[1]); //两个都是a1[0][0]的值 printf("p1[0]+2的地址:%d,a1[0][2]的地址:%d\n",p1[0]+2,&a1[0][2]); //改变指针指向后的地址 printf("p1[0]+2的值:%d,a1[0][2]的值:%d",*(p1[0]+2),a1[0][2]); //改变指针指向后的值
这里面的各种地址变换特别多,这也就是指针的难点所在,但其实也仅仅只是二维数组的变换而已,仔细思考不走神,还是很快就能懂的。
四、行指针(int (*p)[10])
1、引入行指针
行指针是与指针数组最接近的一类指针,也是指针的一大难点,同样也是指向二维数组的首地址。
行指针中前面的(*p)
是它的特色,与行指针类似,后面中括号中的数表示的是二维数组的第二维(列)的大小,与a[][10]
对应。这也说明为什么定义二维数组时可以省略第一维的大下,而必须指定第二维大小的原因。
按照文章第一大点的四个技术来思考,能够理解这两行,那么行指针就理解得差不多了。
printf("a2[0][0]的地址:%d,%d,%d,%d\n",(*p2),&(*p2)[0],&(*(p2[0]+0)),&a2[0][0]);
printf("a2[0][0]的值:%d,%d,%d,%d\n",*(*p2),(*p2)[0],(*(p2[0]+0)),a2[0][0]);
2、完整代码
int a2[][3] = {{2,4,6}, {8,10,12}};printf("a2的首地址:%d,a2[0]的地址:%d\n",a2,a2[0]);int (*p2)[3] = &a2; //在指针数组中这样则会报错,而在行指针中则是正确的printf("a2[0][0]的地址:%d,%d,%d,%d\n",(*p2),&(*p2)[0],&(*(p2[0]+0)),&a2[0][0]); printf("a2[0][0]的值:%d,%d,%d,%d\n",*(*p2),(*p2)[0],(*(p2[0]+0)),a2[0][0]); //表示a2中的12最常用的两个方法:printf("%d,%d",*(p2[1]+2),a2[1][2]);
五、指针的指针(int **p)
在计算机中,任何数据都是要占用内容的,因此指针也不例外,指针本身也需要占用内存空间,即某个指针也有其地址。如果用一个指针来指向这个指针的地址,则使用的这个指针为二级指针,也就是指针的指针,指向指针的地址。
int a[][3] = {{1,2,3}, {4,5,6}};int *p1, **p2;p1 = &a[0][2];p2 = &p1; //*p2 = &a[0][2];是不合法的 printf("p1的地址:%d,p2的地址:%d\n",p1,p2);printf("p1的值:%d,p2的值:%d\n",*p1,*p2);printf("用p2引用p1指向的值:%d\n",**p2);
如上代码所示,p1指针是一级指针,指向的是二维数组a的首地址(1的地址);
而p2是二级指针,指向的是p1的地址
再往下溯源,就可以通过p2这个二级指针引用到p1地址指向的值。
六、指针函数(int *fun( ))
函数除了可以返回整型、浮点型、字符型数据之外,还可以返回一个指针,也就是返回一个地址。
这样的函数,就是一个指针函数。从字面上也可以看出,它其实就是一个函数,因此也叫做返回指针的函数。
int x=2, y=3;int f1(int a,int b){ //普通函数 if(a>b){return a;}elsereturn b;} printf("大数为:%d\n",f1(x,y)); //返回指针的函数 int *f2(int *p1, int *p2){if(*p1 > *p2){return p1;}elsereturn p2;}printf("大数第地址为:%d\n",f2(&x,&y));printf("大数为:%d\n",*f2(&x,&y));
如上代码,这样定义的函数(返回指针的函数)返回的就是一个地址,我们可以引用这个地址,也可以使用*号来引用这个地址指向的值。
七、函数指针(int (*p)( ))
我们都知道指针是用来存放地址的,函数编译后,其指令也会占用内存空间。因此,我们可以定义指针来存放函数的地址。
C语言规定,函数的名字就是函数的首地址,也就是一个地址值,即函数的入口地址。
而函数指针本质就是一个指针,这种指针可以直接被一个函数地址所赋值,赋值过后,这个函数指针就代表了这个函数,可以通过这个函数指针进行调用函数。
int x=2, y=3;int max(int a, int b){return (a>b?a:b);}int min(int a, int b){return (a<b?a:b);}int (*p)(int, int);p = max;printf("最大值为:%d\n",p(x,y));p = min;printf("最小值为:%d",p(x,y));
在以上代码中,我们定义的函数指针p代替了函数max和函数min,然后通过调用函数指针传入参数,就将函数调用了,在作用效果上与直接调用函数完全一样。
好了,以上就是C语言中所有的指针用法,都是按照规律来总结的。其实也有点不严谨,主要是为了让大家理解指针的概念,能够通过规律记住指针的各种用法,希望我们的C语言都能越学越好,越学越开心!
C语言指针超全面透析(原来你一直没有搞懂C语言指针是因为没有理解其中的规律)相关推荐
- 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...
面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...
- 一文搞懂C语言回调函数
转载自:https://segmentfault.com/a/1190000008293902?utm_source=tag-newest 博主:Rdou Typing 来源:segmentfault ...
- char类型怎么输入 c语言_还没搞懂C语言指针?这里有最详细的纯干货讲解(附代码)...
21ic综合自网络信息 指针对于C来说太重要.然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识.所以本文尽可能的通过一篇文章完全讲解指针. 为什么 ...
- 搞懂C语言指针,看这篇就够了!
点击上方"大鱼机器人",选择"置顶/星标公众号" 福利干货,第一时间送达! ID:技术让梦想更伟大 整理:李肖遥 说到指针,估计还是有很多小伙伴都还是云里雾里的 ...
- c语言指针f32*,还没搞懂C语言指针?这里有最详细的纯干货讲解(附代码)
21ic综合自网络信息 指针对于C来说太重要.然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识.所以本文尽可能的通过一篇文章完全讲解指针. 为什么 ...
- 一文搞懂C语言如何用指针来代替变量和数组进行数据的存储
众所周知,指针的用法最常见的无外乎两种,一种是用指针来指向变量的内存地址,通过操控指针进而可以间接的操控变量.另外一种是把指针当成变量来使用,像变量一样可以存储数据.数组也是类似的道理,因为数组实 ...
- Go 语言入门三部曲(一):能看懂 Go 语言
文章目录 三部曲 搭建环境 代码简要讲解 包 导入 导出名 标识符 关键字 和 预定义标识符 基本类型 变量与常量 变量声明 短变量声明 类型转换 常量 流程控制 for 循环 if 分支 switc ...
- c++语言表白超炫图形_青少年编程学习之C语言怎么学?现在知道,将来不亏!...
少儿编程兴起,当孩子们图形化的编程工具使用熟练以后,编程中的概念也逐步理解消化,那么可以尝试慢慢脱离图形化工具,接触真正的编程语言,也就是常说的代码.因为有了图形化的基础,接触代码变得难度小了很多,所 ...
- 如何学习C语言,超详细的经验分享(学习笔记1--C语言的基本数据类型)
前言: 如果你正在学习C语言而又不知道从何处开始学,你可以跟着我一起学习C语言,在寒假期间我每天都会发一篇博客,里面有各种C语言的知识点,如果你想学习下去,想进步,就来每天跟着我一起打卡吧,期待我们能 ...
最新文章
- golang string 字符串 大小写转换
- yii2地址多级联动
- camuda流程引擎如此简单(一)
- SpringBoot_入门-HelloWorld细节-场景启动器(starter)
- AI Boot Camp 分享之 ML.NET 机器学习指南
- 程序员必备的21个Linux命令
- 有100个GMAIL的邀请,需要的来信就可以啦。
- javascript 光标位置
- HITS 算法(Hypertext Induced TopicSelection)
- iOS宏和__attribute__
- 思科路由器防火墙如何配置的方法
- 【转】Xposed+JustTrustMe关闭SSL证书验证解决无法抓取https包问题
- css代码中的ul和li是什么意思呢
- 软件中存在的技术风险
- 人生如梦,一尊还酹江月(评倚天屠龙记)
- echarts年龄饼图_echarts饼图
- 客观分析电子合同是如何提升效率的?
- Python-入门学习
- PS学习笔记 day1
- AdVoice广告录音制作软件如何音乐语音混音穿插制作广告
热门文章
- python单链表实现荷兰国旗问题_快速排序深入之荷兰国旗问题
- mac java myeclipse_Myeclipse mac版-Myeclipse 2015 Mac版下载 V2015免费版-PC6苹果网
- java与安卓接口_Android-Java-接口Interface
- xss绕过字符过滤_IE8 xss filter bypass (xss过滤器绕过)
- nvarchar(max)和表扫描
- 黄金分割圆怎么画matlab,黄金分割线画法图解(操作技巧)
- 固态硬盘怎么看出厂日期_固态到底怎么选?雷克沙NM610和西部数据SN500固态硬盘实测对比...
- hystrix 源码 线程池隔离_springcloud-线程池隔离(consumer)ribbon
- pg 日期和时间的运算操作
- android datebinding学习