【C语言】指针进阶(一)

news2025/1/11 10:17:12

学好指针✊✊✊

还有,男孩子在外面要保护好自己


一、字符指针

字符也有地址,当然可以将其储存——

字符指针,是储存字符地址的指针

对于普通的单个字符:
char ch = 'a';char* pc1 = &ch;

这里的pc是单个变量ch‘(单个字符)的字符指针

而对于多个字符,也就是说字符串:
const char* pc2 = "zhuzhu";

有人会认为 是把 "zhuzhu" 整个放到了 指针pc2 里;而实际上是把 "zhuzhu" 的首字符地址存放到了 pc2 中

而打印时,printf("%s", pc2); 可以把整个字符串 "zhuzhu" 打印出来

也就是说,字符串能通过首字符的地址找到它接下来的字符(和字符数组类似)

要注意的点是:

如果只是把字符串的首元素地址存起来(如 char* pc2 = "zhuzhu";),并没有将其放入新空间(新地址)中,

字符首元素地址是在内存的文字常量区,并且不可通过改地址修改字符串内容(常量区内容改毛啊)

而如果将每个字符都存起来(如放入数组里),每个字符地址也发生变化(赋予新址),这时地址就在数组所在位置了,

相当于字符串全在数组中拷贝了一份,在下就能被修改了

面试可能会这样搞:

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

显然如果理解了刚刚注意的点,会知道字符串是存放到了两个数组str1 和 str2 里,两个数组的栈帧空间不同,字符首地址当然不同

而 str3 和 str4 是指针变量,字符的地址没有赋新址(字符串没有到新的空间里),"zhuzhu" 只在文字常量区(在文字常量区中只有一份),

是把 'zhuzhu' 中首字符 'z' 的地址放到了两个指针 str3 和 str4 中,str3 、str4 都指向文字常量区中的 'z'


二、指针数组

什么叫指针数组?是存放指针的数组

具体见二级指针最后一大点二级指针

首先一定的先了解普通数组:

如 int arr [10] = { 0 };

1、但是一定要分清楚数组名 arr 和 数组:

数组名 arr 表示数组首元素,类型为 int*

而数组意思是整个数组,类型为 int [10]

arr 也是数组名,int [10] 是该数组的类型,表示有10个int类型的元素

2、重点:一定得清楚 我们和数组最接近的是元素的地址,而不直接就是元素,
拿到具体元素要 解引用 一下

etc.

*(arr + i) arr是首元素的地址,这个地址指向的内容才是元素本身的内容

arr [ i ] 这样写与上面等价,相当于解引用操作

3、了解 &arr 和 arr 的区别

会发现数组名 arr 仅表示首元素地址,步长为1

而 &arr 表示取出了整个数组的地址,虽然地址的值和 arr 相同,但是其步长却是整个数组的长度

4、了解数组的权限问题

数组首元素地址加一的步长(一下跳过多少字节),关键在于数组的类型(也是数组类型的意义之一)

而 &arr 问题 和 数组权限问题 结合起来发现:

数组名 arr 与 取地址数组名 &arr 步长不一样 原因在于取出地址后,地址的类型不一样,而类型决定其权限,步长

如 int arr [10] = { 0 };

arr 类型为 int* 该类型步长为 4 个字节

&arr 类型为 int (*) [10] 该类型步长为 整个数组的长度

指针数组

指针数组:即存放指针的数组(地址的集合)

该数组可存放 普通变量的指针、数组首元素地址等,特别是存入数组的首元素地址

存入数组首元素地址的指针数组:

一定得清楚这个,可理清很多指针和指针类型,指针数组和二维数组的关系与联系

定义一个指针数组 相当于 定义多少个地址连续的二级指针(这里是把指针和数组名当做一种东西,只是更好理解,实际上且另论)

而二级指针指向的内容(一级指针)可能地址不在一块,只是把它们集合到了一起

arr0 这种一维数组,这里把 数组名arr0 首元素地址看做一级指针,指向的是其整型元素内容;而 arr 里的元素是数组名 arr0 ,arr1 等,arr0 如果看做一级指针,那么数组名 arr 是首元素 arr0 的地址,是一级指针的地址,看做一个二级指针

