指针(4)

news2024/11/16 7:22:38

目录

1. 字符指针变量

2.数组指针

2.1 数组指针和指针数组的区别

2.2访问数组指针

3. ⼆维数组传参的本质

4. 函数指针变量

4.1两段有趣的代码

4.2 typedef 关键字

5.函数指针数组

6.转移表


1. 字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;

一般使用:

int main() {
    char i = 'a';
    char* p = &i;
    *p = 'q';
    printf("%c", i);
    return 0;
}
  • 然后我们看这个例子,这是把⼀个字符串放到pstr指针变量里了吗?

  • 事实上不是,他只是将这个字符常量的首字符的地址放进指针里

  • 常量字符串不能被修改

int main()
{
 const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
 printf("%s\n", pstr);
 return 0;
}

  • 那我们怎么才可以输出这个字符常量呢?

  • %s 用p 就能输出所字符串

int main() {
    char* p = "abcdef";
    printf("%c", *p);
    printf("%s",p);//用%s 用p 就能输出所字符串
    return 0;
}
  • 常量字符串不能被修改,如果我们直接这样写char* p = "abcdef"; 其实是错的,但是编译器没有报错;有时候往往找到不到bug在哪里

  • 我们最好加一个const 到开头,直接限制他的错误

int main() {
    char* p = "abcdef";   //const char* p = "abcdef";
    printf("%c\n", *p);
    printf("%s",p);//用%s 用p 就能输出所字符串
    *p = 'b';
    return 0;
}

《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:

  • 内容相同的字符串,只需要保存一个就行 因为常量字符串不能修改

  • 常量字符串(放在只读数据区)

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 and str2 are not same  
//str3 and str4 are same

2.数组指针

  • 什么是数组指针?我们来类比一下

  • 数组指针,就是存放数组地址的指针

  • 我们要对比记忆,数组指针和指针数组的区别

  • int (*) [10] 是p的类型 (数组指针的类型) arr -- int * arr+1 跳过四个字节 arr -- int * arr+1 也跳过四个字节 &arr -- int(*)[10] 跳过40个字节

int main() {
    int arr[10] = {0};
    int (*p) [10] = &arr; //p是数组指针,p存放的是数组的地址
    //int (*) [10] 是p的类型 (数组指针的类型)
    //arr -- int * arr+1   跳过四个字节
    //arr -- int * arr+1   也跳过四个字节
    // &arr --  int(*)[10] 跳过40个字节  
    return 0;
}
​
 int (*p) [10] = &arr; //数组指针初始化
 |    |    |
 |    |    |
 |    |    p指向数组的元素个数
 |    p是数组指针变量名
 p指向的数组的元素类型

2.1 数组指针和指针数组的区别

  • 我们要对比记忆,数组指针和指针数组的区别

  • 指针数组,-->数组,存放指针的数组

  • 数组指针-->指针,存放数组地址的指针

2.2访问数组指针

  • 我们可以用(*p)[i] 访问数组的每一个元素

  • (*p)[i] 怎么理解?---->p相当于一个指针 指向arr的地址,然后我们解引用 *p 得到的的是arr ,然后通过下标[i]遍历数组元素

  • 这种方法一般不会使用,复杂化了问题

#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;
    for (i = 0; i < 10; i++) {
        printf("%d ", (*p)[i]);
    }
    return 0;
}

3. ⼆维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。

过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

#include <stdio.h>
​
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(*arr)[5] 数组指针接收 [5]表示一行有几个元素

void test(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} };
    test(arr, 3, 5);
    return 0;
}
  • 或者用另外一种方式

 #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;
}
  • 怎么理解   * (*(p+i)+j)

  • 我们可以看到下图,二维数组是一维数组的数组 ,*(p+i)访问第一个数组(首元素地址)-->

    相当于 p[0] 再解引用访问第二个数组即可

  • p[i] 等于 *(p+i)

  • p[i] [j] 等于 * (*(p+i)+j)

4. 函数指针变量

什么是函数指针变量呢?

根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

那么函数是否有地址呢?

我们做个测试:

void test()
{
 printf("hehe\n");
}
int main()
{
 printf("test: %p\n", test);
 printf("&test: %p\n", &test);
 return 0;
  • 我们来思考一下,&test 和 test

  • 在数组中,&test 是表示整个数组元素的地址, arr 表示首元素地址

  • 但是在函数指针中, &test 和 test是一个东西


如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。如下:

  • int ( * ) ( int , int ) 函数指针的类型

void*test()
​
{
 printf("hehe\n");
​
}
​
void (*pf1)() = &test;
​
void (*pf2)()= test;
​
int Add(int x, int y)
​
{
 return x+y;
​
}
int(*pf3)(int, int) = Add;
​
int(*pf3)(int x, int y) = &Add;*//x和y写上或者省略都是可以的

  • 调用的时候 (*pf) 和 pf 用法一样

  • 注意 我们带*的时候 一定要带上( )---> ( *p) 不然会报错

printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));

4.1两段有趣的代码

这两段代码均来自《C陷阱和缺陷》这本书

一个程序员与我交谈一个问题。他当时正在编写一个独立运行于某种微处理器上的 C程序。当计算机启动时,硬件将调用首地址为0位置的子例程为了模拟开机启动时的情形,我们必须设计出一个 C 语句,以显式调用该子例程。经过一段时间的思考,我们最后得到的语句如下:

//代码1
(*(void (*)( ) )0 ) ();
  • (void) (*) ( )--->表示函数指针的类型,将0(整形)强制转换成函数指针(没有返回值,没有参数)

  • 外面的 * 表示解引用( *号可以省略 )这个函数指针(函数调用,0地址处放的那个函数) 右边的 ( )表示在函数调用的时候无参数调用


//代码2
void (*signal(int , void(*)(int) ) )(int);
  • 我们看图解析

  • 该代码是一次函数声明 类似于int add(int , int ) 不需要传入参数 只需要标明 返回类型和参数数据类型

4.2 typedef 关键字

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

  • 比如,你觉得 unsigned int

    写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:

typedef unsigned int unit;//起别名
int main() {
    unit i = 0;
}
  • 如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

  • int* p = &a; ptr_t p = &a; 这两句代码一样

typedef int* ptr_t;
int main() {
    int a = 3;
    int* p = &a;
    ptr_t p = &a;
}
  • 数组指针和上述的命名方式不太一样, 将重命名放进括号里面

typedef(*parr_t)[10];
//数组指针类型
int main() {
    int arr[10] = { 0 };
    int(*p)[10] = &arr;
    parr_t p2;
    return 0;
}
  • 函数指针也一样,重命名要放到*右边

typedef void(*pfun_t)(int);
  • 那我们来简化一下上面代码二的形式

typedef void (*pfun_t) (int) //  void (*) (int)
​
void (*signal(int , void(*)(int) ) )(int);
//然后我们可以简化成下面的形式
pfun_t signal(int,pfun_t)

5.函数指针数组

  • 怎么用函数指针数组呢?

  • 函数指针的类型假设为-->int (*) (int,int) 那函数指针数组的形式为----> int( * p[10]) (int ,int) = { }

  • 要注意 数组里面存放的指针的类型要一致,返回类型也要一致

  • 那么我们什么场景用函数指针数组呢?

    下列四个函数返回类型和参数类型都是一样的,那么我们分别放到一个指针是不是很麻烦?

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main() {
    int(*p)(int, int) = add;
    int(*p1)(int, int) = sub;
    int(*p2)(int, int) = mul;
    int(*p3)(int, int) = div;
​
}
我们可以将函数指针放进数组里面,通过循环 数组遍历下标即可
#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main() {
    int(*p[4])(int, int) = { add,sub,mul,div };
    int i = 0;
    for (i = 0; i < 4; i++) {
        int ret = p[i](2, 6);
        printf("%d\n", ret);
    }
}

6.转移表

函数指针数组的用途:转移表

