当前位置: 首页 > news >正文

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

目录

前言

1. 字符指针

2. 指针数组

3. 数组指针

3.1 数组指针的定义

 3.2 &数组名VS数组名

 3.3 数组指针的使用

4. 数组参数、指针参数

4.1 一维数组传参

 4.2 二维数组传参

 4.3 一级指针传参

 4.4 二级指针传参

5. 函数指针


 

前言

指针在C语言中可谓是有着举足轻重的存在,初学C语言的我们在《指针》章节已经接触过了一些指针的知识,知道了指针的概念:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。

 指针的基础知识已经了解完毕,那么在这一篇博客里,我们将深入探讨指针的一些高级使用。

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 数组指针的使用

错误使用场景:

 这种场景一点也不方便,在使用数组时反而更麻烦了,有一种 “脱裤子放屁” 的感觉。这种使用方式基本上不会出现。

如果非要用指针接收数组,也应该使用指针接收首元素地址,而不是接收整个数组的地址。这才是正确的访问姿势。

其实,在一维数组传参过程中,形参既可以写成数组的形式,又可以写成指针的形式,因为本质上这两种方式都是传递的是数组首元素地址,是互等的。

既然本质上都是地址,那么为什么还要写成数组形式呢?

那是因为能够写成数组形式完全是为了照顾初学者,因为实参是一个数组,形参也定义一个数组来接收这个数组这种方式更能够让初学者理解。因此即使这么写,本质也还是指针。

同理,二维数组传参也有两种方式:

  1. 形参也是使用二维数组的形式。
  2. 形参使用数组指针的形式。

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

【答案】

  1. arr 是一个能够存放5个整型数据的数组。
  2. parr1 是一个数组,能够存放整型指针的数组,数组大小为10。
  3. parr2 是一个指针,指向元素为10的数组,每个元素的类型是int*
  4. 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&#xff08;Model-View-Controller&#xff09;是一种软件设计模式&#xff0c;用于组织和管理应用程序的代码结构。它将应用程序分为三个主要部分&#xff0c;即模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;…...

排查disabled问题之谷歌新版本特性

问题复现 最近我突然接手一个后台的bug&#xff0c;这个后台很久没有迭代更新了&#xff0c;我也不熟悉业务&#xff0c;所以只能看一下源码&#xff0c;问题很快就复现&#xff0c;测试的修复操作也很正确&#xff0c;就是因为渲染的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)代码解读&#xff1a; 用于改变PyTorch张量&#xff08;tensor&#xff09;y的形状的。 y.size(0)返回y的第一维的大小。 -1表示让PyTorch自动计算该维度的大小&#xff0c;以确保新的张量与原始张量有相同的元素数量。 功能&#xff1a;将y的第一维保持…...

必示科技赋能广发证券运维数字化实践案例,入选信通院《中国AIOps现状调查报告(2023)》

近期&#xff0c;“必示科技赋能广发证券运维数字化实践&#xff0c;打造智能运维数据中台”合作案例被中国信息通信研究院作为优秀金融案例项目&#xff0c;收录在最新的《中国AIOps现状调查报告&#xff08;2023&#xff09;》&#xff08;金融行业仅3家&#xff09;。 以必…...

特斯拉Dojo超算:AI训练平台的自动驾驶与通用人工智能之关键

特斯拉公开Dojo超算架构细节&#xff0c;AI训练算力平台成为其自动驾驶与通用人工智能布局的关键一环 在近日举行的Hot Chips 34会议上&#xff0c;特斯拉披露了其自主研发的AI超算Dojo的详细信息。Dojo是一个可定制的超级计算机&#xff0c;从芯片到系统全部由特斯拉自主设计…...

Linux中的一些常用命令

1.查看Linux系统中自带的GLIBC版本 ldd --version2.Linux中删除文件的命令 在Linux中&#xff0c;删除文件的命令是 rm。 使用 rm 命令时&#xff0c;请小心使用&#xff0c;因为它将直接删除文件&#xff0c;而不会将其移动到回收站。 以下是 rm 命令的一些常用选项&#…...

VRTK4⭐二.VRTK4的项目基础配置