而数组 arr 里的元素地址是连续的,离我们最近的是它每个元素的地址,是一级指针的地址,把它看做二级指针,所以可以把指针数组 看做 一串连续的二级地址 (看做一串地址连续的二级指针)

所以现在可以很好地理解 *(*(arr + i) + j ) 了

第一次解引用: *(arr + i) 相当于二级指针解引用,得到 普通数组首元素地址,这个一级指针

第二次解引用: *(*(arr + i) + j ) 相当于 *(普通数组首元素地址 + i )

例如 i = 1, *(*(arr + 1) + j ) 相当于 *(arr1 + j) ,其实就是变成了普通的一维数组调用元素(也相当于 arr1 [ j ] )

同样发现 *(*(arr + i) + j ) 这样的写法和 二维数组 的调用 arr [ i ][ j ] 是一样的,

这里又有一个重要概念:对于二维数组的元素的理解

二维数组怎么来理解呢?

规定:二维数组中每一行表示它的一个元素,每一行是一个一维数组(我们说的普通数组)

二维数组的每个元素是一维数组,那对于一个 一维数组,我们一旦知道它的首元素地址就是到它的整个数组的元素,

所以我们在二维数组的每个元素中只要存每行的首元素地址就好了(一维数组的首元素地址)

所以二维数组每个元素的类型:int*

(回来看这里) 以 int arr[2][3] 为例

简单来说:二维数组元素内容是 一维数组首元素的地址(类型为 int *)

而离我们最近的是这个二维数组 元素的地址,这个地址代表整个一维数组,相当于一个数组的指针(地址),类型为 int (*) [3]

一定要分清楚二维数组中:

arr 类型为:int (*) [10];

其元素类型为:int *

而存放一维数组首元素地址的指针数组中:

arr 类型为:int **

其元素类型为:int*

类型决定了很多属性,但是类型还是得由具体内容来决定:

如二维数组是真正开辟了那么多元素的栈帧(行列齐全),每个元素表示一行,所以元素地址类型才为 int (*) [10];

而指针数组并没有开辟行列齐全的元素栈帧,只是开辟了行数那么多个元素的栈帧,每个元素斤代表一个指针,所以元素地址类型为:int **

(很多面试题必须要了解这些,才能够进行推理,这是基础)

而 二维数组 和 指针数组 的区别:

二维数组 的每个元素表示一个一维数组,元素的地址虽然是个二级地址(二级指针),但是类型为 int (*) [10] ,代表整个一维数组的地址

存一维数组的 指针数组 它是先开辟了连续的其元素栈帧,每个元素是一个二级地址(看做二级指针),类型为 int** ,步长与二维数组不一样

我们发现 二维数组 与 某些指针数组 有异曲同工之妙,甚至说是一样的

所以记住 指针数组的这个特殊情况不仅能帮我们更好理解指针数组,还能与二维数组联系起来,归纳总结

二维数组 和 指针数组一定要结合起来,去理解,去总结


三、数组指针

什么是数组指针?

意为整个数组的指针,这个指针指向了整个数组

一说到数组指针,得先了解 数组名 arr 和 取地址数组名 &arr 的区别

以 int arr[10] 为例

数组名 arr 是数组首元素地址如 0x12ff40, 类型为 int* 步长

而取地址数组名 &arr 虽然拿到的地址数值和数组首元素地址是一样的都是 0x12ff40,

但是类型不同,&arr 的地址的类型为 int (*) [10],这个地址代表整个数组,而不是一个元素

之前在指针数组中也知道:

arr 是数组首元素的地址,类型为 int*

而 &arr 取出来的是整个数组的地址,我们现在知道 一个元素地址的类型为 int* ,那么整个数组呢?

int* 后加上 [10] 就表示全部元素 即整个数组

注:一定要注意 * 和 [ ] 的优先级,[ ] 的优先级是“四大天王之一”,是 “F4” 之一,优先级很大的,* 你怎么敢和它单挑的啊

