【C语言进阶】C语言指针进阶实战:优化与难题解析

2024-08-29 03:36

本文主要是介绍【C语言进阶】C语言指针进阶实战:优化与难题解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C语言 “ 登神长阶 ”
🤡往期回顾🤡:C语言指针进阶 (上)
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀C语言指针进阶

  • 📒1. 函数指针
  • 📙2. 函数指针数组
  • 📚3. 指向函数指针数组的指针
  • 📜4. 回调函数
  • 📝5. 指针笔试题
  • 📖6. 总结


前言:在C语言的浩瀚宇宙中,指针无疑是那颗最为璀璨而神秘的星辰。它既是连接数据与操作的桥梁,也是让许多初学者望而生畏的迷宫。一旦掌握了指针的精髓,C语言的世界便仿佛被彻底点亮,编程的效率和灵活性得以质的飞跃。然而,仅仅停留在指针的基本使用上,远不足以探索其全部魅力与力量。指针的进阶应用,尤其是如何通过指针优化程序性能、解决复杂难题,是每一位C语言开发者必须攀登的高峰

每个实战案例都将配以详细的代码示例与解释,旨在让读者不仅能够理解其背后的原理,更能够亲手实践,将所学知识转化为解决问题的能力。同时,我们也会对一些常见的指针难题进行深度解析,比如指针运算的陷阱、多级指针的理解难点等,帮助读者彻底克服这些学习障碍

让我们继续一同踏上这场充满挑战与收获的指针进阶之旅吧!


📒1. 函数指针

C语言中的函数指针是一种特殊的指针类型,它存储的不是变量的地址,而是函数的地址。通过函数指针,我们可以在运行时动态地调用函数,这增加了程序的灵活性和模块化。函数指针在回调函数、中断服务例程、以及实现函数表(如多态)等场景中非常有用

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

在这里插入图片描述

输出的是两个地址,这两个地址是 test 函数的地址,那么我们怎么保存函数的地址呢?

定义:

// 保存函数的地址
void (*pfun1)();

解析:pfun1可以存放存储地址。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void


我们再来看一下这两个代码

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

代码1:

这段代码尝试执行一个位于地址0处的函数。首先,(void (*)())0是一个类型转换,它将整数0转换为指向返回类型为void且不接受任何参数的函数的指针。然后,*操作符对转换后的指针进行解引用,尝试获取该函数指针指向的函数。最后,()用于调用该函数

代码2:

  • signal是一个函数,它接受两个参数:
    • 第一个参数是int类型,通常用于指定要处理的信号编号。
    • 第二个参数是一个指向函数的指针,这个函数接受一个int参数(通常是信号编号)并返回void。
  • signal函数的返回类型是一个指向函数的指针,这个函数也接受一个int参数并返回void

📙2. 函数指针数组

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

定义:

int (*parr1[10])();
// parr1 先和 [] 结合,说明 parr1是数组
// 存放int (*)() 类型的函数指针

举个例子来展示如何定义一个函数指针数组,并向其中添加函数指针,最后通过索引来调用这些函数

// 定义三个示例函数  
void func1(int x) 
{printf("Func1 called with %d\n", x);
}void func2(int x) 
{printf("Func2 called with %d\n", x);
}void func3(int x) 
{printf("Func3 called with %d\n", x);
}// 定义一个指向函数的指针类型,该函数接受一个int参数并返回void  
typedef void (*FuncPtr)(int);int main() {// 创建一个函数指针数组,可以存储三个指向函数的指针  FuncPtr funcArr[3] = { func1, func2, func3 };// 通过索引调用函数  for (int i = 0; i < 3; i++) {funcArr[i](i + 1); // 调用函数,并将索引值+1作为参数传递  }return 0;
}

📚3. 指向函数指针数组的指针

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

定义:

