灵魂指针,教给(三)
欢迎来到白刘的领域 Miracle_86.-CSDN博客
系列专栏 C语言知识
先赞后看,已成习惯
创作不易,多多支持!
目录
一、 字符指针变量
二、数组指针变量
2.1 数组指针变量是什么
2.2 数组指针变量如何初始化
三、二维数组传参本质
四、函数指针变量
4.1 函数指针变量的创建
4.2 函数指针的使用
4.3 两段有意思的代码
4.3.1 typedef关键字
五、函数指针数组
六、转移表
一、 字符指针变量
指针的类型中有一种为字符指针char*。
一般我们使用的时候,是这么使用的:
int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}
其实还有一种方法:
int main()
{const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?printf("%s\n", pstr);return 0;
}
其实上述方法是将字符串的首字符的地址放在了指针pstr里了,也就是字符h的地址。
《剑指offer》中收录了这样一道题:
#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";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;
}
来看运行结果:
你猜对结果了嘛,下面我们来简单讲解一下这道题。
首先四个字符串,str1.2.3.4,在3和4的前面用const修饰了,所以这里的str3和str4是常量字符串。
而在C/C++中对于一个常量字符串,是不会额外开辟空间的,所以str3和str4的首地址是一个地址,所以两个字符串也是相同的。而str1和str2不是常量字符串,是可修改操作的,所以分别开辟了两个空间来存放这两个字符串,因此它俩的首字符地址也不相同,所以两个字符串也不相同。
二、数组指针变量
2.1 数组指针变量是什么
前面我们学习了一个类似,非常容易混淆的,叫指针数组,当时也挖坑了,说以后会讲这个。
灵魂指针,教给(二)-CSDN博客
我们之前说,指针数组,它是数组,那数组指针呢?是指针!
我们已经熟悉:
整型指针变量:int * pint; 用来存放整型变量的地址,能指向整型数据的指针。
浮点型指针变量:float * pint; 用来存放浮点型变量的地址,能指向浮点型数据的指针。
所以我们能推断出数组指针,它是用来存放数组地址,能指向数组的指针。
来思考下面两行代码,分别是什么:
int *p1[10];
int (*p2)[10];
第一行是在前面我们学的指针数组,第二行为数组指针。
如何去记忆呢?我们要记住一个点就好,就是[ ]的优先级是比 * 高的,我们去看变量名和谁先结合就好,第一行先和中括号结合,所以是数组,变量名为数组名;第二行加了小括号,所以先和*结合,成了数组指针。
那数组指针的类型是什么呢?我们在学习数组的时候说去掉数组名,剩下的就是类型,那来看看数组指针的类型怎么写呢?
int (*)[10];
2.2 数组指针变量如何初始化
数组指针用来存放数组的地址,那怎么获取数组的地址呢?没错,就是我们之前说的&arr。
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果要存放整个数组的地址,就存放在数组指针中:
int(*p)[10] = &arr;
我们调试也能看到&arr和p的地址是一样的。
数组指针类型解析:
int (*p) [10] = &arr;| | || | || | p指向数组的元素个数| p是数组指针变量名p指向的数组的元素类型
三、二维数组传参本质
有了数组指针,我们就能详细了解二维数组传参本质了。
之前,我们将一个二维数组传给一个函数的时候,我们往往会这么写:
void test(int a[3][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 ", 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;
}
我们学完数组指针之后,还有其它的写法嘛?
我们再次理解一下二维数组,其实它就可以看成一个一维数组,只不过里面元素装的也是一维数组,所以二维数组的每一行都是一个数组。
所以,根据数组名是首元素地址这个规则,二维数组的数组名也就代表着第一行的地址,也就是一个一维数组的地址。根据上面的例子,第一行的数组的类型也就是int [5],所以第一行的地址类型就是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.1 函数指针变量的创建
通过类比关系,不难理解函数指针。它应该是存放指针地址的,可以通过函数地址调用函数的。
那函数指针到底是不是这样的呢?我们来做个测试:
#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}
运行结果如下:
我们确实打印出来了地址,所以函数确实是有地址的,并且函数名就是地址,所以可以用&函数名的方式获得函数的地址。
我们如果想将函数的地址存放起来,也非常简单,这就用到了函数指针变量,写法其实和数组指针的写法非常类似:
void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
就是后面的改成参数了而已,非常简单。
函数指针类型详解:
int (*pf3) (int x, int y)| | ------------ | | || | pf3指向函数的参数类型和个数的交代| 函数指针变量名pf3指向函数的返回类型int (*) (int x, int y) //pf3函数指针变量的类型
4.2 函数指针的使用
通过函数指针调用指针指向的函数。
#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;
}
运行结果:
我们发现,写(*pf3)(2,3),或者pf3(3,5)都可以。
4.3 两段有意思的代码
代码1
(*(void (*)())0)();
//首先void(*)() --这是指针变量
//( void(*)() )0 --强制类型转换
//这就意味着我们假设0地址这个位置放着无参,返回类型为void的函数
代码2
void (*signal(int , void(*)(int)))(int);
//void(* signal(int,void(*)(int)))(int);
//声明了一个函数,signal(int,void(*)(int))
//它有两个参数,一个是int,一个是函数地址
//把signal的地址放进了void类型的函数指针,参数为int
这两段代码均出自于《C陷阱与缺陷这本书》。
4.3.1 typedef关键字
我们好久没讲关键字了,今天就新认识一个,叫typedef。它是用来干什么的呢?用来给类型重命名的,可以将复杂的类型简单化。
举个栗子:比如说你觉得unsigned int 写起来不方便,你想给它改成uint,代码如下:
typedef unsigned int uint;
//将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?当然可以,比如将int*改成ptr_r:
typedef int* ptr_t;
但是对于数组指针和函数指针,稍微有点不同。
比如我们要将int(*arr)[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);
五、函数指针数组
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组。
比如:
int *arr[10];
//数组的每个元素是int*
如果我们想把函数的地址都存储到一个数组中,那这个数组就叫函数指针数组,它是怎么定义的呢?
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
答案是parr1,
parr1先和[3]结合,说明其是数组,然后数组的内容是什么呢?是int(*)( )类型的函数指针。
六、转移表
讲完了函数指针数组,我们来说一个听起来比较牛逼的东西——转移表。 虽然听起来很牛逼,但是它的本质就是我们刚刚学过的函数指针数组。
举个栗子,假如我们现在要写一个计算器的小程序,正常我们会这么写:
#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;
}
很好想,但是这么写稍微有点冗余了(就是switch里有许多重复或者相似的代码片段)。
而我们学完了函数指针数组,我们就可以这么写,我们把加减乘除的函数都装到数组里,代码如下:
#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;
}
相关文章:

灵魂指针,教给(三)
欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看,已成习惯 创作不易,多多支持! 目录 一、 字符指针变量 二、数组指针变量 2.1 数组指针变量是什么 2.2 数组指针变量如何初始化 三、二维数组传参本质 四、函数…...

纯手工搭建一个springboot maven项目
前言:idea社区版无法自动搭建项目,手动搭建的经验分享如下: 1 包结构 参考下图: 2 项目结构 3 maven依赖 具体的项目包结构如下图: 依据这个项目包结构配置一个springboot 的 pom依赖: <?xml ve…...
【Java】使用`LinkedList`类来实现一个队列,并通过继承`AbstractQueue`或者实现`Queue`接口来实现自定义队列
使用LinkedList类来实现一个队列,并通过继承AbstractQueue或者实现Queue接口来实现自定义队列。 以下是一个简单的示例,其中队列的大小与另一个List的容量保持一致: import java.util.LinkedList; import java.util.List; import java.util…...
ChatGPT消息发不出去了?我找到解决方案了
现象 今天忽然发现 ChatGPT无法发送消息,能查看历史对话,但是无法发送消息。 猜测原因 出现这个问题的各位,应该都是点击登录后顶部弹窗邀请[加入多语言 alapha 测试]了,并且语言选择了中文,抓包看到ab.chatgpt.com…...

