C语言_指针(2)

1.指针与数组的关系

1.1 数组名

先看代码:

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);printf("arr = %p\n", arr);return 0;}

运行结果是这样的:
在这里插入图片描述
我们可以看到,数组名和数组首元素地址打印出的结果一模一样。其实,数组名就是数组首元素的地址。但有两个例外

  • sizeof(数组名)sizeof中单独放数组名,表示的是整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名:取出的是整个数组的地址。(整个数组的地址和数组首元素的地址是有区别的)。

除此之外,任何地方的数组名只表示首元素地址。

我猜,这个时候,有些朋友会运行下面的代码,想找出arr&arr的差异:

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);printf("arr = %p\n", arr);printf("&arr = %p\n", &arr);return 0;
}

运行出来三个一模一样的地址,这时,屏幕面前的朋友会质疑区分arr&arr的必要性。

其实他们的差异可以体现在指针的运算上,如arrarr+1相差4个字节,而&arr&arr+1相差40个字节。他们本身的大小不同,运算的跨度自然也相异。

有兴趣的朋友可以运行一下下方的代码:

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);printf("&arr[0]+1 = %p\n", &arr[0]+1);printf("arr = %p\n", arr);printf("arr+1 = %p\n", arr+1);printf("&arr = %p\n", &arr);printf("&arr+1 = %p\n", &arr+1);return 0;
}

1.2 使用指针访问数组

代码如下:

#include<stdio.h>int main(){int arr[10] = {0};int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);int *p = arr;for(i=0; i<sz; i++){scanf("%d",p+i);//可以写arr+i}for(i=0; i<sz ; i++){printf("%d",*(p+i));//可以写*(arr+i)}return 0;
}

这段代码中说明了指针访问数组的方式。
我们知道数组元素的访问是用arr[i]的,那arr已经赋值给p,用p[i]可以达到同样效果吗?

答案是可以,有以下代码:

#include <stdio.h>int main()
{int arr[10] = {0};int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);int* p = arr;for(i=0; i<sz; i++){scanf("%d", p+i);}for(i=0; i<sz; i++){printf("%d ", p[i]);}return 0;
}

运行结果与第一段代码别无二致,所以我们可以知道本质上p[i]等价于*(p+i),同理arr[i]等价于*(arr+i)

数组元素的访问在编译器处理时,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

1.3 一维数组传参的本质

先看代码:

#include<stdio.h>void test(int arr[]){int sz2 = sizeof(arr)/sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}int main(){int arr[10] = {1,2,3,4,5,6,7,8,9,10};int sz1 = sizeof(arr)/sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}

运行结果如下:

在这里插入图片描述
我们可以看到,在函数内部是没有正确获得数组的元素个数的。

我们知道,数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。

所以函数形参理论上应该用指针变量来接收首元素的地址。正是因为函数的参数部分的本质是指针变量,所以当首元素地址传入函数时,数组名变成了一个彻彻底底的地址,sizeof(arr)返回的是一个地址的大小。

注:
一维数组传参,函数形参的部分既可以写成数组的形式,也可以写成指针的形式。如下函数代码段:

void test(int* arr){//指针形式的形参printf("%d",sizeof(arr));//计算的是一个指针变量的大小
}

1.4 指针数组

指针数组,听到这个名字,有些朋友就会郁闷了,指针数组是指针还是数组?

我们可以类比一下,整形数组,即存放整形的数组,既然这样,那指针数组不就是存放指针的数组咯。

1.4.1 指针数组的定义

我们在定义整形数组和字符数组时,分别用了intchar,所以数组的内容是什么类型就用什么类型来定义。

如定义一个字符指针数组整形指针数组

char* arr[5] = {NULL};
int* arr1[6] = {NULL};

1.4.2 指针数组模拟二维数组

代码实现如下:

#include<stdio.h>int main(){int arr1[] = {1,2,3,4,5};int arr2[] = {2,3,4,5,6};int arr3[] = {3,4,5,6,7};int* p[3] = {arr1,arr2,arr3};int i = 0;int j = 0;for(i=0;i<3;i++){for(j=0;j<5;j++){printf("%d\t",p[i][j]);//等同于 *(*(p+1)+j)}}return 0;
}

上方代码模拟出了二维数组的效果,但与二维数组有所差异,它的行与行之间不是连续的

1.5 数组指针变量

1.5.1 对数组指针变量形式的理解

数组指针变量,顾名思义,是用来存放数组地址的。形式如下:

int (*p)[10]

解释:p先与*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整形的数组。所以p是一个指针,指向一个数组,叫数组指针。

数组指针的初始化方式如下:

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


