指针太乱傻傻分不清?教你如何正确快速理解/函数指针/数组参数、指针参数/函数指针数组【C语言/指针/进阶/程序员内功修炼】【中】

简介: 指针太乱傻傻分不清?教你如何正确快速理解/函数指针/数组参数、指针参数/函数指针数组【C语言/指针/进阶/程序员内功修炼】【中】

前言

指针进阶【上】详细讲解了字符指针/指针数组/数组指针,分析了如何理解指针,这对我们今后使用它非常重要,本篇文章将接着以类似的思路讲解函数指针/数组参数、指针参数/函数指针数组

复习回顾

//int arr[5];
//arr是一个整形数组,每个元素是int类型的,有5个元素
//int* parr1[10];
//parr1是一个数组,数组10个元素,每个元素的类型是int*
//int(*parr2)[10];
//parr2是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是int
//int(* parr3[10])[5];
//parr3 是一个数组,数组有10个元素,每个元素的类型是:int(*)[5]
//parr3是存放数组指针的数组

对于int(* parr3[10])[5]

parr3先与括号结合,是一个数组,数组名是parr3,去掉数组名和[10],int(*)[5]是元素类型

这个5表示它指向的每个地址对应的数组有多少个元素,不能省略

4. 数组参数、指针参数

4.1 一维数组传参

写成数组/指针的形式

数组才传参的时候是传递首元素地址(数组名),不是整个数组,数组大小可以省略,(改成其他数字也不会报错,但不推荐),而且函数接收数组,不会再创建一个数组,所以形参可以写成指针(写成数组形式只是语法形式,本质上是一样的)

事实上,编译器在编译时会将数组形式转化为指针形式。

数组形式:1. 初学者更容易学习使用 2. 更容易看得清楚,用数组传,用数组接收

//形参写成数组的形式
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int arr[100])//ok//不会报错,但这样写会误导人的
{}
void test(int *arr)//ok
{}
//形参写成指针形式
void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok//二级指针
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

4.2 二维数组

例子

//形参写成数组的形式
void test(int arr[3][5])//ok
{}
void test(int arr[][])//no
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//因为数组是一块连续存放的内存,而不是形象的“XoY表格”↑,知道列数可以算出行数
//形参写成指针形式
void test(int* arr)//no//一级指针//接收普通变量的地址
{}
void test(int* arr[5])//no//这是指针数组
{}
void test(int (*arr)[5])//ok
{}
void test(int* * arr)//no//二级指针//接收一级指针的地址
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}

对(int (*arr)[5])的理解

数组首元素地址(数组名)对二维数组来说是第一行首元素的地址,它代表着二维数组的第一行。

当函数形参要接收二维数组首元素地址时,需要包含两个信息:数组的元素类型和元素个数。(*arr)表示接收的是一个地址,int和[5]表示接收的地址指向5个int类型的元素。

4.3 一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);//打印数组的函数
return 0;
}

理解

将数组首元素地址给p,将p传给函数,函数中接收的即为数组首元素地址。要使用某一位的数组元素,首地址+对应数字再解引用即可。

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr)//传二级指针,用二级指针接收
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p; 
char* arr[4];//指针数组
test(pp);//直接传二级指针
test(&p);//传一级指针的地址
test(arr);//每个元素是char*型指针,首元素地址即char*型指针的地址,即char* *型
return 0;
//可不可以传二维数组的首元素地址呢?形参如何表示?
//char (*arr)[5];
//char* arr[3][5]可以吗?
}

传递二维数组的数组名和首元素地址

void test1(int (*p)[5])
{}
void test2(int(*p)[3][5])
{
  *p;
}
int main()
{
  int arr[3][5];
  test1(arr);//传递的第一行的地址
  test2(&arr);//传递的是整个二维数组的地址//仅举例,实际上几乎不这么用
  return 0;
}

对arr和&arr的理解

对于二维数组,arr是第一行首元素的地址,它代表着第一行,所以函数形参要定义为int (*p)[5],表明它接收的是以行为单位的地址

&arr是数组的地址,代表着整个数组,所以函数形参要定义为int(*p)[3][5],表示它接收的是整个数组

5. 函数指针

指向函数的指针

例子

int test(char* str)
{
}
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int arr[10];
  //arr
  //&arr
  int (*p)[10] = &arr;//p是一个数组指针变量
  printf("%p\n",  &Add);
  printf("%p\n", Add);
  //**对于函数,写法不同,意义相同**
  int (* pf)(int, int) = Add;//pf是函数指针变量
  int ret = (*pf)(2,3);//解引用找到函数,然后传参
  //int ret = Add(2, 3);
  //int ret = pf(2, 3);
  printf("%d\n", ret);
  //int (*pt)(char*) = test;
  return 0;
}

对于函数,&Add和Add的写法不同,意义相同

对int (* pf)(int, int) = Add的理解

它与数组指针的分析思路非常相似。

  1. 先从指针出发,因为是指针所以有(*pf)
  2. 因为函数的参数都是int型,而且有两个,所以有(int, int)
  3. 因为函数返回类型是int,所以有开头加上int
  4. 关于函数指针类型:去掉变量名即为函数指针类型,如:int (*)(int, int)

