C语言的指针(进阶)

news2025/1/23 22:38:15

目录

数组指针

数组指针的使用

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

数组指针

数组指针是指针?还是数组?

答案是:指针

数组指针也就是指向一个数组的指针

看下面两条代码:

int *p1[10];
int (*p2)[10];

问:p1, p2分别是什么?

在这种情况下,我们需要着重注意*是与谁结合的

对于p1而言,[ ]的优先级大于*(操作符优先级问题),所以p1与[ ]结合

由此可知p1是一个数组的名字,而*指向p1这个数组,所以p1是一个存放指针的数组

对于p2而言,()优先级大于[ ],所以*与p2结合,*p2是一个指针变量的名字

*p2指向[ ],所以p2是一个指向数组的指针


数组指针的使用

那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址

一个一维数组指针的使用:

//形参是数组
void print_arr1(int arr[5])
{
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

//形参是指针
void print_arr2(int* arr, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
}

int main()
{
    int arr[5] = { 1,2,3,4,5 };
    print_arr1(arr);
    print_arr2(arr, 5);
    return 0;
}

对于这个一维数组而言,有两种传参方式

第一种就是print_arr1的数组名作为参数用数组来接收的方式

第二种就是print_arr2的数组名作为参数用指针来接收的方式

由于有的时候不知道传递的数组的大小,所以我们一般使用数组指针来作为形参来接收


一个二维数组指针的使用:

void print_arr1(int arr[3][5], int row, int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

void print_arr2(int(*arr)[5], int row, int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { 1,2,3,4,5 ,6,7,8,9,10 };
    print_arr1(arr, 3, 5);
    print_arr2(arr, 3, 5);
    return 0;
}

输出结果为:

对于这个代码而言,将一个二维数组的数组名作为函数参数传递

数组名代表的是数组的首元素的地址(C语言的数组)

而对于二维数组而言,他的首元素即为二维数组的第一行

由于二维数组可以当作是一个存放多个一维数组的数组

所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址

可以使用数组指针来接收

int (*arr)[5]:arr指针变量指向一个有五个元素的数组

最后来看看下面几个代码,去理解一下他们是什么意思

int arr[5];//一个数组(有五个元素)
int *parr1[10];//一个指针数组(存放指针的数组)
int (*parr2)[10];//一个数组指针(指针指向有10个元素的数组)
int (*parr3[10])[5];
//一个数组(存放有5个指针数组,每个指针数组内存放了10个指针)

函数指针

函数指针指向函数的地址

函数有地址吗?

看下面的代码

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    printf("%p\n", &Add);
    return 0;
}

得到下面的结果

 但其实我们可以发现,函数名与&函数名得到的都是函数的地址

 那么函数的地址要存起来,就要存放在函数指针变量中

int (*pf)(int, int) = Add;

前面的int表示为函数的返回类型,后面两个int表示为函数的参数类型,pf是函数指针变量

也可以通过pf来调用该函数

1.int ret = (*pf)(3, 5);

2.int ret = Add(3, 5);

3.int ret = pf(3, 5);

printf("%d", ret);

实际上,上面三种方式都是可以使用的,由于函数名就是函数的地址,所以在所有的函数调用中实际上都是先找到函数的地址再进行调用

不管在pf前加多少个*,效果都是一样的

下面看两个代码 

(*(void (*)())0)();

 将0强制转换为函数指针(void(*)())类型,对其进行解引用操作,再传参

void (*signal(int , void(*)(int)))(int);

一个函数signal,他有两个参数,int和函数指针类型

他的返回值是一个函数指针类型

可以换另一种表达方式:

void(*)(int)signal(int, void(*)(int));

函数指针数组

把函数的地址存到一个数组中,那这个数组就叫函数指针数组

函数指针的数组如何定义呢?

下面给出几个例子

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

这三个哪个是函数指针数组的定义呢?

parr1是:

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?

是 int (*)() 类型的函数指针

那函数指针数组有什么用处呢?

一个计算器的实现

int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a*b;
}
int div(int a, int b)
{
    return a / b;
}

int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
   {
        printf( "*************************\n" );
        printf( " 1:add           2:sub \n" );
        printf( " 3:mul           4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
        {
        case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
              printf("退出程序\n");
              breark;
        default:
              printf( "选择错误\n" );
              break;
       }
 } while (input);
    
    return 0;
}

对于这个计算器来说,使用的是switch语句调用函数的实现方式,但是过于冗杂

所以我们可以通过函数指针数组来实现

