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

C语言底层学习(3.指针、函数与数组)(超详细)

  • 🌈🌈🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
  • 🔥🔥🔥专栏:《C语言入门知识点》、《C语言底层》
  • 💪💪💪格言:今天多敲一行代码,明天少吃一份苦头

前言:

在之前发布的指针和数组的关系里,我们已经蛮详细地介绍了他们之间的关系,尤其是一维数组传参的本质,相信大家都已经比较熟悉了,那么对于更深的指针、函数和数组关系,我们本篇就来介绍一下


文章目录

    • 前言:
    • 正文:
      • 1. 字符指针变量
      • 2. 数组指针变量
      • 3. 二维数组传参的本质
      • 4. 函数指针变量
        • 4.1 函数指针变量的初始化
        • 4.2 函数指针数组
        • 4.3 函数指针变量的使用
        • 4.4 函数指针的简化
          • 4.4.1 两段《C陷阱与缺陷》代码
          • 4.4.2 typedef关键字
      • 5. 转移表
        • 5.1 什么是转移表?
        • 5.2 转移表的应用


正文:

1. 字符指针变量

字符指针变量,很显然是字符的地址的变量

#include <stdio.h>int main()
{char ch = 'c';char* pch = &ch;printf("%p\n", pch);printf("%c\n", ch);printf("%c\n", *pch);return 0;
}

运行结果:

0000002D0AB8FC64
c
c

很明显我们就能看出来这种使用字符指针变量的方法,但其实还有一种方法来使用字符指针变量:

int main()
{const char* pch = "abcd";printf("%s\n",pch);printf("%p\n",pch);return 0;
}
abcd
00007FF602D4ACA4

这是一种标准的定义字符串常量的方式,这里的pch就是一个字符指针变量,00007FF602D4ACA4就是他指向的地址
注意:打印字符串的时候使用的是pch而不是*pch,这是因为%s打印的时候本身就要求传字符串首个元素的地址,一直读取到\0。而且在定义字符串的时候,pch本身就是第一个元素的地址。

  • 如图
    在这里插入图片描述

为了防止大家混淆头晕,这里着重分辨一下这几种字符串相关的类型:

	char cch[] = ['a','b','c','d'];//纯字符数组char ch[] = "abcdefg";//字符数组,等价于char ch [] = ["abcdefg"];//也等价于char ch [] = ['a','b','c','d','e','f','g','\0']char* pch = "abcdeff";//字符串的定义方式const char* pchh = "abcdeffg";//标准的字符串常量定义方式

这里来看一段代码,我们来看一下字符数组和字符串常量的区别

#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;
}
str1 and str2 are not same
str3 and str4 are same

做一下解释:记得我们之前提到过数组名是地址名,他们的地址不一样,只是里面的内容一致;字符串常量存储在只读数据段,如果是同样的内容,即使变量名不一样,判断出来的也是相同的。

2. 数组指针变量

指向整型的指针变量成为整形指针变量,指向字符的叫字符指针变量,所以指向数组的指针叫数组指针变量
继续类比一下:int* 整形指针变量类型;char* 字符指针变量类型;那么数组指针变量类型长什么样子呢?他们都为本来变量类型加*
所以数组指针变量类型为:int(*) [],但是注意:

int (*p)[] //这是数组指针变量类型
int* p[]   //这是指针数组变量类型
  • 注意 !
    int (*p)[] 一定要带(),其本质是*先和p结合,本质上是指针
    int* p[]不能带(),本质上是存放指针的数组,变量类型为:int* []

3. 二维数组传参的本质

一维数组传参的本质已经我们已经了解过了,对于数组名其实是首元素地址这件事我们也已经牢记于心了
那么接下来我们先来猜猜看二维数组用指针怎么表示吧:

//对于一维数组
int main()
{int i = 0;int arr[3] = { 0,1,2 };for (i = 0;i < 3;i++){printf("%d ", *(arr + i));}return 0;
}
0 1 2

猜测二维数组指针表示法,假设每次二维数组得到的也是地址:

//对于二维数组
int main()
{int i,j = 0;int arr[3][3] = { {0,1,2},{1,2,3},{2,3,4} };for (i = 0;i < 3;i++){for (j = 0;j < 3;j++){printf("%d ", *(*(arr + i))+j);}printf("\n");}return 0;
}

通过对第一个(arr+i)解引用得到第i个数组,再通过对(*(arr + i))+j解引用得到第i个数组中的第j个数
也就是:

*(*(arr + i) + j) == arr[i][j];

需要注意的是:

*(arr + i)    == arr[i];//这是解引用第i个元素地址
*arr + i      == arr[0] + i;//这是在解引用第0个元素地址以后加上i
*(*(arr + i) + j) == arr[i][j];//这是解引用第i个数组首元素地址以后解引用其的第j个元素
*(*(arr + i)) + j == arr[i][0];//这是解引用第i个数组首元素地址以后解引用其的首个元素然后加j

所以一定要注意分辨括号的位置

4. 函数指针变量

4.1 函数指针变量的初始化

还还还还是和其他指针变量一致,函数指针变量就是指向函数的地址的指针嘛
经典去掉名称就是原变量类型,再加*就是指针变量类型

void test()
{printf("hello~");
}int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}
00007FF6825F1159
00007FF6825F1159

可以看得出来,对于函数来说,他和数组类似,函数名就是他的地址。
那我们把他存进指针咯:

void test()
{printf("hello~");
}int main()
{printf("%p\n", test);printf("%p\n", &test);void (*p1_test)() = &test;void (*p2_test)() = test;printf("%p\n", p1_test);printf("%p\n", p2_test);return 0;
}
00007FF61F841159
00007FF61F841159
00007FF61F841159
00007FF61F841159

完全一致

其中我们注意到对于test()这个函数的指针变量类型是void (*)()其实就是去掉函数名而加*
记得加括号!!!

4.2 函数指针数组

这样我们认识到了函数指针变量,既然是变量,我们一般就能把它储存在数组里,以便使用
还是来研究他的变量类型写法
数组:int* arr = [] 指针数组储存的是int*
试试把int*换成函数指针变量类型int (*) ()

是哪一种?

int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];

答案是parr1
parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢?
int (*)() 类型的函数指针。

4.3 函数指针变量的使用

已知函数名是地址,而指针也是地址 ==》用指针代替函数名试试:

int Add(int x, int y)
{return x + y;
}int main()
{int (*p_add)(int, int) = Add;printf("%d\n", Add(2, 3));printf("%d\n", p_add(2, 3));return 0;
}
5
5

好像完全一致,试验成功。

  • 函数指针完全可以代替函数

那,有什么用呢?

4.4 函数指针的简化
4.4.1 两段《C陷阱与缺陷》代码

代码一

(*(void (*)())0)();
  • 剥洋葱

最右端括号的空括号说明他是一个函数的调用,就像我们刚才看到的(*p2_test)();
这样的话就说明(void (*)())0是一个指针,很显然,0是地址。
前面的(void (*)())是一个变量类型,那这就是一个强制类型转换了
所以这段代码的意思就是:将0这个地址的东西强制转换为(void (*)())形式的指针然后调用这个指针指向的函数

代码二

void (*signal(int , void(*)(int)))(int);
  • 剥洋葱

和上面同理嘛,先是最外侧函数void (*)(int);声明
然后呢里面是一个地址signal(int , void(*)(int)),很显然signal是一个函数名(也是地址)
那里面就是他的参数了呗,所以它的参数是intvoid(*)(int)
所以这段代码意思就是:声明一个函数signal,它的返回值是void (*)(int)类型的函数指针,参数是intvoid(*)(int)

4.4.2 typedef关键字

我们看完上面这两个代码时常会想:好麻烦啊,有没有什么方法可以让这些代码看上去简单易懂呢?
还真能有:typedef关键字
比如说:写无符号整型unsigned int好长好麻烦

typedef unsigned int uint;
//把unsigned int 重新定义为uint,以后uint就是无符号整型的意思
uint a = 3;

这样的话,所有类型都能重命名了
只不过需要注意的是,函数指针变量的typedef是这样的:

typedef void(*funct)();//新的类型名必须在*的右边 

他的重新定义要在里面,包括数组指针类型也是

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

这两段代码就能简化为:

typedef void(*funct)();
typedef void(*funct_int)(int);int main()
{(*((funct)0))();// 等价于==>  (*(void (*)())0)();  调用funct_int signal(int, funct_int);// 等价于==>  void (*signal(int, void(*)(int)))(int);  声明return 0;
}

5. 转移表

5.1 什么是转移表?

转移表(Jump Table) 是一种通过函数指针数组实现的分支控制结构,用于替代复杂的switch-case或if-else语句,提高多分支场景下的执行效率和代码可读性。

5.2 转移表的应用

我们先来看一个模拟计算器的代码

