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

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


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

相关文章
|
2月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
182 9
|
2月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
65 7
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
36 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
20 2
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
63 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
52 1
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
62 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
68 15

热门文章

最新文章