《量子计算:下一个大风口,还是一个热炒概念?》
引言 量子计算,作为一项颠覆性的技术,一直以来备受关注。它被认为是未来计算领域的一次革命,可能改变我们对计算能力和数据处理的理解。然而,随着技术的不断进步和商业应用的探索,人们开始思考,量子计算到底是一个即将到来的大风口,还是一个被过度炒作的概念? 量子计…...
在Ubuntu中如何基于conda安装jupyterlab
在Ubuntu中如何创建ipykernel 可以用下面命令完成 conda create -n newenv python3.8conda activate enwenvconda install ipykernel5.1.4conda install ipython_genutilsipython -m ipykernel install --user --namepython3 --display-name Python3conda install -c conda-fo…...
Unity 中的 PlayFab 入门
要开始在 Unity 中使用 PlayFab,你只需执行以下两个简单步骤即可。第一步是设置 PlayFab 帐户。第二步是通过安装 Unity 编辑器扩展将其连接到 Unity。或者,你也可以下载 PlayFab SDK 并在没有扩展的情况下进行配置。 设置你的 PlayFab 帐户 访问 PlayFab 的网站并创建你的…...
常见排序算法(C++)
评判一个排序算法时除了时间复杂度和空间复杂度之外还要考虑对cache的捕获效果如何,cache友好的排序算法应该对数据的访问相对集中,快速排序相较于堆排序优点就是在于对cache的捕获效果好。 堆排序 时间复杂度:O(n log n …...
多线程编程互斥锁mutex的创建
在Linux下的多线程编程中,互斥锁(mutex)的创建主要有两种方式:静态分配和动态分配。这两种方式的主要区别在于互斥锁的生命周期和初始化方式。 静态分配(静态方式) 静态分配方式是在程序编译时就已经确定互…...

在 SpringBoot3 中使用 Mybatis-Plus 报错
在 SpringBoot3 中使用 Mybatis-Plus 报错 Property ‘sqlSessionFactory’ or ‘sqlSessionTemplate’ are required Caused by: java.lang.IllegalArgumentException: Property sqlSessionFactory or sqlSessionTemplate are requiredat org.springframework.util.Assert.no…...

【libwebrtc】基于m114的构建
libwebrtc A C++ wrapper for binary release, mainly used for flutter-webrtc desktop (windows, linux, embedded).是 基于m114版本的webrtc 最新(20240309 ) 的是m122了。官方给出的构建过程 .gclient 文件 solutions = [{"name" : src,"url...

ansible-playbook的角色(role)
1前言 角色目录如下(分别为httpd角色和nginx角色) handlers/ :至少应该包含一个名为 main.yml 的文件; 其它的文件需要在此文件中通过include 进行包含 vars/ :定义变量,至少应该包含一个名为 main.yml 的…...

SNR = 6.02N + 1.76dB 公式推导
简介 接触ADC或DAC时您一定会碰到这个经常被引用的公式,用于计算转换器理论信噪比 (SNR)。与其盲目地相信表象,不如从根本上了解其来源,因为该公式蕴含着一些微妙之 处,如果不深入探究,可能导致对数据手册技术规格和转…...

归并排序 刷题笔记
归并排序的写法 归并排序 分治双指针 1.定义一个mid if(l>r)return ; 2.分治 sort(q,l,mid); sort(q,mid1,r); 3. 双指针 int il,jmid,k0; 将双序列扫入 缓存数组 条件 while(i<mid&&j<r) 两个数列比较大小 小的一方 进入缓存数组 4. 扫尾 while(…...
字节一面:TCP 和 UDP 可以使用同一个端口吗?
数据包是计算机网络通信的核心,包含头部和数据负载。TCP和UDP协议在传输层使用端口号区分服务和应用。操作系统通过IP头部中的协议字段和端口号来管理网络流量,确保TCP和UDP流量即使共用端口号也不会相互干扰。 在现代计算机网络中,数据传输…...

java guide 八股
Java语言特点 简单易学、面向对象(继承、封装、多态)、平台无关性(Java虚拟机jvm)、支持多线程、可靠、安全、高效、支持网络编程、编译与解释共存 JVM:Java虚拟机(跨平台的关键) JREÿ…...
Windows上使用client-go远程访问安装在本地WMware上的Linux虚拟机里的minikube
我在自己的Windows上安装了WMware,并在WMware上安装了CentOS操作系统,然后在CentOS上创建了一个叫minikube的用户,使用minikube用户启动了一个minikube集群,但是我在Windows上使用client-go并无法连通minikube,搜遍全网…...
Linux/Ubuntu/Debian基本命令:命令行历史记录
一组与类 Unix 环境中的命令行(Terminal)历史记录和命令调用相关的键盘快捷键: Ctrl R: 启动对以前使用过的命令的反向搜索。 当你键入时,它将查找并显示与输入的字符匹配的最新命令。Ctrl G: 退出历史搜索模式,不运行命令。 如…...
倒计时32天
L1-032 Left-pad - 2024团体程序设计天梯赛(历年真题)练习集 (pintia.cn) #include<bits/stdc.h> using namespace std; #define int long long const int N2e56; const int inf0x3f3f3f3f; void solve() {int n;char s;cin>>n>>s;ge…...

模型驱动架构MDA
MDE 模型驱动工程(MDE, Model-Driven Engineering)是软件工程的一个分支,它将模型与建模拓展到软件开发的所有方面,形成一个多维建模空间,从而将工程活动建立在这些模型的映射和转换之上。[1] MDE的基本原则是将模型视…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...