由于函数定义相同,只展示下面main函数部分

int main()
{
     int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
      scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
         {
          printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
         }
          else
               printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
      return 0;
}

可以看到,上面的代码通过访问函数指针数组的下标来实现访问对应的函数

从而实现想要的功能,相较于switch语句来实现,这种方法显得更加简洁


指向函数指针数组的指针

(套娃,晕)

指向函数指针数组的指针是一个 指针

指针指向一个 数组数组的元素都是 函数指针 

void test(const char* str)
{
    printf("%s\n", str);
}

int main()
{
    //函数指针pfun
    void (*pfun)(const char*) = test;
    //函数指针的数组pfunArr
    void (*pfunArr[5])(const char* str);
    pfunArr[0] = test;
    //指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[5])(const char*) = &pfunArr;
    return 0;
}

回调函数

回调函数是什么?

回调函数就是一个通过函数指针调用的函数

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

假设写了一个A函数,有个B函数

在调用B函数时,将A函数的地址传给B函数,在B函数内部通过一个函数指针调用A函数

这就叫回调函数(它不是一种函数,是一种调用函数的方式)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Manjaro安装clash-for-windows-bin

安装 安装Clash for Windows yay -S clash-for-windows-bin 配置 命令行进入~/.config/clash/ &#xff0c;配置文件 config.yaml 和 Country.mmdb wget -O config.yaml [订阅链接]wget -O Country.mmdb https://gitee.com/mirrors/Pingtunnel/raw/master/GeoLite2-Country.…

领导给了一千多个需求,这个排序法救了我的命……

什么是MoSCoW排序法&#xff1f; 莫斯科排序法是一种优先级排序法&#xff0c;用于管理需求、任务或功能列表。该方法可以帮助团队确定哪些需求、任务或功能是最重要的&#xff0c;并决定在特定时间段内是否需要完成它们。 所以在对需求进行排序时&#xff0c;可以从以下维度…

优思学院|六西格玛常见问题有哪些?

要实现高质量、高效率和高客户满意度的目标&#xff0c;许多企业采用了六西格玛方法。然而&#xff0c;在实施过程中&#xff0c;往往会遇到各种各样的问题。优思学院会在这里探讨一下几个六西格玛常见问题&#xff0c;并提供解决方案&#xff0c;以帮助企业成功实施六西格玛方…

JSONP数据劫持漏洞

​介绍 JSONP&#xff08;JSON with Padding&#xff09;是 json 的一种"使用模式"&#xff0c;可以让网页从别的域名&#xff08;网站&#xff09;那获取资料&#xff0c;即跨域读取数据&#xff1b;它利用的是script标签的 src 属性不受同源策略影响的特性&#x…

GoNote第二章 Moudles

Go Modules 1. 介绍 Go modules是官方提供的go包管理工具&#xff0c;用于解决go包管理和依赖问题&#xff1b;从1.11开始引入&#xff0c;到现在1.14已经比较完善&#xff0c;1.16已经全面推荐使用&#xff0c;并且默认为开启&#xff1b;Go Modules类似于JS的NPM&#xff0…

校园小助手【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a; 更多系统资源库地址&#xff1a;骚戴的博客_CSDN_更多系统资源 更多系统…

Apple iWork(Pages、Numbers、Keynote)13.0 - 文档、电子表格、演示文稿

请访问原文链接&#xff1a;https://sysin.org/blog/apple-iwork-13/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 苹果今天将其专为 iOS 和 macOS 设备设计的 iWork 应用套件更新为版本 12 (sysin)&#xff0c;引入了许多新…

高并发服务器之泄峰

文章目录 背景前言解决方案泄峰 泄峰结果总结代码示例 背景 行业&#xff1a; 车联网机器配置&#xff1a;阿里云服务 8核 16G内存 3M带宽 阿里云操作系统单台server接入设备&#xff1a;5w终端产品&#xff1a;GPS定位设备终端与平台通信方式&#xff1a;TCP长链 前言 近期…

Linux中的五种IO模型

Linux中有以下五种IO模型 一、同步阻塞IO&#xff08;Blocking IO, BIO&#xff09; 用户进程发起IO调用后就阻塞线程让出CPU&#xff0c;等待内核处理完毕返回结果再唤醒继续执行。 二、同步非阻塞IO&#xff08;Non-Blocking IO, NIO&#xff09; 用户进程发起IO调用后就…

前端研发提质增效利器,TypeScirpt成功迁移详解

