C语言指针与数组深入解析:传参降维、函数指针及实战技巧

远山来客
远山来客
发布时间:2026-10-15 | 3人浏览过
原创

本文深入解析C语言中指针与数组的核心机制,包括数组传参的降维规则、函数指针的定义与使用、函数指针数组在转移表中的应用,以及回调函数的实现原理,并通过典型代码示例帮助读者掌握这些关键概念。

1. 数组参数与指针参数的交互

第1步:理解一维数组传参的降维现象

当数组作为参数传递给函数时,会发生“降维”行为。数组名在绝大多数情况下表示首元素的地址(仅当作为 & 取地址操作符或 sizeof 操作符的操作数时例外)。因此,传递数组实际上是传递指向首元素的指针。

在32位系统中,一个指针占用4个字节。若定义函数参数为 int arr[] 或 int arr[100],编译器实际将其视为 int* arr。这意味着形参接收的是首元素(arr[0])的地址,其类型必须匹配实参元素的类型。

示例代码如下:

void printSize(int arr[], int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printSize(arr, 10);
    return 0;
}

注意:形参中的数组大小(如 100)与实际传递的大小无关,因为此时它本质上只是一个指针。为了代码清晰,建议直接省略大小或使用 int arr[] 写法。

第2步:一级指针的传参与拷贝

指针变量本身也是局部变量,遵循C语言的传值调用规则。当将指针传递给函数时,系统会在栈上为该指针变量开辟新的空间,并拷贝实针变量的值(即内存地址)。

void print(int* p, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", *(p + i));
	}
    printf("\n");
}

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的值(地址)拷贝给函数的形参p
	print(p, sz);
	return 0;
}

虽然指针被拷贝了,但由于拷贝前后两个指针变量存储的地址相同,它们都指向同一块内存区域,因此可以通过修改指针指向的内容来影响原数据(除非对指针本身重新赋值,那只会改变拷贝后的局部指针指向,不影响原指针)。

第3步:二维数组传参与指针类型匹配

二维数组传参同样发生降维。二维数组的“元素”是一维数组,因此降维后是“指向一维数组的指针”(即数组指针)。

若定义函数参数为 char arr[][4],这本质上是一个数组指针 char (*p)[4]。这里必须指定第二维的大小(如 4),因为编译器需要通过这个值来计算下一行数据的起始地址(步长)。如果省略第二维,编译器无法确定数组的行宽,会导致编译错误。第一维的大小可以省略,因为它是首行数组的地址,指针类型不依赖行数。

第4步:野指针的风险防范

在使用指针时需警惕以下形成野指针的场景:

  • 未初始化:指针默认值为随机垃圾值,直接解引用会导致非法内存访问。
  • 越界访问:指针指向不属于当前进程或对象的内存区域。
  • 释放后未置空:动态内存(malloc/free)被释放后,若指针未设为 NULL,它仍指向已回收的内存,再次访问即为野指针。
  • 返回局部变量地址:函数执行结束后,其栈帧销毁,局部变量的内存不再有效,返回其地址将导致悬垂指针。

2. 函数指针的深度应用

第5步:认识函数地址与函数指针定义

函数在内存中占据一段代码空间,也有地址。&函数名 和单独 函数名 均可表示函数的入口地址。要将函数地址保存起来,需定义函数指针。

语法要点:

  • void (*pfun)();:pfun 先与 * 结合,说明是指针;指向无参、无返回值的函数。
  • int (*p)(int, int);:指向返回值类型为 int,且接受两个 int 参数的函数。

调用方式与直接调用函数相同,直接使用指针变量名即可:p(1, 2);。

第6步:特殊指针表达式解析

C语言中存在一些复杂的指针表达式,例如:

(*(void (*)())0)();

解析步骤:

  1. (void (*)())0:将整数 0 强制转换为指向无参、无返回值函数的指针类型。
  2. *...:对转换后的指针进行解引用,定位到地址0处的函数实体。
  3. ():对该函数实体进行调用。

此代码尝试在内存地址0处查找并执行一个无参函数(通常在嵌入式开发中用于复位或跳转至固定地址,在标准应用中可能因访问受保护内存而崩溃)。

3. 函数指针数组与转移表

第7步:构建函数指针数组实现多态逻辑

函数指针数组是存放函数指针的数组,常用于实现简单的“转移表”或模拟多态行为。语法格式如下:

int (*arr[10])(int, int); // 数组arr包含10个函数指针,每个指针指向带2个int参数、返回int的函数

利用此特性,可以实现一个简单的计算器逻辑,通过索引选择运算方式:

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_func(int x, int y) { return x / y; }

int main()
{
	// 将四个函数地址存入数组
	int (*arr[4])(int, int) = { add, sub, mul, div_func };
	
	printf("加法:%d\n", arr[0](1, 2));   // 输出 3
	printf("减法:%d\n", arr[1](5, 2));   // 输出 3
	printf("乘法:%d\n", arr[2](3, 3));   // 输出 9
	printf("除法:%d\n", arr[3](6, 2));   // 输出 3
	return 0;
}

这种方法避免了冗长的 if-else 或 switch 结构,提高了可扩展性。

第8步:指向函数指针数组的指针

这种嵌套结构较为罕见,主要用于需要批量处理函数指针数组的高级场景。其定义极为复杂:

int (*test(char* str)) { ... } // 普通函数

int (*pfunArr[5])(char*);     // 函数指针数组,含5个元素

// 指向该函数指针数组的指针
int (*(*ppfunArr)[5])(char*) = &pfunArr;

解析 int (*(*ppfunArr)[5])(char*):

  1. (*ppfunArr):ppfunArr 是指针。
  2. [5]:它指向一个大小为5的数组。
  3. (char*) 和 int:数组元素的类型是“指向函数(参数为char*,返回值为int)的指针”。

4. 回调函数与综合实战

第9步:理解回调函数机制

回调函数是通过函数指针调用的函数。调用者不需要知道具体执行哪个函数,只需传递函数地址。标准库中的 qsort 就是典型的回调函数应用。

简单演示:使用回调函数比较三个数的大小。

// 通用最大值查找,依赖传入的比较规则
int max(int x, int y, int z, int(*cmp)(int, int))
{
	// 假设cmp实现了判断a是否大于b的逻辑
	if (x > cmp(y, z)) {
		return x;
	} else {
		return cmp(y, z);
	}
}

// 具体的比较逻辑
int tmp(int x, int y) 
{ 
	return x > y ? x : y;
}

int main()
{
	int ret = max(10, 20, 30, tmp);
	printf("%d\n", ret);
	return 0;
}

第10步:复杂指针面试题解析

面对复杂的指针操作题,关键在于理清层级关系和运算符优先级。以下题目涉及多级指针解引用:

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" }; // c是字符串指针数组
	char** cp[] = { c + 3,c + 2,c + 1,c };        // cp是指向char*的指针数组
	char*** cpp = cp;                             // cpp是指向char**的指针
	
	printf("%s\n", **++cpp);      // 解析见下方
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

解题思路提示:建议画图分析内存布局。c 指向四个字符串首字符;cp 中的元素分别指向 c 的不同位置;cpp 指向 cp 的首元素。随着 cpp 自增或下标变化,解引用路径会动态改变。若能独立推导输出结果,表明已熟练掌握多维指针。

以上涵盖了C语言中指针与数组最核心的高阶用法。从基础传参到函数指针数组,再到回调机制,层层递进。建议初学者配合绘图工具梳理内存地址流动过程,以巩固理解。

以上就是C语言指针和数组深入探究详细内容的介绍,更多关于C语言指针数组用法的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表