C语言深入理解指针(非常详细)(四)

news2024/12/24 3:34:20

目录

  • 字符指针变量
  • 数组指针变量
    • 数组指针变量是什么
    • 数组指针变量怎么初始化
  • 二维数组传参的本质
  • 函数指针变量
    • 函数指针变量的创建
    • 函数指针变量的使用
    • 代码
      • typedef关键字
  • 函数指针数组
  • 转移表

字符指针变量

字符指针在之前我们有提到过,(字符)(指针)前面的字符代表着存储的元素为字符类型,而指针则是表示这存储的方式。
写法为char
*
一般使用的方式如下:

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

还有一种使用方式如下:

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

值得注意的是:代码 const char pstr = “hello jack.”; 特别容易以为是把字符串 hello jack 放到字符指针 pstr 里了,但是本质是把字符串 hello jack. 首字符的地址放到了pstr中*
《剑指offer》中有一道和字符串有关的题如下:

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

在这里插入图片描述
这里的char str1[]和charstr2[]在创造时是单独开辟了一个储存空间,因此二者的储存地址是不同的
所以打印出来的是str1 and str2 are not same

char str3和charstr4中因为带有了指针,因此指针指向的字符串hello jack是已经单独创造了一个储存空间,而str3和str4则是存储字符串的储存地址,因此最后是str3 and str4 are same

数组指针变量

数组指针变量是什么

之前的是指针数组,现在的数组指针其实理解方式是相同的(数组)(指针),储存的元素是数组,而储存的方式就是通过指针储存数组的地址
我们举一些其他熟悉的例子:
整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针
浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针

下面代码哪个是数组指针变量?

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

p1[10]是指针数组
(p2)[10]是数组指针
我们发现这二者的差别就在于括号,指针数组是没有括号的,而数组指针是有括号的,我们可以这样理解
* || p [10]我们把指针数组分开,一个是指针
,一个是数组p[10],表示数组p[10]里面存放的是指针
p)|| [10]我们可以看到p和结合起来了,因此可以理解为将数组p的地址通过指针
的形式存放起来

数组指针变量怎么初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名

int arr[10]={0}&arr//取的是整个数组的地址

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

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

在这里插入图片描述
我们通过调试可以看到,p和&arr的内存地址是一样的
我们对int (*p)[10]=&arr进行解析
int(*p)[10] = &arr
| ___ | ___ | ___ |
| ___ | ___ | ___ |
| ___ | ___ | ___ 整个数组的地址
| ___ | ___ p指向数组的元素个数
| ___ p是数组指针的变量名
p指向的数组的元素类型

二维数组传参的本质

过去我们有一个二维数组的需要传参给一个函数的时候,我们是这样写的

#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);//arr传的是数组首元素地址
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;
}

可能会有人感到疑惑,为什么不是指针数组呢?我来谈谈我个人的理解:
我们再来回顾一下指针数组的含义:
指针数组是数组里面存放的指针也就是地址,好像和二维数组存放一维数组的地址是一样的.
但是当我们传递参数时我们发现传递的是指针的地址,而不是一维数组的地址,也就是说因为指针是存放的一维数组的地址,而如果我们用了指针数组的话,我们就是取了指针的地址

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

函数指针变量

函数指针变量的创建

什么是函数指针变量呢?
根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论:
函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的
那么函数是否有地址呢?
我们做个测试:

#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]进行解析
int(pf3)[int x,int y]
| ___ | ___ ___ |
| ___ | ___ ___ |
| ___ | ___ ___ |
| ___ | ___ ___ pf3指向函数的参数类型和个数的交代
| ___ 函数指针变量名
pf3指向函数的返回类型
int (
) (int x, int y) //pf3函数指针变量的类型

函数指针变量的使用

通过函数指针调用指针指向的函数

#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;
}

运行结果如下:
在这里插入图片描述

代码

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

代码1:我们分布解析:( * (void ( * )( ) ) 0 )()可以拆成(a)(),a=( void ( * ) ( ) )0
我们先分析a,首先我们看到void(
)()就应该想到这是一个返回类型为void的函数类型,并且括号将*号括了起来,说明这是函数指针,而那个0可能会把我们头都搞大,但是我们举一个例子

int a=0;
char b='A';
int c=a+(int)b;

