1.指针数组
指针数组是一个数组,数组中的每个元素都是指针。这些指针可以指向各种类型的数据,如整数、字符、结构体等,甚至可以指向其他数组或函数。
指针数组的声明格式通常为:
数据类型 *数组名[数组大小];
其中,数据类型
表示指针所指向的数据的类型,数组名
是指针数组的名称,数组大小
表示数组中元素的个数。
#include <stdio.h>
int getMax(int data1,int data2)
{
return data1>data2?data1:data2;
}
int getMin(int data1,int data2)
{
return data1<data2?data1:data2;
}
int getSum(int data1,int data2)
{
return data1+data2;
}
int main()
{
int a=10;
int b=20;
int ret;
int(*pfunc[3])(int ,int )={getMax,getMin,getSum};//函数指针数组
for(int i=0;i<3;i++){
ret=(*pfunc[i])(a,b);
printf("ret=%d\n",ret);
}
return 0;
}
代码定义了三个函数:getMax
用于获取两个整数中的最大值,getMin
用于获取两个整数中的最小值,getSum
用于计算两个整数的和。在 main
函数中,创建了一个函数指针数组 pfunc
,数组中的每个元素分别指向这三个函数。通过 for
循环遍历该函数指针数组,依次调用每个函数对给定的两个整数 a
和 b
进行处理,并输出每次调用的结果。
代码详细分析
1. 函数定义部分
int getMax(int data1, int data2)
{
return data1 > data2 ? data1 : data2;
}
getMax
函数接收两个整数参数 data1
和 data2
,使用三元运算符 ? :
比较它们的大小,若 data1
大于 data2
则返回 data1
,否则返回 data2
,即返回两个数中的最大值。
int getMin(int data1, int data2)
{
return data1 < data2 ? data1 : data2;
}
getMin
函数同样接收两个整数参数 data1
和 data2
,使用三元运算符比较大小,若 data1
小于 data2
则返回 data1
,否则返回 data2
,也就是返回两个数中的最小值。
int getSum(int data1, int data2)
{
return data1 + data2;
}
getSum
函数接收两个整数参数 data1
和 data2
,直接返回它们的和。
2. main
函数部分
int main()
{
int a = 10;
int b = 20;
int ret;
int(*pfunc[3])(int, int) = {getMax, getMin, getSum};
在 main
函数中,首先定义了两个整数变量 a
和 b
并分别初始化为 10 和 20,ret
用于存储函数调用的结果。然后声明并初始化了一个函数指针数组 pfunc
,数组包含 3 个元素,每个元素都是一个指向返回值为 int
类型、接收两个 int
类型参数的函数的指针,分别指向 getMax
、getMin
和 getSum
函数。
for (int i = 0; i < 3; i++) {
ret = (*pfunc[i])(a, b);
printf("ret=%d\n", ret);
}
使用 for
循环遍历函数指针数组 pfunc
。在每次循环中,通过 (*pfunc[i])(a, b)
调用当前指针所指向的函数,并将 a
和 b
作为参数传递给该函数,将调用结果存储在 ret
中,最后使用 printf
函数输出结果。
return 0;
}
main
函数返回 0,表示程序正常结束。
3.核心定义与本质区别
特性 | 指针数组(Pointer Array) | 数组指针(Array Pointer) |
---|---|---|
本质 | 是一个 数组,元素是 指针(如 int* arr[5] )。 | 是一个 指针,指向一个 数组(如 int (*ptr)[5] )。 |
语法声明 | 类型* 数组名[长度] ,如 char* strs[3]; | 类型 (*指针名)[长度] ,如 int (*arr_ptr)[10]; |
内存布局 | 数组元素是指针,指针指向的内存可能不连续。 | 指针指向一个连续的数组内存块,数组元素在内存中连续。 |
操作对象 | 操作多个独立的指针(每个指针可指向不同内存)。 | 操作一个整体的数组(将数组视为一个整体对象)。 |
- 指针数组:它本质是数组,数组中的每个元素都是指针。这些指针能够指向各种类型的数据,像整数、字符、结构体等,甚至可以指向其他数组或者函数。
- 数组指针:它本质是指针,该指针指向一个数组。也就是说,数组指针保存的是数组的起始地址,并且它的类型与所指向的数组类型相匹配。
二、适用场景对比
1. 指针数组(适用场景)
-
管理多个独立的指针:
当需要存储多个指针(如字符串指针、函数指针、对象指针等)时,使用指针数组。
示例 1:字符串数组char* strs[] = {"hello", "world", "array"}; // 每个元素是char*,指向不同字符串
优势:方便管理长度不同的字符串(无需固定数组总长度),节省内存(仅存储指针,而非复制字符串内容)。
-
函数指针数组:
当需要通过索引快速调用不同函数时,使用函数指针数组。
示例 2:计算器功能选择int (*funcs[])(int, int) = {getMax, getMin, getSum}; // 指针数组存储函数指针 result = funcs[0](a, b); // 调用第一个函数(取最大值)
优势:通过索引直接调用函数,简化代码逻辑,提高可扩展性。
-
动态内存管理:
每个指针可独立分配内存,适合处理动态变化的数据(如不同长度的数组、链表头指针等)。int* arr1 = malloc(5*sizeof(int)); int* arr2 = malloc(10*sizeof(int)); int* ptr_arr[] = {arr1, arr2}; // 指针数组管理多个动态数组
2. 数组指针(适用场景)
-
处理多维数组或固定长度数组:
当需要将数组作为整体处理(尤其是二维数组),或函数参数需要保留数组维度信息时,使用数组指针。
示例 1:二维数组作为函数参数void print_array(int (*arr)[5], int rows) { // arr是指向包含5个int的数组的指针 for (int i=0; i<rows; i++) { for (int j=0; j<5; j++) { printf("%d ", arr[i][j]); } } } int matrix[3][5] = {{1,2,3,4,5}, ...}; print_array(matrix, 3); // 传递二维数组,数组指针保留列数5的信息
优势:直接通过指针操作二维数组,避免退化为
int**
(二维指针数组)导致的维度信息丢失。 -
访问连续内存块:
当需要操作一个连续的数组(如栈上的数组、固定大小的缓冲区),并将其视为整体时,使用数组指针。int arr[10] = {1,2,3,...}; int (*arr_ptr)[10] = &arr; // 数组指针指向整个数组 printf("%d", (*arr_ptr)[5]); // 访问第6个元素
优势:确保指针操作时保持数组的边界,避免越界(编译时可检查数组长度)。
-
类型安全与语法简洁:
数组指针在声明时明确指定了数组元素的类型和长度,编译器可进行更强的类型检查,避免指针运算错误。
三、关键决策点
-
数据结构的形态:
- 如果需要管理 多个独立的指针(如字符串、函数、动态数组),选 指针数组。
- 如果需要操作 一个整体的数组(尤其是多维数组、固定长度数组),选 数组指针。
-
内存连续性:
- 若数据在内存中 不连续(每个指针指向不同位置),用 指针数组(如字符串数组)。
- 若数据 连续存储(如栈上的数组、静态分配的二维数组),用 数组指针。
-
函数参数传递:
- 当函数需要接收 二维数组 时,用数组指针(如
int (*arr)[cols]
),保留列数信息,避免退化为int**
导致的访问错误。 - 当函数需要接收 多个独立指针(如多个字符串),用指针数组(如
char* strs[]
)。
- 当函数需要接收 二维数组 时,用数组指针(如
-
动态性需求:
- 若需要动态添加 / 删除指针(如指针指向的内容动态变化),用 指针数组(可动态分配数组本身或每个指针)。
- 若数组长度固定(编译时已知),用 数组指针(更安全,语法更直接)。
四、示例对比
场景:处理字符串集合
-
指针数组(推荐):
char* names[] = {"Alice", "Bob", "Charlie"}; // 每个字符串独立存储,指针数组管理地址
优势:字符串长度不同,直接存储指针更高效。
-
数组指针(不适用):
char (*arr_ptr)[20]; // 指向固定长度20的字符数组,无法处理长度不同的字符串
劣势:需固定数组长度,浪费内存或导致越界。
场景:处理二维数组
-
数组指针(推荐):
int matrix[3][5]; int (*ptr)[5] = matrix; // 数组指针指向二维数组的一行(每行5个int)
优势:直接通过
ptr[i][j]
访问元素,保留列数信息。 -
指针数组(不适用,除非是 “行指针数组”):
int* rows[3]; // 每行是独立的int*,需手动分配每行内存,适合不规则二维数组(如锯齿数组)
适用场景:当二维数组各行长度不同时(锯齿数组),用指针数组。
五、总结
- 选指针数组:当需要管理 多个独立的指针(如字符串、函数、动态数组),且数据内存不连续或长度可变时。
- 选数组指针:当需要操作 一个整体的连续数组(尤其是多维数组、固定长度数组),并希望保留数组维度信息或进行类型安全的操作时。
2.指针函数
指针函数本质上是一个函数,只不过它的返回值是一个指针。这个指针可以指向各种类型的数据,如整数、字符、结构体等。
指针函数的声明格式通常为:
数据类型 *函数名(参数列表);
其中,数据类型
表示指针所指向的数据的类型,函数名
是函数的名称,参数列表
是函数的参数类型和数量。
#include <stdio.h>
// 沿用例8.25的指针函数,用于获取学生成绩指针
int* getPosPerson(int pos, int (*pstu)[4]) {
int *p;
p = (int *)(pstu + pos);
return p;
}
int main() {
int scores[3][4] = { // 假设的学生成绩(3学生,4门课)
{55, 66, 77, 88}, // 学生0:第1门不及格
{66, 55, 99, 100}, // 学生1:第2门不及格
{11, 22, 33, 59} // 学生2:全部不及格
};
int student_num = 3; // 学生数量
int course_num = 4; // 课程数量
// 遍历每个学生
for (int i = 0; i < student_num; i++) {
int has_failure = 0; // 标记是否有不及格课程
int *student_scores = getPosPerson(i, scores); // 获取当前学生成绩指针
// 遍历学生的每门课程成绩
for (int j = 0; j < course_num; j++) {
if (*(student_scores + j) < 60) { // 检查成绩是否不及格
has_failure = 1;
break;
}
}
// 输出有不及格课程的学生学号
if (has_failure) {
printf("学生号 %d 存在不及格课程\n", i);
}
}
return 0;
}
区别
概念
- 指针函数:本质是一个函数,和普通函数一样具有特定的功能和执行逻辑,只不过其返回值是一个指针类型。这个指针可以指向各种类型的数据,如基本数据类型(整数、字符等)、数组或者结构体等。例如,在内存管理场景中,可能需要返回一个指向动态分配内存块的指针。
- 函数指针:本质是一个指针变量,它存储的是函数的地址。在程序编译时,每个函数都会被分配一段内存空间,函数名就代表该函数的起始地址,函数指针就是用来存放这个地址的。通过函数指针,可以在运行时动态地调用不同的函数。
语法
- 指针函数:声明格式为
数据类型 *函数名(参数列表);
。例如,int *func(int a, int b);
表示func
是一个指针函数,它接受两个int
类型的参数,返回一个指向int
类型数据的指针。这里的*
结合顺序是从右到左,先确定func
是一个函数,再确定其返回值是指针。 - 函数指针:声明格式为
数据类型 (*指针名)(参数列表);
。例如,int (*fp)(int, int);
表示fp
是一个函数指针,它可以指向返回值为int
类型、接受两个int
类型参数的函数。这里的*
被括号括起来,表明fp
首先是一个指针,然后指向一个函数。
用途
- 指针函数:
- 动态内存分配:在程序运行过程中,根据需要动态地分配内存,并将分配的内存地址返回给调用者。例如,
malloc
函数就是一个典型的指针函数,它返回一个指向分配内存块起始地址的指针。 - 返回复杂数据结构:当函数需要返回一个数组或者结构体等复杂数据结构时,由于函数不能直接返回数组,通常会返回指向这些数据结构的指针。例如,一个函数可以返回一个指向自定义结构体的指针,方便在调用者中访问和操作结构体的成员。
- 动态内存分配:在程序运行过程中,根据需要动态地分配内存,并将分配的内存地址返回给调用者。例如,
- 函数指针:
- 实现回调机制:在很多场景下,需要将一个函数作为参数传递给另一个函数,以便在合适的时机调用,这就是回调函数。例如,在一些排序算法(如
qsort
函数)中,可以传递一个比较函数的指针作为参数,从而实现自定义的比较规则。 - 动态函数调用:可以在运行时根据不同的条件决定调用哪个函数,增强程序的灵活性。比如在一个游戏程序中,根据玩家选择的不同角色,调用不同的角色技能函数。
- 构建函数表:在一些状态机或者菜单系统中,使用函数指针数组(函数表)来管理不同的操作函数,通过索引来快速调用相应的函数。
- 实现回调机制:在很多场景下,需要将一个函数作为参数传递给另一个函数,以便在合适的时机调用,这就是回调函数。例如,在一些排序算法(如
3.二级(多级)指针
定义
- 一级指针:普通指针是指向某个变量的内存地址的变量。例如
int a = 10; int *p = &a;
,这里p
是一级指针,存储变量a
的地址。 - 二级指针:二级指针是指向一级指针的指针。它存储的是一级指针的内存地址。比如
int **pp = &p;
,pp
就是二级指针,它指向一级指针p
。 - 多级指针:以此类推,多级指针就是指向更高一级指针的指针。
声明
- 二级指针:
数据类型 **指针名;
,像int **pp;
声明了一个指向int
类型指针的二级指针。 - 多级指针:更多级指针可依此类推,如
int ***ppp;
声明了一个三级指针。
二级指针的声明格式为:
数据类型 **指针名;
其中,数据类型
表示最终指向的数据的类型,指针名
是二级指针变量的名称。例如,int **pp
声明了一个二级指针 pp
,它最终可以指向一个 int
类型的数据,不过中间需要经过一个一级指针。
使用场景
#include <stdio.h>
int main()
{
int data=100;
int *p=&data;
printf("data的地址是:%p\n",&data);
printf("p保存data地址是:%p\n",p);
int **p2=&p;
printf("p2保存p的地址是:%p\n",p2);
printf("*p2是:%p\n",*p2);//对 p2 进行一次解引用操作 *p2,得到 p 本身,也就是 data 的地址
printf("**p2来访问data:%d\n",**p2);
int ***p3=&p2;
printf("p3保存p2的地址是:%p\n",p3);
printf("*p3是:%p\n",*p3);//对 p3 进行一次解引用操作 *p3,得到 p2 本身,也就是 p 的地址
printf("**p3来访问data的值:%d\n",***p3);
return 0;
}
/*int ***p3 = &p2;:定义一个三级指针 p3,并将二级指针 p2 的地址赋给 p3。也就是说,p3 指向 p2。
printf("p3保存p2的地址是:%p\n", p3);:输出三级指针 p3 所保存的地址,即 p2 的地址。
printf("*p3是:%p\n", *p3);:对 p3 进行一次解引用操作 *p3,得到 p2 本身,也就是 p 的地址。
printf("**p3来访问data的值:%d\n", ***p3);:对 p3 进行三次解引用操作 ***p3,先通过 *p3 得到 p2,再对 p2 进行解引用得到 p,最后对 p 进行解引用得到 data 的值,所以这里输出 100。
*/
- int ***p3 = &p2;:定义一个三级指针 p3,并将二级指针 p2 的地址赋给 p3。也就是说,p3 指向 p2。
- printf("p3保存p2的地址是:%p\n", p3);:输出三级指针 p3 所保存的地址,即 p2 的地址。
- printf("*p3是:%p\n", *p3);:对 p3 进行一次解引用操作 *p3,得到 p2 本身,也就是 p 的地址。
- printf("**p3来访问data的值:%d\n", ***p3);:对 p3 进行三次解引用操作 ***p3,先通过 *p3 得到 p2,再对 p2 进行解引用得到 p,最后对 p 进行解引用得到 data 的值,所以这里输出 100。
#include <stdio.h>
void getPosperson(int pos,int (*pstu)[4],int **ppos)//函数指针,返回指针的函数 用于接收 main 函数中 ppos 指针的地址。
{
*ppos=(int *)(pstu+pos);
}
int main()
{
int scores[3][4]={
{55,66,77,88},
{66,55,99,100},
{11,22,33,90},
};
int *ppos;
int pos;
printf("请输入你需要看的学生号数:0,1,2\n");
scanf("%d",&pos);
getPosperson(pos,scores,&ppos);
for(int i=0;i<4;i++){
printf("%d ",*ppos++);
}
return 0;
}
这段 C 语言代码的主要功能是让用户输入一个学生的编号(范围是 0 - 2),然后程序会输出该学生的四门课程成绩。具体实现是通过一个函数 getPosperson
来获取指定学生成绩数组的起始地址,然后在 main
函数中遍历该学生的四门课程成绩并输出。
代码详细分析
1. getPosperson
函数
void getPosperson(int pos, int (*pstu)[4], int **ppos) {
*ppos = (int *)(pstu + pos);
}
- 参数:
pos
:表示要查询的学生编号,范围是 0 - 2。pstu
:这是一个指向包含 4 个int
类型元素的数组的指针,用于指向存储学生成绩的二维数组scores
。ppos
:这是一个二级指针,用于接收main
函数中ppos
指针的地址,通过修改*ppos
的值,可以改变main
函数中ppos
指针的指向。
- 功能:
pstu + pos
计算出指向指定学生成绩数组的起始地址,由于pstu
是指向包含 4 个int
类型元素的数组的指针,所以pstu + pos
会跳过pos
行,指向第pos
个学生的成绩数组。(int *)
是类型转换,将pstu + pos
的类型转换为int *
类型,以便赋值给*ppos
。*ppos = (int *)(pstu + pos);
将计算得到的地址赋值给*ppos
,也就是修改了main
函数中ppos
指针的指向,使其指向第pos
个学生的成绩数组。
2. main
函数
int main() {
int scores[3][4] = {
{55, 66, 77, 88},
{66, 55, 99, 100},
{11, 22, 33, 90},
};
int *ppos;
int pos;
printf("请输入你需要看的学生号数:0,1,2\n");
scanf("%d", &pos);
getPosperson(pos, scores, &ppos);
for (int i = 0; i < 4; i++) {
printf("%d ", *ppos++);
}
return 0;
}
- 变量定义:
scores
:这是一个 3 行 4 列的二维数组,用于存储 3 个学生的四门课程成绩。ppos
:这是一个一级指针,用于指向指定学生的成绩数组。pos
:用于存储用户输入的学生编号。
- 用户输入:
printf("请输入你需要看的学生号数:0,1,2\n");
提示用户输入学生编号。scanf("%d", &pos);
读取用户输入的学生编号。
- 调用函数:
getPosperson(pos, scores, &ppos);
调用getPosperson
函数,将用户输入的学生编号pos
、二维数组scores
的地址以及ppos
指针的地址传递给该函数,以便获取指定学生成绩数组的起始地址。
- 输出成绩:
for (int i = 0; i < 4; i++) { printf("%d ", *ppos++); }
通过for
循环遍历指定学生的四门课程成绩,并使用*ppos++
输出每个成绩,同时ppos
指针向后移动一位。
当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样。
3. 总结
中小公司大概率考题