【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

相关文章
|
2月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
186 9
|
2月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
65 7
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
3月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
72 1
|
3月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
228 3
|
3月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
3月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
3月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
3月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
276 13

热门文章

最新文章