这是强制类型转换,也就是说0被强制转换为一个返回值为void,参数为空的函数指针
而(*a)()又是一个函数指针,因此这段代码有两个函数指针

代码2:
void (signal(int , void()(int)))(int)我们依然将代码才分开:
void(a)(int), a=signal(int,b), b=void()(int)
显然这几个都是函数,第一个和第三个都为函数指针,第二个b为void返回类型的函数,然后a的参数包含了int 和void,返回类型为signal

typedef关键字

typedef是用来类型重命名的,可以将复杂的类型,简单化,也可以理解为设置一个代号,方便理解并且减少代码的长度
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:

typedef unsigned int uint;
//这里感觉和define有点类似
define a 8

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 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);//新的类型名必须在*的右边
要简化代码,可以这样写
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是数组,数组的内容是什么呢?
是 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;
}

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

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

相关文章

ABB MPRC086444-005数字输入模块

ABB MPRC086444-005 是一款数字输入模块&#xff0c;通常用于工业自动化和控制系统中&#xff0c;用于接收和处理数字信号。以下是这种类型的数字输入模块通常可能具备的一般功能和特点&#xff1a; 数字输入接口&#xff1a;MPRC086444-005 模块通常配备多个数字输入通道&…

Android扫码连接WIFI实现

0&#xff0c;目标 APP中实现扫WIFI分享码自动连接WIFI功能 1&#xff0c;前提条件 设备需要有个扫码器&#xff08;摄像头拍照识别也行&#xff09;&#xff0c;APP调用扫码器读取WIFI连接分享码。 2&#xff0c;增加权限 在AndroidManifest.xml中增加权限 <uses-permissi…

PyTorch深度学习实战(14)——类激活图

PyTorch深度学习实战&#xff08;14&#xff09;——类激活图 0. 前言1. 类激活图1.1 基本概念1.2 类激活图生成 2 数据集分析3 使用 PyTorch 生成 CAM小结系列链接 0. 前言 我们已经能够构建性能优异的神经网络模型&#xff0c;但对我们而言&#xff0c;卷积神经网络的决策过…

嵌入式Linux驱动开发(LCD屏幕专题)(一)

一、LCD简介 总的分辨率是 yres*xres。 1.1、像素颜色的表示 以下三种方式表示颜色 1.2、如何将颜色数据发送给屏幕 每个屏幕都有一个内存&#xff08;framebuffer&#xff09;如下图&#xff0c;内存中每块数据对用屏幕上的一个像素点&#xff0c;设置好LCD后&#xff…

西门子PLC与三菱PLC之间能否实现无线MODUBS通讯

对一个大型工厂&#xff0c;由于生产线的不断改造、新老流程的不断更新&#xff0c;这些PLC系统往往是由不同的制造商提供的。那么在智慧工厂的实现中&#xff0c;常会遇到不同品牌PLC之间需要进行相互通讯的情况。由于场地和生产能效的原因&#xff0c;在后期的系统改造中&…

提升效率:PostgreSQL准确且快速的数据对比方法

作为一款强大而广受欢迎的开源关系型数据库管理系统&#xff0c;PostgreSQL 在数据库领域拥有显著的市场份额。其出色的可扩展性、稳定性使其成为众多企业和项目的首选数据库。而在很多场景下&#xff08;开发|生产环境同步、备份恢复验证、数据迁移、数据合并等&#xff09;&a…

基于Python开发的学生信息管理系统控制台程序(源码+可执行程序exe文件+程序配置说明书+程序使用说明书)

一、项目简介 本项目是一套基于Python开发的学生信息管理系统控制台程序&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Python学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都…

《2023人工智能发展白皮书》发布(118页)

导读 本白皮书由七大部分组成。第一章人工智能产业链分析&#xff0c;描绘人工智能产业链全景图&#xff0c;并对产业链各环节进行深入分析;第二章人工智能行业环境&#xff0c;明确中国人工智能行业生命周期和竞争结构;第三章人工智能发展概况&#xff0c;阐述国内外人工智能…

4.3.3 【MySQL】Redundant行格式

现在我们把表demo 的行格式修改为 Redundant &#xff1a; 为了方便大家理解和节省篇幅&#xff0c;我们直接把表 demo 在Redundant 行格式下的两条记录的真实存储数据提供出来&#xff0c;之后我们着重分析两种行格式的不同即可。 下边我们从各个方面看一下 Redundant 行格式有…