[ ]的优先级高于 * 号,所以必须加上()保证p先和 * 结合。

1.5.2 二维数组传参的本质

二维数组传参代码如下:

#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+1)+j));}printf("\n");}
}

2. 指针与函数的关系

2.1 函数指针变量

函数指针变量是用来存放函数地址的,函数地址可以通过函数名和&函数名的方式获得

函数指针类型解析如下:
在这里插入图片描述

2.1.1 函数指针变量的创建

函数指针变量的创建有两种情况,代码如下:

无参数时

void test(){printf("hello world!!");
}void (*pf1)() = test;
void (*pf2)() = &test;

有参数时

int Add(int x, int y){return x+y;
}void (*pf3)(int, int) = Add;//x和y写上或省略都可以
void (*pf4)(int x,int y) = &Add;

2.1.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;
}

输出结果:
在这里插入图片描述
可以通过函数指针调用指针指向的函数。

分享俩个有趣的代码:
代码一:

(*(void (*)())0)();

代码二:

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

这两段代码都出自《C陷阱与缺陷》

2.2 函数指针数组

函数指针数组的定义形式如下:

int (*parr[2])();

parr先和 [ ] 结合,说明 parr 是数组,数组的内容是 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;

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

#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(imput = 0){printf("退出计算器\n")}else{printf("输入有误\n");}}while(input);return 0;
}

3. 字符指针变量

字符指针char* 有两种使用方式。
一般使用:

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

还有一种使用方式如下:

int main(){const char* pstr = "hello";return 0;
}

代码 const char* pstr = “hello”;的本质是把字符串hello首字符的地址放到了pstr中。

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

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

这段代码中str3str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但要是用相同的常量字符串取初始化两个不同的字符数组时,就会开辟出不同的内存块。所以str1str2不同。

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

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

相关文章

云计算Openstack

OpenStack是一个开源的云计算管理平台项目&#xff0c;由美国国家航空航天局&#xff08;NASA&#xff09;和Rackspace公司合作研发并发起&#xff0c;以Apache许可证授权。该项目旨在为公共及私有云的建设与管理提供软件支持&#xff0c;通过一系列相互协作的组件实现云计算服…

【Java数据结构】--- 优先级队列

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励下吧~ 前言 继续来看这张图&#xff0c;我…

php thinkphp 小程序发送订阅模板消息通知