以下两种调用函数的写法等价

int ret = (*pf)(2,3);//解引用找到函数,然后传参
  int ret = pf(2, 3);//直接将pf当作函数名
  1. pf存放的是函数的地址(即Add/&Add),对它解引用符合语法,符合常理,容易让人理解,不过注意要加括号。
  2. 之前我们调用函数是直接用函数名的,但前面将函数地址赋值给pf,说明pf和Add是同一个东西,所以可以直接用pf代替函数名调用函数
  3. 由以上两点表明,解引用的这个*可有可无(且不管有多少个*),只不过这样做容易让人理解,是一种符合语法的补充

解读两段代码

//代码1
(*(void (*)())0)();

对 (*(void (*)())0)()的理解

  1. 括号从外层匹配
  2. 指针去掉函数名即指针类型,所以void (*)()表示指针类型
  3. (指针类型) +对象⇒强制类型转换。所以( void (*)() ) 表示强制类型转换
  4. ( void (*)() )0 表示对0进行强制类型的转换
  5. *( void (*)() )0 表示找到0这个地址的函数,其实这里最前面的*可以省略,请看以上对两种调用函数的写法等价的解读。
  6. ( *( void (*)() )0 )(),表示函数无参
  7. 总的来说:首先是把0强制类型转换为一个函数指针类型(指针是干嘛的?),这就意味着0地址处放一个返回类型是void、无参的一个函数,然后调用0地址处的这个函数
  8. 注意:这里只是用0举例,实际上0地址在一般情况下无法被使用,这种强转给人的感觉就好像一个数字被当成地址使用一样,而且地址本身也是用数字表示的
//代码2
void (*signal(int , void(*)(int)))(int);//这是一个函数声明

对函数声明void (*signal(int , void(*)(int)))(int)的理解

  1. signal先与括号结合,signal(),说明它是一个函数的声明
  2. 括号内表示参数类型 ,int型和void(*)(int) 函数指针型 ,所以signal(int , void(*)(int)) 表示一个函数
  3. 举例:我们这样声明一个函数:返回值类型+函数名+(形参类型),只要将函数名和形参类型去掉就是返回值类型,所以对于 void (*signal(int , void(*)(int)))(int),去掉signal(int , void(*)(int))
    ,剩下void (*)(int) 即为signal函数的返回值类型,它是一个函数指针

函数的一个参数和函数的返回值类型都是相同的,如何简化以上代码?

将void (*)(int)看成一个整体

typedef void(* pf_t)(int) ;
//给函数指针类型void(*)(int)重新起名叫:pf_t
  pf_t signal(int, pf_t);

6. 函数指针数组

例子

//函数指针 数组
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()
{
  //指针 数组
  //字符指针 数组
  char* arr[5];
  //整型指针 数组
  int* arr2[4];
  int (*pf1)(int, int) = Add;
  int (*pf2)(int, int) = Sub;
  int (*pf3)(int, int) = Mul;
  int (*pf4)(int, int) = Div;
  //函数指针 数组
  int (* pf[4])(int, int) = { Add, Sub, Mul, Div };

对int (* pf[4])(int, int)的理解

  1. pf1-pf4是指向函数的指针,用数组存放这些指针,即为函数指针数组
  2. 首先它得是个数组,所以数组名pf要紧跟[ ],其他与函数指针的形式一致
  3. 函数指针数组在形式上:在函数指针的基础上+[ ],初始化时括号内数字可省略

通过函数指针数组调用函数

for (int i = 0; i < 4; i++)
  {
    int ret = pf[i](8, 2);//函数传参
    printf("%d\n", ret);
  }
  return 0;
}

体会:函数指针数组就好像一个中介,它起着链接函数的作用,对于以上代码的更多解读,请戳通过模拟实现计算器掌握函数指针数组的用法

练习

//定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,
//返回的指针指向一个有一个int形参且返回int的函数
//int* ((*p)(int, int))(int)
//一个参数为int *,返回值为int的函数指针
//int (*p)(int*)
//声明一个指向含有10个元素的数组的指针,
//其中每个元素是一个函数指针,该函数的返回值是int,参数是int*
//int(*(*p)[10])(int*)
//设有以下函数void fun(int n,char* s){……},
//则如何对函数指针的定义和赋值?
//void (*p)(int n, char* s);
//p = &fun 或p = fun


目录
相关文章
|
存储 人工智能 程序员
一文彻底搞明白C语言的数组
本文详细介绍了C语言中的数组,包括定义、初始化(静态与动态)、存储方式、访问方法及常用操作,如遍历、修改元素和作为函数参数传递。数组是C语言中最基本的数据结构之一,掌握它对编程至关重要。下篇将介绍二维数组,敬请期待!
1081 0
一文彻底搞明白C语言的数组
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
2326 13
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
1314 6
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
702 5
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
1024 4
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
526 4
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
1068 3
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
1078 4
|
编译器 C语言
【C语言初阶】指针篇—下
【C语言初阶】指针篇—下