十二、集合(4)

本章概要 集合 Set映射 Map队列 Queue 优先级队列 PriorityQueue 集合与迭代器 集合Set Set 不保存重复的元素。 如果试图将相同对象的多个实例添加到 Set 中&#xff0c;那么它会阻止这种重复行为。 Set 最常见的用途是测试归属性&#xff0c;可以很轻松地询问某个对象是否…

SQL Server 2012下载和安装配置详细教程手册

SQL Server 2012 下载和安装详细教程 目录 SQL Server 2012 下载和安装详细教程1、软件下载2、软件安装3、软件验证 1、软件下载 &#xff08;1&#xff09;官网地址 https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads &#xff08;可能不太行&#xff09; &a…

简明SQL别名指南:掌握AS实现列名更名

在 SQL 查询中&#xff0c;使用 {原始字段名} as {别名} 的语法来为查询结果的列赋予更直观的名称&#xff0c;以提高查询结果的可读性和可理解性。 以下是用到的表。 用AS更名 例如&#xff0c;查询表1的name字段&#xff0c;并将其更名为"名字"&#xff0c;同时查…

多语言开发(vant

参考&#xff1a;https://blog.csdn.net/qq_44649801/article/details/131878128?spm1001.2014.3001.5506 一、抛出字段对象A export default { } 二、引入汇总文件&#xff0c;&#xff08;主要的是 模块分割 汇总&#xff0c;对A 等的处理 export default { A&#xff0c;B,…

设计模式之桥接模式、组合模式与享元模式

目录 桥接模式 简介 优缺点 结构 实现 运用场景 组合模式 简介 优缺点 结构 实现 运用场景 享元模式 简介 优缺点 结构 实现 运用场景 桥接模式 简介 将抽象与实现分离&#xff0c;使它们可以独立变化。它是用组合关系代替继承关系来实现&#xff0c;从而降…

【rar密码】如何修改rar压缩包密码?

rar压缩包设置了密码&#xff0c;想要修改密码&#xff0c;有方法吗&#xff1f;目前看来&#xff0c;还是没有&#xff0c;我们只输入密码将压缩包中的文件解压出来。 然后将解压出来的文件再重新压缩&#xff0c;重新设置一个密码进行压缩即可达到修改密码的目的了 想要修改密…

leetcode 925. 长按键入

2023.9.7 我的基本思路是两数组字符逐一对比&#xff0c;遇到不同的字符&#xff0c;判断一下typed与上一字符是否相同&#xff0c;不相同返回false&#xff0c;相同则继续对比。 最后要分别判断name和typed分别先遍历完时的情况。直接看代码&#xff1a; class Solution { p…

B094-人力资源项目-微服务授权Oauth2

目录 背景OAUTH2总体流程Oauth2授权码模式oauth2的三方授权流程图案例演示代码讲解-整合oauth2springsecurityjwt先创建一个用于认证的服务增加AuthorizationServerConfig 模块总结认证服务整合zuul获取令牌加入网关后的变化代码详解测试 背景 微服务架构下应用散步在不同的服…

DC电源模块对电磁干扰的影响

BOSHIDA DC电源模块对电磁干扰的影响 DC电源模块是一种常用的电源转换设备&#xff0c;可以将交流电转换成直流电&#xff0c;并通过电路电子元件对电压、电流等参数进行调整&#xff0c;以满足外部设备对电源的需求。然而&#xff0c;由于DC电源模块自身的工作特性&#xff…

Deep Java Library(四)使用DJL Serving部署JAVA模型 For Windows

1.下载Windows版DJL Serving Windows版DJL Serving下载地址&#xff1a; https://publish.djl.ai/djl-serving/serving-0.23.0.zip 下载下来是一个zip压缩包&#xff0c;大约50M左右&#xff0c;目前最新版本为0.23.0 2.安装DJL Serving 解压serving-0.23.0.zip后目录如下 …

网络技术七:命令行基础

命令行操作基础 命令类型 常见设备管理命令 H3C路由交换产品连接方法 使用console线本地连接 协议Serial&#xff0c;接口com口&#xff0c;波特率9600 适用于设备的初次调试 使用Telnet远程访问 适用于设备上架配置好后的维护管理 使用SSH远程访问 数据传输过程加密&…