我们在学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. 怎样在vue中隐藏el-form-item中的值、设置输入框的值是只读

    1.如何在前端vue中隐藏某一个元素(el-form-item怎样隐藏) 给每项表单项添加一个自己的id名,并用v-model绑定相对应的数据,利用v-if根据上一个表单项的数据值来进行显示或隐藏 & ...

  2. 解决springboot+vue+mybatis中,将后台数据分页显示在前台,并且根据页码自动跳转对应页码信息

    文章目录 先看效果 1.要考虑的问题,对数据进行分页查询 2.前端和后台的交互 先看效果 1.要考虑的问题,对数据进行分页查询 mapper文件这样写 从每次开始查询的位置,到每页展示的条数, < ...

  3. Linux 文件操作接口

    目录 Linux 文件操作接口 C语言文件操作接口 C语言文件描述 fopen() r模式打开文件 w模式打开文件 a模式打开文件 其他模式类似 fclose() fwrite() fread() 系 ...

  4. 15行python代码实现人脸识别

    方法一:face_recognition import cv2 import face_recognition img_path = "C:/Users/CJK/Desktop/1.jpg& ...

  5. 故事 --- Linux和UNIX之间的那些爱恨与情仇

    Linux和UNIX具体有哪些关系及区别? UNIX 与 Linux 之间的关系是一个很有意思的话题.在目前主流的服务器端操作系统中,UNIX 诞生于 20 世纪 60 年代末,Windows 诞生于 ...

  6. 35.ListSerializer详解

    ListSerializer继承了BaseSerializer 一般情况下我们不直接使用ListSerializer ListSerializer会自动调用,序列化many=True的字段时,会自动调 ...

  7. 【React】学习笔记(一)——React入门、面向组件编程、函数柯里化

    课程原视频:https://www.bilibili.com/video/BV1wy4y1D7JT?p=2&spm_id_from=pageDriver 目录 一.React 概述 1.1.R ...

  8. 什么是ForkJoin?看这一篇就能掌握!

    摘要:ForkJoin是由JDK1.7之后提供的多线程并发处理框架. 本文分享自华为云社区<[高并发]什么是ForkJoin?看这一篇就够了!>,作者: 冰 河. 在JDK中,提供了这样一 ...

  9. 图文详解丨iOS App上架全流程及审核避坑指南

    App Store作为苹果官方的应用商店,审核严格周期长一直让用户头疼不已,很多app都"死"在了审核这一关,那我们就要放弃iOS用户了吗?当然不是!本期我们从iOS app上架流 ...

  10. KatalonRecorder系列(一):基本使用+XPath元素定位

    一.简介 Katalon Recorder是基于selenium的浏览器插件,支持火狐和chrome.可以录制web上的操作并回放,还能导入导出脚本. 二.安装 可在谷歌商店或者火狐附件组件中搜索并选 ...