深入理解c指针(六)

目录

九、函数指针数组

1、字符指针变量

2、数组指针变量

3、二维数组传参的本质

 4、函数指针变量

4.1 分析《C陷阱和缺陷》中的两端代码

4.2 typedef关键字

5、函数指针数组

6、函数指针数组的用途---转移表


九、函数指针数组

1、字符指针变量

在指针的类型中我们知道有一种指针类型为字符指针 char* ;一般使用:

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

       还有⼀种使用方式如下:代码 const char* pstr = "hello bit."; 特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是本质是把字符串 hello bit. 首字符的地址放到了pstr中

int main()
{const char* pstr = "hello bit.";//这⾥是把⼀个字符地址存放在p中printf("%s\n", pstr);return 0;
}

 所以可以把字符串想象成一个数组:

注:①当常量字符串常量出现在表达式中的时候,他的值是第一个字符的地址。                            虽然可以把字符串看成一个字符数组,但是这个数组是不能修改的,因为它是常量不是变量。 

《剑指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;
}

        这里 str3 和 str4 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

2、数组指针变量

注意:指针数组是一种数组,数组中存放的是地址(指针)。

数组指针变量是指针变量,而不是数组。

#include <stdio.h>
int main()
{int n = 100;int* pn = &n;char ch = 'w';char* pc = &ch;float f = 3.14;float* pf = &f;int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int(*parr)[10] = &arr;//取出的是数组的地址//parr就是数组指针return 0;
}

元素地址与数组地址的区别: 

 数组指针如何应用:(打印数组元素为例)(因为数组指针的特点,一维数组基本不会使用) 

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//指针访问数组int(*p)[10] = &arr;  //p3是数组地址int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", (*p)[i]);//  p == &arr// *p == *&arr == arr}return 0;
}
3、二维数组传参的本质