所以 int* 后要加 [ ] ,为了 * 不和 [ ] 结合,所以加上老大哥 ( )

即: int (*) [10] 并且方块 [ ] 里必须标明元素个数

这就是 整个数组的地址 的类型,表示整个数组

而现在的 数组指针,是个指针,里面存的地址 指向 整个数组

既然 数组指针 里面的地址 要指向整个数组,那么这个 地址 的类型就要表示整个数组

所以类型就如上面一样 : int (*) [10] 地址表示整个数组

那现在知道 指针 里存的 地址的类型,那么就可知道数组指针该怎么创建和初始化

如创建一个 int类型 变量,我们知道这个 变量里内容的类型,那就 int a = 0;

现创建一个要创建一个数组指针,我们知道这个数组指针里 地址的类型为 int (*) [10] ,

那么创建: int (*parr) [10] = &arr;

同样可以这样理解:&arr 的 地址类型为 int (*) [10] ,那么要一个相同类型的 (指针)变量来接收

所以 int (*parr) [10] = &arr;

因为变量名不可能说放到 (*) 外 int (*) parr[10] 这样,这样会使 parr 和 [10] 先结合,就变成 int* parr[10] 一个指针数组了


四、数组传参

(一)一维数组传参

(二)二维数组传参


五、函数指针

    • 函数名是该函数的地址

2、函数指针

函数名既然是地址,那么当然可以把它存起来,和指针联系起来,ga函数不就有又快乐多多?

so 格式呢?这里以 int test(int x, int y) 函数为例

函数有类型,int * pt 不合适吧,不知道你的参数类型,也得关照一下参数

那 : int * pt( int, int ) 这样写呢,我们知道 ( ) 也是 “F4” 四大天王之一,优先级大,* 怎么敢和 () 单挑呢

如果不把 * 和 test 括起来 test 会先和 () 结合,这样 int * test (int, int) 就相当于一个函数了,返回类型为 int*

那就: int (* pt) ( int, int ) ,这终于为正确格式, 函数指针 的创建 just 这样

其中 * 和 test 括起来表示 test 为指针,

而后面的 括号(带两个类型),表示这是一个 函数指针 ,函数有两个为int类型的参数,

最左边的 int 表示该函数 返回类型为int

函数指针的解引用:

如上的 int (* pt) ( int, int ) = test;

创建了一个函数指针 pt ,因为 test 本身是地址,调用函数时直接是 函数地址+(类型)

而现在创建一个了 函数指针,把函数地址给了 pt ,pt 这个指针变量内容就是 函数的地址

所以需要用函数时 ,可直接 pt(2, 3) ,就相当于 test(2, 3) ,或者说 (* pt) (2, 3)

提升

阅读下面两段代码

//代码1
( *( void (*)() )0 )();

//代码2
void ( * signal(int , void(*)(int)) )(int);

代码1:

代码1 中的 void (*) ( ) 就是我们刚刚学的 函数指针如何创建 的内容

如 int (* test) (int, int) 表示创建一个名为 test 函数指针,该指针的类型为 int (*) (int, int)

所以说 看到这里的 void (*) ( ) 我们知道这是一个 函数指针的类型

而 类型放在括号 () 里表示 强制类型转换 (如 (int)a ,强制类型转换)

上面即是强制类型转换 0 这个数(前置类型转换先和0结合,而不是 * )

强制类型转换后 剩下:(* 0) ( ) ,仔细想想这是啥,会发现是在 引用地址为 0 处的函数

而代码2:

代码2 中 signal( int , void(*)(int) ) 是一个函数,会返回一个值我们,设为 S

剩下: void ( * S ) (int); 我们知道 void ( * ) (int); 是一个 函数指针的类型

所以 * 表示 函数signal 返回的值 S 是一个指针,该函数指针的类型 为 void (*) (int)

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

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

相关文章

防火门监控系统在智能建筑消防的重要性及应用介绍

