[C笔记]指针进阶-数组与函数与指针

回顾:
数组名是首元素的地址——除以下两种情况外:
1.sizeof(数组名)
2.&数组名
这两种情况下数组名代表的是数组整体

指针是地址,指针变量是存放地址的变量,而指针变量经常简称为"指针"

数组下标与指针的关系

p[i] == *(p+i)
parr[i][j] == *(parr[i]+j) == *(*(parr+i)+j)

指针数组变量与数组指针变量

数组指针变量的定义格式

元素类型 (*数组指针名) [元素数量]

数组指针的类型格式

把“数组指针名”去掉就是其类型:

元素类型 (*) [元素数量]

例子

已知[]的结合优先级比*更高

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

一维数组传参

本质上传的是首元素地址
形参数组元素个数无关紧要,可以省略

#include <stdio.h>

void test1(int arr[]){}//数组形式的形参
void test2(int arr[10]){}
void test3(int arr[100]){}//这样虽然语法正确,但乱填会使人误会
void test4(int* arr){}//指针形式的形参

void test5(int *arr[20]){}
void test6(int *arr[]){}
void test7(int **arr){}//arr2是int*类型元素的指针数组

int main(){
	int arr[10]={0};
	int* arr2[20]={0};
	
	test1(arr);
	test2(arr);
	test3(arr);
	test4(arr);
	
	test5(arr2);
	test6(arr2);
	test7(arr2);
	
	return 0;
}

二维数组传参

函数形参只能省略第一个[]的数字。因为对于一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

二维数组看作以一维数组为元素的一维数组。
因此二维数组的“首元素地址”,首元素指的是第一行。
所以“首元素的地址”存放在一个指向一维数组的指针中

二维数组与二级指针没有必然联系,传一级指针变量的地址才可以用二级指针,但二维数组传的是一维数组的地址

#include <stdio.h>

void test1(int arr[3][5]){}
void test2(int arr[][5]){}
void test3(int(*p)[5]){}//指向首元素----二维数组的第一行(一个一维数组)的指针

int main(){
	int arr[3][5] = { 0 };
	test1(arr);
	test2(arr);
	test3(arr);

	return 0;
}

一级指针传参

#include <stdio.h>