小程序需要在我的模板中先选用模板 小程序需要先订阅模板 wx.requestSubscribeMessage({tmplIds: ["XII_0By8D9WabnUjVPB_8S1itsm2d4_xxx"],success:

【每天学个新注解】Day 5 Lombok注解简解(四)—@Cleanup

NonNull 自动管理输入输出流等各种需要释放的资源&#xff0c;确保安全地调用close方法。 1、如何使用 声明的资源前加上Cleanup。释放资源的方法名不是close&#xff0c;也可以指定要调用的方法名。 2、代码示例 例1&#xff1a;取自Lombok官网&#xff0c;加在有close的…

记录linux环境下搭建本地MQTT服务器实现mqtt的ssl加密通讯

1、ubuntu安装mosquitto sudo apt-get update//安装服务端 sudo apt-get install mosquitto//安装客户端 sudo apt-get install mosquitto-clients 2、安装openssl 3、mqtts/tls加密传输 mosquitto原生支持了TLS加密&#xff0c;TLS&#xff08;传输层安全&#xff09;是SSL&…

如何设置显卡驱动以实现深度学习推理的最佳性能

快速解决深度学习推理过程cuda或tensorRT推理速度变慢的办法&#xff0c;记录一下方便自己以后查看。 一、显卡性能设置&#xff1a; 低延时模式——超高、最大帧速度——1000每秒帧数、电源管理模式——最高性能优先 二、cmd终端输入nvidia-smi -q -d SUPPORTED_ClOCKS 三…

FortiWLC 控制器系统恢复操作介绍

简介 对于 FortiWLC 控制器在有些实际操作过程中,会由于某些原因导致升级失败, 无法升级,或是系统文件错乱等情况, 对于这些问题,我们可以通过重新恢复控制 器系统来解决这些问题. 本文详细说明恢复控制器系统的操作步骤. 注意: 本操作需要通过串口登录到控制器设备上,另外在控…

树木检测系统源码分享

树木检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

什么是电商云手机?可以用来干什么?

随着电商行业的迅速发展&#xff0c;云手机作为一种创新工具正逐渐进入出海电商领域。专为外贸市场量身定制的出海电商云手机&#xff0c;已经成为许多外贸企业和出海电商卖家的必备。本文将详细介绍电商云手机是什么以及可以用来做什么。 与国内云手机偏向于游戏场景不同&…

计算机前沿技术-人工智能算法-生成对抗网络-算法原理及应用实践

计算机前沿技术-人工智能算法-生成对抗网络-算法原理及应用实践 1. 什么是生成对抗网络&#xff1f; 生成对抗网络&#xff08;Generative Adversarial Networks&#xff0c;简称GANs&#xff09;是由Ian Goodfellow等人在2014年提出的一种深度学习模型&#xff0c;主要用于数…

分布式算法

分布式场景下的核心问题 分布式场景下困扰我们的3个核心问题&#xff08;CAP&#xff09;&#xff1a;一致性、可用性、分区容错性。 1、一致性&#xff08;Consistency&#xff09;&#xff1a;无论服务如何拆分&#xff0c;所有实例节点同一时间看到是相同的数据。 2、可用性…

【C++笔试强训】

​ 学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 目录 循环渐进Forward-CSDN博客 第一题&#xff1a;除2&#xff01; 第二题&#xff1a;dd爱框框 第三题&#xff1a;简写单词 第一题&#xff1a;除2&#xff01; 牛客网…

ROS理论与实践学习笔记——1 Ros概述与环境搭建

1、ROS概述 ROS全称Robot Operating System(机器人操作系统)&#xff1b; “ROS Plumbing Tools Capabilities Ecosystem”&#xff0c;即ROS是通讯机制、工具软件包、机器人高层技能以及机器人生态系统的集合体。 2、ROS安装 2.1 安装配置虚拟机软件 VMware或virtualbox…

【项目实战】如何在项目中基于 Spring Boot Starter 开发简单的 SDK

什么是SDK 通常在分布式项目中&#xff0c;类和方法是不能跨模块使用的。为了方便开发者的调用&#xff0c;我们需要开发一个简单易用的SDK&#xff0c;使开发者只需关注调用哪些接口、传递哪些参数&#xff0c;就像调用自己编写的代码一样简单。实际上&#xff0c;RPC(远程过…

从碎片到整合:EasyCVR平台如何重塑城市感知系统的视频数据生态

随着城市化进程的加速&#xff0c;城市感知系统作为智慧城市的重要组成部分&#xff0c;正逐步成为提升城市管理效率、保障公共安全、优化资源配置的关键手段。EasyCVR视频汇聚融合平台&#xff0c;凭借其强大的数据整合、智能分析与远程监控能力&#xff0c;在城市感知系统中扮…

short-link笔记

1.Accessors(chain true) (见于Result类的注解) 不写默认为false&#xff0c;当该值为 true 时&#xff0c;对应字段的 setter 方法调用后&#xff0c;会返回当前对象。 -->可用于链式编程 参:Accessors 注解详解-CSDN博客 2.关键信息脱敏 利用将class通过jackon序列化为…

分布式计算框架

进入Scala模式 终端里输入Scala 创建一个新的Scala文件 vim 文件名.scala 复制粘贴代码 ctrlshift c/v 使用vim 先进入插入模式&#xff0c;可以通过按i键来实现&#xff0c;然后粘贴代码&#xff0c;完成后按Esc键退出插入模式&#xff0c;保存并退出可以通过输入:wq然后按…

【中台设计】数字中台,大数据中台解决方案,中台建设指南(资料Word分享)

1. 中台概念 2. 推动企业组织模式演进 3. 建设方法 4 .中台内容 5. 数据安全体系 中台内容围绕数据中台建设评估、整体框架、数据采集&#xff0c;结构化、半结构化、非结构化的数据采集&#xff0c;数据计算能力、存储计算引擎、数据架构、数据挖掘、各种不同数据层建设、模型…

拒绝信息泄露!VMD滚动分解 + Informer-BiLSTM并行预测模型

前言 在时间序列预测任务中&#xff0c;像 EMD&#xff08;经验模态分解&#xff09;、CEEMDAN&#xff08;完全集合经验模态分解&#xff09;、VMD&#xff08;变分模态分解&#xff09; 等分解算法的使用有可能引入信息泄露&#xff0c;具体情况取决于这些方法的应用方式。信…

Vue+Tui-image-editor实现图片编辑(涂鸦,裁剪,标注,旋转,滤镜)

目录 前言 效果展示 涂鸦 裁剪 标注 旋转 滤镜 安装 使用 中文化自定义样式按钮优化 参考链接 前言 需求&#xff1a;对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本等。 效果展示 涂鸦 裁剪 标注 旋转 滤镜 安装 npm i tui-image-editor // or yarn add tui-image…