int add(int x, int y)
{return x + y;
}int sub(int x, int y)
{return x - y;
}int mul(int x, int y)
{return x * y;
}int div(int x, int y)
{return x / y;
}void mune()
{printf("*************************\n");printf("******** 1.add    *******\n");printf("******** 2.sub    *******\n");printf("******** 3.mul    *******\n");printf("******** 4.div    *******\n");printf("******** 0.exit   *******\n");printf("*************************\n");
}int main()
{int input = 0;do{int x, y, ret = 0;mune();printf("please enter your choice:\n");scanf("%d", &input);switch (input){case 1:{printf("please enter two oprands,separated by a apace:\n");scanf("%d %d", &x, &y);ret = add(x,y);printf("%d\n", ret);break;}case 2:{printf("please enter two oprands,separated by a apace:\n");scanf("%d %d", &x, &y);ret = add(x, y);printf("%d\n", ret);break;}case 3:{printf("please enter two oprands,separated by a apace:\n");scanf("%d %d", &x, &y);ret = add(x, y);printf("%d\n", ret);break;}case 4:{printf("please enter two oprands,separated by a apace:\n");scanf("%d %d", &x, &y);ret = add(x, y);printf("%d\n", ret);break;}case 0:{printf("exit the program!\n");break;}defult:{printf("enter error!please re-enter!\n");break;}}}while (input);return 0;
}

通过这段代码可以实现简单的计算问题
但是我们不难发现,这代码也太长了吧,而且很多重复的地方
这个时候就可以用转移表来简化它:

int add(int x, int y)
{return x + y;
}int sub(int x, int y)
{return x - y;
}int mul(int x, int y)
{return x * y;
}int div(int x, int y)
{return x / y;
}void mune()
{printf("*************************\n");printf("******** 1.add    *******\n");printf("******** 2.sub    *******\n");printf("******** 3.mul    *******\n");printf("******** 4.div    *******\n");printf("******** 0.exit   *******\n");printf("*************************\n");
}int main()
{int input = 0;int x, y, ret = 0;int (*(arr[5]))(int x, int y) = { 0,add,sub,mul,div };//创建一个函数指针变量来存放这些函数do{mune();printf("please enter your choice:\n");scanf("%d", &input);if (input > 0 && input < 5){printf("please enter two oprands,separated by a apace:\n");scanf("%d %d", &x, &y);ret = (*arr[input])(x, y);//通过对函数指针数组的应用,我们就能简化这段代码printf("%d\n", ret);}else if(input == 0){printf("exit the program!\n");}else{printf("enter error!please re-enter!\n");}}while (input);return 0;
}
  • 本节完…
http://www.dtcms.com/a/406205.html

相关文章:

  • 基于XTDIC-SPARK三维高速测量系统的电子产品跌落测试研究
  • 前端终极布局方案Grid
  • 微服务与面向服务编程(SOA)入门指南:从架构演进到 Spring Cloud 实践(初学者友好版)
  • 微服务配置中心高可用设计:从踩坑到落地的实战指南(二)
  • 【信号处理】检波算法
  • 【Web前端|第三篇】JavaScript事件
  • 【数据结构】二叉树全面详解
  • 信号处理与系统设计,第二节课笔记
  • 设计模式(C++)详解——解释器模式(2)
  • Spring Cloud构建分布式微服务架构的完整指南
  • php网站做多久郑州建设网
  • jsp网站开发的使用表格电子商务网站建设的核心是
  • 快速将多个PPT、PPTX幻灯片统一转换成浏览器能直接打开的HTML网页文件
  • IROS 2025将于10月在中国杭州举办,爱迪斯通携手机器人训练与遥操作专家XSENS、HAPTION参展
  • 后海做网站公司网址搜索引擎入口
  • Java之链表
  • IDEA 高效配置指南:从基础到进阶的设置全解析
  • 用 SeaTunnel 同步 MySQL 到 Doris:全量增量 + SQL 过滤
  • C++项目:仿muduo库高并发服务器--------Any类的实现
  • ELK 企业级日志分析系统实战教程
  • 驻马店怎么建设自己的网站wordpress 导出到pdf
  • 网站建设成本表一般什么行业做网站的多
  • 阳台光伏、储能系统再升级!双路电能表,您家能源的“智能管家”
  • 【Unity 入门教程】四、如何制作一个 Perfab
  • 浅谈高校门户网站建设的规范标准找做废薄膜网站
  • Word和WPS文字中的题注没有更新?全选按F9刷新
  • Spring Boot集群 集成Nginx配置:负载均衡+静态资源分离实战
  • 本地生活软件开发指南:从用户需求到商业闭环的实战逻辑
  • 建设网站需要租赁主机吗重庆模板建站代理
  • CSP-J/S初赛赛后总结