点击蓝字&#x1f446; 关注Agilean&#xff0c;获取一手干货 直播预告&#xff1a;Adapt 系列直播又双叒叕来啦&#xff01;最新一期我们将围绕「版本分支与环境」进行深入探讨&#xff0c;欢迎大家来直播间和主播互动哟&#xff5e; 点击下方右上角红色按钮「预约」&#x1f…

ESP32设备驱动-PAJ7620手势传感器驱动

PAJ7620手势传感器驱动 文章目录 PAJ7620手势传感器驱动2、硬件准备3、软件准备4、驱动实现PAJ7620 将手势识别功能与通用 I2C 接口集成到单个芯片中,形成图像分析传感器系统。 可识别上、下、左、右、前、后、顺时针、逆时针、挥手等9种人手手势。 它还提供内置的接近检测,以…

网络编程及项目思路

计算机和计算机之间通过网络进行数据传输 常见的软件架构&#xff1a; C/S:客户端/服务器 画面可以做的非常精美&#xff0c;用户体验好需要开发客户端&#xff0c;也需要开发服务端用户需要下载和更新的时候太麻烦 B/S:浏览器/服务器 不需要开发客户端&#xff0c;只需要…

java IO流_1

目录 分类 字节流 InputStream OutputStream 文件拷贝 字符流 FileReader FileWriter 处理流 BufferedReader BufferedWriter 文本拷贝 流是从起源到接受的有序数据&#xff0c;通过流的方式允许程序使用相同的方式来访问不同的输入/输出源。 分类 按数据…

SDK(动态链接库dll)的封装技巧

SDK(动态链接库dll)的封装技巧 一、说明 通过上篇文章&#xff0c;我们知道对于封装API&#xff0c;目的为了代码复用等&#xff0c;其中还有一个重要的原因&#xff0c;就是隐藏实现。 说到隐藏实现&#xff0c;在封装C的SDK库&#xff08;动态dll库&#xff09;时&#xff…

【获奖案例巡展】信创先锋之星——浙江省某市区视频能力中心

为表彰使用大数据、人工智能等基础软件为企业、行业或世界做出杰出贡献和巨大创新的标杆项目&#xff0c;星环科技自2021年推出了“新科技 星力量” 星环科技科技实践案例评选活动&#xff0c;旨在为各行业提供更多的优秀产品案例&#xff0c;彰显技术改变世界的力量&#xff0…

【id:33】【20分】C. 分数类(类与构造)

题目描述 完成下列分数类的实现: class CFraction { private: int fz, fm; public: CFraction(int fz_val, int fm_val) ; CFraction add(const CFraction &r); CFraction sub(const CFraction &r); CFraction mul(const CFraction &r);…

elementui的table组件,大量使用v-if导致列表渲染错乱,有的列渲染的值不对,有的列渲染出来的空值解决办法

记录一下&#xff1a; 这是同事碰上的&#xff0c;感觉挺奇怪的就研究了研究。 先说一下&#xff0c;之所以是同事那边碰到的而我这边碰不到这个问题&#xff0c;是因为我这边做 el-table-column 的时候&#xff0c;是先定义的 tableHeader: [...] 然后通过 v-for 遍历出来的…

【Linux】CentOS桥接模式配置静态IP

文章目录 1 前言2 桥接模式和NAT模式有什么区别3 校园网环境下配置桥接模式 1 前言 最近在安装的虚拟机上面用mosquitto搭建MQTT服务器&#xff0c;但是很奇怪的是每次电脑上的测试软件能顺利连接服务器&#xff0c;但是连接电脑热点的外部设备却不行&#xff0c;让我很是困惑。…

程序员随时担心被抛弃......大厂外包值不值得去?

外包”这个词经常被人提及&#xff0c;而且也经常被我们所“鄙夷”&#xff0c;很多人都在四处问&#xff1a;“软件外包公司到底能不能去”&#xff1f; 外包公司到底能不能学到真正的技术&#xff1f; 外包大厂能不能去&#xff1f; 今天就给大家详细分享下外包的利与弊 做…

DNS缓存失效,死循环访问造成连接数瞬间飚高的问题

0.背景介绍 某服务domain.com.cn 之前DNS解析到服务真实地址10.1.1.11&#xff0c;后面需要对用户登录增加黑名单功能&#xff0c;于是在openresty针对服务domain.com.cn的特性完成了黑名单功能。黑名单功能已经上线几个月&#xff0c;但是DNS从服务真实地址10.1.1.11切换到ope…