【C语言进阶】指针进阶的详细讲解--(数组指针、指针数组、函数指针、函数指针数组、指向函数指针数组)(下)

简介: 【C语言进阶】指针进阶的详细讲解--(数组指针、指针数组、函数指针、函数指针数组、指向函数指针数组)(下)

5.指针参数:

5.1一级指针传参:

当我们在函数调用,并使用一级指针作为参数时,很容易理解:一级指针 p 中存放的是数组 arr 中首元素的地址,即传址做参,于是我们就可以在函数参数设计时,使用一级指针进行接收,就可以达到我们的目的。

void test(int* p)void test(int* p)
//传递的是一级指针,存储的是arr首元素的地址,使用一级指针进行接收
{
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    printf("%d ", *p + i);
  }
}
int main()
{
  int arr[5] = { 1,2,3,4,5 };
  int* p = arr;
  //数组名为首元素地址
  test (p);
    //等价于:test(arr);
  return 0;
}

5.2 二级指针传参:

首先最基础的用法是传递二级指针,就使用二级指针进行接收。我们直接上示例即可:

void test(int** p)
//传递二级指针,使用二级指针进行接收
{
  printf("%c", **p);
}
int main()
{
  char a = 'w';
  char* pa = &a;
  char** ppa = &pa;
 test(ppa);
  return 0;
}

当函数参数为二级指针时,都可以接收什么参数?

我们都知道,二级指针是用来存储一级指针的地址的,那么除了定义二级指针、存储一级指针地址,我们在前面还学过一个知识点也是用来存储地址的——指针数组。那么指针数组可以做为函数参数进行传递吗?答案是:可

//指针数组做函数参数:
void test(int** p)
{
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%c ", **p + i);
  }
}
int main()
{
  char a = 'a';
  char b = 'b';
  char c = 'c';
  char* arr[3] = { &a,&b,&c };
 test(arr);
return 0;
}

6.函数指针:

6.1函数指针:

在我们的程序中,各种值和组成成分都有自己的一片空间,我们的自定函数也不例外:

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

自定义函数也有着自己的储存空间,那么当我们想要将函数的地址储存起来时,又该如何进行处理呢?函数指针给出了答案。其定义格式为:

函数返回类型( * + 函数指针名 )(函数参数类型)= 函数名;

int Add(int x, int y)
{
  int z = x + y;
  return z;
}
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  int ret = Add(a, b);
  printf("a + b = %d\n", ret);
  printf("\n");
 int(*p)(int, int) = Add;
    //Add函数的返回类型为int类型,函数指针名为p,两个参数类型分别为int类型、int类型
  printf("%p\n", p);
 return 0;
}

我们很清楚的看到,通过使用函数指针就可以将函数的地址存储起来,并且我们可以通过使用函数指针优化我们的代码,提升我们代码的可读性:

比如:

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

很明显这段代码的可读性非常差,理解起来非常麻烦,于是我们可以通过使用函数指针来提升我们代码的可读性:

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

6.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 main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  int ret1 = Add(a, b);
  int ret2 = Sub(a, b);
  int ret3 = Mul(a, b);
  printf("ADD = %d SUB = %d Mui = %d\n", ret1, ret2, ret3);
  //使用函数指针从存放函数地址:
  int(*padd)(int, int) = Add;
  int(*psub)(int, int) = Sub;
  int(*pmul)(int, int) = Mul;
//函数指针数组:
  int(*p[3])(int, int) = { padd,psub,pmul };
//通过函数指针数组打印函数地址:
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("指针p[%d]中存放的地址为:%p\n", i, p[i]);
  }
 //通过函数指针数组调用函数:
  for (i = 0; i < 3; i++)
  {
    int RET = p[i](a, b);
    printf("%d ", RET);
  }
  printf("\n");
  return 0;
}

7.指向函数指针数组的指针:

7.1 书写格式:

函数返回类型( * ( * 指针名 ))( 函数参数类型) = &函数名;

7.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 main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  //使用函数指针从存放函数地址:
  int(*padd)(int, int) = Add(a, b);
  int(*psub)(int, int) = Sub(a, b);
  int(*pmul)(int, int) = Mul(a, b);
  //函数指针数组:
  int(*p[3])(int, int) = { padd,psub,pmul };
