指针详解(3)

各位少年,大家好,我是博主那一脸阳光,今天介绍 二级指针 指针数组,还有个指针数组模拟二维数组。
前言:在浩瀚的C语言编程宇宙中,指针犹如一把打开内存世界大门的独特钥匙,它不仅是理解程序运行机制的关键要素,也是提升代码执行效率的重要工具。如同寻宝图上的经纬坐标,指针精准地指向了内存中的特定位置,使得我们能够直接操作数据的核心。

想象一下,计算机内存就像一座巨大的储物仓库,每个存储单元都承载着独一无二的信息宝藏。而指针,则如同仓库管理员手中的定位器,通过它可以迅速找到并访问任何一个角落的数据。当我们改变指针所指向的位置时,就好比调整了探索目标,实现对不同信息资源的快速定位和灵活调动。

因此,在深入学习C语言的过程中,掌握指针这一概念及其使用方法,就如同掌握了驾驭数据流动的秘密通道。它不仅有助于我们更深入地洞察程序运行的本质,还能使代码更为简洁高效,更具表现力。接下来,让我们一同踏上这段揭示指针奥秘的旅程,揭开其背后深藏的编程智慧与艺术。

二级指针的定义

int a10;
int*p=&a;
&p;
//p是指针变量,是一级指针
int **pp=&p;//int *是在说明,pp对象指向的对象的int*类型
//*说明pp是指针变量

这里的pp是二级指针,指针类型进行+1 -1的操作,执行解引用的权限。
注意这里的pp是另外开辟了一块空间。

int a = 10;int* p = &a;int**pp = &p;int*** ppp = &pp;return 0;
}

指针数组

我们类比一下
指针数组是指针还是数组呢?(数组中每个元素都是整形类型)
整形数组-存放整形数据的数组(数组中每个元素都是字符类型)
指针数组-存放指针的数组(数组中每个元素都是指针类型)

int arr[10];//整形数组 
char ch[5];//字符数组
double data[4];//多浮点型数组

希望有一个数组,数组有四个元素,每个元素是整形指针

int arr[4];

每个元素是整形指针,所以指针数组。

指针模拟二维数组

模拟二维数组的效果,但不是二维数组!
二维数组其实每一行都是一维数组。

#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 *arr[3] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0;
}

上面指针的方式存储了三个数组的地址,然后进行遍历,最后进行每一位,最后执行了二维数组的打印。

字符串指针类型

char ch='w';
char*pc=&ch;   //pc是字符指针
char *p"abcdef"://叫做常量字符串
printf("%c\n","abcdef"[3]);

字符指针的类型是可以赋值的,但非传统赋值,
不是把字符串abcdef\0存放在p中,
而是把第一个字符的地址存放在p中
意思就是说p存储a的地址,你可以把abcdef这个看做出一个数组。

1你可以把字符串想象为一个字符数组,但是这个数组是不能修改的
2当常量字符串出现在表达式中的时候,它的值是第一个字符的地址。

#include<stdio.h>
int main()
{char* p = "abcdef";printf("%c\n", p[3]);p[3] = 'q';return 0;
}

此时注意p没办法间接修改它,因为p的字符串是常量,所以建议在char前面加const以避免误导。

剑指offer面试题

今天来介绍《剑指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和str2不同这是为什么呢?
这就好比两个一模一样的背包,其中有一个可能不是你的,所以地址是不相同的。
str3和str4常量字符串,不可能修改了,就好比你和你女朋友的包,你都得背一样。
在这里插入图片描述
因为str3和srt4都是常量,无法更改,所以计算机偷懒了,只开辟了一块空间。

数组指针变量

指针数组:是数组,是存放指针的数组!
哪数组指针是什么呢?
我们类比一下:
整形指针:指向整形的指针。
字符指针:指向字符的指针。
浮点型指针:指向浮点型的指针。

数组指针 指向数组的指针!

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

整形指针变量存放的就是整形的地址
字符指针变量存放的就是字符的地址
数组指针变量存放的应该是数组的地址

 int*pa)[10];

