进阶C语言 第二章-------《进阶指针》 (指针数组、数组指针、函数指针、回调指针)知识点+基本练习题+深入细节+通俗易懂+完整思维导图+建议收藏(二)

简介: 进阶C语言 第二章-------《进阶指针》 (指针数组、数组指针、函数指针、回调指针)知识点+基本练习题+深入细节+通俗易懂+完整思维导图+建议收藏(二)

4.数组传参、指针传参

4.1 一维数组和指针数组传参

能够接收一维数组和指针数组的参数有:

对数组的参数进行接收一般有两种情况:

直接和原类型数据相同

传过来地址用指针接收;此时注意满足 类型 + * + 变量名 这一指针确定规则即可,如接收指针数组时用到的是二级指针,而不是一级指针的原因是原本传过来的类型就是int * 的类型所以要  类型 + *   所以就应该写成二级的int *  * ,或者还可以理解为传过来的是指针的地址,所以要用二级指针接收。

void test1(int arr[])//括号内加不加数组大小都行
{
  ;
}
void test1(int* parr)
{
  ;
}
void test2(int* arr2[])
{
  ;
}
void test2(int** arr2)
{
  ;
}
int main()
{
  int arr1[10] = { 0 };
  int* arr2[10] = { 0 };
  test1(arr1);
  test2(arr2);
  return 0;
}

4.2 二维数组传参

知识点:

同上这总结一般只有两种情况:

同上,也就是用原类型/指针类型

分析:

int main()
{
  int arr[3][4] = {0};
  test(arr);
  return 0;
}

test(arr)中的arr是数组名,数组名表示首元素的地址,而此处是一个二级数组,所以数组名就表示成了第一行arr[0]的地址即&arr[0],他是一个数组(第一行),所以我们在用指针时就应该用数组指针来存数组

所以,最终能在test函数中接收的参数类型有:

int arr[][COL]//此处行可以省略但是列不能省略
int (*parr)[COL]//数组指针接收数组

可能会感觉二级指针可以接收,但是事实上是不行的因为

二级指针:接收的应该是一级指针的地址(而不是数组的地址,

报错如下:

image.png

4.3一级指针传参

知识点:

一级指针传参时:只用相同类型接收(不能用指针接收了此处因为二级指针是接收一级指针的地址的,并且其余的也用不了)

void test(int* p)
{
  ;//code
}
int main()
{
  int arr[] = { 1,2,3,4,5,6,7 };
  int* p = arr;
  test(p);
  return 0;
}

反过来思考:

函数以一级指针接收时传的参数可以是什么?

不难可以想出,首先什么是以指针接收的无非就是地址,而在函数内还要加一个可能就是传过来的值的原本的类型也是指针。

所以最终的可能性就是:

传递一个变量的地址test(&a)、一维数组名(首元素的地址)test(arr)

以及指针变量名test(p)  (和上面的代码一样)

4.4二级指针传参

知识点:

同一级指针:你传的是一级指针的就用一级指针接收、传的是二级指针就用二级指针接收

void test(int** ptr)
{
  ;//code
}
int main()
{
  int arr[] = { 1,2,3,4,5,6,7 };
  int* p = arr;
  int** pa = &p;
  test(pa);//pa 其实可以看成存着 &p
  test(&p);//传的是一级指针的地址其原理和二级指针变量名一样都是&p
  return 0;
}

反过来:当函数参数是一个二级指针接收

二级指针接收一级指针的地址:

所以可以是:

int * p = NULL;
int **parr = &p;
test(&p);
test(parr);

还可以存指针数组:因为指针数组内存的都是一个个一级指针

如:int * arr[10] 这里面存了10个int *类型的指针所以当我们把arr数组名传进去的时候就表示把首元素的地址(&arr[0] == & (int *))及第一个一级指针的地址传了进去所以是一级指针就可以用二级指针来接收

5.函数指针

5.1

知识点:

函数指针:一个存储函数地址的指针

其表现形式为:类型  + * +变量名 = 函数的地址

细节:

5.1.1函数指针的创建

下面通过代码来具体的展示:

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 3;
  int b = 2;
  Add(a, b);
  //函数指针 类型+ * + 变量名 = 函数的地址
  //此处Add的类型为 int (int , int)
  int (*ptr)(int, int) = &Add;
  int (*ptr)(int, int) = Add;//此处函数名和数组名类似都可以直接表示地址
  return 0;
}