【摘要】&#xff1a; 安全、舒适的生活及办公环境是人们所追求的&#xff0c;因此&#xff0c;在建筑中各种智能化的设备及布控系统显得尤为重要。近年各种频发的高危火灾事件严重威胁到了国民的生命安全&#xff0c;所以火灾监控系统在建筑中的应用显得尤为重要。本文主要从智…

【涵子来信python大全】——第二季——opencv第一篇

各位亲爱的读者&#xff0c;博主&#xff1a; 首先恭喜大家&#xff0c;涵子来信已经到达第二季——2023年篇。今天&#xff0c;我们要步入机器学习的初级内容&#xff1a;python opencv图片&#xff01; 目录 一、提前准备 二、程序代码学习 2.1.如何读取图片 2.2.显示图…

MySQL架构,以及redo log、undo log和binlog的区别(六)

一、Mysql的基本架构图 二、连接器 连接器负责跟客户端建立连接&#xff0c;获取权限、维持和管理连接&#xff1a; 用户名密码验证&#xff1b;查询权限信息&#xff0c;分配对应的权限&#xff1b;可以使用show processlist查看现在的连接&#xff1b;如果太长时间没有动静…

【rt-thread网络】第0篇:使用paho-mqtt软件包连接腾讯云mqtt服务器

文章目录一、mqtt介绍二、paho mqtt介绍三、连接腾讯云的步骤3.1 在腾讯云控制台的IOT HUB创建产品和设备&#xff08;略&#xff09;3.2 根据产品信息填充MQTTClient的连接参数3.3 编译和下载到开发板&#xff08;略&#xff09;四、测试五、参考一、mqtt介绍 MQTT(消息队列遥…

【Linux操作系统】如何实现Linux中软件安装进度条?

文章目录一.回车与换行二.缓冲区问题三.倒计时小程序四.进度条小程序Linux下安装软件时&#xff0c;经常会看到类似上图的进度条&#xff0c;今天带大家用C语言来演示其原理&#xff01; 一.回车与换行 俗话&#xff1a;回车换行&#xff0c;实际是回车和换行的组合 回车是回…

助力工业物联网,工业大数据项目之数据采集

文章目录01&#xff1a;Sqoop命令回顾02&#xff1a;YARN资源调度及配置03&#xff1a;MR的Uber模式04&#xff1a;Sqoop采集数据格式问题05&#xff1a;问题解决&#xff1a;Avro格式06&#xff1a;Sqoop增量采集方案回顾01&#xff1a;Sqoop命令回顾 目标&#xff1a;掌握Sqo…

TypeScript环境搭建 下载/安装 ,编译运行的三种方式:tsc命令行/tsc-node库/webpack搭建环境

目录 什么是TypeScript? 首先来进行全局安装 &#xff1a; 编译运行 方式一&#xff1a;命令行(cmd终端)--->tsc命令行 1.将代码编译为JavaScript的代码&#xff0c;使用cmd终端或者命令行运行以下命令&#xff1a; 2.在浏览器或者Node环境下运行JavaScript代码 方式…

声纹识别与声源定位(一)

针对目前智能计算机及大规模数据的发展&#xff0c;依据大脑处理语音、图像数据方法的deep learning技术应运而生。deep learning技术是应用于音频信号识别&#xff0c;模仿大脑的语音信号学习、识别的模式。在音频信号处理的过程中&#xff0c;运用deep learning进行音频数据的…

极海APM32F072RB开发环境测试

极海APM32F072RB开发环境测试通过自制的开发板进行测试。 &#x1f3ac;基于STM32cubemx工程配置 Keil MDK编译 ST-LINK/V2烧录 &#x1f33b;基于APM32F0xx_SDK Keil MDK编译 ST-LINK/V2烧录 &#x1f33f;官方的SDK包下载地址&#xff1a;https://www.geehy.com/support/…

DMDW主备集群搭建备库先open引发的问题

一、问题描述及配置主备集群搭建成功后&#xff0c;主备库启动脚本中START_MODEmount&#xff0c;备库的lsn号大于等于备库&#xff0c;N_OPN打开次数主库大于备库。假如搭建主备集群后&#xff0c;备库首先OPEN一下后引发的问题如下图&#xff1a;启动脚本中START_MODEopen3、…