数组指针变量存放数组的地址,里面每个类型都是int类型。

 int arr[10]={1,2,3,4,5,6,7,8,9,10};int(*parr)[10]=&arr;

在这里插入图片描述

解释:先和结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以
p是⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[]的优先级要⾼于
号的,所以必须加上()来保证p先和*结合。

我们调试也能看到 &arr 和 p 的类型是完全⼀致的。
数组指针类型解析:

int (*p) [10] = &arr;| | || | || | p指向数组的元素个数| p是数组指针变量名p指向的数组的元素类型
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int(*p)[10]=&arr;
printf("%p\n",arr);
printf("%p\n,&arr+1);printf("%p\n",p);
printf("%p\n,p+1);

还记得我们之前说过这个代码,如果打印整个数组的地址+1跳过整个数组,
如果取地址数组名打印跳过整个数组。那好我们看打印结果
在这里插入图片描述
接下来介绍数组指针是怎么打印的呢?

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

在这里插入图片描述
因为数组指针,是数组的地址+1跳过了整个数组,但是解决办法还是有的。
p==&arr
因为数组指针,数组本来就是地址所以p等于取地址arr。

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

这样就可以是数组指针每个元素了。

二维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

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

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

函数指针变量

函数指针顾名思义函数的指针的,函数的地址,哪问题来了,它有什么用呢?

数组名- -数组首元素的地址
&数组名–整个数组的地址。
函数名:函数的地址
&函数名:函数的地址

函数指针的写的方法和数组指针的创建方式非常的类似的

data type(*Pointer name)(Function Parameter type)

数据类型 指针名字 和指针类型组成的函数指针,
这里大家可能看不懂,下面我来分享给大家例子。

int(*pf)(int,int)=&Add;

pf就是函数指针变量。下面例子是函数指针基本的格式。这里必须加(),否则int先和*结合就是函数传参了。

int *pf=(int,int);.//这里不加()就变成函数传参 如果再加等于Add的话直接报错

函数的使用的例子

#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf)(int,int) = &Add;//pf就是函数指针变量//原来我们是不是这样写int ret=Add(35);printf("%d\n",ret);//8//新的写法int ret2=(*pf)(4,9);//pf解引用,然后传入两个值printf("%d\n",ret2);//13return 0;
}

接下来再引导出一个概念,函数名和&数组名是一样的,函数的地址都是一样的。

int*pf2)(int,int)=Add;
int ret=(*pf2)(5,6);
printf("%d\n",ret3);

新问题来了 add把哪个地址放到pf2里头,pf2也是地址呀,大家可能不理解,大家只要记住可以这样写就好。

int ret4=pf2(5,6);
printf("%d\n",ret4);
#include<stdio.h>
char* test(int a, char c)
{return NULL;
}
int main()
{pt = test;return 0;
}

接下来分享一道题,大家不要自定义函数,以及如何使用的NULL
大家想想它该怎么写成函数指针

char*(*pt)(int,char) = test;

既然上面是char了,那我们这块也要写成char才能对称。

C陷阱与缺陷

接下来分享两段有趣的代码均出自C陷阱和缺陷这本书中。

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

在这里插入图片描述
上面代码中红色代表括号,蓝色代表函数指针类型,
(int)0这叫什么意思呢?(void(*))0叫做强制转换了
把0强制转换成地址 然后解引用了,然后后面括号是 参数。

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

我们发现*signed没有阔括号再一起,因为优先级所以signal是函数名
还记得我们之前写函数指针,**都是和函数阔在一起的,所以叫做函数名
它的第一个参数是int 第二次参数是函数指针类型,函数返回的是函数指针类型

typedef关键字

typedef叫做类型的重定义 把一个复杂名字简单化,把int改成uint

typedef unsigned int uint
int main()
{
unsigne int num;
uint num2;
return 0}
typedef int*PArr_t)[10];