#include <stdio.h>
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)); //p = &arr,p+i=&arr+i,*(p+i)=arr[i]}printf("\n");}
}方式二:
void test(int(*arr)[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 ", arr[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;
}

通俗理解:由于二维数组由若干个一维数组构成,那么二维数组的数组名即为第一行的一维数组地址, 地址+整数=地址,所以 + i 表示指到第 i 行的地址。当拿到第 i 行数组名(即地址),解引用后( *(p+i) )则得到了该行首元素的地址,此时与一维数组原理相似。

 4、函数指针变量

       函数指针变量也是指针变量,其存储的地址指向函数而不是普通变量。在C语言和C++中,函数指针变量可以用来存储函数的地址,从而可以通过该指针调用相应的函数。 下面做个测试:

       可以发现函数的确有地址。函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址。

数组名:数组首元素的地址

&数组名:整个数组的地址

函数名==&函数名:函数的地址

函数指针变量的声明通常形式为:

return_type (*function_pointer_name)(parameter_list);

       其中 return_type 表示函数返回类型,function_pointer_name 是函数指针变量的名称,parameter_list是函数的参数列表。 如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针非常类似。

void test()
{printf("hehe\n");
}void (*pf1)() = &test;//&test等价于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写上或者省略都是可以

函数指针变量的使用:

       声明了一个指向函数的指针 pf3,它接受两个整型参数并返回一个整型结果。在这里,pf3 被赋值为 Add 函数的地址,也就是说,它指向了 Add 函数的代码块。

  • pf3 是一个指向函数的指针,它里面包含了函数的地址。
  • *pf3 表示解引用这个指针,也就是得到指针所指向的函数。
  • (*pf3)(2, 3) 表示调用这个函数,传递参数 2 和 3 给它。

        而当写 pf3(2, 3) 时,因为函数名实际上就是函数的地址,所以直接使用 pf3(函数指针)并传递参数给它,就相当于直接调用了 Add 函数,所以 (*pf3)(2,3) 和 pf3(2,3) 结果相同。

4.1 分析《C陷阱和缺陷》中的两端代码
(  *( void (*)() )0  )();

 void(*)( ) --- 函数指针类型,    (int)3.14 --- 强制类型转换

上面的代码是一次函数调用:

1.把0这个整数值强制类型转换成一个函数地址,这个函数没有参数,返回类型是void

2.去调用0地址处的函数

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

signal没有和指针括在一起,由于括号的优先级更高,所以signal是函数名。

4.2 typedef关键字

       typedef是用来类型重命名的,可以将复杂的类型简单化。比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用: 

typedef unsigned int uint;
//将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t;

但是对于数组指针和函数指针稍微有点区别:比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边

函数指针类型的重命名也是一样的,比如,将 void(*)(int,int) 类型重命名为 pf_t ,就可以这样写:

typedef void(*pfun_t)(int,int);//新的类型名必须在*的右边
#include<stdio.h>typedef unsigned int uint;typedef int(*pArr_t)[10];   //对于数组、函数指针时不能将名字放在后面,而是*的右边void (*pf_t)(int, int);int main()
{unsigned int num;uint num2;int(*pb)[10];pArr_t pa;void(*pf1)(int,int);pf_t pf;return 0;
}

 利用 tepedef 对 void (*signal(int , void(*)(int) ) )(int); 进行简化:

void (*signal(int, void(*)(int)))(int);typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
5、函数指针数组

 指针数组 --- 是数组,每个元素是指针,char* arr[5], double* arr[5]

 数组指针 --- 是指针,指向数组的指针,int(*pa)[10]=&arr, char(*pa)[5]=&arr

       数组是一个存放相同类型数据的存储空间,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,是数组,里面的元素是函数指针。


再次理解函数指针:

#include<stdio.h>
int test(char* c,int n)
{//...return 0;
}
int main()
{int (*pf)(char*, int) = &test;(*pf)("abcdef", 10);  pf("abcdef", 10);test("abcdef", 10)return 0;
}

函数指针数组实例:

#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
int main()
{int (*pf)(int, int) = Add; //pf是函数指针int (*pf1[4])(int,int) = { Add,Sub,Mul,Div };//存放函数指针的数组// 0   1   2   3int i = 0;for (i = 0; i < 4; i++){int result=pf1[i](6, 2);/*int result = (*pf1[i])(6, 2);*/printf("%d\n", result);}return 0;
}
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_s("%d", &input);if ((input <= 4 && input >= 1)){printf("输⼊操作数:");scanf_s("%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;
}

 


 


 

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

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

相关文章

【C++精简版回顾】15.继承派生

1.继承派生的区别 继承&#xff1a;子继父业&#xff0c;就是子类完全继承父类的全部内容 派生&#xff1a;子类在父类的基础上发展 2.继承方式 1.public继承为原样继承 2.protected继承会把public继承改为protect继承 3.private继承会把public&#xff0c;protected继承改为pr…

怎么抠图把把人物扣下来?简单快捷的抠图方法

相信很多新手小白在初入设计行业时&#xff0c;对于抠图怎么把人物扣下来都是一头雾水。抠图作为设计中常用的一种技术&#xff0c;能够帮助我们快速提取图片中的某个部分&#xff0c;进行合成或者修改。对于老手来说&#xff0c;抠图或许是再熟悉不过的操作&#xff0c;但对于…

c语言---数组(超级详细)

数组 一.数组的概念二. 一维数组的创建和初始化2.1数组的创建2.2数组的初始化错误的初始化 2.3 数组的类型 三. 一维数组的使用3.1数组的下标3.2数组元素的打印3.2数组元素的输入 四. 一维数组在内存中的存储五. 二维数组的创建5.1二维数组的概念5.2如何创建二维数组 六.二维数…

【嵌入式学习】QT-Day4-Qt基础

简单实现闹钟播报&#xff0c;设置时间&#xff0c;当系统时间与设置时间相同时播报语音5次&#xff0c;然后停止。如果设置时间小于当前系统时间&#xff0c;则弹出消息提示框&#xff0c;并清空输入框。 #include "my_clock.h" #include "ui_my_clock.h&quo…

批量处理图片,像素随心所欲,创意无限释放!

在数字化时代&#xff0c;图片批量处理已成为设计、摄影、电商等多个领域不可或缺的一部分。然而&#xff0c;传统的图片批量处理方式往往效率低下&#xff0c;难以满足现代人对高效和精准的需求。现在&#xff0c;我们为您带来了一款一键图片批量处理工具&#xff0c;让您自由…

【Vue】更换浏览器默认 logo

更换浏览器默认logo为自定义图片 一. 浏览器默认 logo二. 替换为自定义logo三. 步骤3.1 转换大小3.1.1 查看图片尺寸3.1.2 修改尺寸&#xff08;为32px 32px&#xff09; 3.2 替换成功 一. 浏览器默认 logo 二. 替换为自定义logo 三. 步骤 3.1 转换大小 将自定义 logo 转为323…

docker搭建zookeeper集群

文章目录 1. 集群搭建2. Leader选举3. Zookeeper集群角色 1. 集群搭建 这里我们使用docker-compose 搭建伪集群 version: 3.1 services:zoo1:image: zookeeperrestart: alwayscontainer_name: zoo1ports:- 2181:2181volumes:- /home/zk/zoo1/data:/data- /home/zk/zoo1/datal…

React富文本编辑器开发(二)

我们接着上一节的示例内容&#xff0c;现在有如下需求&#xff0c;我们希望当我们按下某个按键时编辑器有所反应。这就需要我们对编辑器添加事件功能onKeyDown, 我们给 Editor添加事件&#xff1a; SDocor.jsx import { useState } from react; import { createEditor } from…

协方差矩阵计算

文章目录 协方差矩阵计算原理python实现 协方差矩阵 协方差矩阵反映了两个随机变量变化时是同向还是反向的&#xff08;相关性&#xff09;。 如果协方差>0&#xff0c;则说明这两个随机变量同向变化。 协方差矩阵<0&#xff0c;则说明是反向变化。 协方差矩阵0&#xf…

【LeetCode】347.前 K 个高频元素

今日学习的文章链接和视频链接 leetcode题目地址&#xff1a;347.前 K 个高频元素 代码随想录题解地址&#xff1a;代码随想录 题目简介 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 看到题目的第一…

掌握 MySQL 的数据类型

知道了表是由不同数据类型的列组成的&#xff0c;然后填充了一行一行的数据。 当我们要创建表的时候&#xff0c;就要根据业务需求&#xff0c;选择合适的数据类型。比如在实战项目中&#xff0c;文章表就是由下面这些不同数据类型的字段定义的。 目前用到了 bigint、tinyint、…

vue3+ts+vite使用mock数据

安装以下命令 npm i vite-plugin-mock --save-dev npm i mockjs --save-dev 在根路径下创建mock文件夹 mock\user.ts const menuList [{path: /system,component: Layout,name: system,meta: {title: 系统管理,icon: Setting,roles: [sys:manage]},children: [{path: /depar…

Java中使用poi+poi-tl实现根据模板导出word文档

场景 若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出: 若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出_若依导出前端获得到后端的execl流之后怎么操作-CSDN博客 上面讲的是Excel的导出&#xff0c;如果是需要根据w…

即插即用篇 | YOLOv8 引入 MHSA 注意力机制 | 《Bottleneck Transformers for Visual Recognition》

论文名称:《Bottleneck Transformers for Visual Recognition》 论文地址:https://arxiv.org/pdf/2101.11605.pdf 文章目录 1 原理2 源代码3 添加方式4 模型 yaml 文件template-backbone.yamltemplate-small.yamltemplate-large.yamltemplate-neck.yaml

Mac 重新安装系统

Mac 重新安装系统 使用可引导安装器重新安装&#xff08;可用于安装非最新的 Mac OS&#xff0c;系统降级&#xff0c;需要清除所有数据&#xff09; 插入制作好的可引导安装器&#xff08;U盘或者移动固态硬盘&#xff09;&#xff0c;如何制作可引导安装器将 Mac 关机将 Ma…

排序——冒泡排序

冒泡排序的基本思想 从前往后&#xff08;或从后往前&#xff09;两两比较相邻元素的值&#xff0c;若为逆序&#xff08;即 A [ i − 1 ] < A [ i ] A\left [ i-1\right ]<A\left [ i\right ] A[i−1]<A[i]&#xff09;&#xff0c;则交换它们&#xff0c;直到序列…

标准PoE交换机、非标准PoE交换机和非PoE交换机三者到底有何区别?

目录 前言&#xff1a; 一、标准PoE交换机 1.1 工作原理 1.2 应用场景 1、视频监控 2、无线接入点 3、IP电话 1.3 优势 1、简化布线 2、简化安装 3、提高可靠性 二、非标准PoE交换机 2.1 工作原理 2.2 应用场景 1、无线路由器 2、IP电话 3、数据中心 2.3 优势…

c++面试三 -- 智能指针--7000字

一、智能指针 C 中的智能指针是一种用于管理动态分配的内存的对象&#xff0c;它们可以自动进行内存管理&#xff0c;避免内存泄漏和悬挂指针等问题。 1. 悬挂指针 悬挂指针&#xff08;dangling pointer&#xff09;是指在程序中仍然存在但已经不再指向有效内存地址的指针。悬…

IO多路复用 poll模型

poll 是一种在 Linux 系统中进行 I/O 多路复用的模型&#xff0c;它与 select 类似&#xff0c;但具有一些不同之处。poll 允许监视的文件描述符数量不受限制&#xff0c;而不像 select 有一定的限制。 基本概念&#xff1a; poll 函数&#xff1a; 通过 poll 函数&#xff0c…

队列的结构概念和实现

文章目录 一、队列的结构和概念二、队列的实现三、队列的实现函数四、队列的思维导图 一、队列的结构和概念 什么是队列&#xff1f; 队列就是只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出 如上图所示&#x…