一篇文章了解区分指针数组,数组指针,函数指针,链表。

简介: 一篇文章了解区分指针数组,数组指针,函数指针,链表。

最近在学习指针,发现指针有这许多的知识,其中的奥妙还很多,需要学习的也很多,今天那我就将标题中的有关指针知识,即指针数组数组指针函数指针,给捋清楚这些知识点,区分这些名词,望各位能赏眼观看一下,如有不足,还请指点。下面就开始敲下这篇文章了。\

指针数组

指针数组,那么何为指针数组?仅仅看这个名词,各位觉得它是数组还是指针变量呢?那我们先来看一下官方的解释,何为指针数组。


在 C 语言中,指针数组是一种特殊的数据结构,它是一个数组,其中每个元素都是一个指针。

指针数组的定义方式如下:

 

类型 *数组名[数组大小];

 

 

其中, 类型  是指针所指向的元素的类型, 数组名  是指针数组的名称, 数组大小  表示指针数组的大小。


例如,以下代码定义了一个整数指针数组:

 

 

int *ptrArray[10];


这个指针数组 ptrArray  可以存储 10 个指向整数的指针。

 

指针数组的成员可以像普通指针一样进行操作,可以通过指针间接访问所指向的元素。例如:

 

int num = 10;
ptrArray[0] = #

上述代码将整数变量  num  的地址赋值给指针数组  ptrArray  的第一个元素。

需要注意的是,指针数组中的每个指针都需要单独进行内存分配和释放,确保在使用指针数组时正确管理内存。


指针数组的概念我们已经了解得差不多了,那么我们也实际运用一下指针数组吧!


运用指针数组来模拟出一个二维数组

相信各位那么聪明也是很快就想出怎么敲出这个简单的代码了吧。下面展示本人的代码。如下:

int main()
{
  int arr1[5] = { 1,2,3,4,5 };
  int arr2[5] = { 11,22,33,44,55 };
  int arr3[5] = { 6,7,8,9,10 };
 
  int* p[3] = { &arr1,&arr2,&arr3 };
 
  int i = 0;
  int j = 0;
  for (i = 0; i < 3; i++)
  {
    for (j = 0; j < 5; j++)
    {
      printf("%d ", p[i][j]);
    }
    printf("\n");
  }
 
 
 
 
 
  return 0;
}


根据运行结果来看,代码是完美的运行成功了。

需要注意的是:这里虽然是好像 *p[ 3] 确实可以完成一个二维数组的功能,但也是和真正的二维数组有所区别的,二维数组中的相邻元素中的地址也是连续的,而用指针数组模拟出来的每个元素地址,并不是连续的。大家可看下下面的图。


很明显3个数组的地址是不连续的,故里面的元素地址肯定也不是连续的。


数组指针

数组指针,咋一看怎么和刚刚说的指针数组那么像呢?那数组指针是指针还是数组呢?老样子我们看看官方的回答。


在 C 语言中,数组指针是一个指向数组的指针。它用于通过指针访问和操作数组中的元素。

数组指针的定义方式如下:

类型 (*指针名)[数组大小];

其中, 类型  是数组元素的类型, 指针名  是数组指针的名称, 数组大小  表示数组的大小。

例如,以下代码定义了一个整数数组指针:

 

 

int (*ptrArray)[10];

这个数组指针  ptrArray  可以指向一个包含 10 个整数元素的数组。

 

使用数组指针,可以通过指针间接访问和操作数组中的元素。例如:

 

 

1. int arr[10] = {0};
2. ptrArray = &arr;
3.


上述代码将整数数组  arr  的地址赋值给数组指针  ptrArray ,使得  ptrArray  指向  arr  数组。

通过  ptrArray  可以访问和操作  arr  数组中的元素,如下所示:

 

 

1. printf("%d\n", *ptrArray[0]);
2.


上述代码使用间接寻址运算符  *  来访问数组指针  ptrArray  所指向的数组的第一个元素。

需要注意的是,在使用数组指针时,要确保指针所指向的数组的边界和指针的类型匹配,以避免越界访问和错误。