举例:计算器的⼀般实现:

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add 2:sub \n");
        printf(" 3:mul 4:div \n");
        printf(" 0:exit \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("输⼊操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输⼊操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输⼊操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输⼊操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}
  • 我们如果往下加入新的运算,这个case越加越多,代码就会很长

  • 用函数指针数组来实现,这个代码就更灵活了

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
​
void menu() {
        printf("*************************\n");
        printf(" 1:add 2:sub \n");
        printf(" 3:mul 4:div \n");
        printf(" 0:exit \n");
        printf("*************************\n");
}
int main() {
    int input = 0;
    int x = 0;
    int y = 0;
    int (*pfArr[5])(int, int) = { 0,add ,sub,mul,div };//我们前面加一个0,数组下标恰好对应菜单
    do {
        menu();
        printf("请选择你想要的运算:");
        scanf("%d", &input);
        if (input >= 1 && input <= 4) {
            printf("请输入两个操作数:");
            scanf("%d %d", &x, &y);
            int ret = pfArr[input](x, y);
            printf("%d\n", ret);
        }
        else if(input==0)
        {
            printf("退出计算器");
            break;
        }
        else
        {
            printf("输入错误请输入(1-4):\n");
        }
​
    }while(input);
​
    return 0;
}

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

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

相关文章

【devops】Linux 日常磁盘清理 ubuntu 清理大文件 docker 镜像清理

日常磁盘清理 1、查找大文件 find / -type f -size 1G2、清理docker无用镜像&#xff08;drone产生的残余镜像文件&#xff09; docker system prune -a一、清理服务器磁盘 1、查找大文件 在Ubuntu系统中&#xff0c;你可以使用find命令来查找大文件。find命令是一个强大的…

渣土车上路识别报警摄像机

随着城市建设的不断推进&#xff0c;渣土车在城市道路上的数量也逐渐增加。然而&#xff0c;一些不法渣土车司机往往会超载、超速行驶或者闯红灯&#xff0c;给道路交通安全和城市环境带来了一定的隐患。为了有效监管渣土车上路行驶的情况&#xff0c;渣土车上路识别报警摄像机…

企业微信hook接口协议,ipad协议http,获取二次验证二维码接口

