指针太乱傻傻分不清?教你如何正确快速理解/函数指针/数组参数、指针参数/函数指针数组【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,说明pfAdd是同一个东西,所以可以直接用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


目录
相关文章
|
1天前
|
安全 C语言
【C语言】如何规避野指针
【C语言】如何规避野指针
9 0
|
1天前
|
存储 编译器 C语言
【C语言】初步解决指针疑惑
【C语言】初步解决指针疑惑
6 0
|
3天前
|
存储 算法 C语言
C语言进阶:顺序表(数据结构基础) (以通讯录项目为代码练习)
C语言进阶:顺序表(数据结构基础) (以通讯录项目为代码练习)
|
18天前
|
存储 C语言
C语言 — 指针进阶篇(下)
C语言 — 指针进阶篇(下)
20 0
|
18天前
|
存储 C语言 C++
C语言 — 指针进阶篇(上)
C语言 — 指针进阶篇(上)
27 0
|
21天前
|
存储 测试技术 C语言
【C语言进阶】 假期测评③
【C语言进阶】 假期测评③
41 1
|
24天前
|
存储 程序员 C语言
C语言指针的概念、语法和实现
在C语言中,指针是其最重要的概念之一。 本文将介绍C语言指针的概念、语法和实现,以及如何使用它们来编写高效的代码。
14 0
|
24天前
|
存储 C语言
【C语言】深入解开指针(三)2
【C语言】深入解开指针(三)
|
1月前
|
存储 人工智能 编译器
C语言指针详解
指针运算,指针和数组,二级指针
C语言指针详解
|
1月前
|
存储 C语言
C语言第二十四弹---指针(八)
C语言第二十四弹---指针(八)