当前位置:网站首页>C语言指针(下篇)
C语言指针(下篇)
2022-07-07 06:26:00 【一只大喵咪1201】
作者:一只大喵咪1201
专栏:《C语言学习》
格言:你只管努力,剩下的交给时间!
在C语言指针(中篇)中,本喵对指针的一些更深层的用法以及指针类型做了介绍,接下来本喵来介绍一下一些更秒的指针和用法。
数组、指针传参
我们在调用函数的时候,经常会将指针变量类型以及数组当作实参传给函数来实现预期的目的。
一维数组传参
我们知道,在一维数组传参的时候,主要就是传数组名,而一维数组的数组名本质就是数组首元素的地址,所以也相当于在传一个地址。
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
test(arr);
return 0;
}
我们将这样一个数组传参给test()函数时,形参的可以是什么呢?
- 形参可以是数组
void test(int arr[])
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//打印数组中的元素
}
}
数组访问操作符([])中的数组元素个数可以省略,也可以写上。
- 形参可以是指针
void test(int* str)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(str + i));//打印数组中的元素
}
}
实参是数组名,本质是首地址元素,所以传过来的地址就是一个元素的地址,只需要用一个int * 类型的指针接收就可以。
- 形参可以是数组指针
void test(int(*parr)[10])
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(*(parr)+i));//打印数组中的元素
//printf("%d ", *(parr)[i]);
}
}
当然,这这里主函数中的实参是&数组名,虽然这种方式不常用,但是也在一种一维数组传参的方式。
对于指针数组道理和一维数组的一样,只是类型有所不同而已,请读者自行尝试。
二维数组传参
在二维数组传参的时候,同样主要传的参数是数组名,而二维数组的数组名的本质是首行元素的地址。
int main()
{
int arr[3][5] = {
1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
test(arr);
return 0;
}
将这样一个二维数组的数组名当作实参传给test()函数的时候,形参可以是什么呢?
- 形参可以是二维数组
void test(int arr[3][5])
{
//打印二维数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
形参可以写成二维数组,其中行和列的下标中只能省略行,不能省略列,也可以都写上。即
void test(int arr[][5])
void test(int arr[3][5])
而
void test(int arr[3][])
void test(int arr[][])
这俩种写法是错误的。
- 形参可以是数组指针类型
{
//打印二维数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(*(parr + i) + j));
//printf("%d ", *(parr + i)[j]);
}
}
}
因为二维数组的数组名是首行元素的地址,相当于一个有5个元素的一维数组的地址,所以形参必须由数组指针变量类型来接收。这也是数组指针经常使用的场景。
一级指针传参
一级指针变量中的内容就是一个变量的地址。
int main()
{
int a = 10;
int* pa = &a;
tset(pa);
return 0;
}
这样的一个指针变量当作实参的时候,形参只能是一个相同的指针变量类型。
void test(int* pa)
{
printf("%d\n", *pa);
}
如果只给我们形参类型是一个指针变量
void test(int* pa)
实参可以是什么呢?
int a = 10;
int* pa = &a;
test(pa);
实参可以是一级指针变量。
int a = 10;
test(&a);
实参可以是对一个变量取地址。
int arr[10] = {0};
test(arr);
实参可以是一个int类型的一维数组的数组名。
二级指针传参
二级指针变量的内容是一级指针变量的地址。
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
test(ppa);
return 0;
}
这样的一个指针变量当作实参的时候,形参只能是一个相同类型的二级指针变量。
void test(int** ppa)
{
printf("%d\n", **ppa);
}
同样如果只给我们形参的类型是一个二级指针变量类型那么实参可以是什么呢?
void test(int** ppa)
那么实参可以是什么呢?
int a = 10;
int* pa = &a;
int** ppa = &pa;
test(ppa);
实参可以是二级指针变量。
int a = 10;
int* pa = &a;
test(&pa);
实参可以是一级指针变量取地址。
int* arr[10] = {
0};
test(arr);
实参可以是指针数组的数组名。
函数指针
函数指针是一个很秒的指针,它可以让我们在调用函数时更加的方便。
先来看一段代码:
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
分别打印函数名和取地址函数名
可以看到结果是一样。
函数的地址就是对函数名取地址。
而函数名不取地址,它的本质也是函数的地址。
所以我们在使用函数地址的时候,可以对函数名取地址,也可以不取地址,得到的都是函数的地址。
我们拿到了一个函数的地址,就得把它放在一个变量里,那么该将函数的地址放在什么样的变量中呢?
void test()
{
printf("hehe\n");
}
int main()
{
void (*pf)() = test;
printf("%p\n", test);
printf("%p\n", &test);
printf("%p\n", pf);
return 0;
}
还是上面的代码,我们创建一个函数指针变量,将函数的地址放在这个函数指针变量中。
可以看到,函数指针变量的值和函数名以及取地址函数名的值是相同的。
这样的一个变量就是函数指针变量。
函数指针变量创建规则。
void (*pf)() = test;
这个语句等号的左边可以写成三个部分,void, * pf,()。
- pf先和 * 号结合,说明这是一个指针变量。
- 再和后面的(),也就是函数调用操作符,说明这是一个函数指针变量
- 而()括号里没有内容,说明函数指针指向的函数的形参没有内容。
- void代码的是函数指针指向的函数的返回类型是void
按照这个规则我们将一个实现俩个数相加的函数
int Add(int x, int y) { return x + y; }的地址放在一个指针变量中
int main() { int (*pf)(int, int) = Add; return 0; }
在函数指针变量创建好以后我们就要用了。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int (*pf)(int, int) = Add;
int sum1 = pf(a, b);
int sum2 = (*pf)(a, b);
int sum3 = (**pf)(a, b);
printf("sum1 = %d\n", sum1);
printf("sum2 = %d\n", sum2);
printf("sum3 = %d\n", sum3);
return 0;
}
在上面的代码中我们是通过函数指针变量来调用的Add函数,并且将所得的结果打印出来,但是我们在使用函数指针变量的时候用了三种不同的方式。
但是它们的结果是相同的。
在函数取地址的时候
- 函数名无论是否有&取地址操作符所得到的都是是函数的地址
在使用函数指针变量时
- 函数指针变量无论是否有*解引用操作符所调用的都是这个函数
所以解引用操作符 * 就是一个摆设,它有没有,或者有很多个都不影响函数的调用。
函数指针的使用
我们之前在调用时一直使用的都是函数名,感觉和自然也很方便,那么为什么还要有函数指针变量呢?通过函数指针变量调用函数是不是多此一举呢?
我们编写一个简易的计算器
#include <stdio.h>
void menu()
{
printf("************************\n");
printf("****1. Add 2. Sub****\n");
printf("****3. Mul 4. Div****\n");
printf("***** 0.exit *****\n");
printf("************************\n");
}
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;
}
int main()
{
int input = 0;
int a = 0;
int b = 0;
int sum = 0;
do
{
menu();
printf("请选择功能:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入俩个数:>");
scanf("%d %d", &a, &b);
sum = Add(a, b);
printf("结果是:%d\n", sum);
break;
case 2:
printf("请输入俩个数:>");
scanf("%d %d", &a, &b);
sum = Sub(a, b);
printf("结果是:%d\n", sum);
break;
case 3:
printf("请输入俩个数:>");
scanf("%d %d", &a, &b);
sum = Mul(a, b);
printf("结果是:%d\n", sum);
break;
case 4:
printf("请输入俩个数:>");
scanf("%d %d", &a, &b);
sum = Div(a, b);
printf("结果是:%d\n", sum);
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
break;
}
}
while (input);
return 0;
}
以上是一个可以实现简单的加减乘除计算的程序
我们可以看到,在switch语句中,每一种情况里的代码都有很多重复的部分,只有调用的函数是不同的,这样代码就会很冗余。
- 我们可以将这部分代码中的不同部分抽离出来
- 然后将其写成一个函数
- 每种情况中只需调用这个函数就可以
- 如此一来代码就不会再冗余
void caul(int (*pf)(int, int))
{
int a = 0;
int b = 0;
int sum = 0;
printf("请输入俩个数:>");
scanf("%d %d", &a, &b);
sum = pf(a, b);
printf("结果是:%d\n", sum);
}
创建一个caul函数,形参是接收不同功能所传过来的函数地址,所以需要一个函数指针变量类型。
通过函数指针变量来调用不同的功能模块。
如此一来,每种情况中只剩下了不同的部分,冗余的部分被封装在了caul函数中,而且只有一份。