//使用指针指向函数指针数组:
  //即pp为指向函数指针数组的指针
  int* pp = &p;
//上面这种形式是为了便于我们理解
  //而更多的会写成下面这种形式:
  int(*(*p_p))(int, int) = &p;
//验证指向函数指针数组的指针指向是否正确:
  printf("%p\n", &p);
  printf("%p\n", pp);
  printf("%p\n", p_p);
 return 0;
}

8.回调函数:

8. 1 定义:

我们前面都知道了,函数的调用除了我们最基础的调用方式之外,通过函数指针我们也可以实现对函数的调用。而这种通过函数指针调用函数的方式,就称为回调函数。在实际的使用中,如果你把函数的地址(即使用指针)传递给另一个函数 ,且当这个指针调用了它指向的函数时我们就把这样的函数称作回调函数。

8.2 使用实例:

我们一起来看一个回调函数的基础使用实例:

//函数1:
void test()
{
  printf("test\n");
}
//函数2:
//使用函数指针接收,并对函数test进行调用:
void TEST(void(*p)())
{
  //使用函数指针调用test函数:
  p();
  printf("TEST\n");
}
int main()
{
  //将函数地址传递给另一个函数:
  TEST(test);
  return 0;
}

在这个过程中,函数 test 就被作为函数参数传递了出去,并且在函数 TEST 中被函数指针接收,且该指针调用了它指向的 test 函数,于是我们就可以说 test 函数是回调函数

结合我们之前写的三个算数函数我们再来看一看回调函数在实际使用中的真实使用方式

//计算函数功能实现:
void Add(int x, int y)
{
  int z = x + y;
  printf("%d + %d = %d\n", x, y, z);
}
void Sub(int x, int y)
{
  int z = x - y;
  printf("%d - %d = %d\n", x, y, z);
}
void Mul(int x, int y)
{
  int z = x * y;
  printf("%d * %d = %d\n", x, y, z);
}
//使用函数指针调用函数:
void calc(void(*p)(int, int))
{
  int x = 0;
  int y = 0;
  printf("请输入两个操作数:>");
  scanf("%d %d", &x, &y);
  p(x, y);
}
void menu()
{
  printf("********************\n");
  printf("*****   1.ADD  *****\n");
  printf("*****   2.SUB  *****\n");
  printf("*****   3.MUL  *****\n");
  printf("*****  0.EXIT  *****\n");
  printf("********************\n");
  printf("请输入:");
}
int main()
{
  int input;
  do
  {
    menu();
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      calc(Add);
      break;
    case 2:
      calc(Sub);
      break;
    case 3:
      calc(Mul);
      break;
    default:
      break;
    }
  } while (input);
 return 0;
}

8.3 使用回调函数模拟库函数 qsort 的实现:

库函数 qsort 基于快速排序法实现的一个排序函数。而通过上面的学习,我们可以通过使用回调函数来模拟出库函数 qsort 的功能实现:(注:这里我们的模拟实现方式采用冒泡方式

int int_cmp(const void* p1, const void* p2)
{
    return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
    int i = 0;
    for (i = 0; i < size; i++)
    {
        char tmp = *((char*)p1 + i);
        *((char*)p1 + i) = *((char*)p2 + i);
        *((char*)p2 + i) = tmp;
    }
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++)
    {
        for (j = 0; j < count - i - 1; j++)
        {
            if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
            {
                _swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
            }
        }
    }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

9.总结:

今天我们对指针的知识又有了新的了解,学习了字符数组、数组指针、指针数组、数组参数、指针参数、函数指针、函数指针数组、指向函数指针数组的指针以及回调函数的相关知识,算是给我们的指针进阶内容画上了一个完美的句号,希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

c3ad96b16d2e46119dd2b9357f295e3f.jpg

相关文章
|
10天前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
26 3
|
22天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
44 0
|
10天前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
19 2
|
18天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
24 1
|
21天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
22天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
22天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
28天前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
28天前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。
|
21天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
16 0