C 语言学习笔记(指针4)

内容提要

  • 指针
    • 函数指针与指针函数
    • 二级指针

指针

函数指针与指针函数

函数指针
定义

函数指针本质上是指针,是一个指向函数的指针。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。(这里的函数名就代表入口地址

函数指针存在的意义:

  • 让函数多了一种调用方式
  • 函数指针可以作为形式,可以形式调用(回调函数)

语法

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

举例

int (*p)(int a, int b);
函数指针的初始化

①定义的同时赋值

// 函数指针需要依赖于函数,先有函数,后有指针// 定义一个普通函数
// 普通函数
int add(int a, int b){return a + b};// 定义一个函数指针,并初始化
// 观察,函数指针的返回类型和指向函数的返回类型一致,函数的形参列表个数、类型、顺序跟指向函数的形参列表一致
int (*p)(int a, int b) = add; // 函数指针p指向函数add,这里的add不能带(),add就是该函数的入口地址

在这里插入图片描述

②先定义后赋值

// 函数指针需要依赖于函数,先有函数,后有指针// 定义一个普通函数
// 普通函数
int add(int a, int b){return a + b};// 定义一个函数指针,并初始化
// 观察,函数指针的返回类型和指向函数的返回类型一致,函数的形参列表个数、类型、顺序跟指向函数的形参列表一致
int (*p)(int, int) = add; //形参列表的参数名可以省略p = add; //此时是将add的入口地址赋值给指针

注意:
1.函数指针指向的函数要和函数指针定义的返回值类型,形参列表对应,否则编译报错

2.函数指针是指针,但不能指针运算,如p++等,没有实际意义

3.函数指针作为形参,可以形成回调

4.函数指针作为形参,函数调用时的实参只能是与之对应的函数名,不能带小括号()

5.函数指针的形参列表中的变量名可以省略

注意:函数不能作为函数的形参,但是指向函数的函数指针是可以作为函数的形参的。

案例
  • 需求:求a,b两个数的最大值
  • 代码:
/**
* 定义一个函数,求两个数的最大值
**/
int ger_max(int a, int b)
{return  a > b ? a : b;
}int main()
{// 定义测试数据int a = 3, b = 4, max;// 直接调用函数max = get_max(a,b);printf("%d,%d中的最大值是%d\n",a,b,max);// 定义一个函数指针int (*p)(int,int) = get_max;// 间接调用函数:方式1max = p(a,b); //直接将指针名作为函数名printf("%d,%d中的最大值是%d\n",a,b,max);// 间接调用函数:方式2max = (*p)(a,b); //直接将指针名作为函数名printf("%d,%d中的最大值是%d\n",a,b,max);return 0;
}
回调函数

定义

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针备用来调用其所指向的函数时。我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

为什么要用回调函数

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。

简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

/**
*回调函数1
*/
int callback_1(int a)
{printf("hello, this is callback 1:a=%d\n", a),return a;
}/**
*回调函数2
*/
int callback_2(int b)
{printf("hello, this is callback_2:b=%d\n", b);return b;
}/**
*实现回调函数(函数的参数是函数指针)
*/
int handle(int x, int(*callback)(int))
{printf("日志:开始执行任务!\n");int res = callback(x);printf("日志:执行结果:%d\n", res);printf("日志:结束执行任务!\n");
}int main(int argc,char *argv[])
{handle(100,callback_1);handle(200,callback_2);    return 0;
}

在这里插入图片描述

指针函数
定义

本质上是函数,这个函数的返回值类型是指针,整个函数称之为指针函数。

  • int *p:普通指针。
  • int (*p)[3]:数组指针。
  • int *p[]:指针数组。
  • int (*p)():函数指针。
  • int *p():指针函数。
语法:
// 写法1
数据类型* 函数名(形参列表)
{函数体;return 指针变量;
}
// 写法2
数据类型 *函数名(形参列表)
{函数体;return 指针变量;        
}
举例:
int *get(int a)
{int *p = &a;return 0;
}
int main()
{int *a= get(5);printf("%d\n"*a);
}
注意:

在函数中不要直接返回一个局部变量的地址,因为函数调用完毕后,局部变量会被回收,使得返回的地址就不明确,此时返回的指针就是野指针。

解决方案

如果非要访问,可以给这个局部变量添加(定义的时候添加)static ,可以延长它的生命周期,从而避免野指针(尽量少用,因为存在内存泄漏)

演示案例

int *add(int a, int b)
{static int sum; // 使用static修饰局部变量,会提升生命周期,但是作用域不会发生改变,不建议sum = a + b;return ∑ // 执行完return 作为函数作用域的布局变量sum的空间被释放
}int main()
{int *res = add(5,3); // 接收到了地址,但是地址对应的空间已经被释放printf("%d\n", *res);return 0;        
}
案例
  • 需求:有若干个学生,每个学生有4门成绩,要求在用户输入学号(int id)后,能输出该学生的全部成绩(float scores[4]),用指针函数实现。
/**
* 定义一个函数,要求输入学生序号,返回该学生所有成绩
* @param all:所有人的成绩
* @param id:要检索学生的序号
* @return id:对应血行的成绩数组(指针)
*/
float* search(float (*all)[4], int id)
{// 定义一个指针变量,用来接受查询到的某个学生的所有成绩float *pt;pt = *(all + id); //行偏移return pt; // 赋值运算中 float pt[4] == float *pt;
}int main()
{// 准备一个二维数组,存储3个学生的成绩float scores[][4] = {{60,70,80,90},{66,77,88,99},{61,71,81,91}};// 定义一个变量,用来接收学生序号int m;printf("请输入学生序号(0~2):\n");scanf("%d", &m);printf("第%d个学生的成绩:\n", m);// 创建一个指针,用来接收成绩float *p = search(scores, m);// 遍历成绩for(; p < scores[m] + 4; p++){printf("%5.2f\t", *p);}printf("\n");return 0;
}

二级指针

定义

二级指针(多重指针)用于存储一级指针的地址,需要两次解引用才能访问原始数据,其他多级指针的用法类似,实际开发中最常见的多级指针是二级指针

int a = 10; // a是普通变量
int *p = &a; // 一级指针(p指向a,p存储的是a的地址)
int **w = &p; // 二级指针(w指向p,w存储的是p的地址)
int ***x = &w // 三级指针(x指向w,x存储的是w的地址)
语法
数据类型 **指针变量名 = 指针数组的数组名 | 一级指针的地址
特点

与指针数组的等效性二级指针与指针数组等效性,但与二维数组不等效。二维数组名是数组指针类型,如int (*)[3],而非二级指针。

// 指针数组
int arr[] = {11,22,33};
int* arr_[] = {&arr[0], &arr[1], &arr[2]}; // 正确的指针数组的定义// 二级指针接受指针数组
char* str[3] = {"abc","aaa034","12a12"}; // str存储的是三个字符串的首地址
char **p = str;

与二维数组的差异二维数组名是数组指针类型,直接赋值给二级指针会导致类型不匹配

int arr[2][3] = {{1,3,5}{11,33,55}};
int (*p)[3] = arr; //arr这个数组名就是数组指针类型int **k = arr; // 编译报错,arr类型 int(*)[3] 不兼容 k类型 int**
解引用

字符型二级指针可直接遍历字符串数组,类似一维数组

void fun1()
{// 定义一个字符类型的指针数组char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i <len; i++){printf("%s\n",arr[i]);}printf("\n");
}void fun2()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二级指针等效于指针数组char **p = arr;for(int i = 0; i < len; i++){printf("%s\n",p[i]); //下标法printf("%s\n",*(p+i)); //指针法}printf("\n");
}void fun3()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二级指针等效于指针数组char **p;// 定义循环变量int i = 0// 遍历指针数组do{p = arr + i; // p = arr ----> p = arr + 0printf("%s\n", *p);i++;}while(i < len);
}int main()
{fun1();fun2();fun3();return 0;
}

