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

第四节——从深层剖析指针(让你不再害怕指针)

在这里插入图片描述

文章目录

  • 1. 字符指针变量
    • 剑指offer例题
  • 2. 数组指针变量
    • 2.1 数组指针变量是什么?
    • 2.2 数组指针变量怎么初始化
  • 3. ⼆维数组传参的本质
    • 代码实现
  • 4. 函数指针变量
    • 4.1 函数指针变量的创建
    • 4.3 两段有趣的代码
      • 4.3.1 typedef 关键字
  • 5. 函数指针数组的定义


1. 字符指针变量

在指针的类型中有一种指针类型叫字符指针char*
简单代码如下:

int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}

再看如下代码:

 int main(){const char* pstr = "hello bit.";printf("%s\n", pstr);return 0;}

这里是把⼀个字符串(hello bit.)放到指针变量里了吗?
其实本质是把字符串hello bit 首字符的地址放到了pstr中(pstr指向字符串的首字符的地址)。
在这里插入图片描述

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

a.首先,我们要清楚:创建数组名str1为其开辟了一块空间,str2也等同开辟了一块空间,显然,二者的地址并不相同。
我们又知道,数组名比较的是首元素的地址,因此str1 != str2

b.字符串“hello bit.”向内存申请了一块空间,指针str3指向了字符串的首元素的地址,str4也是指向字符串首元素的地址。
由于hello bit.的地址并不发生变化,即str3和str4都是指向同一个地址,那么str3 == str4


2. 数组指针变量

2.1 数组指针变量是什么?

前面我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。

数组指针变量是指针变量?还是数组?
答案是:指针变量。(存放的应该是数组的地址,能够指向数组的指针变量。)

 int (*p)[10];

p先和*结合,说明p是⼀个指针变量,然后指针指向的是⼀个⼤小为10个整型的数组。所以p是
⼀个指针,指向⼀个数组,叫数组指针。(由于[]的优先级高于*,故要用()保证*和p集合)

2.2 数组指针变量怎么初始化

数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?

int arr[10] = {0};
&arr;//得到的就是数组的地址

在这里插入图片描述


3. ⼆维数组传参的本质

前言:有⼀个⼆维数组的需要传参给⼀个函数的时候是这样写的。

#include<stdio.h>void print(int arr[][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 ", arr[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 };print(arr, 3, 5);return 0;
}

那么我们是否可以通过指针的方式实现二维数组的传参呢?
答案是肯定的。
首先,我们先来回忆下一维数组如何进行传参。
在这里插入图片描述

我们再来理解下二维数组:⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

所以,根据⼆维数组的数组名表示的就是第⼀行的地址,那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀行这个⼀维数组的地址

代码实现

void print(int (*p)[][5], int r, int c)
{int i = 0;for (i = 0;i < r;i++){int j = 0;for (;j < c;j++){//这段也可以写成printf("%d", *(*(p+i)+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 };print(arr, 3, 5);return 0;
}

4. 函数指针变量

4.1 函数指针变量的创建

学习完数组指针后,根据类比关系,我们不难得出结论:
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
那么,我们该如何知道函数是否有地址呢?

int Add(int x, int y)
{return x + y;
}int main()
{//二者是等价的printf("%p\n", &Add);printf("%p\n", Add);return 0;
}

在这里插入图片描述
可以发现:函数名就是函数的地址
那么有什么办法能将函数地址存起来呢?
仿效数组指针的方式,那肯定有函数指针变量将函数地址存起来(且此形式和数组指针类似)。

通过函数指针变量的方式完成对应操作
#include<stdio.h>int Add(int x, int y)
{return x + y;
}int main()
{int (*pf)(int, int) = &Add; //pf函数指针变量int ret = (*pf)(2, 3);//int ret = (pf)(2, 3);printf("%d\n", ret);return 0;
}

在这里插入图片描述

4.3 两段有趣的代码

《C陷阱和缺陷》有这样两个代码:

  1. (*(void (*)())0)();
  2. void (*signal(int , void(*)(int)))(int);

我们先来解读第一段代码:

int main()
{( * ( void (*)() ) 0 ) ();return 0;
}

我们先从0着手
在这里插入图片描述
1.将0强制类型转换成void(*) () 这种类型的函数指针,这个函数没有参数,返回类型是void.
2.然后去调用()地址处的函数.


第二段代码:
在这里插入图片描述

4.3.1 typedef 关键字

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

例:若你觉得unsigned int 写起来不方便

typedef unsigned int uint;

那数组指针重命名又是咋样的呢?

typedef int(*parr_t)[5]

新的命名并不是写在最右边,而应该写在()里边。

同理,函数指针重命名也是如此

typedef void(*pfun_t)(int);

了解了typedef关键字后,我们清楚了它的作用是化繁为简(缺点:旁人并不能明白这个代码是啥类型,因此在旁边加上注释更有利于理解)。
我们可以将上面两段有趣的代码进行简化:

1. typedef void(*pfun_t)(int);2. pfun_t signal(int, pfun_t);

5. 函数指针数组的定义

上一节我们学习了指针的概念及使用,那么是否有一种方式能将函数的地址存放到数组中呢?
这个数组就叫函数指针数组,那函数指针的数组如何定义呢

int (*parr1[3])();

parr1先和[]结合,说明parr1是一个数组,其指向的内容是int (*)()类型的函数指针。
下一节会详细讲解函数指针数组的应用内容(明白其重要性)。

http://www.lryc.cn/news/454356.html

相关文章:

  • openpnp - 吸嘴校正失败的opencv参数分析
  • 【Python】Marmir 使用指南:Python 驱动的电子表格生成器
  • 深入理解 JavaScript 事件循环机制:单线程中的异步处理核心
  • Stream流的终结方法(二)——collect
  • 【C语言系统编程】【第一部分:操作系统知识】1.1.操作系统原理
  • 基于Java+VUE+echarts大数据智能道路交通信息统计分析管理系统
  • leetcode-42. 接雨水 单调栈
  • ThinkPHP和PHP的区别
  • clientWidth,offsetWidth,scrollHeight
  • SVN版本回退
  • IDEA关联Tomcat
  • MongoDB mongoose 的 save、insert 和 create 方法的比较
  • Maven安装使用
  • 微信第三方开放平台接入本地消息事件接口报错问题java.security.InvalidKeyException: Illegal key size
  • 如何只修改obsidian图片链接为markdown
  • AI不可尽信
  • [C++]使用纯opencv部署yolov11旋转框目标检测
  • Python入门--函数
  • winFrom界面无法打开
  • 【Linux】Ubuntu20.04上使用RabbitVCS的图形化SVN
  • DMA直接存储器存取
  • java计算机毕设课设—坦克大战游戏
  • Vue入门-指令学习-v-on
  • Maven的生命周期与依赖作用域介绍
  • Django学习笔记四:urls配置详解
  • NIO的callback调用方式
  • 百度文心智能体平台开发萌猫科研加油喵
  • Hive数仓操作(十六)
  • 第十二届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)(第一套)
  • MongoDB入门:安装及环境变量配置