tcp紧急指针,mss,rto,零窗口探测等

三次握手、四次挥手、重传机制、滑动窗口、流量控制、拥塞控制、TCP/UDP全解析-蒲公英云 (dandelioncloud.cn)(511条消息) TCP零窗口探测_redwingz的博客-CSDN博客_tcp0窗口TCP系列32—窗口管理&流控—6、TCP zero windows和persist timer - 走看看 (zoukankan.com)TCP协议…

VUE-Axios-解决跨域问题.

vue-axios跨域问题发现: https://github.com/Aealen/TopicSelection-VUE 使用vue-axios向后端发送请求的时候遇到如下报错: vue-axios跨域问题原因: 什么是跨域 **跨域&#xff1a;**指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的&#xff0c;是浏览器…

从零开始编写一个上位机(串口助手)QT Creator + Python

提示&#xff1a;本博客作为学习笔记&#xff0c;有错误的地方希望指正&#xff0c;此文可能会比较长&#xff0c;作为学习笔记的积累&#xff0c;希望对来着有帮助。   绪论&#xff1a;笔者这里使用的是QTCreator和Python来实现一个简单的串口上位机的开发的简单过程&#…

软件测试复习02:静态测试

作者&#xff1a;非妃是公主 专栏&#xff1a;《软件测试》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录评审评审过程角色和职责评审类型静态分析控制流分析数据流分析编码标准一致性检查桌面检查代码走查…

Unity 之 资源加载 -- 可寻址系统面板介绍 -- 入门(二)

可寻址系统面板介绍 -- 入门&#xff08;二&#xff09;一&#xff0c;可寻址系统目录介绍1.2 创建分组1.2 目录介绍二&#xff0c;可寻址系统设置介绍2.1 Profile - 配置文件2.2 Catalog - 目录2.3 Content Update - 内容更新2.4 Downloads - 下载2.5 Build - 构建2.6 Build a…

69. 单发多框检测(SSD)代码实现以及QA

之前&#xff0c;我们分别介绍了边界框、锚框、多尺度目标检测和用于目标检测的数据集。 现在我们已经准备好使用这样的背景知识来设计一个目标检测模型&#xff1a;单发多框检测&#xff08;SSD&#xff09; 。该模型简单、快速且被广泛使用。尽管这只是其中一种目标检测模型&…

Filebeat从入门到实战

文章目录Filebeat的概念简介Filebeat特点Filebeat与Logstash对比Filebeat安装安装地址Logstash部署安装Filebeat实战对接LogstashFilebeat模块使用&#xff08;配置Kafka&#xff09;对接ES案例展示对接Kafka案例展示总结Filebeat的概念 简介 Filebeat是一种轻量型日志采集器…

【GP学习笔记三】锁与死锁

锁 一、概述 在GP中&#xff0c;定义了三种锁 自旋锁&#xff08;Spinlocks&#xff09;轻量级锁&#xff08;LWLocks&#xff09;普通锁&#xff08;Regular locks&#xff0c;也叫重量级锁&#xff09; 自旋锁 与互斥锁有点类似。针对某一项资源&#xff0c;在任何时刻&…

《Dubbo源码剖析与实战》学习笔记 Day6

问题引出&#xff0c;从dubbo2升级到了dubbo3版本&#xff0c;出现了一些消费方调用超时的现象&#xff0c;通过日志发现异常信息中的timeout竟然是1000ms&#xff0c;明明在暴漏接口的时候指定了超时时间&#xff0c;为什么没有生效。 经过debug分析调试后&#xff0c;找到了…

基础2-用卷积神经网络进行颅内和头皮脑电图数据分析的广义癫痫预测

A Generalised Seizure Prediction with Convolutional Neural Networks for Intracranial and Scalp Electroencephalogram Data Analysis 为了改善耐药癫痫和强直性癫痫患者的生活&#xff0c;癫痫预测作为最具挑战性的预测数据分析工作之一已引起越来越多的关注。许多杰出的…