其他类型的二级指针需要两次解引用访问数据,常用于操作指针数组

int main()
{// 普通的一维数组int arr1[] = {11,22,33,44,55,66};// 创建一个指针数组int *arr[] = {&arr1[0],&arr1[1],&arr1[2],&arr2[3],&arr1[4],&arr1[5]};// 用一个二级指针接收指针数组int **p = arr;// 遍历数组for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%-6d", *p[i]); //下标法printf("%-6d", **(p + i)); // 指针法 等价于*(*(p+i))}pritnf("\n");return 0;
}
总结

①二级制指针与指针数组等效,可简化指针数组的遍历操作

②二维数组名是数组指针类型(如: int(*)[3]),与二级指针( int**)类型不兼容。

③操作非字符型二级指针时,须通过两次解引用访问实际数据。

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

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

相关文章

C#串口打印机:控制类开发与实战

C#串口打印机&#xff1a;控制类开发与实战 一、引言 在嵌入式设备、POS 终端、工业控制等场景中&#xff0c;串口打印机因其稳定的通信性能和广泛的兼容性&#xff0c;仍是重要的数据输出设备。本文基于 C# 语言&#xff0c;深度解析一个完整的串口打印机控制类Printer&…

通过vue-pdf和print-js实现PDF和图片在线预览

