文章目录

  • 写在前面
  • 一、思考指针的基础
    • 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语言指针是因为没有理解其中的规律)相关推荐

  1. 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...

    面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...

  2. 一文搞懂C语言回调函数

    转载自:https://segmentfault.com/a/1190000008293902?utm_source=tag-newest 博主:Rdou Typing 来源:segmentfault ...

  3. char类型怎么输入 c语言_还没搞懂C语言指针?这里有最详细的纯干货讲解(附代码)...

    21ic综合自网络信息 指针对于C来说太重要.然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识.所以本文尽可能的通过一篇文章完全讲解指针. 为什么 ...

  4. 搞懂C语言指针,看这篇就够了!

    点击上方"大鱼机器人",选择"置顶/星标公众号" 福利干货,第一时间送达! ID:技术让梦想更伟大 整理:李肖遥 说到指针,估计还是有很多小伙伴都还是云里雾里的 ...

  5. c语言指针f32*,还没搞懂C语言指针?这里有最详细的纯干货讲解(附代码)

    21ic综合自网络信息 指针对于C来说太重要.然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识.所以本文尽可能的通过一篇文章完全讲解指针. 为什么 ...

  6. 一文搞懂C语言如何用指针来代替变量和数组进行数据的存储

      众所周知,指针的用法最常见的无外乎两种,一种是用指针来指向变量的内存地址,通过操控指针进而可以间接的操控变量.另外一种是把指针当成变量来使用,像变量一样可以存储数据.数组也是类似的道理,因为数组实 ...

  7. Go 语言入门三部曲(一):能看懂 Go 语言

    文章目录 三部曲 搭建环境 代码简要讲解 包 导入 导出名 标识符 关键字 和 预定义标识符 基本类型 变量与常量 变量声明 短变量声明 类型转换 常量 流程控制 for 循环 if 分支 switc ...

  8. c++语言表白超炫图形_青少年编程学习之C语言怎么学?现在知道,将来不亏!...

    少儿编程兴起,当孩子们图形化的编程工具使用熟练以后,编程中的概念也逐步理解消化,那么可以尝试慢慢脱离图形化工具,接触真正的编程语言,也就是常说的代码.因为有了图形化的基础,接触代码变得难度小了很多,所 ...

  9. 如何学习C语言,超详细的经验分享(学习笔记1--C语言的基本数据类型)

    前言: 如果你正在学习C语言而又不知道从何处开始学,你可以跟着我一起学习C语言,在寒假期间我每天都会发一篇博客,里面有各种C语言的知识点,如果你想学习下去,想进步,就来每天跟着我一起打卡吧,期待我们能 ...

最新文章

  1. golang string 字符串 大小写转换
  2. yii2地址多级联动
  3. camuda流程引擎如此简单(一)
  4. SpringBoot_入门-HelloWorld细节-场景启动器(starter)
  5. AI Boot Camp 分享之 ML.NET 机器学习指南
  6. 程序员必备的21个Linux命令
  7. 有100个GMAIL的邀请,需要的来信就可以啦。
  8. javascript 光标位置
  9. HITS 算法(Hypertext Induced TopicSelection)
  10. iOS宏和__attribute__
  11. 思科路由器防火墙如何配置的方法
  12. 【转】Xposed+JustTrustMe关闭SSL证书验证解决无法抓取https包问题
  13. css代码中的ul和li是什么意思呢
  14. 软件中存在的技术风险
  15. 人生如梦,一尊还酹江月(评倚天屠龙记)
  16. echarts年龄饼图_echarts饼图
  17. 客观分析电子合同是如何提升效率的?
  18. Python-入门学习
  19. PS学习笔记 day1
  20. AdVoice广告录音制作软件如何音乐语音混音穿插制作广告

热门文章

  1. python单链表实现荷兰国旗问题_快速排序深入之荷兰国旗问题
  2. mac java myeclipse_Myeclipse mac版-Myeclipse 2015 Mac版下载 V2015免费版-PC6苹果网
  3. java与安卓接口_Android-Java-接口Interface
  4. xss绕过字符过滤_IE8 xss filter bypass (xss过滤器绕过)
  5. nvarchar(max)和表扫描
  6. 黄金分割圆怎么画matlab,黄金分割线画法图解(操作技巧)
  7. 固态硬盘怎么看出厂日期_固态到底怎么选?雷克沙NM610和西部数据SN500固态硬盘实测对比...
  8. hystrix 源码 线程池隔离_springcloud-线程池隔离(consumer)ribbon
  9. pg 日期和时间的运算操作
  10. android datebinding学习