我们在学C语言时,指针是我们最头疼的问题之一,针对C语言指针,博主根据自己的实际学到的知识以及开发经验,总结了以下使用C语言指针时常见问题。

指针

指针做函数参数

学习函数的时候,讲了函数的参数都是值拷贝,在函数里面改变形参的值,实参并不会发生改变。

如果想要通过形参改变实参的值,就需要传入指针了。

注意:虽然指针能在函数里面改变实参的值,但是函数传参还是值拷贝。不过指针虽然是值拷贝,但是却指向的同一片内存空间。

指针做函数返回值

返回指针的函数,也叫作指针函数。

和普通函数一样,只是返回值类型不同而已,先看一下下面这个函数,非常熟悉对不!

int fun(int x,int y);

接下来看另外一个函数声明

int* fun(int x,int y);

这样一对比,发现所谓的指针函数也没什么特别的。

注意:

  • 不要返回临时变量的地址

  • 可以返回动态申请的空间的地址

  • 可以返回静态变量和全局变量的地址

函数指针

如果在程序中定义了一个函数,那么在运行时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

函数指针定义

函数返回值类型 (* 指针变量名) (函数参数列表);

  • “函数返回值类型”表示该指针变量所指向函数的 返回值类型;

  • “函数参数列表”表示该指针变量所指向函数的参数列表。

那么怎么判断一个指针变量是指向变量的指针,还是指向函数的指针变量呢?

  • 看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。

  • 函数指针没有++和 --运算

函数指针使用

定义一个实现两个数相加的函数。

int add(int a,int b)
{
   return a+b;
}
int main()
{
   int (*pfun)(int,int) = add;
   int res = pfun(5,3);
   printf("res:%d\n",res);
   
   return 0;
}

在给函数指针pfun赋值时,可以直接用add赋值,也可以用&add赋值,效果是一样的。

在使用函数指针时,同样也有两种方式,1,pfun(5,3); 2,(*pfun)(5,3)

案例

计算器

用函数指针实现一个简单的计算器,支持+、-、*、/、%

//plus sub multi divide mod     //加 减 乘 除 取余

当功能太多时,switch语句太长,因此不是一种好的编程风格。好的设计理念应该是把具体的操作和和选择操作的代码分开。

函数指针作为转换表

转换表就是一个函数指针数组。

#include<stdio.h>
#include<math.h>

// 转换表
// 转换表 step1:
//(1.1)声明 转台转移函数
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
double hypotenuse(double, double);
//(1.2)声明并初始化一个函数指针数组   pfunc:数组   数组元素:函数指针 返回值:double型数据
double(*pfunc[])(double, double) = { add, sub, mul, div, hypotenuse };//5个转移状态

//状态转移函数的实现
double add(double a, double b){ return a + b;}
double sub(double a, double b){ return a - b; }
double mul(double a, double b){ return a * b; }
double div(double a, double b){ return a / b; }
double hypotenuse(double a, double b){ return sqrt(pow(a, 2) + pow(b, 2)); }

void test()
{
//转换表 step2:调用 函数指针数组
int n = sizeof(pfunc) / sizeof(pfunc[0]);//转移表中 包含的元素个数(状态转移函数个数)
for (int i = 0; i < n; ++i){
printf("%.2lf\n",pfunc[i](3, 4));
}
}
int main()
{
test();
return 0;
}

typedef

一,使用typedef为现有类型创建别名,给变量定义一个易于记忆且意义明确的新名字。

  • 类型过长,用typedef可以简化一下

typedef unsigned int UInt32
  • 还可以定义数组类型

typedef int IntArray[10];
IntArray arr; //相当于int arr[10]

二、使用typedef简化一些比较复杂的类型声明。

例如:

typedef int (*CompareCallBack)(int,int);

上述声明引入了PFUN类型作为函数指针的同义字,该函数有两个类型分别为int、int、char参数,以及一个类型为int的返回值。通常,当某个函数的参数是一个回调函数时,可能会用到typedef简化声明。 例如,承接上面的示例,我们再列举下列示例:

int callBackTest(int a,int b,CompareCallBack cmp);

callBackTest函数的参数有一个CompareCallBack类型的回调函数。在这个示例中,如果不用typedef,callBackTest函数声明如下:

int callBackTest(int a,int b,int (*cmp)(int,int));

从上面两条函数声明可以看出,不使用typedef的情况下,callBackTest函数的声明复杂得多,不利于代码的理解,并且增加的出错风险。

所以,在某些复杂的类型声明中,使用typedef进行声明的简化是很有必要的。

回调函数

首先要明确的一点是,函数也可以作为函数的参数来传递。

当做函数参数传入的函数,称之为 回调函数(至于为什么要叫“回调函数”,不能叫别的呢?其实这只是人为规定的一个名字。你也可以叫“maye专属函数”,但是到时候你又会问为什么要叫“maye专属函数”,它特么的总的有个名字吧!所以叫“回调函数”就是王八的屁股:规定!)。