获取二次验证二维码接口 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信 请求示例 {"uuid":"f5a22e9b-9664-4250-b40a-08741dba549c" } 返回示例 {"data": {"qrcode": "http://47.9…

Linux之内存管理-malloc \kmalloc\vmalloc\dma

1、malloc 函数 1.1分配内存小于128k,调用brk malloc是C库实现的函数&#xff0c;C库维护了一个缓存&#xff0c;当内存够用时&#xff0c;malloc直接从C库缓存分配&#xff0c;只有当C库缓存不够用&#xff1b; 当申请的内存小于128K时&#xff0c;通过系统调用brk&#xff…

C++的数据结构(八):线段树

线段树是一种高效的树形数据结构&#xff0c;用于处理区间查询和区间更新问题。它的基本思想是将一个大的区间分解为若干个小的、不相交的区间&#xff0c;每个小区间对应线段树中的一个节点。线段树的每个节点保存了该区间的信息&#xff08;如区间最大值、区间和等&#xff0…

Spring Boot实现多数据源快速入门

1.为什么需要多数据源&#xff1f; 多数据源既动态数据源&#xff0c;项目开发逐渐扩大&#xff0c;单个数据源、单一数据源已经无法满足需求项目的支撑需求。本文采用dynamic-datasource-spring-boot-starter实现多数据源&#xff0c; 主要特性 支持 数据源分组 &#xff0…

Ansible自动化运维中的User用户管理模块应用详解

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月14日14点12分 在Ansible中&#xff0c;user 模块主要用于管理系统用户账户。它可以创建、修改、删除用户&#xff0c;并管理用户的属性&#xff0c;比如密码、…

RK3576 Camera:资源介绍

RK3576是RK今年上市的中高端旗舰芯片&#xff0c;定位弱于RK3588。这篇文章主要分享一下RK3576这颗主控芯片的camera资源。 &#xff08;1&#xff09;RK3576 camera资源 ①RK3576 camera硬件框图 RK3576的camera硬件框图如图所示&#xff0c;拥有一路4lane的DCPHY&#xff…

中关村论坛 | 区块链与隐私计算论坛倒计时1天!

「区块链与隐私计算论坛」 倒计时1天&#xff01; 地址&#xff1a;中关村国家自主创新示范区会议中心&#xff08;新建宫门路2号&#xff09;万春厅 时间&#xff1a;2024年4月27日&#xff0c;下午14:30-17:00 本次论坛围绕释放数据要素价值深入探讨如何将区块链与隐私计算…

六西格玛培训证书攻略2024:一站式解决方案助你快速上手

目前&#xff0c;企业对于员工的专业能力和综合素质要求越来越高。六西格玛作为一种先进的质量管理方法&#xff0c;已经成为众多企业提升运营效率、降低成本的重要手段。张驰咨询针对2024年六西格玛培训证书考取&#xff0c;为广大学员制定了实用的攻略&#xff0c;帮助学员们…

【SRC实战】文件名回显导致反射型XSS,URL重定向

挖个洞先 https://mp.weixin.qq.com/s/hnrm-snkETuR-gqPOSnQXQ “ 以下漏洞均为实验靶场&#xff0c;如有雷同&#xff0c;纯属巧合 ” 01 — 漏洞证明 一、反射型XSS “ 文件名回显&#xff0c;能否触发XSS&#xff1f;” 1、灯塔扫到敏感文件&#xff0c;发现1.txt会在…

使用apifox连接WebSocket请求,重发消息

背景&#xff1a;在 在线文档中&#xff0c;通常使用的都是WebSocket请求&#xff0c;就会导致F12抓包不是通常的http/https 在浏览器中查看WebSocket的方法 打开F12&#xff0c;切换到网络&#xff0c;选择过滤WebSocket请求&#xff0c;刷新页面 连接WebSocket请求的方法 …

python 脚本压缩文件linux 正常,windows 文件夹/文件名称 被加上了上级文件夹名

场景&#xff1a; php 在调用python 脚本&#xff0c;进行文件压缩&#xff08;因为php的压缩大文件总是超时&#xff09;&#xff0c;linux/mac 环境文件/文件夹名压缩前后一致&#xff0c;windows 压缩后 文件/文件夹名被改变为 上级 文件夹原名 原因&#xff1a; window…

国标GB28181协议EasyCVR视频汇聚平台获取设备录像仅展示部分片段的原因排查

国标GB28181协议EasyCVR安防平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流&#xf…

umi项目运行时配置,app.ts

运行时配置和配置的区别是他跑在浏览器端&#xff0c;基于此&#xff0c;我们可以在这里写函数、tsx、import 浏览器端依赖等等&#xff0c;注意不要引入 node 依赖。 getInitialState 用于获取初始化数据&#xff0c;初始化数据使用 useModel 在各个组件中使用&#xff0c;在…

信创替代后的设备处置

信创替代后的设备处置 在信创项目中替换下来的设备&#xff0c;如果从技术层面讲还具有较高的应用价值&#xff0c;如何处置呢&#xff1f; 一、数据处置 信创适配完成后&#xff0c;这些被替换下来的服务器上有大量的数据&#xff08;包括结构化和非结构化&#xff09;&…

打造动态图文展示,电子翻书一体机成展厅新宠!

随着多媒体技术的飞速演进&#xff0c;传统的静态展示方式已逐渐无法满足现代社会对于创新的渴望&#xff0c;为了跟上这一步伐&#xff0c;展厅中的展览形式也在不断地迭代&#xff0c;其中&#xff0c;电子翻书一体机以其独特的魅力&#xff0c;成为了备受瞩目的新型展项&…

JAVA实验项目(一):JAVA面向对象特征性实验

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

2024CCPC郑州邀请赛暨河南省赛

比赛记录&#xff1a;看群里大家嘎嘎拿牌&#xff0c;自己个人来solo了一下&#xff0c;发现简单到中等题很多&#xff0c;写了两小时出了7题&#xff0c;但是写的比较慢&#xff0c;对难题把握还是不准确 补题 &#xff1a; A题确实巧妙充分利用题目的数据范围来思考问题&…

Python邮件处理库之flanker使用详解

概要 Flanker是一个开源的邮件处理库,专门设计用于解析、验证和构建电子邮件地址和MIME消息。由Mailgun开发,它旨在提高邮件处理的效率和准确性,尤其适用于需要高效邮件验证和解析的应用程序。 安装 安装Flanker非常简单,可以通过Python的包管理器pip进行安装: pip ins…