指针(4)

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.hqwc.cn/news/701928.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软推出的Microsoft Fabric 到底是什么?

近期&#xff0c;总有客户问小编&#xff0c;微软推出的 Microsoft Fabric 是什么&#xff1f;这个产品有什么特别之处呢&#xff1f;希望下面这篇文章能为大家解开一些疑惑。 微软Fabric是2023年5月推出的一个数据分析平台&#xff0c;它将关键数据管理和分析工作负载整合到一…

联合新能源汽车有限公司出席2024年7月8日杭州快递物流展

参展企业介绍 青岛联合新能源汽车有限公司&#xff08;简称&#xff1a;联合汽车&#xff09;&#xff0c;是一家专注于纯电动汽车领域创新的科技公司&#xff0c;在国内率先提出车电分离&#xff0c;电池标准化并共享的方案&#xff0c;研发了包含标准电池、电池仓、可换电纯电…

【问题实操】银河高级服务器操作系统实例分享,网卡drop问题分析

1.服务器环境以及配置 系统环境 物理机/虚拟机/云/容器 物理机 网络环境 外网/私有网络/无网络 私有网络 硬件环境 机型 华鲲振宇 TG225B1 处理器 kunpeng 920 内存 1024GB 主板型号 TG225B1 HZKY 整机类型/架构 aarch64 固件版本 6.57 软件环境 具体操作系…

2025年第十一届北京国际印刷技术展览会

2025年第十一届北京国际印刷技术展览会 展览时间&#xff1a;2025年5月15-19日 展览地点&#xff1a;北京中国国际展览中心&#xff08;顺义馆&#xff09; 主办单位&#xff1a;中国印刷及设备器材工业协会中国国际展览中心集团有限公司 承办单位&#xff1a;北京中印协华港国…

Java | Leetcode Java题解之第90题子集II

题目&#xff1a; 题解&#xff1a; class Solution {List<Integer> t new ArrayList<Integer>();List<List<Integer>> ans new ArrayList<List<Integer>>();public List<List<Integer>> subsetsWithDup(int[] nums) {Arra…

两大DRAM巨头20%产能转给HBM

随着人工智能(AI)需求的激增&#xff0c;全球领先的内存芯片制造商三星(Samsung)和SK海力士(SK Hynix)预计&#xff0c;由于高性能芯片需求不断增长&#xff0c;今年DRAM和高带宽内存(HBM)的价格将保持强劲。据《韩国经济日报》报道&#xff0c;三星和SK海力士已将其超过20%的D…

浅析Free RTOS中Queue的应用

目录 概述 1 认识Queue 1.1 Queue定义 1.2 FreeRTOS中的Queue 1.3 Queue状态 1.4 Queue内容 1.5 发送和接收Message 1.5.1 发送message 1.5.2 接收Message 2 Queue的特性 2.1 数据存储 2.2 可被多任务存取 2.3 读Queue时阻塞 2.4 写Queue时阻塞 3 使用Queue 3.1…

【JavaScript】WeakMap 和 WeakSet

Map Map 用于存储键值对。 添加属性&#xff1a; 使用 Map 的 set() 方法可以向 Map 对象中添加键值对。例如&#xff1a; const map new Map(); map.set(key1, value1); map.set(key2, value2);通过二维数组快速创建 map 键值对。 let arr [[1, 2],[2, 3],[3, 4]]let map …

数据可视化训练第7天(json文件读取国家人口数据,找出前10和后10)

数据 https://restcountries.com/v3.1/all&#xff1b;建议下载下来&#xff0c;并不是很大 import numpy as np import matplotlib.pyplot as plt import requests import json #由于访问url过于慢&#xff1b;将数据下载到本地是json数据 #urlhttps://restcountries.com/v3…

【java】异常与错误

Throwable包括Error和Expected。 Error Error错误是程序无法处理的&#xff0c;由JVM产生并抛出的。 举例&#xff1a;StackOverflowError \ ThreadDeath Expected Expected异常包括两类&#xff0c;即受检异常(非运行时异常)和非受检异常(运行时异常)&#xff0c;异常往往…

海豚调度器如何看工作流是在哪个worker节点执行

用海豚调度器&#xff0c;执行一个工作流时&#xff0c;有时成功&#xff0c;有时失败&#xff0c;怀疑跟worker节点环境配置不一样有关。要怎样看是在哪个worker节点执行&#xff0c;在 海豚调度器 Web UI 中&#xff0c;您可以查看任务实例&#xff0c;里面有一列显示host&a…

太阳能光伏发电应用过程中会用到哪些光伏组件?

随着全球对可再生能源的需求日益增加&#xff0c;太阳能光伏发电已成为一种重要的清洁能源解决方案。在太阳能光伏发电系统的运行过程中&#xff0c;光伏组件作为系统的核心部分&#xff0c;起着至关重要的作用。本文将详细介绍太阳能光伏发电应用过程中会使用到的关键光伏组件…