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

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


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

相关文章
|
4天前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
4天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
4天前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
5天前
|
C语言
C语言指针(3)
C语言指针(3)
9 1
|
10天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
25 3
|
1天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
17 10
|
4天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
11天前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
27 2
|
13天前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
16 3
|
16天前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数