【进阶指针二】数组传参&指针传参&函数指针&函数指针数组&回调函数

简介: 【进阶指针二】数组传参&指针传参&函数指针&函数指针数组&回调函数

1.数组传参

1-1一维数组传参

正向:实参给定,猜猜看形参可以怎么写?

测试1:

注:这里没有要求打印,所以没有传元素个数sz

f9213e53c9ad454e9803efb4a95ab174.png

测试2:

void test2(int* arr2[10])//bingo
void test2(int* arr2[])//bingo
void test2(int** arr2)//bingo
int main()
{
  int* arr2[10] = { 0 };
  test2(arr2);//数组名是首元素(int*类型)的地址
  return 0;
}

2815905a8ad5430282431bb3089e60ae.png

1-2 二维数组传参

void test(int arr[3][5])//bingo-列必须写上,且必须写对
void test(int arr[][5])//bingo -敲黑板!!!行可以省略,但是列不可以省略
void test(int arr[][])//error -列必须写上,且必须写对
void test(int(*p)[5])//bingo -数组指针,指针指向的是一个一维数组
int main()
{
  int arr[3][5] = { 0 };
  test(arr);//数组名是首元素(int [5]类型)的地址
  return 0;
}

关于我对二维数组及多维数组的理解:


5421a4cdfaac421ea8c6abbe46e5424c.png


由二维数组引申的两个普遍规律:


1. 我们所知的复合类型(比如数组,结构体等)的类型名都是首元素的地址(两个特殊情况除外)。


2. 对于多维数组定义或者传参时,只有第一维数组的数组元素可以省略,其余维必须写上,且必须写对!!!


2 指针传参

反向:形参给定,猜猜看实参可以怎么写?

2-1 一级指针传参


void test1(int* ptr)//一级指针:存放普通变量的地址
{
  //...
}
int main()
{
  int a = 10;
  int* p = &a;
  test1(&a);
  test1(p);//p是一级指针变量的内容
  int arr[10];
  test1(arr);
  return 0;
}

2-2 二级指针传参

void test2(int** pp)//二级指针:存放一级指针的地址
{
  //...
}
int main()
{
  char ch = 'a';
  char* pch = &ch;
  char** ppch = &pch;
  test2(&pch);
  test2(ppch);
    //错误范例1:
    test2(&&ch);ch的地址(数据)是没有地址的,只有ch的地址被变量存起来(变量)才有地址
  char* arr[5];
  test2(arr);//数组名是首元素(也就是一级指针变量)的地址
    //错误范例2:
    char arr2[3][5];
    test2(arr);//error!!!!// arr是二维数组数组名,表示的是一维数组的地址
  return 0;
}

2-3 关于传&arr和arr

2-3-1 这里以二维数组为例,讲一讲实参和形参的匹配问题

void test1(int(*p)[5])// int[5]*类型
{
  ;
}
void test2(int(*p)[3][5]) //int[3][5]*类型
{
  ;
}
int main()
{
  int arr[3][5];
  test1(arr);//二维数组首元素(整个一维数组)的地址
  test2(&arr);//整个二维数组的地址
  return 0;
}

这里以一维数组为例,讲一讲函数内要想打印的具体实现(&arr的鸡肋问题):


如果在主函数调用的时候传&arr的话就太鸡肋了!(因为你传整个数组的地址,你又不能一次性打印出来,你还得对整个数组的地址进行解引用。


解引用后就是一维数组的数组名,因为这个数组名不是那两个特殊情况,所以这个数组名又摇身一变,变成数组首元素的地址,到这里就和直接在主函数调用的时候传arr的效果是一样的)

void Print1(int* arr,int n)
{
  printf("Print1:>\n");
  for (int i = 0; i < n; i++)
  {
    printf("arr[%d]=%d\t", i, *(arr + i));
  }
}
void Print2(int(*arr)[3], int n)
{
  printf("Print2:>\n");
  for (int i = 0; i < n; i++)
  {
    printf("arr[%d]=%d\t", i, *(*arr + i));//鸡肋
  }
}
int main()
{
  int arr[3] = { 1,2,3 };
  Print1(arr,3);
  printf("\n\n");
  Print2(&arr,3);
  return 0;
}

7ec820e3d0544b6c8ed7f45134dbc2c3.png

3 函数指针

函数是放在代码区的,只要是定义了就会在编译阶段就会分配空间,和全局变量一样。

3-1 函数指针的引入

int Add(int a, int b)
{
  return a + b;
}
int main()
{
  //函数也有地址
  printf("%p\n", Add);
  printf("%p\n", &Add);//-取地址只是摆设捏
  //有地址能不能用能不能用指针变量存起来-函数指针
  int (*p1)[3];//-数组指针
  int(*p2)(int,int) = &Add;//-函数指针,指向函数的指针
  printf("%p\n", p2);
  return 0;
}

[]和()的运算符优先级都比*高

4033f5e647af427599c315dadf458704.png

c6134dcc48e1449686109a14ded0f126.png

关于为什么要有函数参数的一点思考:

-这和数组指针类似,int(*p)[5],这个[5]是对指针指向的内容的必要说明,也就是所指向数组类型的一部分,不可省略。

