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

#C语言——学习攻略:深挖指针路线(四)--字符指针变量,数组指针变量,二维数组传参的本质,函数指针变量,函数指针数组

🌟菜鸟主页:@晨非辰的主页

👀学习专栏《C语言学习》

💪学习阶段:C语言方向初学者

名言欣赏:"暴力解法是上帝给的,优化解法是魔鬼教的。"


目录

1. 字符指针变量

1.1 使用方式

1.2 例题解释

2. 数组指针变量

2.1 数组指针变量定义        

 2.2 数组指针变量的初始化

3. 二维数组传参本质

4. 函数指针变量

4.1 函数指针变量的创建

4.2 函数指针变量的使用

4.3 有趣的代码

4.3.1 typedef关键词

5. 函数指针数组


1. 字符指针变量

1.1 使用方式

--已经知道有一种指针类型为字符指针:char*,一般有两种使用方式:

int main()
{//第一种char ch = 'w';char* pc = &ch;//第二种char arr[] = "abcdef";char* pc = arr;return 0;
}

--当然还有另外方式:

int main()
{const char* pc = "abcdef";//这里代表把整个字符串放进了指针嘛?//结果显而易见,字符串为常量字符串,只是将首字符的地址存放printf("%c\n", *pc);//打印aprintf("%s\n", pc);//打印abcdef//%s打印字符串需要的是地址才能找到下一个字符,所以用pcreturn 0;
}

--对于方式3和方式2对比,方式3略过了数组,这就导致了无法通过*pc来改变字符串,也就是上面说的常量字符串。

1.2 例题解释

--经典笔试题:

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");//1elseprintf("str1 and str2 are not same\n")//2;if (str3 == str4)printf("str3 and str4 are same\n");//3elseprintf("str3 and str4 are not same\n");//4return 0;
}

--代码打印;2、3;

        --首先请注意,根据博客指针(三)的内容,这里比较的都是数组首元素地址而不是数组内容;所以因为str1与str2虽然内容相同但为不同数组,首元素地址自然也不同;

        --对于str3与str4,都指向同一个字符串"abcdef"且为常量字符串,内容不会被修改,C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串时,他们实际会指向同一块内存,所以str3和str4相同。

--特别注意,比较字符串内容要使用strcmp函数


2. 数组指针变量

2.1 数组指针变量定义        

--在上一篇博客,学习了指针数组,数组存放的是指针;那下面也进行类比:

  • 整型指针变量:int* p,存放的整型变量地址,指向整型数据;
  • 字符指针变量;char* p,存放的字符型变量地址(字符串,存放的是首字符地址),指向字符型数据

--可知,数组指针存放的是数组地址,指向数组数据;

--下面对数据指针变量进行辨析:

1. int* p1[10]; --首先p1与[10]结合,那么p1就成了数组名,这也就是前面学的指针数组

2. int(*p2)[10]; --首先p2与*结合,p2成为指针变量名,[10]代表指针所指向的是大小为10的数组;所以是数组指针(必须确保加上(),使p先于和*结合)

 2.2 数组指针变量的初始化

--数组指针变量用来存放数组地址,那么初始化就要获取数组地址:&数组名。

int(*p) [10] = &arr; --&arr得到数组地址

--数组类型解释:

int  (*p)  [10]  =  &arr

  |      |        |

  |      |     p指向数组的元素个数

  |    p为指针变量名

p指向的数组的元素类型


3. 二维数组传参本质

--认识了数组指针,下面来理解二维数组是如何传参的吧:

//构建函数
void test(int arr[][5], int a, int b)
{int i = 0;int j = 0;for (i = 0; i < a; i++){for (j = 0; j < b; 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;
}

--哎,在前面说一维数组传参时,形参可以写成数组也可以写成指针,那二位可以吗?

        --首先来看二维数组,可以看成每个元素都是一维数组,也就是每一行就是一个一维数组:

 

 --所以根据数组名含义来说,二维数组的数组名代表的是首元素地址也就是第一行这个一维数组的地址:类型就是int  [5]、数组指针类型是int(*) [5]。

--就意味着二维数组传参本质上也是传递地址,传递的是第一行⼀维数组地址,那么形参也是可以写成指针形式的:

void test(int(*arr) [5], int a, int b)
{int i = 0;int j = 0;for (i = 0; i < a; i++){for (j = 0; j < b; j++){printf("%d ", *(*(arr+i)+j));//*(arr + i) 解引用得到 arr[i](第 i 行的数组名)//+j再解引用是访问一维数组的内容,等价于arr[][]}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;

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。


4. 函数指针变量

4.1 函数指针变量的创建

--同样类比其他指针,函数指针就是存放函数地址的,通过地址调用函数,那函数地址怎么获取呢?

void test()
{printf("hehe\n");
}
int main()
{printf("test:  %p\n", test);printf("&test: %p\n", &test);return 0;
}

        --可见函数是有地址的,函数名就是地址(与数组不一样)。

--下面就要开始创建变量来存放地址:


int* test(int n, char* p)
{(...);
}int* (*pf)(int, char*) = test;int Add(int x, int y)
{return x + y;
}//参数可写可不写
int(*pf)(int, int) = Add;

--函数指针类型图解:

4.2 函数指针变量的使用

--通过函数指针调用指针指向的函数

int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf)(int, int) = Add;printf("%d\n", (*pf)(10, 20));printf("%d\n", pf(10, 20));//解不解引用都可以,因为pf在这里等价于Addreturn 0;
}

--  注意:C 语言标准规定,函数指针的解引用会自动转换回函数地址,因此 *pf 仍然等同于 pf。

4.3 有趣的代码

--代码1

(* (void (*)())0)();

--解释:这段代码是在调用0地址处的函数

  1.  void(*)()是一个函数指针类型,这个指针指向的函数没有参数,返回类型void;
  2.  (void (*)())0 是将0强转为这种函数指针类型,意味着0处有这么一个函数;
  3. (* (void (*)())0)();对0地址进行解引用,调用函数;

--来自《C陷阱和缺陷》

 --代码2

void  (*signal (int , void(*)(int)) ) (int);

--解释:是一次函数声明,函数名叫signal

  1. signal函数有两个参数,第一个参数是int类型,第二个参数是函数指针类型 void(*)(int),该指针指向的函数参数是int,返回类型是void;
  2. signal函数的返回类型也是一个函数指针类型 void(*)(int) ,指针指向的函数参数是int,返回类型是void;
  3. 直观表达:void * (int) signal (int , void(*)(int)) ;//但是不能这么写

--来自《C陷阱和缺陷》

4.3.1 typedef关键词

--显而易见,typedef用来类型重命名的,可以将复杂的类型简单化。

 --比如:

unsigned int 太长不方便,可以用关键词重定义为uint :typedef  unsigned int  uint;

         --指针类型也可以简化命名的,将int *重命名

typedef  int*  ptr_t;

        --但是对于数组指针和函数指针就有不同:

       -- 数组指针类型 int (*) [5] ,需要重命名为parr_t,要这样写:

typedef int(*parr_t)[5]; --新的类型名必须在*的右边

        --函数指针类型重命名是一样的,将 void(*) (int) 类型重命名为pfun_t,可以这样写:

typedef void(*pfun_t)(int); --新的类型名必须在*的右边

 --那么为了更好理解上面的代码2,这样命名:

typedef void(*pfun_t)(int); ——> pfun_t signal(int, pfun_t);


5. 函数指针数组

--在上一篇博客分享了指针数组,那么把函数的地址放到数组中,就成为了函数指针数组,如何定义呢?

int (*parr1[3])();

 --parr1先和 [ ] 结合,说明parr1是数组,内容是 int (*)()类型的指针;

--对于函数指针的用途:转移表,小子会在下一篇进行分享,千万别急~~ 


往期复习:

1. #C语言——学习攻略:深挖指针路线(一)--指针变量、地址、意义与指针运算

2. #C语言——学习攻略:深挖指针路线(二)--const修饰、野指针分析、断言和指针的作用

3. #C语言——学习攻略:深挖指针路线(三)--数组与指针的结合、冒泡排序


结语:本篇内容就到这里了,主要分享了指针变量类型的一些内容,后续仍会分享指针的相关知识;指针的内容需要反复研读 ,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅、这么美给个三连吧~~~

http://www.dtcms.com/a/307052.html

相关文章:

  • ConvertX:自托管的在线文件转换器,支持1000+种格式!
  • Linux系统编程Day1-- 免费云服务器获取以及登录操作
  • CH347使用笔记:CH347作为FPGA下载器的几种方式
  • Maven 配置阿里云镜像加速
  • huggingface是什么?2025-07-30
  • Mac 上配置jdk 环境变量
  • 2. Agent与 React流程
  • 【LY88】双系统指南及避坑
  • Python 的 match-case
  • 从映射到共生:元宇宙、物联网与AI的智能融合生态图谱
  • (LeetCode 面试经典 150 题) 141. 环形链表(快慢指针)
  • HPCtoolkit的下载使用
  • Oracle11g数据库迁移达梦8数据库方案
  • Python序列化和反序列化
  • 如何用Docker部署ROS2
  • (C++)C++类和类的方法(基础教程)(与Python类的区别)
  • c++之基础B之sort排序(第三个参数没有)(第二课)
  • Fiddler中文教程 从入门到进阶的网络抓包与接口调试实战指南
  • Python Pandas.merge_asof函数解析与实战教程
  • VUE前端
  • [Agent开发平台] API网关 | 业务领域 | DTO格式 | 分页令牌
  • React 服务端渲染(SSR)详解
  • 使用 cron 配合 Docker 实现定时任务
  • 神经网络的并行计算与加速技术
  • 模型相关类代码回顾理解 | BatchNorm2d\fc.in_features\nn.Linear\torchsummary
  • Haproxy 七层代理深度解析
  • Ubuntu 本地部署和使用 n8n 指南and ai almost anything
  • REST、GraphQL、gRPC、tRPC深度对比
  • Python Day19 时间模块 和 json模块 及例题分析
  • Dify案例2:基于Workflow的小红书笔记AI智能体以及AI绘图过程中遇到的问题