【C语言】指针的进阶2

简介: 【C语言】指针的进阶2

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int* arr[10];//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。那下面哪一个是函数指针数组呢?

int (*parr1[10])();
int* parr2[10]();
int (*)() parr3[10];

答案是:parr1。 parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的用途:转移表。

请看下面的一个简单计算器的实例:

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()
{
  int x, y;
  int input = 1;
  int ret = 0;
  do
  {
    printf("========================\n");
    printf(" 1:add     2:sub \n");
    printf(" 3:mul     4:div \n");
    printf("========================\n");
    printf("请选择:");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      printf("请输入操作数:");
      scanf("%d %d", &x, &y);
      ret = add(x, y);
      printf("ret = %d\n", ret);
      break;
    case 2:
      printf("请输入操作数:");
      scanf("%d %d", &x, &y);
      ret = sub(x, y);
      printf("ret = %d\n", ret);
      break;
    case 3:
      printf("请输入操作数:");
      scanf("%d %d", &x, &y);
      ret = mul(x, y);
      printf("ret = %d\n", ret);
      break;
    case 4:
      printf("请输入操作数:");
      scanf("%d %d", &x, &y);
      ret = div(x, y);
      printf("ret = %d\n", ret);
      break;
    case 0:
      printf("退出程序\n");
      break;
    default:
      printf("选择错误\n");
      break;
    }
  } while (input);
  return 0;
}

那么用函数指针数组怎么实现呢?请看下面的代码:

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()
{
  int x, y;
  int input = 1;
  int ret = 0;
  //函数指针数组
  int(*p[5])(int x, int y) = { NULL, add, sub, mul, div }; //转移表
  while (input)
  {
    printf("======================\n");
    printf(" 1:add    2:sub \n");
    printf(" 3:mul    4:div \n");
    printf("======================\n");
    printf("请选择:");
    scanf("%d", &input);
    if ((input <= 4 && input >= 1))
    {
      printf("请输入操作数:");
      scanf("%d %d", &x, &y);
      ret = (*p[input])(x, y);
    }
    else
      printf("输入有误\n");
    printf("ret = %d\n", ret);
  }
  return 0;
}

指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针 。我们来看下面的代码:

int (*pf)(int, int);//函数指针
  int (*pfArr[5])(int, int);//函数指针数组
  //&pfArr函数指针数组的地址,p就是指向函数指针数组的指针
  int (*(*p)[5])(int, int) = &pfArr;

解析:p先和 * 结合说明是一个指针,之后与[]结合,说明是一个数组指针,再与*结合说明用一个指针指向了数组指针,之后又指向了一个函数的地址,该函数有两个int类型参数,返回值是int。

总而言之,指向函数指针数组的指针就是在函数指针数组的基础上,再加一个 * 表示一个指针去指向它。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

首先演示一下qsort函数的使用:

#include <stdlib.h>//qsort需要引入头文件
int int_cmp(const void * p1, const void * p2)
{
  return (*( int *)p1 - *(int *) p2);
}
int main()
{
  int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  qsort(arr, sz, sizeof (int), int_cmp);
  for (i = 0; i< sz; i++)
  {
    printf( "%d ", arr[i]);
  }
  printf("\n");
  return 0;
}

使用回调函数,模拟实现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;
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble(arr, sz, sizeof(int), int_cmp);
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
  return 0;
}

void* 的指针是无具体类型的指针,它可以接收任意类型的地址,这种类型的指针是不能直接进行解引用操作,也不能直接进行指针运算。

测试qsort排序结构体数据

