指针详解(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.mzph.cn/news/665872.shtml

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

相关文章

React Hooks 学习笔记

1.useState&#xff08;&#xff09; 实现对页面数据的存储&#xff0c;当数据改变时候&#xff0c;自动触发render函数 2.useRef 用来解决两个问题&#xff1a; 1).是获取DOM元素或子组件的实例对象 2).存储渲染周期之间共享的数据 3.useEffect 4.useLayoutEffect 5…

Javaweb之SpringBootWeb案例之 @ConfigurationProperties的详细解析

4.3 ConfigurationProperties 讲解完了yml配置文件之后&#xff0c;最后再来介绍一个注解ConfigurationProperties。在介绍注解之前&#xff0c;我们先来看一个场景&#xff0c;分析下代码当中可能存在的问题&#xff1a; 我们在application.properties或者application.yml中配…

图论练习2

内容&#xff1a;路径计数DP&#xff0c;差分约束 最短路计数 题目大意 给一个个点条边的无向无权图&#xff0c;问从出发到其他每个点的最短路有多少条有自环和重边&#xff0c;对答案 解题思路 设边权为1&#xff0c;跑最短路 表示的路径数自环和重边不影…

WPS Office18.7软件日常更新

【应用名称】&#xff1a;WPS Office 【适用平台】&#xff1a;#Android 【软件标签】&#xff1a;#WPS 【应用版本】&#xff1a;18.6.1➡18.7 【应用大小】&#xff1a;160MB 【软件说明】&#xff1a;软件日常更新。WPS Office是使用人数最多的移动办公软件。独有手机阅读模…

正点原子--STM32定时器学习笔记(1)

这部分是笔者对基本定时器的理论知识进行学习与总结&#xff01;&#xff0c;主要记录自己在学习过程中遇到的重难点&#xff0c;其他一些基础点就一笔带过了&#xff01; 1. 定时器概述 1.1 软件定时原理 使用纯软件&#xff08;CPU死等&#xff09;的方式实现定时&#xf…

机器学习_15_贝叶斯算法

文章目录 1 贝叶斯定理相关公式2 朴素贝叶斯算法2.1 朴素贝叶斯算法推导2.2 朴素贝叶斯算法流程 3 高斯朴素贝叶斯4 伯努利朴素贝叶斯5 多项式朴素贝叶斯6 贝叶斯网络6.1 最简单的一个贝叶斯网络6.2 全连接贝叶斯网络6.3 “正常”贝叶斯网络6.4 实际贝叶斯网络&#xff1a;判断…

算法学习——华为机考题库5(HJ31 - HJ35)

算法学习——华为机考题库5&#xff08;HJ31 - HJ35&#xff09; HJ31 单词倒排 描述 对字符串中的所有单词进行倒排。 说明&#xff1a; 1、构成单词的字符只有26个大写或小写英文字母&#xff1b; 2、非构成单词的字符均视为单词间隔符&#xff1b; 3、要求倒排后的单…

LeAPI 后端接口开发 - 发布、下线接口

一、上线接口&#xff08;仅管理员&#xff09; 1. 校验请求参数 2. 判断&#xff08;测试&#xff09;接口是否可以调用 引入调用接口的客户端&#xff08;自己写的 SDK&#xff09;注入客户端实例调用接口 3. 修改数据库中接口的状态 /*** 上线&#xff08;发布&#xff…

爬虫(二)

1.同步获取短视频 1.只要播放地址对Json数据解析&#xff0c;先把列表找出&#xff1a; 2.只想要所有的播放地址&#xff0c;通过列表表达式循环遍历这个列表拿到每个对象&#xff0c;再从一个个对象里面找到Video,再从Video里面找到播放地址(play_addr),再从播放地址找到播放…

动态内存管理 智能指针 shared_ptr、unique_ptr、weak_ptr + 定制删除器

动态内存管理常出现的两种问题&#xff1a; 1.忘记释放内存,造成内存泄漏 2.这块内存还有其他指针指向的情况下&#xff0c;就释放了它&#xff0c;会产生引用非法内存的指针&#xff0c;例如 如果类中有属性指向堆区&#xff0c;做赋值操作时会出现浅拷贝的问题 内存泄漏分…

在jetbrains IDEA/Pycharm/Android Studio中安装官方rust插件,开始rust编程

在idea插件市场搜索rust&#xff1a;JetBrains Marketplace &#xff0c;就可以找到rust插件&#xff1a; jetbrains官方rust插件地址&#xff1a;[Deprecated] Rust - IntelliJ IDEs Plugin | Marketplace 直接在idea中搜索rust好像是搜不到的&#xff1a; 需要在这个插件市场…

【数据结构】二叉树链式结构的实现

简单不先于复杂&#xff0c;而是在复杂之后。 文章目录 1. 二叉树链式结构的实现1.1 前置说明1.2 二叉树的遍历1.2.1 前序、中序以及后序遍历1.2.2 层序遍历 1.3 节点个数以及高度等1.4 二叉树基础oj练习1.5 二叉树的创建和销毁 1. 二叉树链式结构的实现 1.1 前置说明 在学习二…

Cambalache in Ubuntu

文章目录 前言apt install flatpak这很ok后记 前言 gtkmm4相比gtkmm3有很多改革, 代码也干净了许多, 但在windows上开发 有ui设计器那自然方便很多, 但glade又不支持gtkmm4, windows上装Cambalache很是困难. 各种问题都找不到答案.于是 我用VMware虚拟机Ubuntu20.xx安装Cambal…

macOS虚拟机安装全过程的详细教程

macOS虚拟机安装全过程的详细教程 一、安装虚拟机软件 选择软件&#xff1a;首先&#xff0c;你需要选择一个适合macOS的虚拟机软件。在本教程中&#xff0c;我们以VirtualBox为例。下载与安装&#xff1a;访问VirtualBox的官网&#xff0c;下载适用于macOS的安装包。运行安装…

高校建设AI算力平台方案探索

近年来&#xff0c;人工智能行业发展迅速&#xff0c;在自动驾驶、金融、医疗、教育等行业广泛应用。尤其是ChatGPT发布以后更是掀起了生成式AI的热潮&#xff0c;国内各大互联网厂商也相继发布自己的AI大模型。这也造成了大量的AI人才缺口&#xff0c;同时促进了高校的AI专业建…

CSP-202305-2-矩阵运算

CSP-202305-2-矩阵运算&#xff1a;题目链接 知识点一&#xff1a;申请矩阵 1.动态分配 // 申请 int** dynamicArray new int*[rows]; for (int i 0; i < rows; i) {dynamicArray[i] new int[cols]; }// 释放 for (int i 0; i < rows; i) {delete[] dynamicArray[…

解决浏览器端 globalThis is not defined 报错

解决浏览器端 globalThis is not defined 报错 前言解决办法&#xff1a; 前言 在使用低版本火狐浏览器出现报错globalThis is not defined 解决办法&#xff1a; 在vue的index.html 中添加 this.globalThis || (this.globalThis this) <head><script>this.g…

Lambda表达式(匿名函数)

C11中引入了lambda表达式&#xff0c;定义匿名的内联函数。 我们可以直接原地定义函数而不用再跑到外面去定义函数跳来跳去。 同时在stl的排序上也有作用。 [capture] (parameters) mutable ->return-type {statement}下面逐一介绍各个参数的含义. [capture] : 捕获&#…

Java语法学习线程基础

Java语法学习线程基础 大纲 概念创建线程线程终止常用方法用户线程和守护线程线程的七大状态线程的同步互斥锁线程死锁释放锁 具体案例 1.概念 2. 创建线程 第一种&#xff1a; class Cat extends Thread {int time 0;Overridepublic void run() {while (true) {System.o…

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器&#xff08;5、IO协程调度模块的设计&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务器&#xff08;1、…