-同理,函数指针的类型里的返回值和形参都是对所指向函数的必要说明。

    char* test(int(*p)[5], char*)
    {
    return NULL;
    }
  //问题:来照猫画虎写一个指向这个函数的函数指针
  //答案:char*(*p)(int(*)[5], char*) = &test;

3-2 函数指针的脱裤子放屁使用【先见一见基本操作】

int Add(int a, int b)
{
  return a + b;
}
int main()
{
  printf("test1:\n%p\n", &Add);
  printf("test2:\n%p\n", Add);
  int(*p)(int, int) = &Add;
  //int(*p)(int, int) = Add;
  printf("test3:\n%d\n", Add(2, 3));
  printf("test4:\n%d\n", (*p)(2,3));
  printf("test5:\n%d\n", p(2,3));
  //在获得函数地址时,&和不加&都可以,但是加上&更好理解
  //在通过调用函数时, *和不加*都可以,但是加上*更好理解,且必须要带上括号
  return 0;
}

3-3 试图看懂大佬写的代码

代码1:

1. (*(void(*)())();
2. //提示:这个整体是函数调用


692e442bd8844e81b85cd1a2c6382ebd.png

子例程:函数

参考:《C陷阱和缺陷》


d1bf7c56c7bc4227a418fda2d38cc3ab.png

代码2:

1. void(* signal(int,void(*)())(int);
2. //提示:这个整体是函数声明


1078c47b62b44277865a489099525c42.png

小小勘误:图片中第3步中指针类型应该改为函数指针类型

4 函数指针数组

4-1函数指针数组的引入和基本使用

只要你前面学会了,这里就是一样的套用,我这里就不啰嗦了

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()
{
  //字符指针数组
  char* arr1[5];
  //整型指针数组
  int* arr2[5];
  //函数指针数组
  //int(*pf[4])(int, int);
  //在没有函数指针数组之前...
  int(*pf1)(int, int) = Add;
  int(*pf2)(int, int) = Sub;
  int(*pf3)(int, int) = Mul;
  int(*pf4)(int, int) = Div;
  //有函数指针数组之后...
  int(*pf[4])(int, int) = { Add,Sub,Mul,Div };
  for (int i = 0; i < 4; i++)
  {
    int ret=pf[i](6, 2);
    printf("%d\n", ret);
  }
  return 0;
}

3756332be54646d39d58ca18ea08c17a.png


函数指针数组的优缺点:

- 优点:不用一个一个定义变量去存储函数的地址,然后一个一个去调用

- 缺点:函数指针数组既然是数组,就要求是相同类型元素的集合,也就是返回值和参数类型的一样才能放到函数指针数组内,统一进行操作。


4-2 函数指针数组的妙用

函数指针数组实现加减乘除运算器,这里的函数指针数组被称为转移表

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 1.0*a / b;
}
void meau(void)
{
  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 operand1 = 0;
  int operand2 = 0;
  int(*pf[5])(int, int) = {0,Add,Sub,Mul,Div};
  meau();
  do
  {
    printf("请输入你的选择:>");
    scanf("%d", &input);
    if (input == 0)
    {
      printf("退出程序\n");
      break;
    }
    else if (input >= 1 && input <= 4)
    {
      printf("请输入两个操作数:>");
      scanf("%d%d", &operand1, &operand2);
      printf("%d\n", pf[input](operand1,operand2));
    }
    else
    {
      printf("输入非法,请重新输入\n");
      continue;
    }
  } while (input);
  return 0;
}


62467e167d524b7094877e1863db1adf.png

5 回调函数

回调函数:把函数1的地址作为函数2的函数参数,从而调用函数2,然后再函数2实现过程中通过指针调用函数1,那么这个被其他函数调用的函数(函数1)就被称为回调函数。

void test1(void(*p)())
{
  (*p)();
}
void test2()
{
  printf("test2\n");
}
int main()
{
  test1(&test2);
}


5-1 回调函数的使用举例1:计算器

原来的switch  case 语句好多冗余的语句,又有前提减加乘除的函数参数和返回值类型相同,所以可以使用回调函数处理这个问题。

void cal(int (*p)(int, int))
{
  int o1 = 0;
  int o2 = 0;
  printf("请输入两个操作数:>");
  scanf("%d%d", &o1, &o2);
  printf("%d\n", p(o1, o2));
}
int main()
{
  int input = 0;
  meau();
  do
  {
    printf("请输入你的选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      cal(Add);
      break;
    case 2:
      cal(Sub);
      break;
    case 3:
      cal(Mul);
      break;
    case 4:
      cal(Div);
      break;
    case 0:
      printf("exit\n");
      break;
    default:
      printf("非法\n");
    }
  } while (input);
  return 0;
}

这里的Add,Sub,Mul,Div函数都是回调函数,通过传不同函数的地址给Cal函数,Cal函数内部用函数指针接收,从而实现了Cal函数的多重功能。


目录
相关文章
|
19天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
23天前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
23天前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
23天前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
26天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
92 13
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
35 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
121 4
|
4月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
4月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)