当前位置:网站首页>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类型。
我们在写代码的时候很少写这样的代码,只需要在遇到时认出这是一个指向函数指针数组的指针即可。
边栏推荐
- LeetCode 736. LISP syntax parsing
- 【ChaosBlade:节点磁盘填充、杀节点上指定进程、挂起节点上指定进程】
- Output a spiral matrix C language
- Greenplum6.x-版本变化记录-常用手册
- Common operating commands of Linux
- [wechat applet: cache operation]
- Gson converts the entity class to JSON times declare multiple JSON fields named
- 2022-06-30 Unity核心8——模型导入
- selenium自动化集成,八年测试经验软测工程师,一篇文章带你学懂
- With an annual salary of 50W, Alibaba P8 will come out in person to teach you how to advance from testing
猜你喜欢

ncs成都新電面試經驗
![[Yugong series] February 2022 U3D full stack class 007 - production and setting skybox resources](/img/e3/3703bdace2d0ca47c1a585562dc15e.jpg)
[Yugong series] February 2022 U3D full stack class 007 - production and setting skybox resources

H3C VXLAN配置

MySQL主从延迟的解决方案

Introduction to data fragmentation

Greenplum6.x搭建_安装

数字三角形模型 AcWing 275. 传纸条

Greenplum6.x搭建_环境配置

Markdown editor Use of MD plug-in
![[step on the pit] Nacos registration has been connected to localhost:8848, no available server](/img/ee/ab4d62745929acec2f5ba57155b3fa.png)
[step on the pit] Nacos registration has been connected to localhost:8848, no available server
随机推荐
Quick sorting (detailed illustration of single way, double way, three way)
Digital triangle model acwing 275 Pass a note
Redis fault handling "can't save in background: fork: cannot allocate memory“
[Nanjing University] - [software analysis] course learning notes (I) -introduction
【Istio Network CRD VirtualService、Envoyfilter】
xray的简单使用
Frequently Asked Coding Problems
平台化,强链补链的一个支点
模拟卷Leetcode【普通】1706. 球会落何处
Nanjing commercial housing sales enabled electronic contracts, and Junzi sign assisted in the online signing and filing of housing transactions
Greenplum 6.x version change record common manual
RuntimeError: Calculated padded input size per channel: (1 x 1). Kernel size: (5 x 5). Kernel size c
Opencv converts 16 bit image data to 8 bits and 8 to 16
Shell script for changing the current folder and the file date under the folder
Greenplum6.x-版本变化记录-常用手册
Tronapi wave field interface - source code without encryption - can be opened twice - interface document attached - package based on thinkphp5 - detailed guidance of the author - July 6, 2022 - Novice
ESP32-ULP协处理器低功耗模式RTC GPIO中断唤醒
Gson converts the entity class to JSON times declare multiple JSON fields named
Image segmentation in opencv
个人力扣题目分类记录