实现一个与类型无关的查找函数

如何看懂复杂的指针

指针大家都学过了,简单的指针相信大家都不放在眼里,就不再赘述,但是复杂的你能理解吗?能理解指针就学的差不多了,至于如何运用只要你看懂指针就知道应该给它赋什么值,怎么用。

  • 首先咱们一起来看看这个: int (*fun)(int *p)

    • 首先需要分析这个是不是一个指针,如果是,是什么指针?如果不是,那是什么?

      1. 根据(*fun)可知,fun是一个指针

      2. 然后看fun的后面是一个函数参数列表,可以确定是一个指向函数的指针

      3. 指向的函数的返回值是什么类型呢,再回头看看最前面发现是一个int

      4. 最后我们可以根据这个函数指针写出对应的函数

结果如下:

int foo(int *p)
{
   return 0;
}

右左法则

上面我们分析了一个函数指针,那结果是如何得出来的呢?全靠经验吗,NO,其实是有方法的。

这个方法叫做右左法则

  • 右左法则不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的。

  • 右左法则使用:

      1. 首先从最里面的圆括号(应该是标识符)看起,然后往右看,再往左看;

      2. 每当遇到圆括号时,就应该调转阅读方向;

      3. 一旦解析完圆括号里面所有东西,就跳出圆括号;

      4. 重复这个过程知道整个声明解析完毕。

案例走起

1.int (*p[5])(int*)

解析:

  1. 从标识符p开始,p先与[]结合形成一个数组,然后与*结合,表示是一个指针数组;

  2. 然后跳出这个圆括号,往后看,发现了一个函数的参数列表,说明数组里面装的是函数指针;

  3. 在跳出圆括号,往前看返回类型,可以确定函数指针的类型。

2. int (*fun)(int *p,int (*pf)(int *))

解析:

  1. fun与*结合形成指针;

  2. 往后看是一个参数列表,说明是一个函数指针,只不过参数里面还有一个函数指针;

  3. 往前看可以确定函数指针的返回类型。

3. int (*(*fun)[5])(int *p)

解析:

  1. fun与*结合,形成指针;

  2. 往后看发现了一个[5]说明是一个指向数组的指针;

  3. 再往前看,发现有一个*,说明数组里面存的是指针;

  4. 跳出圆括号往后看,发现了参数列表,说明数组里面存的是函数指针;

  5. 再往前看可以确定函数指针的返回类型。

4. int (*(*fun)(int *p))[5]

解析:

  1. fun与*结合,形成指针;

  2. 往后看发现了参数列表,说明fun是一个函数指针;

  3. 往前看遇到了*说明,函数指针的返回类型是一个指针,是什么指针继续往后解析;

  4. 往后看发现了[5] 说明是一个数组指针,最前面一个int,说明fun这个函数指针的返回类型是一个数组的指针

    类型为int (*)[5]

5. int(*(*fun())())()

解析:

  1. fun与()结合,说明fun是一个函数;

  2. 往前看发现了一个*,说明函数返回类型为指针,什么指针呢?

  3. 往后看发现了参数列表,fun函数返回的是一个函数指针,那这个函数指针的返回类型是什么呢?

  4. 往前看又发现了一个*,说明函数指针返回类型也是一个指针,那这个指针是什么指针呢?

  5. 往后看又发现了一个参数列表,说明是个函数指针,往前看这个函数指针返回的是int类型

总结

实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性

指针变量有两种类型:指针变量的类型和指针所指向的对象的类型

指针变量的类型 只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。

  • int* ptr; //指针的类型是int

  • char* ptr; //指针的类型是char

  • int** ptr; //指针的类型是int**

  • int(*ptr)[3]; //指针的类型是int()[3]

  • int*(*ptr)[4]; //指针的类型是int*(*)[4]

指针变量指向的对象的类型

  • 你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。

    • int*ptr; //指针所指向的类型是int

    • char*ptr; //指针所指向的的类型是char

    • int**ptr; //指针所指向的的类型是int*

    • int(*ptr)[3]; //指针所指向的的类型是int()[3]

    • int*(*ptr)[4]; //指针所指向的的类型是int*()[4]

注意事项:

  • 指针变量也是变量,也有存储空间,存的是别的变量的地址。

    • 要注意指针的值,和指向的对象的值得区别

    • 普通变量中的内存空间存放的是,数值或字符等。 ----直接存取

    • 指针变量中的内存空间存放的是,另外一个普通变量的地址。----间接存取

  • 连续定义多个指针变量时,容易犯错误,比如:int *p,p1;只有p是指针变量,p1是整型变量

  • 避免使用为初始化的指针,很多运行错误都是由于这个原因导致的,而且这种错误又不能被编译器检查所以很难被发现,解决方法:初始化为NULL,报错就能很快找到原因

  • 指针赋值时一定要保证类型匹配,由于指针类型确定指针所指向对象的类型,操作指针是才能知道按什么类型去操作

  • 在用动态分配完内存之后一定要判断是否分配成功,分配成功后才能使用。

  • 在使用完之后一定要释放,释放后必须把指针置为NULL

  •  

