【C语言】指针的进阶(一)
目录
前言
1. 字符指针
2. 指针数组
3. 数组指针
3.1 数组指针的定义
3.2 &数组名VS数组名
3.3 数组指针的使用
4. 数组参数、指针参数
4.1 一维数组传参
4.2 二维数组传参
4.3 一级指针传参
4.4 二级指针传参
5. 函数指针
前言
指针在C语言中可谓是有着举足轻重的存在,初学C语言的我们在《指针》章节已经接触过了一些指针的知识,知道了指针的概念:
- 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
- 指针的大小是固定的4/8个字节(32位平台/64位平台)。
- 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
- 指针的运算。
指针的基础知识已经了解完毕,那么在这一篇博客里,我们将深入探讨指针的一些高级使用。
1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char*
一般使用:
int main()
{char ch = 'w';char* pc = &ch;return 0;
}
还有一种使用方式:
int main()
{const char* pstr = "abcdef";printf("%s\n", pstr);return 0;
}
上面这种用法很多人会有一个误区,以为是把字符串 abcdef 放到字符指针 pstr 里了,但是本质是把字符串 abcdef 中的首字符的地址放到了pstr中。
下面可以试着证明一下:
- 字符串"abcdef"的地址就是a所在地址,那么"abcdef"[3]相当于 ““a地址[3]”” ,侧面印证了确实是把首字符地址存入了指针pstr中。
- 数组名就是首元素地址,既然说字符指针存放的是首字符的地址,那么试着用数组下标的方式访问字符指针指向的内容,结果发现一样可以打印出来。
- 因此完全可以把常量字符串想象成一个数组,然后用字符指针接收,操作起来与数组一致。
注意:最好使用 const 修饰存放字符串的字符指针,因为字符串是常量,不允许被修改,如果修改了程序会崩溃。
一道经典【面试题】,出自《剑指offer》:
以下的最终输出是什么呢?
#include <stdio.h>
int main()
{char str1[] = "nash.";char str2[] = "nash.";const char* str3 = "nash.";const char* str4 = "nash.";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 and str2 are not same
str3 and str4 are same
【解释】
- str1 和 str2 :用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,str1和str2其实是各自创建了一个空间存放nash.,因此它们的地址是不一致的。
- str3 和 str4 :当用指针指向字符串时,因为nash是常量字符串,是不会被修改的,那么既然不能被修改,编译器就没必要保存多份,只需要一份,然后让指针都指向同一块内存即可,因此地址值是一致的。
【扩展】
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (&str3 == &str4)printf("Yes");elseprintf("No");return 0;
【答案】
结果为No,因为str3和str4本身的地址值是不一样的,只是str3和str4指向的内容是一样的。
2. 指针数组
这里我们复习一下,下面指针数组是什么意思?
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
类比一下:
字符数组 —— 存放字符的数组
整型数组 —— 存放整形的数组
那么:
指针数组 —— 存放指针的数组,即存放在数组中的元素都是指针类型。
那么很多人有个疑问,指针数组到底有什么用呢?
很多人直观的感受就是定义abcd,然后将它们的地址都存入整型指针数组中。如下:
错误的使用方式,【没有意义】
int main()
{int a = 0;int b = 1;int c = 2;int* arr[3] = { &a,&b,&c };return 0;
}
但是很少有人会这样使用的,没有这种使用场景,也没有人会这样使用,这样写的没有任何意义。
正确的使用场景之一:
【可以使用指针数组模拟一个二维数组】
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;
}
使用指针数组维护多个数组,模拟出一个二维数组,因此操作也与而二维数组类似。
还有使用指针数组维护多个字符串:
当然还有很多其他的应用场景,但是由于篇幅有限,这里就不再一一列举。
3. 数组指针
3.1 数组指针的定义
数组指针是指针?还是数组?
类比一下:
整型指针 —— 指向整形的指针
字符指针 —— 指向字符的指针
那么:
数组指针 —— 指向数组的指针
下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
【解释】
p2是数组指针。
p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p2先和*结合。
3.2 &数组名VS数组名
在往期博客(链接:点击前往)中有提到过:
数组名就是地址,通常来说:数组名是数组首元素的地址。
但是,存在两种特殊情况:
1、sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2、&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
除此之外,所有遇到的数组名都是数组首元素的地址。
由上图可以证明,第一组和第二组+1时都只是跳过四个字节,即arr表示的是首元素地址。
而第三组可以发现,&arr和&arr+1之间跳过了40个字节,即一整个数组的大小,因此证明&arr表示整个数组。
指针类型决定了指针+1到底+几个字节。
那么我们大胆猜测一下,arr的类型是int*,&arr[0]的类型也是int*,
那么&arr的类型就是int (*)[10],即数组指针存放数组。
既然看完了什么是数组指针,那么做一道题来检测一下自己是否理解明白。
【练习】
下面代码中pc的类型是什么?
int main()
{char* arr[5];pc = &arr; //给pc定义类型return 0;
}
【答案】
char* (*pc)[5] = &arr;
【解释】
首先pc得是个指针,即(*pc)
指向一个元素为5的数组,即(*pc)[5]
数组每个元素的类型是char*,即char* (*pc)[5]
那么数组指针有哪些使用场景呢?
3.3 数组指针的使用
错误使用场景:
这种场景一点也不方便,在使用数组时反而更麻烦了,有一种 “脱裤子放屁” 的感觉。这种使用方式基本上不会出现。
如果非要用指针接收数组,也应该使用指针接收首元素地址,而不是接收整个数组的地址。这才是正确的访问姿势。
其实,在一维数组传参过程中,形参既可以写成数组的形式,又可以写成指针的形式,因为本质上这两种方式都是传递的是数组首元素地址,是互等的。
既然本质上都是地址,那么为什么还要写成数组形式呢?
那是因为能够写成数组形式完全是为了照顾初学者,因为实参是一个数组,形参也定义一个数组来接收这个数组这种方式更能够让初学者理解。因此即使这么写,本质也还是指针。
同理,二维数组传参也有两种方式:
- 形参也是使用二维数组的形式。
- 形参使用数组指针的形式。
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
【答案】
- arr 是一个能够存放5个整型数据的数组。
- parr1 是一个数组,能够存放整型指针的数组,数组大小为10。
- parr2 是一个指针,指向元素为10的数组,每个元素的类型是int*
- parr3 是一个数组,数组有10个元素,指向一个指针,指针指向的内容是元素为5的数组,每个元素的类型是int,即是一个存放数组指针的数组。
当然parr3看不懂也不要着急,这种形式是很少用到的,只是作为拓展知识点了解即可。
4. 数组参数、指针参数
4.1 一维数组传参
4.2 二维数组传参
二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。
4.3 一级指针传参
一级指针传参,形参就写成一级指针就行。
#include <stdio.h>
void print(int* p, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(p + i));}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;
}
【思考】
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
【答案】
4.4 二级指针传参
#include <stdio.h>
void test(int** ptr)
{printf("num = %d\n", **ptr);
}
int main()
{int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}
【思考】
当函数的参数为二级指针的时候,可以接收什么参数?
【答案】
5. 函数指针
函数指针 —— 指向函数的指针 —— 存放的是函数的地址
&Add和Add完全等价,下面的例子可以证明。
可以定义 int (*pf2)(int,int) = &Add; 将&Add赋值给pf2, pf2便是函数指针。
- Add引用函数返回ret1
- 此时对pf2解引用在调用函数返回ret2
- 对pf2直接调用函数返回ret3
- 在pf2前面加很多解引用返回ret4
最后打印结果居然发现,四个结果都是一样的。
所以得出结论:函数指针在调用所指向函数时,可以不写*直接和函数名一样调用函数,而*号在这里其实就只是一个摆设,同样是为了照顾初学者的使用习惯,所以才会导致当加了很多*号去解引用时得出来的结果依然是正确的结果。
下面来看两端有趣的代码:
1、
(*(void (*)())0)();
- void (*)() ———— 函数指针类型
- ( void (*)() )0 ———— 对0进行强制类型转换,转换成函数指针。即将地址0当做存放函数指针的地址。
- (*( void (*)() )0 )() ———— 通过函数指针调用函数,函数的参数为空。
即上面的代码实在调用0地址处的函数,这个函数没有参数,返回值是void。
该代码出自《C陷阱和缺陷》
2、
void (*signal(int , void(*)(int)))(int);
- signal(int, void(*)(int)) ———— signal是一个函数,它有两个参数,整型int和函数指针类型void(*)(int)。
- void (*signal(int , void(*)(int)) )(int); ———— signal函数的返回类型也是函数指针类型void (*)(int)
但是这个代码看着太复杂了,有没有办法简化呢?
有没有办法将代码写成符合我们习惯的形式这样:返回类型在前,函数名在中间,函数参数在后的:void (*)(int) signal(int , void(*)(int)),这样直接写是肯定不支持的,但是可以通过typedef优化一下:
typedef void(*pfun_t)(int); //对void(*)(int)重新起名为pfun_t
pfun_t signal(int, pfun_t);
对void(*)(int)重新起名为pfun_t,这样写出来的代码就清楚多了。
如果觉得作者写的不错,求给作者一个大大的点赞支持一下,你们的支持是我更新的最大动力!
相关文章:
【C语言】指针的进阶(一)
目录 前言 1. 字符指针 2. 指针数组 3. 数组指针 3.1 数组指针的定义 3.2 &数组名VS数组名 3.3 数组指针的使用 4. 数组参数、指针参数 4.1 一维数组传参 4.2 二维数组传参 4.3 一级指针传参 4.4 二级指针传参 5. 函数指针 前言 指针在C语言中可谓是有着举足轻重的…...
Spring学习(三):MVC
一、什么是MVC MVC(Model-View-Controller)是一种软件设计模式,用于组织和管理应用程序的代码结构。它将应用程序分为三个主要部分,即模型(Model)、视图(View)和控制器(…...
排查disabled问题之谷歌新版本特性
问题复现 最近我突然接手一个后台的bug,这个后台很久没有迭代更新了,我也不熟悉业务,所以只能看一下源码,问题很快就复现,测试的修复操作也很正确,就是因为渲染的input标签中存在disableddisabled’属性导…...
三、开发工具
开发工具 开发工具1.1.熟悉IDEA1.2.下载IDEA1.3.IDEA中文插件1.4.IDEA输出中文乱码1.5.使用IDEA —————————————————————————————————————————————————— —————————————————————————————————…...
代码解读:y.view(y.size(0), -1)---tensor张量第一维保持不变,其余维度展平
y.view(y.size(0), -1)代码解读: 用于改变PyTorch张量(tensor)y的形状的。 y.size(0)返回y的第一维的大小。 -1表示让PyTorch自动计算该维度的大小,以确保新的张量与原始张量有相同的元素数量。 功能:将y的第一维保持…...
必示科技赋能广发证券运维数字化实践案例,入选信通院《中国AIOps现状调查报告(2023)》
近期,“必示科技赋能广发证券运维数字化实践,打造智能运维数据中台”合作案例被中国信息通信研究院作为优秀金融案例项目,收录在最新的《中国AIOps现状调查报告(2023)》(金融行业仅3家)。 以必…...
特斯拉Dojo超算:AI训练平台的自动驾驶与通用人工智能之关键
特斯拉公开Dojo超算架构细节,AI训练算力平台成为其自动驾驶与通用人工智能布局的关键一环 在近日举行的Hot Chips 34会议上,特斯拉披露了其自主研发的AI超算Dojo的详细信息。Dojo是一个可定制的超级计算机,从芯片到系统全部由特斯拉自主设计…...
Linux中的一些常用命令
1.查看Linux系统中自带的GLIBC版本 ldd --version2.Linux中删除文件的命令 在Linux中,删除文件的命令是 rm。 使用 rm 命令时,请小心使用,因为它将直接删除文件,而不会将其移动到回收站。 以下是 rm 命令的一些常用选项&#…...
VRTK4⭐二.VRTK4的项目基础配置
文章目录 🟥 硬件基本配置🟧 设置XR Plug-in Management🟨 添加项目Tilia🟩 配置项目Hierarchy 🟥 硬件基本配置 解决使用OpenXR,HTC头显正常追踪,但手柄无法使用的问题. 问题如下: 当我们按照官方的标准流程配置完Op…...
word-doc和docx区别
office从业者路过。 文件结构上doc文件数据是以二进制形式存放的。 docx是以xml文件形式存放的。 doc兼容较差,docx效果更好。...
深度学习-偏导数复习
文章目录 前言1.偏导数2.偏导数概念1.对x的偏导数2.对y的偏导数3.多元函数偏导数4.如何计算偏导数1.二元函数的偏导数2.复杂函数的偏导数3.分段函数1.分界点的偏导数 5.偏导数与连续之间的关系6.偏导数的几何意义7.高阶偏导数1.定义2.高阶偏导数例题(二阶偏导数&…...
linux之jq命令
jq命令用于linux命令行对json进行处理 参数 option -r:去掉字符串的引号"例子 tt.json文件如下: [{"metric": "httpcode","tags": {"cluster": "tt","domain": "www.baidu.…...
nginx知识点详解:反向代理+负载均衡+动静分离+高可用集群
一、nginx基本概念 1. nginx是什么,做什么事情? Nginx是一个高性能的HTTP和反向代理服务器,特点是占有内存少,并发能力强。Nginx转为性能优化而开发,能经受高负载考验。支持热部署,启动容易,运…...
powerDesigner 的基本使用
打开powerDesigner 新建 PDM(物理数据模型) 添加表字段 双击表,设置ID自增 选择导出数据库表SQL 导出成功 使用三方工具连接数据库,然后运行对应SQL文件即可 导入SQL文件数据到powerDesigner...
Java下打印一个等腰三角型
想达到这个结果,通常的做法是通过拼结两个三角型达到,但是实际上还有最右边的第三个三角型没有处理,这个拼结的方法总让人看起来有一点不完美的感觉,于是我自创了一个思路,一气合成,代码如下(本…...
HR的职业规划
CHRO可以说是HR职业发展的天花板了。CHRO对一个企业来说至关重要,是CEO的左膀右臂。那从CEO的角度来看CHRO,应该具备什么样的素质和能力,又能为公司带来什么样的价值呢? 在公司的不同发展阶段,HR部门有着不同的战略和…...
avi怎么转换成视频?
avi怎么转换成视频?在我们日常使用的视频格式中,AVI是一种常见且经常被使用的音频视频交叉格式之一。它的优点之一是占用的存储空间相对较小,但也明显存在着画质损失的缺点。虽然AVI格式的视频在某种程度上也很常见,但与最常见的M…...
爬虫数据存储:技术、策略与实践(一)
文章目录 🍋引言🍋xlrd库和xlwt库🍋创建Excel文件🍋通过Python代码向Excel写入数据🍋案例实战 🍋引言 本节主要介绍一下在使用网络爬虫技术的时候,如何将数据存储到Excel中去 🍋xl…...
【音视频】ffplay解析-音视频同步
音视频同步 主要解析:以音频为基准,让视频合成音频 思路 视频慢了则丢掉部分视频帧(视觉->画⾯跳帧) 视频快了则继续渲染上⼀帧 具体实现 一个国际标准:音频帧-视频帧时间戳的差值在-100ms~25ms内流畅 1.差值音频…...
虚拟列表 - Vue3实现一个可动态改变高度的虚拟滚动列表
虚拟列表 - Vue3实现一个可动态改变高度的虚拟滚动列表 前言 在开发中经常遇到大量的渲染列表数据问题,往往我们就只是简单地遍历渲染,没有过多地去关注是否会存在性能问题,这导致如果数据量较大的时候,比如上万条数据ÿ…...
PyTorch实战:实现Cifar10彩色图片分类
目录 前言 一、Cifar10数据集 class torch.utils.data.Dataset torch.utils.data.DataLoader 二、定义神经网络 普通神经网络: 定义损失函数和优化器 训练网络-Net CPU训练 模型准确率 编辑 GPU训练 训练网络-LeNet 模型准确率 点关注,防走丢&#x…...
Vue模板语法(下)
一.事件处理器 什么是事件处理器 建立一个HTML编写事件处理器 测试结果 二.表单的综合案例 什么是表单综合案例 建立一个HTML来编写表单案例 测试结果 三.局部组件 什么是组件通信 自定义组件 测试结果 组件通信-父传子 测试结果 组件通信-子传父 测试结果 一.事件…...
uniapp掉完接口后刷新当前页面方法
uniapp掉完接口后刷新当前页面方法 掉完接口,里面加下面这个方法uni.redirectTo({}) setTimeout(() > {uni.redirectTo({// 当前页面路由url: /pages/property/mutualrotation/mutualrotation);}, 500)实例 mutualRotationSubmit() {let self this;uni.showMod…...
linux安装redis超级详细教程
redis源码安装 安装gcc redis是C语言编写的,所以我们需要先在Linux上安装和升级,C语言的编译环境。 #安装gcc yum install -y gcc-c autoconf automake#centos7 默认的 gcc 默认是4.8.5,版本小于 5.3 无法编译,需要先安装gcc新版才能编译 gcc -v#升级…...
2023-09-20 事业-代号z-个人品牌-数据库内核专家-分析
摘要: 在个人品牌层面, 必然脱离不开技术本身, 而身为数据库内核专家, 让别人尽快感知到我的专家身份至关重要. 本文从过去的经历中分析和思考, 如何尽快以技术专家的身份被感知. 过去所见过的高管的技术特点: 不在一线处理具体的事情技术理论深厚, 广度非常厉害, 知道很多相…...
UVA-1343 旋转游戏 题解答案代码 算法竞赛入门经典第二版
GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 题目其实不难,但是耗费了我较多时间。 这种题关键就是在于找到约束条件,我在DFS的基础上,试了很多种策略: 1. 对3种数字,每种数字…...
【运维篇】二、配置文件与多环境控制
文章目录 1、临时属性2、IDEA中的临时属性3、配置文件4级分类4、关于四级分类的思考5、自定义配置文件6、多环境开发(yaml版)7、配置文件按环境分类8、include与group再细粒度9、一点思考10、多环境开发兼容问题 1、临时属性 jar包或者镜像已经打完了&a…...
【WFA】 VHT-5.2.27 Pre-requisite throughput lower than expected
先看仪表log,可以看到log中只有0.00346666666667Mbps,说明了速率很低 ~~~~~ Storing throughput ~~~~~ Mon, 11 Sep 2023 13:13:06 INFO strmTimeStampList2 count 1 Mon, 11 Sep 2023 13:13:06 INFO Storing $X1 = 0.00346666666667 [Mbps] Mon, 11 Sep 2023 13:13:…...
Pytorch史上最全torch全版本离线文件下载地址大全(9月最新)
以下为pytorch官网的全版本torch文件离线下载地址 torch全版本whl文件离线下载大全https://download.pytorch.org/whl/torch/其中的文件版本信息如下所示(部分版本信息,根据需要仔细寻找进行下载):...
CentOS服务器利用docker搭建中间件命令集合
一、挂载服务器磁盘 #挂盘语句 fdisk /dev/vdb 在分别输入n、p、1、2048、1048575999、w mkfs.ext4 /dev/vdb mkdir /data echo /dev/vdb /data ext4 defaults 0 0 >> /etc/fstab mount -a df -hfirewall-cmd --zonepublic --add-port8002/tcp --permanent firewall-c…...
不用js做网站/广州网站优化服务商
基本数据类型的包装类java.lang.Integer是我们频繁使用的一个系统类,那么通过一个示例反应出的几个问题来深入理解一下此类的源码。需求:实现Integer类型的两个数值交换。1 packagecn.integer;23 public classDemo {4 public static voidmain(String[] a…...
做企业官网需要多少钱/seo营销技巧培训班
小练习,小练习哈,直接上代码,上课上的太无聊了,来玩一玩vue来了~ <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"…...
湖南做网站公司/seo教学网站
要想在地址栏隐藏url传递的参数,不能直接隐藏,但有几下几个变通的方法. 使用类似Base64编码,将URL参数进行简单加密. 使用框架页; 使用POST方式传递数据; 使用Cookie传递数据; 下面主要介绍模拟表单提交的post方式: function post(URL, PARAMS) {var tem…...
手机网站开发计划/seo推广是什么意思
#define,typedef,struct,enmu是我们阅读单片机底层代码时最经常碰到几个概念。 首先分开解释一下 宏定义#define与定义别名typedef,它俩一起说,因为它俩的功能十分相似,具体的的用法不说了,注…...
网站备案网站建设方案书/视频互联网推广选择隐迅推
https://github.com/r-spatial...
wordpress 获取主题路径/今日油价92汽油
关注“心仪脑”查看更多脑科学知识的分享。 许多研究者使用EEG这项技术开展科研工作时,经常会遇到这样一个问题:有很好的idea但苦于缺乏足够的数据支持和验证。尤其是在2019 - 2020年COVID-19期间,许多高校实验室处于封闭状态,不…...