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

news2025/5/24 14:58:04

内容提要

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

指针

函数指针与指针函数

函数指针
定义

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

函数指针存在的意义:

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

语法

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

举例

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;
    
    // 间接调用函数:方式1
    max = p(a,b); //直接将指针名作为函数名
    printf("%d,%d中的最大值是%d\n",a,b,max);
    
    // 间接调用函数:方式2
    max = (*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 + 0
        printf("%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.coloradmin.cn/o/2384680.html

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

相关文章

MySQL的相关操作

目录 一. 字符串函数 二. group by分组 2.1 作用 2.2 格式 2.3 举例 三. order by排序 3.1 格式 3.2 举例 四. limit 4.1 作用 4.2 举例 五. having 5.1 作用 5.2 举例 六. 正则表达式 七. 多表查询 7.1 定义 7.2 子查询 7.3 联合查询 纵向合并 7.4 交叉连…

鸿蒙HarmonyOS多设备流转:分布式的智能协同技术介绍

随着物联网和智能设备的普及&#xff0c;多设备间的无缝协作变得越来越重要。鸿蒙&#xff08;HarmonyOS&#xff09;作为华为推出的新一代操作系统&#xff0c;其分布式技术为实现多设备流转提供了强大的支持。本文将详细介绍鸿蒙多设备流转的技术原理、实现方式和应用场景。 …

XXE(外部实体注入)

目录 学习xxe前提&#xff1a;了解xml格式 1. XML基础 2. XXE基础知识 2.1. 结构 2.2. 定义与原理 2.3. XML实体类型 2.4. 攻击类型 2.5. 防御措施 3. pikachu靶场xxe练习 学习xxe前提&#xff1a;了解xml格式 1. XML基础 文档结构包括XML声明、DTD文档类型定义&…

jenkins凭据管理

用途: 存储构建需要与其他系统认证所使用的账户或者密码信息. Username with password类型存储Harbor或者其他系统的用户名和密码。GitLab API token类型存储Gitlab的用户API token。Secret text类型可以用来存储OpenShift等系统中的token。Certificate类型可以用户存储证书&am…

驱动开发硬核特训 · Day 31:理解 I2C 子系统的驱动模型与实例剖析

&#x1f4da; 训练目标&#xff1a; 从驱动模型出发&#xff0c;掌握 I2C 子系统的核心结构&#xff1b;分析控制器与从设备的注册流程&#xff1b;结合 AT24 EEPROM 驱动源码与设备树实例&#xff0c;理解 i2c_client 与 i2c_driver 的交互&#xff1b;配套高质量练习题巩固理…

【python】局域网内通过python远程重启另一台windows电脑

&#x1f449;技__能&#x1f448;&#xff1a;C/C/C#/Python/Java/PHP/Vue/Node.js/HTML语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 局域网内通过python远程重启另一台windows电脑 目录 局域网内通过python远程…

超越感官的实相:声、光、气味的科学与哲学探微

在人类的感官世界中&#xff0c;声、光、气味是日常生活中最直接的现象&#xff1a;我们聆听音乐、观赏光影、呼吸花香。然而&#xff0c;若深入探究它们的本质&#xff0c;科学与哲学竟以截然不同的视角&#xff0c;揭示了一个超越感官的实相世界。本文将从经典物理学、佛教哲…

什么是VR场景?VR与3D漫游到底有什么区别

在数字化时代&#xff0c;虚拟现实&#xff08;Virtual Reality, 简称VR&#xff09;场景与3D漫游作为两种前沿技术&#xff0c;改变着人们的生活方式和体验模式。通过计算机模拟真实或假想的场景&#xff0c;让用户仿佛身临其境&#xff0c;并能与虚拟环境进行互动。尽管VR场景…

python学习day2:进制+码制+逻辑运算符

进制 Python 中的进制表示与转换 进制的基本概念 二进制、八进制、十进制、十六进制的定义与特点不同进制在计算机科学中的应用场景 Python 中的进制表示 二进制表示&#xff1a;使用 0b 前缀八进制表示&#xff1a;使用 0o 前缀十六进制表示&#xff1a;使用 0x 前缀示例…

【分布式文件系统】FastDFS

1.简介 讲这个之前&#xff0c;相信很多人特别是学java的&#xff0c;肯定在做苍穹外卖的时候肯定接触过一个东西&#xff0c;叫做阿里云OSS&#xff0c;他们的功能都差不多&#xff0c;但是阿里云的这个是要付费的&#xff0c;而FastDFS是免费开源的&#xff0c;是由淘宝资深…

word为章节标题添加自动编号

问题&#xff1a; 如何为word文档中的多级标题添加自动编号&#xff1f; 方法&#xff1a; 1、首先为文档各级标题设置格式样式&#xff0c;一级标题使用样式中的“标题 1”&#xff0c;二级标题使用“标题 2”&#xff0c;三级使用“标题 3”&#xff0c;也就是直接在开始—…

无人机飞行间隔安全智能评估、安全风险评估

无人机空中安全飞行评估需结合改进碰撞模型、蒙特卡洛仿真、安全间隔反推及动态避障策略&#xff0c;通过多机型分类与实时数据融合&#xff0c;实现从理论建模到实际部署的全流程管控&#xff0c;为城市低空密集飞行提供安全保障。 需求 无人机飞行间隔安全智能评估 无人机…

【VLNs篇】03:VLMnav-端到端导航与视觉语言模型:将空间推理转化为问答

栏目内容论文标题End-to-End Navigation with Vision-Language Models: Transforming Spatial Reasoning into Question-Answering (端到端导航与视觉语言模型&#xff1a;将空间推理转化为问答)核心问题如何利用大型视觉语言模型&#xff08;VLM&#xff09;实现端到端的机器人…

PCB设计实践(二十五)贴片电阻与插件电阻的全面解析:差异、演进与应用场景

一、基础结构与技术原理差异 物理结构差异 贴片电阻&#xff08;SMD Resistor&#xff09;采用表面贴装技术&#xff08;SMT&#xff09;&#xff0c;其主体为扁平长方体或圆柱形结构&#xff0c;两端金属化电极直接与PCB焊盘接触。典型封装尺寸包括0402&#xff08;1.00.5mm&a…

Canvas设计图片编辑器全讲解(一)Canvas基础(万字图文讲解)

一、前序 近两年AI发展太过迅速&#xff0c;各类AI产品层出不穷&#xff0c;AI绘图/AI工作流/AI视频等平台的蓬勃发展&#xff0c;促使图片/视频等复杂内容的创作更加简单&#xff0c;让更多普通人有了图片和视频创作的机会。另一方面用户内容消费也逐渐向图片和视频倾斜。在“…

利用Qt绘图随机生成带多种干扰信息的数字图片

背景 在学习AutoML或ML的过程中&#xff0c;需要一些图片类型的数据做分类预测训练&#xff0c;于是想到尝试最简单的数字识别&#xff0c;且单个数字的识别&#xff0c;也就是y的取值只有10种可能&#xff0c;即0到9。 以下参考代码分别考虑了数字字体的大小、数字颜色的深浅…

STM32——从点灯到传感器控制

STM32基础外设开发&#xff1a;从点灯到传感器控制 一、前言 本篇文章总结STM32F10x系列基础外设开发实例&#xff0c;涵盖GPIO控制、按键检测、传感器应用等。所有代码基于标准库开发&#xff0c;适合STM32初学者参考。 二、硬件准备 STM32F10x系列开发板LED模块有源蜂鸣器…

java day14

接昨天&#xff0c;响应 响应 就是我们在处理请求的时候&#xff0c;里面的return 其实方法里面写的return的返回平常的什么字符串啊什么等等&#xff1b;这些东西都是直接返回&#xff1b;如果是一个对象的话&#xff0c;我们会按json的格式返回&#xff1b; 这些都依赖于一…

Tailwind css实战,基于Kooboo构建AI对话框页面(一)

在当今数字化时代&#xff0c;AI 助手已成为网站和应用不可或缺的一部分。本文将带你一步步使用 Tailwind CSS 和 Kooboo 构建一个现代化的 AI 对话界面框。 一、选择 Kooboo平台 的核心优势 智能提示&#xff1a;在输入 class 属性时&#xff0c;会自动触发 Tailwind CSS 规则…

重塑数学边界:人工智能如何引领数学研究的新纪元

目录 一、人工智能如何重新定义数学研究的边界 &#xff08;一&#xff09;数学与AI的关系&#xff1a;从基础理论到创新思维的回馈 &#xff08;二&#xff09;AI的创造力&#xff1a;突破传统推理的局限 &#xff08;三&#xff09;AI对数学研究的潜在贡献&#xff1a;创…