void test(const char* str)
{printf("%s\n", str);
}int main()
{//函数指针pfunvoid (*pfun)(const char*) = test;//函数指针的数组pfunArrvoid (*pfunArr[5])(const char* str);pfunArr[0] = test;//指向函数指针数组pfunArr的指针ppfunArrvoid (*(*ppfunArr)[5])(const char*) = &pfunArr;return 0;
}

📜4. 回调函数

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

// 定义一个回调函数的类型,该函数接受一个int参数并返回void  
typedef void (*CallbackFunc)(int);// 一个需要回调函数的函数  
void processData(int data, CallbackFunc callback) 
{printf("Processing data: %d\n", data);// 调用回调函数  callback(data);
}// 用户定义的回调函数  
void myCallback(int data) {printf("Callback called with data: %d\n", data);
}int main() {// 调用需要回调函数的函数,并传递用户定义的回调函数作为参数  processData(10, myCallback);return 0;
}

processData函数接受一个整数和一个回调函数作为参数。在processData函数内部,首先执行一些处理,然后调用回调函数callback,并将之前接收到的整数data作为参数传递给回调函数。用户定义的回调函数myCallback被传递给processData函数,并在适当的时候被调用

回调函数广泛应用于事件处理、排序算法(如快速排序中的比较函数)


📝5. 指针笔试题

题目一:

int main()
{int a[5] = { 1, 2, 3, 4, 5 };int* ptr = (int*)(&a + 1);printf("%d,%d", *(a + 1), *(ptr - 1));return 0;
}

在这里插入图片描述


题目二:

int main()
{int a[3][2] = { (0, 1), (2, 3), (4, 5) };int* p;p = a[0];printf("%d", p[0]);return 0;
}

数组初始化时使用的 (0, 1) 形式的初始化实际上是逗号运算符(comma operator)的使用。逗号运算符会评估其两个操作数,但只返回最后一个操作数的值

这里 p 被赋值为 a[0] 的地址,即 &a[0][0]。然后 p[0] 访问了这个地址的内容,即 a[0][0],其值为1
在这里插入图片描述


题目三:

int main()
{char* a[] = { "work","at","alibaba" };char** pa = a;pa++;printf("%s\n", *pa);return 0;
}

这道题目比较简单,各位可以自己看看


题目四:

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); // 16 数组大小
printf("%d\n",sizeof(a+0)); // 4 数组元素的大小
printf("%d\n",sizeof(*a)); // 4 首元素大小
printf("%d\n",sizeof(a+1)); // 4 数组元素的大小
printf("%d\n",sizeof(a[1])); // 4 数组元素的大小
printf("%d\n",sizeof(&a)); // 4 数组首元素的大小
printf("%d\n",sizeof(*&a)); // 16 数组大小
printf("%d\n",sizeof(&a+1)); // 4 数组元素的大小
printf("%d\n",sizeof(&a[0])); // 4 数组元素的大小
printf("%d\n",sizeof(&a[0]+1)); // 4 数组元素的大小

📖6. 总结

随着指针进阶学习的深入,我们仿佛揭开了C语言世界中最为神秘而又引人入胜的一角。在上一部分,我们已经见证了指针如何成为连接数据与内存的桥梁,而在这部分,我们更是将这份力量发挥到了极致

回顾这段学习之旅,我们不难发现,指针不仅是C语言的核心特性之一,更是编程世界中一把强大的双刃剑。它既能让我们实现高效的内存管理与复杂的数据操作,也可能因不当使用而引发难以察觉的错误与漏洞。因此,在享受指针带来的便利与乐趣的同时,我们也必须时刻保持警惕,遵循最佳实践,确保代码的安全与可靠

至此,我们的C语言指针进阶之旅即将画上圆满的句号。但请记住,学习永无止境,技术的海洋浩瀚无垠。愿每一位编程爱好者都能保持对技术的热爱与追求,继续在编程的道路上探索前行,创造属于自己的辉煌篇章

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

这篇关于【C语言进阶】C语言指针进阶实战:优化与难题解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1116729

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份