当前位置: 首页 > article >正文

C语言【指针篇】(四)

    • 前言:
    • 正文
      • 1. 字符指针变量
      • 2. 数组指针变量
        • 2.1 数组指针变量是什么?
        • 2.2 数组指针变量怎么初始化
      • 3. 二维数组传参的本质
      • 4. 函数指针变量
        • 4.1 函数指针变量的创建
        • 4.2 函数指针变量的使用
        • 4.3 两段有趣的代码
        • 4.3.1 typedef关键字
      • 5. 函数指针数组
      • 6. 转移表
    • 总结

前言:

这是指针第四篇,主要介绍:字符指针变量、数组指针变量、二维数组传参的本质、函数指针变量、函数指针数组以及函数指针数组的应用——转移表

正文

1. 字符指针变量

在指针的类型中有一种指针类型为字符指针char*
一般使用方式如下:

int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}

还有一种使用方式如下:

int main()
{const char* ps = "hello C.";printf("%s\n", ps);return 0;
}

代码const char* ps = "hello C";容易让人以为是把字符串hello C放到字符指针ps里了,但是本质是把字符串hello C.首字符的地址放到了ps中。实际是把一个常量字符串的首字符h的地址存放到指针变量ps中。

《剑指offer》中又一道与之相关的题目,代码如下:

#include <stdio.h>
int main()
{char str1[] = "hello C.";char str2[] = "hello C.";const char *str3 = "hello C.";const char *str4 = "hello C.";if(str1 ==str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3 ==str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

结果
为什么会有这样的结果呢?const修饰常量字符串,str3和strr4是指向同一个字符串的,在C语言中会把常量字符串单独存储到一个内存区域,虽然str3和str4变量名不同,但实际上指向同一片内存空间。然而,用同样的字符串初始化不同数组时会形成不同空间,即不同的内存块。所以str1和str2不同,str3和str4相同。

2. 数组指针变量

2.1 数组指针变量是什么?

先区分一下,之前学过指针数组,它是一种数组,用来存放指针(也就是地址)
数组指针变量,和·指针数组·不同,它是指针变量
举个栗子:

int* pint 表示整型指针变量用来存放整型变量的地址
float* pf表示浮点型指针变量 , 用来存放浮点型数据的地址

数组指针变量存放的是数组的地址,能够指向数组的指针变量。

这里用两个代码经常混淆

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

哪一个是数组指针变量呢?答案是p2即int(*p2)[10];p1是指针数组,p2先和结合,说明p2是一个指针变量,然后指针指向的是一个大小为 10 个整型的数组,所以p2是一个指针,指向一个数组,叫数组指针。
注意:[]的优先级是高于
的,所以必须加上()保证*和p是一起的

2.2 数组指针变量怎么初始化

数组指针变量是用来存放数组地址的,通过&数组名可以获得数组的地址。例如:

int arr[10] = {0};
&arr;

如果要存放数组的地址,就得存放在数组指针变量中,如下:

int(*p)[10] = &arr;

调试可以看到&arrp的类型是完全一致的。
数组指针类型解析:

描述

3. 二维数组传参的本质

有了数组指针的基础理解,就能够讲一下二维数组传参的本质。

过去二维数组传参给一个函数时,写法如下:

#include <stdio.h>
void test(int a[3][5], int a, int b)
{int i = 0;int j = 0;for(i=0; i<a; i++){for(j=0; j<b; j++){printf("%d ", a[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}
0 0 0 1 1 1 2 2 2 3 3 3 4 4 4
0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5
1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6
2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7

arr数组 内容如上
实参是二维数组,形参也是二维数组,但是前面说过二维数组其实是一维数组的数组,二维数组的首元素是第一行,是一维数组,所以我们可以这样理解:二维数组数组名是第一行的地址,是一维数组的地址(指针),第一行的一维数组类型就是int [5],so,第一行的地址类型就是数组指针类型int(*) [5].这表示二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的,如下:

#include <stdio.h>
void test(int (*p)[5], int r, int c)
{int i = 0;int j = 0;for(i=0; i<r; i++){for(j=0; j<c; j++){printf("%d ", *(*(p+i)+j));}printf("\n");}
}
int main()
{int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};test(arr, 3, 5);return 0;
}

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

4. 函数指针变量

4.1 函数指针变量的创建

函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数。

函数是有地址的,通过以下测试可以证明:

#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}

结果

输出结果中test&test的地址相同,函数名就是函数的地址,也可以通过&函数名的方式获得函数的地址。

如果要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法和数组指针非常类似。例如:

void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{return x+y;
}
int(*pf3)(int x, int y) = &Add;
int(*pf3)(int, int) = Add;

函数指针类型解析:

int (*pf3) ( int x, int y)
//( int x, int y)——指向函数的参数类型和个数的交代
//(*pf3)         ——函数指针变量名
//int            ——pf3指向函数的返回类型int (*) (int x, int y) //pf3函数指针变量的类型
4.2 函数指针变量的使用

现在写一个加法的函数,你可能会这样写:

int add(int x,int y)
{return x + y;
}
int main()
{int r = add(2,5);printf("%d",r);return 0;
}

但我们学过上述内容后,通过函数指针调用指针指向的函数,示例代码如下:

#include <stdio.h>
int Add(int x, int y)
{return x+y;
}
int main()
{int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));printf("%d\n", pf3(3, 5));return 0;
}
4.3 两段有趣的代码
(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);

两段代码均出自《C陷阱和缺陷》这本书。
可以自己写出来理解一下,正常情况我们不会这样写,实在理解不了也没关系,(也可以问问deepseek).

4.3.1 typedef关键字

typedef是用来类型重命名的,可以将复杂的类型简单化。比如将unsigned int重命名为uint

typedef unsigned int uint;

对于指针类型也能重命名,将int*重命名为ptr_t

typedef int* ptr_t;

对于数组指针和函数指针重命名稍有区别。将数组指针类型int(*)[5]重命名为parr_t

typedef int(*parr_t)[5]; 

将函数指针类型void(*)(int)重命名为pf_t

typedef void(*pfun_t)(int);

简化代码2可以这样写:

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

5. 函数指针数组

数组是一个存放相同类型数据的存储空间,已经学习了指针数组,例如int * arr[10];,数组的每个元素是int*

把函数的地址存到一个数组中,这个数组就叫函数指针数组。函数指针数组定义为int (*parr1[3])();parr1先和[]结合,说明parr1是数组,数组的内容是int (*)()类型的函数指针。

6. 转移表

函数指针数组的用途之一是转移表。

例如计算器的一般实现代码如下:

#include <stdio.h>
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(" 0:exit \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");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

使用函数指针数组的实现:

#include <stdio.h>
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;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n" );printf("*************************\n");printf("请选择:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("输入操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);printf("ret = %d\n", ret);}else if(input == 0){printf("退出计算器\n");}else{printf("输入有误,重新选择\n");}}while (input);return 0;
}

总结

本文就到这里了,如有误,欢迎指正,指针很难,但坚持下去,终有收获,朋友,把这件事坚持下去吧。
敬,不完美的明天。

相关文章:

C语言【指针篇】(四)

前言&#xff1a;正文1. 字符指针变量2. 数组指针变量2.1 数组指针变量是什么?2.2 数组指针变量怎么初始化 3. 二维数组传参的本质4. 函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使用4.3 两段有趣的代码4.3.1 typedef关键字 5. 函数指针数组6. 转移表 总结 前言&am…...

Python基于Django的网络课程在线学习平台【附源码】

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…...

vscode集成DeepSeek

vscode 扩展 安装 Cline Meet Cline&#xff0c;一个可以使用你的CLI和编辑器的AI助手。 得益于 Claude 3.5 Sonnet的代理编码功能&#xff0c;Cline 可以逐步处理复杂的软件开发任务。借助让他创建和编辑文件、探索大型项目、使用浏览器和执行终端命令(在您授予权限后)的工具&…...

【TCAD】Sentaurus 中的“陷阱trap”仿真设置

13.1 陷阱类型 13.2 定义陷阱 13.3 陷阱态密度的类型 13.4 陷阱空间分布 13.5 陷阱占据 13.6 陷阱横截面 13.7 陷阱作为掺杂 13.8 陷阱填充控制 13.9 陷阱可视化 目标 演示如何使用 Sentaurus 设备在模拟中使用陷阱。13.1 陷阱类型...

Lucene硬核解析专题系列(三):查询解析与执行

Lucene的索引构建为高效搜索奠定了基础,而查询解析与执行则是将用户意图转化为实际结果的关键环节。本篇将从查询的解析开始,逐步深入到查询类型、评分模型和执行流程,揭示Lucene搜索能力的底层原理。 一、查询语法与QueryParser的工作原理 Lucene的查询过程始于用户输入的…...

Linux操作系统5-进程信号3(信号产生总结与核心转储)

上篇文章&#xff1a;Linux操作系统5-进程信号2&#xff08;信号的4种产生方式&#xff0c;signal系统调用&#xff09;-CSDN博客 本篇Gitee仓库&#xff1a;myLerningCode/l25 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com) 本篇重点&#xff1a;核心…...

家用可燃气体探测器——家庭燃气安全的坚实防线

随着社会的发展和变迁&#xff0c;天然气为我们的生活带来了诸多便利&#xff0c;无论是烹饪美食&#xff0c;还是温暖取暖&#xff0c;都离不开它的支持。然而&#xff0c;燃气安全隐患如影随形&#xff0c;一旦发生泄漏&#xff0c;可能引发爆炸、火灾等严重事故&#xff0c;…...

【学习笔记】网络设备(华为交换机)基础知识 9 —— 堆叠配置

提示&#xff1a;学习华为交换机堆叠配置&#xff0c;含堆叠的概念、功能、角色、ID和优先级&#xff1b;堆叠的建立过程以及注意事项&#xff1b;包含堆叠的配置命令&#xff0c;以及堆叠的配置案例 一、前期准备 1.已经可以正常访问交换机的命令行接口 Console口本地访问教…...

【Linux】Linux的进程控制

目录 1. 学习思维导图 2.进程创建&#xff08;fork&#xff09; 2.1 fork创建进程失败 3.进程终止 3.1 进程退出情况 3.1.1main函数 3.1.2 退出码 3.2 exit/_exit函数 1. exit() 函数 2. _exit() 函数 4.进程等待 4.1 实现进程等待的方法 wait/waitpid方法 区别&a…...

电子电气架构 --- 汽车行业技术变革

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...

【告别双日期面板!一招实现el-date-picker智能联动日期选择】

告别双日期面板&#xff01;一招实现el-date-picker智能联动日期选择 1.需求背景2.DateTimePicker 现状图3.日期选择器实现代码4.日期选择器实现效果图5.日期时间选择器实现代码6.日期时间选择器实现效果图 1.需求背景 在用户使用时间查询时&#xff0c;我们经常需要按月份筛选…...

利用 Python 爬虫进行跨境电商数据采集

1 引言2 代理IP的优势3 获取代理IP账号4 爬取实战案例---&#xff08;某电商网站爬取&#xff09;4.1 网站分析4.2 编写代码4.3 优化代码 5 总结 1 引言 在数字化时代&#xff0c;数据作为核心资源蕴含重要价值&#xff0c;网络爬虫成为企业洞察市场趋势、学术研究探索未知领域…...

2. 在后端代码中加入日志记录模块

1. 说明 日志模块基本上是每一个软件系统开发中必不可少的&#xff0c;主要用于持久记录一些代码运行中的输出信息&#xff0c;辅助编码人员进行代码调试&#xff0c;以及后期软件上线运行报错分析。在Python中加入日志模块比较简单&#xff0c;只需要借助logging和RotatingFi…...

C++ 17 允许在 for 循环,if 语句,switch 语句中初始化变量

看到 c 有这个特性&#xff0c;python 和 java 似乎都没有&#xff0c;根据 AI 的回答进行了一些整理总结。 文章目录 **1. 在 for 循环中初始化变量****特点****多个变量初始化** **2. 在 if 语句中初始化变量&#xff08;C17 及以上&#xff09;****示例****特点** **3. 在 s…...

Pycharm中怎么加快下载三方包速度

Pycharm中怎么加快下载三方包速度 使用命令行下载,-i pip install transformers -i https://mirrors.aliyun.com/pypi/simple/ 在Windows系统的PyCharm中使用Python 3.12环境时,可通过以下几种方式配置不同镜像源来加快下载包的速度。 方式一:在PyCharm界面中直接配置镜…...

Transformer 代码剖析7 - 词元嵌入(TokenEmbedding) (pytorch实现)

一、类定义与继承关系剖析 1.1 代码结构图示 #mermaid-svg-9COHbtmHJhpiroHM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-9COHbtmHJhpiroHM .error-icon{fill:#552222;}#mermaid-svg-9COHbtmHJhpiroHM .error-t…...

Unity中动态切换光照贴图的方法

关键代码&#xff1a;LightmapSettings.lightmaps lightmapDatas; LightmapData中操作三张图&#xff1a;lightmapColor,lightmapDir,以及一张ShadowMap 这里只操作前两张&#xff1a; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI;public cl…...

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter27-工作者线程

二十七、工作者线程 工作者线程 前端开发者常说&#xff1a;“JavaScript 是单线程的。”这种说法虽然有些简单&#xff0c;但描述了 JavaScript 在浏览器中的一般行为。因此&#xff0c;作为帮助 Web 开发人员理解 JavaScript 的教学工具&#xff0c;它非常有用。单线程就意味…...

Qt基于等待条件QWaitCondition实现的任务队列模型示例

核心概念 Qt中的QWaitCondition是一个用于多线程同步的类&#xff0c;允许线程在某些条件满足时唤醒其他等待的线程。它通常与QMutex配合使用&#xff0c;协调线程之间的执行顺序&#xff0c;适用于生产者-消费者模型、任务队列调度等场景。 ​wait()&#xff1a;使当前线程进…...

本地大模型编程实战(26)用langgraph实现基于SQL数据构建的问答系统(5)

本文将将扩展上一篇文章完成的 langgraph 链&#xff0c;继续使用基于 langgraph 链 &#xff0c;对结构化数据库 SQlite 进行查询的方法。该系统建立以后&#xff0c;我们不需要掌握专业的 SQL 技能&#xff0c;可以用自然语言询问有关数据库中数据的问题并返回答案。主要完善…...

【RAG生成】生成模块核心技术解密:从理论到实践的全链路优化

RAG知识系列文章 【RAG实践】手把手Python实现搭建本地知识问答系统【RAG进阶】从基础到模块化&#xff1a;深度解析RAG技术演进如何重塑AI知识边界【RAG检索】RAG技术揭秘&#xff1a;检索≠召回&#xff1f;【RAG增强】解密RAG系统排序优化&#xff1a;从基础原理到生产实践…...

近似最近邻(ANN)算法库实战

引言&#xff1a;从“精确”到“近似”的思维跃迁 在电商推荐系统中&#xff0c;每秒需要为上百万用户找到最相关的商品——传统KNN的暴力搜索甚至无法完成一次查询&#xff0c;而近似最近邻&#xff08;ANN&#xff09;算法库如Faiss&#xff08;Facebook&#xff09;和Annoy…...

Linux与UDP应用2:简易聊天室

UDP应用2&#xff1a;简易聊天室 本篇介绍 在前面的基本使用过程中已经完成了本地和网络通信&#xff0c;既然一个人和一台服务器可以进行通信&#xff0c;那么多个人连接一台服务器也可以和这台服务器实现通信。在这个基础上&#xff0c;如果服务器可以将某个人发给服务器的…...

张雪峰教育观点及争议分析

李升伟 整理 张雪峰&#xff08;网络常用名&#xff0c;本名张子彪&#xff09;是中国知名的考研辅导教师、教育领域自媒体人&#xff0c;因其幽默犀利的语言风格和直击痛点的教育观点走红网络。以下是对他的基本介绍及综合评价&#xff1a; --- ### **一、基本情况** 1. **个…...

从0开始的IMX6ULL学习篇——裸机篇之分析粗略IMX6ULL与架构

目录 简单的说一下Cortex-A7架构 讨论ARMv7a-cortex系列的运行模式 寄存器 后言 让我们到NXP的官网上扫一眼。 i.MX 6ULL应用处理器_Arm Cortex-A7单核&#xff0c;频率为900 MHz | NXP 半导体 我们先看CPU Platform&#xff0c;这个是我们的核心。 这里我们的芯片是基于Ar…...

面向实时性的超轻量级动态感知视觉SLAM系统

一、重构后的技术架构设计(基于ROS1 ORB-SLAM2增强) #mermaid-svg-JEJte8kZd7qlnq3E {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JEJte8kZd7qlnq3E .error-icon{fill:#552222;}#mermaid-svg-JEJte8kZd7qlnq3E .…...

Hue UI展示中文

个人博客地址&#xff1a;Hue UI展示中文 | 一张假钞的真实世界 如果使用开发分支代码如master分支&#xff09;编译安装&#xff0c;需要自己编译语言文件。例如Hue安装目录为“/opt/hue”&#xff0c;则安装后执行以下命令&#xff1a; $ cd /opt/hue $ make locales 如果…...

C#贪心算法

贪心算法&#xff1a;生活与代码中的 “最优选择大师” 在生活里&#xff0c;我们常常面临各种选择&#xff0c;都希望能做出最有利的决策。比如在超市大促销时&#xff0c;面对琳琅满目的商品&#xff0c;你总想用有限的预算买到价值最高的东西。贪心算法&#xff0c;就像是一…...

【新人系列】Python 入门专栏合集

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…...

SQL命令详解之数据的查询操作

目录 1 简介 2 基础查询 2.1 基础查询语法 2.2 基础查询练习 3 条件查询 3.1 条件查询语法 3.2 条件查询练习 4 排序查询 4.1 排序查询语法 4.2 排序查询练习 5 聚合函数 5.1 一般语法&#xff1a; 5.2 聚合函数练习 6 分组查询 6.1 分组查询语法 6.2 分组查询…...