进阶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

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


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

相关文章
|
24天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
25天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
25天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
24天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
17 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
23 6
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
21天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
26天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
54 7
|
26天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4