文章目录 &#x1f7e5; 硬件基本配置&#x1f7e7; 设置XR Plug-in Management&#x1f7e8; 添加项目Tilia&#x1f7e9; 配置项目Hierarchy &#x1f7e5; 硬件基本配置 解决使用OpenXR,HTC头显正常追踪,但手柄无法使用的问题. 问题如下: 当我们按照官方的标准流程配置完Op…...

word-doc和docx区别

office从业者路过。 文件结构上doc文件数据是以二进制形式存放的。 docx是以xml文件形式存放的。 doc兼容较差&#xff0c;docx效果更好。...

深度学习-偏导数复习

文章目录 前言1.偏导数2.偏导数概念1.对x的偏导数2.对y的偏导数3.多元函数偏导数4.如何计算偏导数1.二元函数的偏导数2.复杂函数的偏导数3.分段函数1.分界点的偏导数 5.偏导数与连续之间的关系6.偏导数的几何意义7.高阶偏导数1.定义2.高阶偏导数例题&#xff08;二阶偏导数&…...

linux之jq命令

jq命令用于linux命令行对json进行处理 参数 option -r&#xff1a;去掉字符串的引号"例子 tt.json文件如下&#xff1a; [{"metric": "httpcode","tags": {"cluster": "tt","domain": "www.baidu.…...

nginx知识点详解:反向代理+负载均衡+动静分离+高可用集群

一、nginx基本概念 1. nginx是什么&#xff0c;做什么事情&#xff1f; Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;特点是占有内存少&#xff0c;并发能力强。Nginx转为性能优化而开发&#xff0c;能经受高负载考验。支持热部署&#xff0c;启动容易&#xff0c;运…...

powerDesigner 的基本使用

打开powerDesigner 新建 PDM(物理数据模型) 添加表字段 双击表&#xff0c;设置ID自增 选择导出数据库表SQL 导出成功 使用三方工具连接数据库&#xff0c;然后运行对应SQL文件即可 导入SQL文件数据到powerDesigner...

Java下打印一个等腰三角型

想达到这个结果&#xff0c;通常的做法是通过拼结两个三角型达到&#xff0c;但是实际上还有最右边的第三个三角型没有处理&#xff0c;这个拼结的方法总让人看起来有一点不完美的感觉&#xff0c;于是我自创了一个思路&#xff0c;一气合成&#xff0c;代码如下&#xff08;本…...

HR的职业规划

CHRO可以说是HR职业发展的天花板了。CHRO对一个企业来说至关重要&#xff0c;是CEO的左膀右臂。那从CEO的角度来看CHRO&#xff0c;应该具备什么样的素质和能力&#xff0c;又能为公司带来什么样的价值呢&#xff1f; 在公司的不同发展阶段&#xff0c;HR部门有着不同的战略和…...

avi怎么转换成视频?

avi怎么转换成视频&#xff1f;在我们日常使用的视频格式中&#xff0c;AVI是一种常见且经常被使用的音频视频交叉格式之一。它的优点之一是占用的存储空间相对较小&#xff0c;但也明显存在着画质损失的缺点。虽然AVI格式的视频在某种程度上也很常见&#xff0c;但与最常见的M…...

爬虫数据存储:技术、策略与实践(一)

文章目录 &#x1f34b;引言&#x1f34b;xlrd库和xlwt库&#x1f34b;创建Excel文件&#x1f34b;通过Python代码向Excel写入数据&#x1f34b;案例实战 &#x1f34b;引言 本节主要介绍一下在使用网络爬虫技术的时候&#xff0c;如何将数据存储到Excel中去 &#x1f34b;xl…...

【音视频】ffplay解析-音视频同步

音视频同步 主要解析&#xff1a;以音频为基准&#xff0c;让视频合成音频 思路 视频慢了则丢掉部分视频帧&#xff08;视觉->画⾯跳帧&#xff09; 视频快了则继续渲染上⼀帧 具体实现 一个国际标准&#xff1a;音频帧-视频帧时间戳的差值在-100ms~25ms内流畅 1.差值音频…...

虚拟列表 - Vue3实现一个可动态改变高度的虚拟滚动列表

虚拟列表 - Vue3实现一个可动态改变高度的虚拟滚动列表 前言 在开发中经常遇到大量的渲染列表数据问题&#xff0c;往往我们就只是简单地遍历渲染&#xff0c;没有过多地去关注是否会存在性能问题&#xff0c;这导致如果数据量较大的时候&#xff0c;比如上万条数据&#xff…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...