C语言指针常见问题的更多相关文章

  1. C语言指针转换为intptr_t类型

    1.前言 今天在看代码时,发现将之一个指针赋值给一个intptr_t类型的变量.由于之前没有见过intptr_t这样数据类型,凭感觉认为intptr_t是int类型的指针.感觉很奇怪,为何要将一个指针 ...

  2. [转]C语言指针学习经验总结浅谈

    指针是C语言的难点和重点,但指针也是C语言的灵魂 . 这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较 ...

  3. 不可或缺 Windows Native (7) - C 语言: 指针

    [源码下载] 不可或缺 Windows Native (7) - C 语言: 指针 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 指针 示例cPointer.h #i ...

  4. C语言指针学习

    C语言学过好久了,对于其中的指针却没有非常明确的认识,趁着有机会来好好学习一下,总结一下学过的知识,知识来自C语言指针详解一文 一:指针的概念 指针是一个特殊的变量,里面存储的数值是内存里的一个地址. ...

  5. (转载)c语言指针学习

    前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...

  6. 关于C语言指针的问题

    在学习关于C语言指针的时候,发现这样一个问题,代码如下: #include<stdio.h> #include<stdlib.h> #include<string.h&g ...

  7. C语言指针类型 强制转换

    关于C语言指针类型 强制转换  引用一篇文章: C语言中,任何一个变量都必须占有一个地址,而这个地址空间内的0-1代码就是这个变量的值.不同的数据类型占有的空间大小不一,但是他们都必须有个地址,而这个 ...

  8. C语言指针和数组知识总结(上)

    C语言指针和数组知识总结(上) 一.指针的基础 1.C语言中,变量的值能够通过指针来改变,打印指针的语句符号可以是:  %08x 2.指针的本质 指针的本质就是变量,那么既然是变量,那么一定会分配地址 ...

  9. C语言指针操作

    欢迎访问我的新博客:http://www.milkcu.com/blog/ 原文地址:http://www.milkcu.com/blog/archives/pointer-manipulation. ...

  10. C语言指针声明探秘

    C语言指针声明探秘

随机推荐

  1. StampedLock:一个并发编程中非常重要的票据锁

    摘要:一起来聊聊这个在高并发环境下比ReadWriteLock更快的锁--StampedLock. 本文分享自华为云社区<[高并发]一文彻底理解并发编程中非常重要的票据锁--StampedLoc ...

  2. mybatisPlus在Springboot中的使用

    文章目录 1.简介 2.支持的数据库 3.框架 4.创建一个springboot项目 4.1 .pom文件中加入依赖 4.2.yml文件的配置 4.3 .数据库脚本 4.4.实体类 4.5 .启动类添 ...

  3. 齐博x1再来个抛砖引玉 内容页根据关键词调用相关内容 新功能哦!

    昨天升级了一个隐藏的功能,今天就简单的做个说明怎么用,反正也不能浪费不是 那就用内容页面关键词读取相关内容为例吧. 前台是你模型中已经存在keywords字段  关键词支持 空格分割,号分割 那么就开 ...

  4. LcdToos如何在线对屏进行读写指令调试

    在实际屏调试过程中,工程师经常需要对屏的寄存器频繁进行参数修改和读取测试,LcdTools针对这个做了很好的支持,可以在线进行指令调试,大大提高调试效率. 打开点屏工程,连接PX01并使模组上电点亮. ...

  5. go基础语法50问,来看看你的go基础合格了吗?

    目录 1.使用值为 nil 的 slice.map会发生啥 2.访问 map 中的 key,需要注意啥 3.string 类型的值可以修改吗 4.switch 中如何强制执行下一个 case 代码块 ...

  6. Redis系列9:Geo 类型赋能亿级地图位置计算

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...

  7. oracle日常命令

    ---查询锁表(查出后,再执行查询结果进行释放:可多次查询,存在循环锁表的情况)-- select 'alter system kill session '|| ''''|| sess.sid || ...

  8. <五>关于类的各类成员

    类的各种成员-> 成员方法 & 成员变量 普通的成员方法=>编译器会添加一个this形参变量 1:属于类的作用域 2:调用该方法时,需要依赖一个对象,而且常对象不能调 3:可以任意 ...

  9. 当 xxl-job 遇上 docker → 它晕了,我也乱了!

    开心一刻 公交车上,一位老大爷睡着了,身体依靠在背后的一位年轻小伙子身上 小伙子一直保持站姿十几分钟,直到老人下车 这位在校大学生,接受采访时说:"当时就觉得背后这个人很轻盈,以为是个姑娘! ...

  10. RabbitMq发布确认

    RabbitMq发布确认 发布确认原理 生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被 ...