struct Stu
{
  char name[10];
  int age;
};
//按年龄排序
int cmp_stu_age(const void* p1, const void* p2)
{
  return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
//按姓名排序
int cmp_stu_name(const void* p1, const void* p2)
{
  return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{
  struct Stu arr[] = { {"zhangsan", 17}, {"lisi", 18},{"wangwu", 15} };
  int sz = sizeof(arr) / sizeof(arr[0]); 
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
  int i = 0;
  for (i = 0; i < sz; i++) 
  {
    printf("%d\n", arr[i].age);
  }
  return 0;
}

指针和数组经典题目的解析

数组名是数组首元素的地址但有两个例外:

1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址。

请看下面的题目:

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//4*4=16字节
printf("%d\n",sizeof(a+0));//数组名a是数组首元素地址,a+0还是首地址,地址大小为4/8字节
printf("%d\n",sizeof(*a));//数组名a是数组首元素地址,*a就是首元素,大小为4字节
printf("%d\n",sizeof(a+1));//数组名a是数组首元素地址,a+1就是第二个元素的地址,大小为4/8字节
printf("%d\n",sizeof(a[1]));//数组第二个元素,大小为4字节
printf("%d\n",sizeof(&a));//&a是数组的地址,数组的地址也是地址大小为4/8字节
printf("%d\n",sizeof(*&a));//*和&相互抵消,所以*&a相当于a,所以大小为16个字节
printf("%d\n",sizeof(&a+1));//&a是整个数组的地址,&a+1就是跳过整个数组,但结果任然是一个地址,大小为4/8字节
printf("%d\n",sizeof(&a[0]));//表示首元素地址,大小为4/8个字节
printf("%d\n",sizeof(&a[0]+1));//表示第二个元素的地址,大小为4/8个字节
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//arr表示整个数组,计算的是整个数组的大小,总共6个字节
printf("%d\n", sizeof(arr+0));//arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址就是4/8个字节
printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]就是数组第二个元素,大小是1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,但是数组的地址也是地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过整个数组后的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//表示第二个元素的地址,是4/8个字节
printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找直到找到\0,所以结果就是随机值
printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值
printf("%d\n", strlen(*arr));//error
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参,就会从97这个地址开始统计字符串长度,这就非法访问内存了
printf("%d\n", strlen(arr[1]));//error 原因同上
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计
printf("%d\n", strlen(&arr+1));//原因同上也是随机值
printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址。结果也是随机值
char arr[] = "abcdef";//等价于[a b c d e f \0]
printf("%d\n", sizeof(arr));//7个字节
printf("%d\n", sizeof(arr+0));//arr + 0是首元素的地址,大小1个字节
printf("%d\n", sizeof(*arr));//*arr其实就是首元素,大小1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过一个数组的地址,结果仍然是地址大小为4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//&arr[0] + 1是第二个元素的地址 大小为4/8个字节
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//6
printf("%d\n", strlen(*arr));//error
printf("%d\n", strlen(arr[1]));//error
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr+1));//跳过整个数组向后数,后面是未知的所以结果是随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素往后数,长度为5
char* p = "abcdef";
printf("%d\n", sizeof(p));//p是一个指针变量大小就是4/8个字节
printf("%d\n", sizeof(p+1));//p+1是'b'的地址,是地址大小就是4/8个字节
printf("%d\n", sizeof(*p));//*p 就是'a',大小就是1个字节
printf("%d\n", sizeof(p[0]));//p[0]--> *(p+0) --> *p 大小1个字节
printf("%d\n", sizeof(&p));//&p --> char** 大小4/8个字节
printf("%d\n", sizeof(&p+1));//直接跳到字符串后面的,实际还是地址,大小4/8个字节
printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址,大小4/8个字节
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p+1));//5
printf("%d\n", strlen(*p));//error
printf("%d\n", strlen(p[0]));//error
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p+1));//随机值
printf("%d\n", strlen(&p[0]+1));//5
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//3*4*4 = 48
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名算是单独放在sizeof内部了,计算的是整个数组的大小,大小是16个字节
printf("%d\n",sizeof(a[0]+1));//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&,a[0]表示数组首元素的地址,也就是a[0][0]的地址,所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节
printf("%d\n",sizeof(*(a[0]+1)));//计算的是第一行第2个元素的大小,为4个字节
printf("%d\n",sizeof(a+1));//a是数组首元素的地址,是第一行的地址,a+1就是第二行的地址,是地址大小就是4/8个字节  (它的类型是int(*)[4])
printf("%d\n",sizeof(*(a+1)));//*(a+1) --> a[1] -> sizeof(*(a+1))->sizeof(a[1]) 计算的是第二行的大小,就是16个字节
printf("%d\n",sizeof(&a[0]+1));//&a[0]是第一行的地址,&a[0]+1 是第二行的地址,是地址大小就是4/8个字节
printf("%d\n",sizeof(*(&a[0]+1)));//计算的是第二行的大小,16个字节
printf("%d\n",sizeof(*a));//a是数组首元素的地址,就是第一行的地址,*a 就是第一行,*a --> *(a+0) --> a[0],大小为16个字节
printf("%d\n",sizeof(a[3]));//第三行的大小,16个字节

总结:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。
相关文章
|
1月前
|
存储 编译器 C语言
【C语言】【指针1】指针难?看这个就够了!
【C语言】【指针1】指针难?看这个就够了!
|
8天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
11天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
1月前
|
C语言
C语言------指针
这篇文章是关于C语言中指针的实训,通过示例代码展示了指针的基本概念、定义、赋值、使用和传递,以及指针运算和指针在函数参数中的应用,如交换两个变量的值和找出两个数中的较小值。
C语言------指针
|
16天前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
1月前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
1月前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
1月前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
1月前
|
存储 编译器 C语言
【C语言】指针练习题目
【C语言】指针练习题目
|
1月前
|
C语言 Python
C语言指针(2)
C语言指针(2)
23 5