看完大家是不是又对这个知识有了更加透彻的理解了呢?


那看完了我们也用数组指针来实践应用到代码当中!


二维数组传参,数组指针来接收


在实践之前我们首先要知道一个知识点,我上篇文章说过,传参时如果用数组名进行传参,传过去的是数组的首元素大小,那么我们知道一维数组的首元素为数组的第一个值,那么二维数组呢?也是第一行第一个数吗?事实上二维数组的首元素其实是第一行,注意这里说的是整整一行,而不是一个,那么怎么证明呢?继续往下看。

 
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
  int arr[3][4] = { {1,2,3,4},{11,22,33,44},{7,8,9,10} };
  
  printf("%d\n", sizeof(*arr));         //首元素大小,也就是第一行元素的大小
  printf("%d\n", sizeof(*(arr+1)));           //第二行元素的大小
  
  return 0;
}

从结果上不难发现这里 显示的16,正是第一行的4个元素的大小。故其实二维数组的首元素为数组的第一行。

那么知道这个知识点之后我们就可以开始完成上面说的实际应用了,二维数组传参,数组指针来接收 。因为传的是第一行,那么我们不妨就用数组指针来接收。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
 
void Print(int(*p)[4], int x, int y)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < x; i++)
  {
    for (j = 0; j < y; j++)
    {
      printf("%d ",(*(*(p + i) + j)));
    }
    printf("\n");
  }
 
 
 
 
}
 
 
 
int main()
{
  int arr[3][4] = { {1,2,3,4},{11,22,33,44},{7,8,9,10} };
  Print(arr, 3, 4);
  
  return 0;
}


在这串代码中,我们通过数组名将arr数组的首元素传到函数Print中,用数组指针p来接收,最后完成数组打印。

到这里我们先来一个小总结:指针数组就是存着指针的数组数组指针就是指向数组的指针,我们只需要抓住后面的名词即可区分住指针数组数组指针了。



函数指针

函数指针“,当初我刚学的时候也在想函数也有指针吗?直到学到这个知识点才逐渐明白。

那么什么是函数指针呢?老样子我们看一下官方的解释。


在 C 语言中,函数指针是一个指向函数的指针。它用于通过指针调用函数。

 

函数指针的定义方式如下:

 

 

返回值类型 (*函数指针名)(参数列表);

 

 

其中, 返回值类型  是函数返回值的类型, 函数指针名  是函数指针的名称, 参数列表  是函数的参数列表。


例如,以下代码定义了一个指向整数函数的指针:

 

 

int (*funcPtr)(int, int);


个函数指针  funcPtr  可以指向一个返回整数并接受两个整数参数的函数。

 

使用函数指针,可以通过指针调用所指向的函数。例如:

 

 

int add(int a, int b)
{
    return a + b;
}
 
funcPtr = add;
 


述代码将函数  add  的地址赋值给函数指针 funcPtr 使得  funcPtr  指向  add  函数。

 

然后,可以使用  funcPtr  来调用  add  函数,如下所示:

 

 

int result = funcPtr(3, 4);


上述代码通过函数指针  funcPtr  调用了  add  函数,并将结果存储在变量  result  中。

使用函数指针可以实现函数的动态调用,使代码更加灵活和可复用。


看完上面的内容,相信各位对函数指针也有所了解了。


那我们在深入一点,回想当初我们调用函数时所用的都是数组名进行调用,那么这里我们就要思考一下了,函数的数组名是什么呢?

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
 
int Add(int a, int b)
{
 
  return a + b;
}
 
 
 
 
 
int main()
{
 
  int (*p)(int, int) = &Add;
  printf("%p\n", p);
  printf("%p\n", Add);
 
  int c=(*p)(3, 4);
  int d = p(3, 4);
  int f = Add(3, 4);
 
  printf("c=%d\n", c);
  printf("d=%d\n", d);
  printf("f=%d\n", f);
 
 
  return 0;
}

