C语言指针 中篇

153 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

一、数组传参和指针传参

❓❔ 写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

1、一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int* arr)//ok
{}
void test2(int *arr[20])//ok,arr2是指针数组,这里使用指针数组来接收可以匹配
{}
void test2(int **arr)//ok,arr2传的是首元素地址 ———— int*,这里使用二级指针来接收一级指针没有问题
{}
int main()
{
	int arr[10] = {0};
	int* arr2[20] = {0};
	test(arr);
	test2(arr2);
	return 0;
}

2、二维数组的传参

#include <stdio.h>
void test(int arr[3][5])//ok
{}
void test(int arr[][])//err,二维数组传参,参数可以写成数组,但列不能省略
{}
void test(int arr[][5])//ok
{}
void test(int *arr)//err,二维数组传参,传过来的是数组首地址 ———— 一维数组,不能匹配
{}
void test(int* arr[5])//err,这里是存放指针的数组不能匹配
{}
void test(int (*arr)[5])//ok,这里使用数组指针,它刚好指向二维数组的首地址
{}
void test(int **arr)//err,二级指针不能匹配一维数组的地址
{}
int main()
{
	 int arr[3][5] = {0};
	 test(arr);
}

3、一级指针传参

#include<stdio.h>
void print(int* ptr, int sz)//一级指针变量传参用一级指针接收
{
	int i = 0;
	for(i = 0; i < sz; i++)
	{
		printf("%d ", *(ptr + 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]);
	print(p, sz);	
	return 0;
}

❓❔ 思考:函数参数部分为一级指针的时候,函数能接收什么参数? 当参数部分为一级指针的时候,函数能接收变量的地址或者一级指针变量

#include<stdio.h>
void test(char* p)
{}
int main()
{
	char ch = 'w';
	char* p1 = &ch;
	test(&ch);//ok
	test(p1);//ok			
	return 0;
}

4、二级指针传参

#include<stdio.h>
void test(int** p2)//二级指针变量传参使用二级指针来接收
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	//把二级指针ppa传参
	test(ppa);
	printf("%d\n", a);
	return 0;
}

❓❔ 思考:当函数的参数为二级指针的时候,可以接收什么参数? 当参数部分为二级指针的时候,函数可以接收一级指针变量的地址或者二级指针变量或者指针数组的数组名

#include<stdio.h>
void test(int** p2)
{

}
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	int* arr[10] = {0};
	test(&pa);
	test(ppa);	
	test(arr);
	return 0;
}

二、函数指针

1、什么是函数指针

💨 函数指针顾名思义是指向函数的指针

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//对于变量或数组的地址使用 &,当然对于一个函数的地址也可以使用 &
	printf("%p\n", &Add);
	printf("%p\n", Add);
	//这里有同学就有疑问了,对于函数名同数组名是一样的概念吗?
	//&数组名 != 数组名
	//&函数名 == 函数名
	//-----------------------------------------------------
	//如何存储函数的地址
	//这里pf就是一个函数指针变量,它指向2个int类型参数,返回类型是int的函数
	int (*pf)(int, int) = &Add;
	 
	return 0;
}

2、小例1:

💨 现有一个函数,void test (char* str),将test函数存储

void (*pt) (char) = &test;

3、函数指针的使用

❓❔ 指针指向函数,但是我们如何通过指针调用函数

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;
	//把指向函数的指针解引用找到函数,传参即可
	int ret = (*pf)(3, 5);
	//int ret = *pf(3, 5);//err,这里相当于是把3和5传参后的返回值再解引用
	printf("%d\n", ret);
	return 0;
}

❓❔ 思考 :指向函数的指针必须需要解引用吗

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//int (*pf)(int, int) = &Add;
	//&Add == Add,所以:
	int (*pf)(int, int) = Add;//这里就说明了pf == Add
	
	//int ret = (*pf)(3, 5);
	//int ret = Add(3, 5);//函数名调用函数
	int ret = pf(3, 5);//说明那颗星是个摆设,无意义(写几颗星都没问题)。实际上只上为了方便理解
	printf("%d\n", ret);
	return 0;
}

4、函数指针实例1(出自《C陷阱和缺陷》):

🍳 有一次,一个程序员与我交谈一个问题。他当时正在编写一个独立运行于某种微处理器上的C程序,当计算机启动时,硬件将调用首地址为0位置的子例程为了 为了模拟开机启动时的情形,我们必须设计一个C语句,以显示调用该子例程。经过一段时间的思考,我们最后得到的语句如下: 在这里插入图片描述

分析: 1、void () () -> 函数指针类型 2、(void () () ) 0 -> 对0进行强制类型转换,被解释为一个函数地址 3、* (void () () ) 0 -> 对地址进行解引用操作 4、 (void (*) () ) 0 () -> 调用0地址处的函数 在这里插入图片描述 总结: ==这是一次函数调用==

5、函数指针实例2(出自《C陷阱和缺陷》):

在这里插入图片描述

分析: 1、signal和()先结合,说明signal是函数名 2、signal函数的第1个参数的类型是int,第2个参数的类型是函数指针 3、signal的返回类型也是一个函数指针 在这里插入图片描述 ==其实这样更容易理解,但是语法不是这样规定的== 在这里插入图片描述 总结: ==signal是一个函数的声明==