数组指针也可以重新命名,但必须在星号的右边```

pArr_t pa;
typedef int(*pf_t)(int,int);
typedef int(*pf2)(int,int);

好,我们把之前的代码也简化一下

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

今天就分享到这里,剩下今天给大家分享给大家指针的使用

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

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

相关文章

PXIe-5842第三代PXI矢量信号收发器简介

内容 简介​PXIe-5842 VST的主要特性PXI VST软件工具PXI VST应用结论下一步 简介 NI于2012年引入了矢量信号收发器(VST)的概念。VST将RF信号发生器、RF信号分析仪和功能强大的FPGA集成在单个PXI模块上。PXIe-5842 VST是首款提供30 MHz到26.5 GHz连续频率覆盖范围的VST。PXIe…

RT-Thread线程管理(使用篇)

layout: post title: “RT-Thread线程管理” date: 2024-1-26 15:39:08 0800 tags: RT-Thread 线程管理(使用篇) 之后会做源码分析 线程是任务的载体&#xff0c;是RTT中最基本的调度单位。 线程执行时的运行环境称为上下文&#xff0c;具体来说就是各个变量和数据&#xff0c…

【云原生】docker-compose单机容器集群编排工具

目录 一、docker-compose容器编排的简介 二、docker-compose的使用 1、docker-compose的安装 2、docker-compose的配置模板文件yaml文件的编写 &#xff08;1&#xff09;布尔值类型 &#xff08;2&#xff09;字符串类型 &#xff08;3&#xff09;一个key有多个值 &am…

Qt应用开发(安卓篇)——调用ioctl、socket等C函数

一、前言 在 Qt for Android 中没办法像在嵌入式linux中一样直接使用 ioctl 等底层函数&#xff0c;这是因为因为 Android 平台的安全性和权限限制。 在 Android 中&#xff0c;访问设备硬件和系统资源需要特定的权限&#xff0c;并且需要通过 Android 系统提供的 API 来进行。…

单片机学习笔记---定时器/计数器(简述版!)

目录 定时器的介绍 定时计数器的定时原理 定时计数器的内部结构 两种控制寄存器 &#xff08;1&#xff09;工作方式寄存器TMOD &#xff08;2&#xff09;控制寄存器TCON 定时计数器的工作方式 方式0 方式1 方式2 方式3 定时器的配置步骤 第一步&#xff0c;对…

Github 上传项目(个人令牌token)

1.点击 github头像 &#xff1a; setting -> Developer Settings -> Personal access tokens 2.在要上传的文件夹下运行以下命令&#xff1a; git init git commit -m "first commit" git branch -M main 利用以下命令模…

【01】Linux 基本操作指令

带⭐的为重要指令 &#x1f308; 01、ls 展示当前目录下所有文件&#x1f308; 02、pwd 显示用户当前所在路径&#x1f308; 03、cd 进入指定目录&#x1f308; 04、touch 新建文件&#x1f308; 05、tree 以树形结构展示所有文件⭐ 06、mkdir 新建目录⭐ 07、rmdir 删除目录⭐…

telnet笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、场景二、介绍1.测试端口2.访问百度3. 简单的爬虫 前言 最近telnet命令用的比较多&#xff0c;所以记录一下。 一、场景 ping应该是大家最常用的命令&…

Linux开发工具

前言&#xff1a;哈喽小伙伴们&#xff0c;经过前边的学习我们已经掌握了Linux的基本指令和权限&#xff0c;相信大家学完这些之后都会对Linux有一个更加深入的认识&#xff0c;但是Linux的学习可以说是从现在才刚刚开始。 这篇文章&#xff0c;我们将讲解若干个Linux的开发工…

事务、MVCC、锁

目录 事务MVCC锁 事务 四大特性&#xff1a;ACID 脏读&#xff1a;事务A读取到未提交事务B修改的数据 不可重复读&#xff1a;事务A修改了未提交事务B读取的数据 幻读&#xff1a;事务A增删了未提交事务B读取的数据 不可重复读与幻读都是读取的结果不同&#xff0c;前者侧重于…

数据结构+算法(第11篇) :无死角“盘”它!二分查找树

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

05 MyBatis之表关系的声明+事务+SqlSession三件套的作用域

MyBatis 支持一对一&#xff0c;一对多&#xff0c;多对多查询。XML 文件和注解都能实现关系的操作。多对多实质就是一对多 1. 表关系的维护 1.1 One一对一 一对一查询和多表(两表)查询很相似, 都能查询两表的全部属性 区别是一对一可以在对象中嵌套对象, 呈现包含关系; 多表…

在Linux中对Nginx进行安全加固

准备工作 在IP为x.x.x.x的服务器上安装nginx&#xff0c;确保Linux系统为nginx环境。 检查nginx是否配置nginx账号锁定策略 配置nginx账号锁定策略&#xff0c;降低被攻击概率。 第一步&#xff0c;查看nginx的锁定状态。 命令&#xff1a;passwd -S nginx 若结果出现“P…

安装配置sqoop

一、了解Sqoop 1、Sqoop产生的原因 A. 多数使用hadoop技术的处理大数据业务的企业,有大量的数据存储在关系型数据中。 B. 由于没有工具支持,对hadoop和关系型数据库之间数据传输是一个很困难的事。 以上是sqoop产生的主要原因,也因此Sqoop主要用于hadoop与关系型数据库之…

数据湖系列之二 | 打造无限扩展的云存储系统,元数据存储底座的设计和实践

海量数据对数据湖存储的扩展能力提出了极高的要求。元数据面作为云存储最核心、最底层的系统之一&#xff0c;直接决定了存储系统的扩展性。 本文作为数据湖系列的第二篇&#xff0c;将为大家揭开元数据面存储底座的秘密&#xff0c;如何设计能够支撑存储容量的“无限扩展”。…

监测Tomcat项目宕机重启脚本(Linux)

1.准备好写好的脚本 #!/bin/sh # 获取tomcat的PID TOMCAT_PID$(ps -ef | grep tomcat | grep -v tomcatMonitor |grep -v grep | awk {print $2}) # tomcat的启动文件位置 START_TOMCAT/mnt/tomcat/bin/startup.sh # 需要监测的一个GET请求地址 MONITOR_URLhttp://localhost:…

Vue3_基础使用_2

这节主要介绍&#xff1a;标签和组件的ref属性&#xff0c;父子组件间的传递值&#xff0c;ts的接口定义&#xff0c;vue3的生命周期 1.标签的ref属性。 1.1ref属性就是给标签打标识用的&#xff0c;相当于html的id&#xff0c;但是在vue3中用id可能会乱&#xff0c;下面是ref…

卸载Ubuntu双系统

卸载Ubuntu双系统 我们卸载Ubuntu双系统&#xff0c;可能出于以下原因&#xff1a; 1、Ubuntu系统内核损坏无法正常进入 2、Ubuntu系统分配空间不足&#xff0c;直接扩区较为复杂 3、以后不再使用Ubuntu&#xff0c;清理留出空间 123无论出于哪种原因&#xff0c;我们都是要…

Power BI案例-链接Mysql方法

Power BI案例-连锁Mysql 方法1-通过组件mysql-connector-net-8.3.0&#xff1a; 选择文件–获取数据–选择MySQL数据库–选择链接 提示无组件&#xff0c;选择了解详细情况 弹出浏览器&#xff0c;选择下载 不用登陆&#xff0c;可以直接下载 下载的组件如下&#xff1a…

2024年数学建模美赛 A~E 题目解析

2024美赛A题&#xff1a;资源可用性和性别比例 背景 尽管一些动物物种不属于通常的雄性或雌性&#xff0c;大多数物种在出生时要么显著地为雄性&#xff0c;要么为雌性。虽然许多物种在出生时表现出1:1的性别比&#xff0c;但其他物种则偏离了这个均衡的性别比例。这被称为性…