void print(int *p, int sz){
	int i = 0;
	for (i = 0; i < sz; i++){
		printf("%d\n", *(p + i));//p[i] == *(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;
}

已知一个函数的参数为一级指针,可以传什么样的参数给这个函数?

#include <stdio.h>

void test(int *p){}

int main(){
	int a = 10;
	int* p1 = &a;
	int arr[10] = { 0 };

	test(&a);//传普通整型变量地址
	test(p1);//传一级指针
	test(arr);//传数组名

	return 0;
}

二级指针传参

#include <stdio.h>

void test(int **ppa){}

int main(){
	int a = 10;
	int* pa = &a;
	int** ppa= &pa;

	int* arr[5];

	test(ppa);//传二级指针变量
	test(&pa);//传一级指针变量的地址
	test(arr);//传一级指针数组的数组名,也是传一级指针变量的地址

	return 0;
}

函数指针变量

#include <stdio.h>

void test(){
printf("hehe\n");
}

int main(){
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}

输出结果:
输出结果
“数组名”和“&数组名”值一样但意义不同,而“函数名”与“&函数名”的值和意义相同

函数指针变量的格式

函数的返回类型 (*函数指针名) (第一个参数类型, ... ,第n个参数类型)

函数指针的类型

把“函数指针名”去掉就是其类型:

函数的返回类型 (*) (第一个参数类型, ... ,第n个参数类型)

使用函数指针调用函数

#include <stdio.h>

int Add(int x, int y) {
	return x + y;
}

int main() {
	int (*pf)(int, int) = &Add;//pf是函数指针变量
	int (*pf)(int, int) = Add;//效果相同,因为“函数名”与“&函数名”的值和意义相同
	int ret = Add(2, 3);//ret == 5//直接调用函数
	
	ret = (*pf)(4, 5);//ret == 9//对pf解引用并传参,效果相同
	ret = pf(4, 5);//因为含义与值都相同,不解引用也一样
	ret = (****pf)(4, 5);//多来几个*也没区别,*只是摆设
	
	return 0;
}

函数指针用*解引用的意义主要在于从语法层面更方便初学者理解

函数指针例

以下两例取自《C陷阱和缺陷》一书

(*(void(*)())0)();
//void(*)() 为无参数且不返回的函数指针的类型,
//,然后将0这个原本为int类型的值进行强制类型转换,
//,转换为void(*)() ,也就是将0当作函数地址。
//之后使用*解引用这个函数指针,调用地址为0的函数,
//由于该函数无参,所以最后的()内无参。
//综上,该代码表示了一次函数调用
void (* signal(int,void(*)(int)))(int);
//signal是函数名
//signal函数有两个参数,第一个是int类型,
//,第二个是void(*)(int)的函数指针类型
//signal函数的返回类型还是void(*)(int)的函数指针类型
//综上,该代码是一次函数声明

//上面的代码可以简化:
typedef void(*pf)(int);//将pf作为void(*)(int)类型的别名
//注意,不能写成typedef void(*)(int) pf; 这是错误写法
pf signal(int, pf);

函数指针数组

存放函数指针的数组

函数指针数组的格式

函数的返回类型 (*函数指针数组名[元素个数]) (第一个参数类型, ... ,第n个参数类型)

函数指针数组的用途例:转移表(简易计算器)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int Add(int x, int y) {
	return x + y;
}//类型为 int (*) (int, int)
int Sub(int x, int y) {
	return x - y;
}//类型同为 int (*) (int, int)
int Mul(int x, int y) {
	return x * y;
}//类型同为 int (*) (int, int)
int Div(int x, int y) {
	return x / y;
}//类型同为 int (*) (int, int)

int main() {
	int x, y;
	int input = 1;
	int ret = 0;
	int (*p[5])(int, int) ={0,Add,Sub,Mul,Div};//转移表-参阅《C和指针》
	while (input) {
		printf("0.exit 1.add 2.sub 3.mul 4.div\n");
		printf("输入选项数字\n");
		scanf("%d", &input);
		if (input == 0) {
			printf("退出计算器...\n");
			return 0;
		}
		if ((input <= 4 && input >= 1)){
			printf("输入两个整型操作数,用空格隔开:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
		printf("\n");
	}
	return 0;
}

指向函数指针数组的指针

指向函数指针数组的指针是一个指针,该指针指向一个数组 ,该数组的元素都是函数指针:

void test(const char* str){
	printf("%s\n", str);
}
int main(){
	//函数指针pf
	void (*pf)(const char*) = test;
	//函数指针的数组pfArr
	void (*pfArr[5])(const char* str);
	pfArr[0] = test;
	//指向函数指针数组pfArr的指针ppfArr
	void (*(*ppfArr)[10])(const char*) = &pfArr;
	return 0;
}

回调函数-依赖函数指针实现

-回调函数是通过函数指针调用的函数。
-若将函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就称之为回调函数。
-回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数例

不使用转移表实现前文中的计算器:

#define _CRT_SECURE_NO_WARNINGS 1
#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 Calc(int(*pf)(int, int)){
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("输入两个整型操作数,用空格隔开:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);//由Calc函数根据所传地址调用计算用的函数,而不是单独写出各计算函数的调用
	printf("ret = %d\n", ret);
}

int main(){
	int input = 0;
	do{
		printf("0.exit 1.add 2.sub 3.mul 4.div\n");
		printf("输入选项数字\n");
		scanf("%d", &input);
		switch (input){
		case 1:
			Calc(Add);//Calc是Add函数的调用方
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:
			printf("退出计算器...\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值