这是它的执行结果
通过俩种代码的对比,我们发现,使用函数指针来调用函数能够使代码更加的简洁。
函数指针数组(转移表)
本喵在之前的文章中介绍过指针数组,把指针数组中的元素类型都换成同一种类型的函数指针,得到的就是一个函数指针数组。
int (*arr[5])(int, int) = {
0,Add,Sub,Mul,Div };
这句代码就是将上面计算器功能的四个函数的地址以及一个0地址放在函数指针数组arr中。
函数指针数组创建规则
int (*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };这个语句可以拆分为俩个部分
- arr[5]和int (*)(int, int)
arr先和[]结合,结合后是一个数组,数组中有5个元素。
去掉arr[5]后剩下的内容就是该数组中元素的类型。
- 剩下的int (*)(int, int)是一个函数指针类型
- 该指针指向的函数返回类型是int类型,俩个形参的类型也是int类型
如次我们便知道了该数组的一个具体情况。
函数指针数组的使用
还是上面计算器的代码,我们换一种方式来实现相同的功能。
int main()
{
int (*arr[5])(int, int) = {
0,Add,Sub,Mul,Div };//为了与菜单功能对应,数组中补1个0
int input = 0;
do
{
menu();
printf("请选择功能:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出\n");
break;
}
else if (input >= 1 && input <= 4)
{
caul(arr[input]);
}
else
{
printf("选择错误\n");
}
}
while (input);
return 0;
}
只需对主函数中的代码进行修改,其他的都不用动便也可以实现计算器的功能。
- 输入的有效数字是1到4
- 数字1到4分别对应着数组中Add函数到Div函数的地址
- 只要将对应数字元素的函数指针传给caul函数就可以调用相应的函数
这样的方式就像是在查表一样,函数指针数组就是那个表,所以又被叫做转移表。
指向函数指针数组的指针
指向函数指针数组的指针——指向函数指针数组
它是一个指针,和指向数组指针数组的指针一样,也类似于套娃的过程。
int main()
{
int (*arr[5])(int, int) = {
0,Add,Sub,Mul,Div };
int (*(*parr)[5])(int, int) = &arr;
return 0;
}
以上代码是让一个指针变量parr指向计算器功能模块组成的函数指针数组。
创建规则
int (*(*parr)[5])(int, int) = &arr;该语句可以分为两个部分。
- (* parr)[5]和int (*)(int, int)
parr先后 * 号结合,结合后是一个指针变量
(* parr)再和[5]结合,结合后是一个数组指针,指向的数组中有5个元素,类型是一个函数指针变量类型
int (*)(int, int)
而且该函数指针变量指向函数的返回类型是int类型,俩个形参都是int类型。
我们在写代码的时候很少写这样的代码,只需要在遇到时认出这是一个指向函数指针数组的指针即可。
边栏推荐
- Uniapp wechat applet monitoring network
- Implement custom memory allocator
- oracle一次性说清楚,多种分隔符的一个字段拆分多行,再多行多列多种分隔符拆多行,最终处理超亿亿。。亿级别数据量
- Quick sorting (detailed illustration of single way, double way, three way)
- Simulation volume leetcode [general] 1567 Length of the longest subarray whose product is a positive number
- let const
- 硬核分享:硬件工程师常用工具包
- 【ChaosBlade:节点磁盘填充、杀节点上指定进程、挂起节点上指定进程】
- Simulation volume leetcode [general] 1705 The maximum number of apples to eat
- Port occupation troubleshooting
猜你喜欢