观察上面代码,是否发现数组名Add和指针p打印出来都是同一个地址,说明其实数组名也是一个地址,而且我上面的代码运用了三种方法去调用这个函数,可以发现调用时即使是指针也不一定要用”*“。


了解完后想必大家都在想那函数指针有什么用呢?数组名就可以调用函数了,要指针来干嘛。这个就要涉及到链表函数回调了。


链表

链表的话我们可以简单理解为函数指针数组,咋一听是不是又发现了一个新名词了。其实它也不是很难,可以参考指针数组函数指针数组多了两字,也就是储存函数指针的数组,这么一听是不是又对它明了许多呢。

函数指针数组定义如下:

返回值类型 (*函数指针名)[数组个数](参数列表);

举例如下:

int (*p)[10](int ,int)

下面是一个用链表的应用完成一个计算器功能:

#include <stdio.h>
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) = { 0, Add, Sub, Mul, Div };
  do
  {
    printf("*************************\n");
    printf(" 1:加法   2:减法 \n");
    printf(" 3:乘法   4:除法 \n");
    printf(" 0:退出 \n");
    printf("*************************\n");
    printf("请选择你所需的功能:");
    scanf("%d", &input);
    if ((input <= 4 && input >= 1))
    {
      printf("输⼊操作数:");
      scanf("%d %d", &x, &y);
      ret = (*p[input])(x, y);
      printf("ret = %d\n", ret);
    }
    else if (input == 0)
    {
      printf("退出计算器\n");
    }
    else
    {
      printf("输⼊有误\n");
    }
 
  } while (input);
  return 0;
}


上面我们将运算的加减乘除函数写出来后,利用函数指针数组将其储存起来,然后根据用户选择的选项调用其数组的元素,也就是加减乘除的函数,最后通过指针进行调用,完成功能。

这就是函数指针的实践运用了。至于函数回调,就要留到下一篇指针文章了。敬请期待哦!

文章已到末尾,望各位多多支持。


目录
相关文章
|
6天前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
81 64
|
1月前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
7天前
|
编译器 C语言
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
|
2月前
|
搜索推荐 C语言
指针与数组
指针与数组
53 9
|
2月前
|
存储 开发者 C#
WPF与邮件发送:教你如何在Windows Presentation Foundation应用中无缝集成电子邮件功能——从界面设计到代码实现,全面解析邮件发送的每一个细节密武器!
【8月更文挑战第31天】本文探讨了如何在Windows Presentation Foundation(WPF)应用中集成电子邮件发送功能,详细介绍了从创建WPF项目到设计用户界面的全过程,并通过具体示例代码展示了如何使用`System.Net.Mail`命名空间中的`SmtpClient`和`MailMessage`类来实现邮件发送逻辑。文章还强调了安全性和错误处理的重要性,提供了实用的异常捕获代码片段,旨在帮助WPF开发者更好地掌握邮件发送技术,提升应用程序的功能性与用户体验。
42 0
|
2月前
|
存储 Java 开发者
揭秘!HashMap底层结构大起底:从数组到链表,再到红黑树,Java性能优化的秘密武器!
【8月更文挑战第24天】HashMap是Java集合框架中的核心组件,以其高效的键值对存储和快速访问能力广受开发者欢迎。在JDK 1.8及以后版本中,HashMap采用了数组+链表+红黑树的混合结构,实现了高性能的同时解决了哈希冲突问题。数组作为基石确保了快速定位;链表则用于处理哈希冲突;而当链表长度达到一定阈值时,通过转换为红黑树进一步提升性能。此外,HashMap还具备动态扩容机制,当负载因子超过预设值时自动扩大容量并重新哈希,确保整体性能。通过对HashMap底层结构的深入了解,我们可以更好地利用其优势解决实际开发中的问题。
76 0
|
4月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
|
4月前
|
存储 SQL 算法
LeetCode 题目 86:分隔链表
LeetCode 题目 86:分隔链表
|
4月前
|
存储 算法 Java
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
34 2
|
5月前
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点.
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点
46 1