npm install vue-pdf npm install print-js <template><div><!-- PDF 预览模态框 --><a-modal:visible"showDialog":footer"null"cancel"handleCancel":width"800":maskClosable"true":keyboard"…

SQL解析工具JSQLParser

目录 一、引言二、JSQLParser常见类2.1 Class Diagram2.2 Statement2.3 Expression2.4 Select2.5 Update2.6 Delete2.7 Insert2.8 PlainSelect2.9 SetOperationList2.10 ParenthesedSelect2.11 FromItem2.12 Table2.13 ParenthesedFromItem2.14 SelectItem2.15 BinaryExpressio…

安装完dockers后就无法联网了,执行sudo nmcli con up Company-WiFi,一直在加载中

Docker服务状态检查 执行 systemctl status docker 确认服务是否正常 若未运行&#xff0c;使用 sudo systemctl start docker && sudo systemctl enable docker 网络配置冲突 Docker会创建docker0虚拟网桥&#xff0c;可能与宿主机网络冲突 检查路由表 ip route sho…

Docker 运维管理

Docker 运维管理 一、Swarm集群管理1.1 Swarm的核心概念1.1.1 集群1.1.2 节点1.1.3 服务和任务1.1.4 负载均衡 1.2 Swarm安装准备工作创建集群添加工作节点到集群发布服务到集群扩展一个或多个服务从集群中删除服务ssh免密登录 二、Docker Compose与 Swarm 一起使用 Compose 三…

软媒魔方——一款集合多种系统辅助组件的软件

停更4年&#xff0c;但依旧吊炸天&#xff01; 亲们&#xff0c;是不是觉得电脑用久了就像老牛拉车&#xff0c;慢得让人着急&#xff1f;别急&#xff0c;我今天要给大家安利一个超好用的电脑优化神器——软媒魔方&#xff01; 软件介绍 首先&#xff0c;这货真心是免费的&a…

upload-labs通关笔记-第19关文件上传之条件竞争

目录 一、条件竞争 二、源码分析 1、源码分析 2、攻击原理 3、渗透思路 三、实战渗透 1、构造脚本 2、制作图片马 3、获取上传脚本URL 4、构造访问母狼脚本的Python代码 5、bp不断并发上传母狼图片马 &#xff08;1&#xff09;开启专业版bp &#xff08;2&#xf…

分布式消息队列kafka详解

分布式消息队列kafka详解 引言 Apache Kafka是一个开源的分布式事件流平台&#xff0c;最初由LinkedIn开发&#xff0c;现已成为处理高吞吐量、实时数据流的行业标准。Kafka不仅仅是一个消息队列&#xff0c;更是一个完整的分布式流处理平台&#xff0c;能够发布、订阅、存储…

uni-app(3):互相引用

1 绝对路径和相对路径 在日常开发中&#xff0c;经常会遇到使用绝对路径还是相对路径的问题&#xff0c;下面我们介绍下这两种路径。 1.1 绝对路径 绝对路径&#xff1a;是指从项目根目录开始的完整路径。它用于指定文件或目录的确切位置。绝对路径通常以斜杠&#xff08;/&am…

python与flask框架

一、理论 Flask是一个轻量级的web框架&#xff0c;灵活易用。提供构建web应用所需的核心工具。 Flask依赖python的两个库 Werkzeug&#xff1a;flask的底层库&#xff0c;提供了WSGI接口、HTTP请求和响应处理、路由等核心功能。 Jinja2&#xff1a;模板引擎&#xff0…