5.1.2函数指针的使用

和正常的指针一样,我们存了函数的地址所以就可以解应用找到这个函数   (*ptr)(a,b)  ==  Add(a,b)

在平常我们使用函数时是直接Add(a,b) 的 ,所以我们在使用(*ptr)(a,b)时甚至也可以直接写成ptr(a,b)

代码即:

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 3;
  int b = 2;
  Add(a, b);
  //函数指针 类型+ * + 变量名 = 函数的地址
  //此处Add的类型为 int (int , int)
  int (*ptr)(int, int) = &Add;
  //上下一样  即:&Add == Add
  int (*ptr)(int, int) = Add;//此处函数名和数组名类似都可以表示其地址
  int ret = Add(a, b);
  // 等于
  int ret1 = (*ptr)(a, b);
  // 等于
  int ret2 = ptr(a, b);
  //即 Add(a,b) == (*ptr)(a,b) == ptr(a,b)
  return 0;
}

Add(a,b) == (*ptr)(a,b) ==  ptr(a,b)  //此处的(*ptr)中的*其实就是个摆设你甚至可以写成(***** ptr)(a,b)他只是便于理解 ,所以在我们用函数指针时可以存好一个函数的地址后可以直接写成 ptr(a,b)来代替Add(a,b),如果要写成(*ptr)(a,b)这种形式的话其中 (*ptr) 的括号不能少(因优先级问题)

&Add ==  Add

附:*arr 可以看成 *(arr+0) 及   arr[0]   所以    **arr -> *(arr[0])  -> (arr[0][0])    (还可以将arr、arr[0] 看成首元素地址  *arr == arr【0】、* (arr[0]) -> arr[0][0]  )

我们在上面所用到的函数指针 ptr  其用法其实并不像上面那种而是一般会用于当把一个函数的地址传进到另一个函数内部时要在另一个函数内使用时可以用来接收这个地址

练习:

(* (  void  ( * )  ( ) ) 0 )  ( )  : 这段代码的意义是:对0处的地址进行函数访问,不用传参

分析:

首先我们要知道最里面的void( * ) ( )  这是一个函数指针用来存地址 ,所以此处将整形0 用括号 强制转换成函数指针类型  就如 ptr 一样再(*(....)0)() 来访问0所指向的函数,因为函数类型中的参数是空的,所以也不用再进行传参。

void (* signal(int ,void (*)(int)))(int);这段代码的意义是:对该函数进行声明

分析:

首先我们要知道,函数声明的结构是:返回类型 + 函数名 + 传参的类型  

而此处的返回类型是void (*) ( int )  ;函数名:signal   ;

传参的类型 : (int , void (*)(int))

可能此处不好看但是当写成返回类型为int就会变成:int signal(int , void (*)(int))

只是因为我们需要把函数名写到*的旁边所以看起来比较的奇怪:

现在分颜色来更清晰的写:void ( *  signal (int , void (*)(int)))(int);(注意声明时别漏了分号)

附:

我们还可以将复杂的函数指针给进行typedef陈一个简单点的类型,但是要注意写成(对指针类型要把名字放在星号旁边 ):

typedef void (* v_int) (int) ;//注意应该将想要改成的新类型名写到*的旁边
//那么就可以将signal函数的声明写成:
v_int signal (int , v_int);

6.函数指针数组

6.1函数指针数组定义

知识点:

函数指针数组还是和其他数组一样遵循3步原则:

首先,在第五节我们已经知道了函数指针的写法如:void (*p) (int)

所以,3步原则已经满足了两:变量名、*  ,此处为函数指针数组不同于函数指针要加上数组所以就可以写成:void (*p[5])(int) (   此处我们可以这样理解为什么要把[ ]放到里面:通过类比的方法看int arr[ ] 、int * arr[ ] 这些数组类型都是将他们的1.方括号放在了其变量名的后面 并且2.其类型写在变量名前面所以又因为3.变量名要写到*旁边所以写在里面,函数指针数组其本质还是个数组,所以类型为void   ...(int)、在加上其* 、变量名、数组 [ ] 就可以容易的想出void ( * p [5]  ) (int)   )

细节:

函数指针数组的用法:其就和其余数组一样,如int arr[] 存的是多组相同类型(int)的元素(即多组整形1,2,3)

所以函数指针数组就是存多组函数指针类型所对应的数(即多组函数Add,test的地址)

练习:

函数指针数组(在某些时候又称为:转移表)