Routing information protocol rip

Three updates to build applications for different types of devices | 2022 i/o key review

MySQL主从延迟的解决方案

let const

Troublesome problem of image resizing when using typora to edit markdown to upload CSDN

Goldbach conjecture C language

外部中断实现按键实验
![[Nanjing University] - [software analysis] course learning notes (I) -introduction](/img/57/bf652b36389d2bf95388d2eb4772a1.png)
[Nanjing University] - [software analysis] course learning notes (I) -introduction

【istio简介、架构、组件】
![Data analysis methodology and previous experience summary 2 [notes dry goods]](/img/e1/643e847a777e1effcbd39f8b009e2b.png)
Data analysis methodology and previous experience summary 2 [notes dry goods]
随机推荐
The longest ascending subsequence model acwing 1017 Strange thief Kidd's glider
【ChaosBlade:节点磁盘填充、杀节点上指定进程、挂起节点上指定进程】
模拟卷Leetcode【普通】1567. 乘积为正数的最长子数组长度
指针进阶,字符串函数
Three usage scenarios of annotation @configurationproperties
Shell script for changing the current folder and the file date under the folder
面板显示技术:LCD与OLED
【ChaosBlade:根据标签删除POD、Pod 域名访问异常场景、Pod 文件系统 I/O 故障场景】
Nanjing commercial housing sales enabled electronic contracts, and Junzi sign assisted in the online signing and filing of housing transactions
Required String parameter ‘XXX‘ is not present
LeetCode 715. Range 模块
Common operating commands of Linux
Gson converts the entity class to JSON times declare multiple JSON fields named
Markdown editor Use of MD plug-in
模拟卷Leetcode【普通】1557. 可以到达所有点的最少点数目
H3C VXLAN配置
Newly found yii2 excel processing plug-in
[step on the pit] Nacos registration has been connected to localhost:8848, no available server
ncs成都新電面試經驗
Image segmentation in opencv