esp32-idf框架学习笔记/教程

esp32型号: 环境搭建 安装:就按这个来,别的试了好多次都不行,这个一次成功!!!! vscode下ESP32开发环境配置&#xff08;100%成功&#xff09;_哔哩哔哩_bilibili esp芯片的两种模式: ESP32 固件烧录教程_哔哩哔哩_bilibili 1.运行模式 2.下载模式 esp32s3程序下载 1.数据…

VKontakte(VK)注册教程

VKontakte&#xff08;简称VK&#xff09;是俄罗斯最大的社交网络平台&#xff0c;类似于Facebook&#xff0c;用户可以通过它进行社交、分享图片、视频、音乐等内容&#xff0c;并参与各类社群讨论&#xff0c;是与俄罗斯及其他东欧地区的朋友建立联系的便捷平台。对于做俄罗斯…

STM32+ESP8266+ONENET+微信小程序上传数据下发指令避坑指南

之前只做过类似的但是以为这种烂大街的功能应该不难结果还是踩了不少坑&#xff0c;记录几个需要注意的点 首先贴一个非常有用的视频&#xff0c;里面讲的很详细&#xff0c;给的资料也很全【【新版OneNet云平台】STM32ESP8266上传数据&#xff0c;简单易上手&#xff01;】 h…

【知识点】关于vue3中markRow、shallowRef、shallowReactive的了解

首先我们先了解一下这三个函数的定义以及区别 markRow 定义&#xff1a; 一个用于标记对象为非响应式的工具函数 shallowRef 定义&#xff1a; 一个用于创建浅层响应式引用的函数&#xff0c;只对 .value 本身进行响应式处理&#xff0c;不会递归地将 .value 指向的对象或…

后端开发实习生-抖音生活服务

职位描述 ByteIntern&#xff1a;面向2026届毕业生&#xff08;2025年9月-2026年8月期间毕业&#xff09;&#xff0c;为符合岗位要求的同学提供转正机会。 团队介绍&#xff1a;生活服务业务依托于抖音、抖音极速版等平台&#xff0c;致力于促进用户与本地服务的连接。过去一…

OceanBase 共享存储:云原生数据库的存储

目录 探会——第三届 OceanBase 开发者大会 重磅发布&#xff1a;OceanBase 4.3 开发者生态全面升级 实战演讲&#xff1a;用户案例与行业落地 OceanBase 共享存储架构解析 什么是共享存储架构&#xff1f; 云原生数据库的架构 性能、弹性与多云的统一 为何OceanBase能…

C++ 结构体封装模式与 Promise 链式调用:设计思想的异曲同工

C 结构体封装模式与 Promise 链式调用&#xff1a;设计思想的异曲同工 在软件开发中&#xff0c;我们常常追求代码的可维护性、可扩展性和可读性。不同的编程语言和场景下&#xff0c;虽然实现方式各异&#xff0c;但背后的设计思想往往存在着奇妙的相似性。本文将探讨 C 中结…

【Go】1、Go语言基础

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言的特点 Go语言由Google团队设计&#xff0c;以简洁、高效、并发友好为核心目标。 具有以下优点&#xff1a; 语法简单、学习曲线平缓&#xff1a;语法关键字很少&#xff0c;且…

AI时代的新营销范式:生成式引擎优化(GEO)的崛起——品牌如何被大模型收录

在数字化浪潮席卷全球的今天&#xff0c;我们正站在一个前所未有的历史拐点。如果说过去二十年&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;重塑了企业与消费者的连接方式&#xff0c;那么未来二十年&#xff0c;生成式引擎优化&#xff08;GEO&#xff09;将彻底颠覆…

实用蓝牙耳机哪款好?先做好使用场景分析!

市面上的蓝牙耳机款式繁多&#xff0c;618到来之际&#xff0c;消费者如何选择适合自己的蓝牙耳机&#xff1f;实用蓝牙耳机哪款好&#xff1f;关键在于做好使用场景分析&#xff01;今天&#xff0c;就带大家结合不同的使用场景&#xff0c;分享三款倍思音频的精品蓝牙耳机。 …