通过函数指针数组实现计算器:

其用法和就是数组用法+函数指针的用法:先数组调用后在调用各个函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
  printf("********************************\n");
  printf("**********1.Add   2.Sub*********\n");
  printf("**********3.Mul   4.Div*********\n");
  printf("**********   0.exit    *********\n");
  printf("********************************\n");
}
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 Div(int x, int y)
{
  return  x / y;
}
int main()
{
  int input = 0;
  int a = 0;
  int b = 0;
  do
  {
    menu();
    printf("请输入:");
    scanf("%d", &input);
        //下面的函数指针数组有一种可以转移的特性所以就可以称其转移表
    int(*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };
    if (input == 0)
    {
      printf("退出计算器\n");
      break;
    }
    else if (input <= 4 && input >= 1)
    {
      printf("输入两个值:");
      scanf("%d %d", &a, &b);
      printf("%d\n", p[input](a, b));
    }
    else
    {
      printf("输入错误请重新输入\n");
    }
  } while (input);
    return 0;
}

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

7.1

知识点:

指向函数指针数组的指针 如何定义 同样的满足三步法

首先类型 : 函数指针数组:如int (  *   p  [5]   )(  int   ,    int);  --- 标蓝的表示其类型

其次:他是一个数组,所以大致和数组指针相同:int (  *  p  )[5]    --- ( *p )确保其和*优先结合

最后,变量名 ,设其为pf       :   int (* (*  pf  )[ 5 ]  )(  int   ,  int  )   = &p;

细节(注意点):

虽然这个名字一大串,但是我们任然要记得他还是一个指针(地址)其大小仍然时4/8byte的

并且他是一个指向数组的指针所以若+1就是跳过了整个数组,当然若要用也和数组指针类似:(*pf)找到整个数组 再要进行小标访问访问其内部存的内容 (*pf)[1]  == p[1]

在转移表中进行测试Add(  [1]   )

image.png

image.png

练习:

在创建一个.....指针

//函数指针数组
int (*pa [20]) (char ,short);
//对于的函数指针数组的指针
int (*   (* ppa )  [20])  (char , short); 

8.回调指针

8.1

知识点:

8.1.1回调函数的定义:

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

细节:上面的大体意思有:

一个函数的地址(指针)被传递给另一个函数,在另一个函数内被间接性的调用,就称该函数(传地址的函数)为回调函数

其实回调函数,也就是函数指针所调用的函数

回调函数不是由自身的名字调用的,而是由另外的一方调用

练习:

下面是再次修改过后的转移表:

通过三个部分来展示其回调函数的特性

image.png

image.png

其在第二张中其实就是用了函数指针来调用


本章完。预知后事如何,暂听下回分说。

相关文章
|
6天前
|
C语言
c语言指针总结
c语言指针总结
12 1
|
12天前
|
存储 程序员 C语言
【C 言专栏】C 语言指针的深度解析
【4月更文挑战第30天】C 语言中的指针是程序设计的关键,它如同一把钥匙,提供直接内存操作的途径。指针是存储其他变量地址的变量,通过声明如`int *ptr`来使用。它们在动态内存分配、函数参数传递及数组操作中发挥重要作用。然而,误用指针可能导致错误,如空指针引用和内存泄漏。理解指针的运算、与数组和函数的关系,以及在结构体中的应用,是成为熟练 C 语言程序员的必经之路。虽然挑战重重,但掌握指针将增强编程效率和灵活性。不断实践和学习,我们将驾驭指针,探索更广阔的编程世界。
|
12天前
|
存储 C语言
C语言进阶---------作业复习
C语言进阶---------作业复习
|
12天前
|
存储 Linux C语言
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
12天前
|
自然语言处理 Linux 编译器
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
12天前
|
存储 编译器 C语言
C语言进阶第十课 --------文件的操作-1
C语言进阶第十课 --------文件的操作
|
12天前
|
存储 程序员 C语言
C语言进阶第九课 --------动态内存管理-2
C语言进阶第九课 --------动态内存管理
|
12天前
|
编译器 C语言
C语言进阶第九课 --------动态内存管理-1
C语言进阶第九课 --------动态内存管理
|
12天前
|
C语言
C语言进阶第八课 --------通讯录的实现
C语言进阶第八课 --------通讯录的实现
|
13天前
|
算法 搜索推荐 程序员
C语言中的函数指针和回调函数
C语言中的函数指针和回调函数
10 2