6、使用typedef对实例2中的代码进行简化

💨 typedef可以对类型进行重命名

#include<stdio.h>
struct Student
{
	int age;
	char name[20];
	char sex[10];
};
int main()
{
	//struct Student s = { 0 };
	//===================================================
	typedef struct Student s_Stu;
	s_Stu s = { 0 };//同上注释
	return 0;
}

💨 所以这里就使用typedef将实例2中的代码简化

int main()
{
	void (*signal (int , void(*) (int) ) ) (int);
	//typedef void(*)(int) pfun_t;//err,这里又有点特殊,pfun_t不能写在后面
	typedef void(*pfun_t)(int);//而应该写在里面。这里就将void(*)(int)类型重命名为pfun_t
	//所以,简化后:
	pfun_t signal(int, pfun_t);
}

三、函数指针数组

1、什么是函数指针数组

💨 整型指针 -> int* 💨 整型指针数组 -> 存放整型指针的数组 -> int* arr[5] 💨 函数指针数组 -> 存放函数指针的数组 -> ???

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	//将函数Add和Sub存储于指针pf1和pf2。发现pf1和pf2的类型是一样的
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = &Sub;
	//数组是存放相同类型元素的集合,那当然能定义一个数组来存储Add和Sub
	//这里pfArr就是一个函数指针数组,而对于数组而言去掉数组名和[]剩下的就是类型int (*)(int, int) -> 每个元素就是函数指针类型
	int (*pfArr[2])(int, int) = {Add, Sub};
	return 0;
}

2、实例1(转移表):实现计算器

1、错误示范:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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;
}
void menu()
{
	printf("********************************\n");
	printf("********1.add      2.sub********\n");
	printf("********3.mul      4.div********\n");
	printf("********     0.exit     ********\n");
	printf("********************************\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		printf("请输入2个操作数:>\n");	
		scanf("%d %d", &x, &y);
		switch(input)
		{
		case 1:
			ret = Add(x, y);
			break;
		case 2:
			ret = Sub(x, y);
			break;
		case 3:
			ret = Mul(x, y);
			break;
		case 4:
			ret = Div(x, y); 
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;	
		}
		printf("ret = %d\n", ret);
	}while(input);
	return 0;
}

💢 这里是有问题的:其1当这里输入的数字不符合要求或者为0时,它必不可少的都要先执行"printf("请输入2个操作数:>\n"); scanf("%d %d", &x, &y);"这两条语句。其2是当这里输入的数字没有任何返回结果时,它也会输出ret = 0。所以这段代码是有bug的 在这里插入图片描述

2、纠正1

💨 在知识储备不够完善的情况下大多数人都会这样去改

//... ...以上省略
int main()
{
	int input = 0;
	do
	{
		menu();
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			printf("请输入2个操作数:>\n");	
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:>\n");	
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:>\n");	
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数:>\n");	
			scanf("%d %d", &x, &y);
			ret = Div(x, y); 
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;	
		}
	}while(input);
	return 0;
}

💨 结果:这2处bug都解决了 💨 缺陷: ▶1、代码冗余(大量重复的代码)     ▶2、可维护性低(后期需要增加其它功能时,不方便)     ▶3、可读性差(对于程序员来说,在读代码的过程中,要不断的向上翻和向下翻) 在这里插入图片描述

3、纠正3(转移表)

💨 转移表是通过数组的下标访问具体的函数 ▶ 使用函数指针数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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;
}
void menu()
{
	printf("********************************\n");
	printf("********1.add      2.sub********\n");
	printf("********3.mul      4.div********\n");
	printf("********     0.exit     ********\n");
	printf("********************************\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();
		//pfArr就是函数指针数组,通过下标可以访问函数。但前提是函数的参数,返回类型都是统一的才行
		//这里就好像一个跳板,所以经常把这样一个数组叫转移表,在《C和指针》中有提到
		int(*pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		if(input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>\n");	
			scanf("%d %d", &x, &y);
			ret = (pfArr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出程序\n");
			break;
		}	
		else
		{
			printf("选择错误\n");
		}
	}while(input);
	return 0;
}

四、指向函数指针数组的指针

1、什么是指向函数指针数组的指针

💨 函数指针数组本质上是数组,取出函数指针数组的地址给指针就是指向函数指针数组的指针

对于整型数组,要存储arr1数组 int arr1 [5]; int (p1) [5] = &arr; ------------------------------------分割线------------------------------------ 对于整型指针数组,要存储arr2数组 int arr2 [5]; int* (*p2) [5] = &arr2; ------------------------------------分割线------------------------------------ ==对于函数指针数组,要存储arr3数组== ==这里p3就是指向函数指针数组的指针,这个数组有5个元素,每个元素是函数指针类型== int (*arr3 [5]) (int, int) int ( * (*p3) [5]) (int, int) = &arr3;

2、小例1:

#include<stdio.h>
void test(const char* str) 
{
	;
}
int main()
{
 	//函数指针pfun
	 void (*pfun)(const char*) = test;
	 //函数指针的数组pfunArr
	 void (*pfunArr[5])(const char* str);
	 //指向函数指针数组pfunArr的指针ppfunArr
	 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	 return 0;
  }