前面咱们把函数指针讲完了,那节课说的是“指针指向函数”。今天咱们反过来——函数返回一个指针。
这俩听着好像差不多,但其实完全是两码事。函数指针是“你手里拿个指针,指着某个函数”。返回指针的函数是“你调用一个函数,它甩给你一个地址”。方向正好反过来。
那问题来了:函数返回指针有啥用?啥时候需要这么干?
咱们慢慢说。
先回忆一下普通函数返回啥
你写过的函数,返回值大部分是整数、浮点数、字符。比如:
int add(int a, int b) { return a + b; }
调用add(3,5),它返回8。这个8是个值,你拿个变量接着就行。
那如果我想让函数返回一个地址呢?比如我写个函数,从数组里找到最大值,然后把最大值所在的位置(地址)返回给你,而不是把最大值本身返回给你。
这时候就需要返回指针的函数了。
返回指针的函数长啥样?
格式特别简单,就是在函数名前面加个*:
返回类型 *函数名(参数列表) { // ... return 指针; }
比如:
int* find_max(int arr[], int n) { int *maxPos = &arr[0]; // 假设第一个是最大的 for (int i = 1; i < n; i++) { if (arr[i] > *maxPos) { maxPos = &arr[i]; // 更新最大值的位置 } } return maxPos; // 返回最大值的地址 }
这个函数的返回值类型是int*——一个指向int的指针。它不返回最大值本身,返回的是最大值在数组里的地址。
调用它的时候:
int a[5] = {3, 7, 1, 9, 4}; int *p = find_max(a, 5); printf("最大值:%d\n", *p); // 输出9 printf("位置:%d\n", p - a); // 输出3(下标)
你看,find_max返回了一个地址,p接住了它。通过*p拿到最大值,通过p - a算出它在数组里的下标。
这就是返回指针的函数最典型的用法——返回一个位置,而不是一个值。
啥时候需要返回指针?
我总结了几种常见场景:
场景一:从数组里找东西,返回找到的位置
就像上面的find_max。你不仅想知道最大值是多少,还想知道它在哪儿。返回指针就能同时满足这两个需求——指针本身就包含了位置信息,解引用又能拿到值。
场景二:在字符串里找字符,返回找到的位置
C标准库里的strchr函数就是这么干的:
char *strchr(const char *str, int ch);
它在字符串str里找字符ch,找到了就返回指向那个字符的指针,找不到返回NULL。你拿到这个指针之后,不仅能知道字符在哪儿,还能从这个位置开始继续往后操作。
咱们自己写一个简化版:
#include <stdio.h> char* my_strchr(char *str, char target) { while (*str != '\0') { if (*str == target) { return str; // 找到了,返回当前位置 } str++; } return NULL; // 没找到 } int main() { char s[] = "Hello World"; char *p = my_strchr(s, 'W'); if (p != NULL) { printf("找到了:%s\n", p); // 输出 World } else { printf("没找到\n"); } return 0; }
注意这个printf("%s", p)——因为p指向了'W'的位置,%s会从这个位置开始一直打印到字符串末尾。这就是返回指针的好处——你拿到的不是一个孤立的字符,而是字符串中的一个位置,你可以从这个位置继续做很多事情。
场景三:动态内存分配,返回申请到的内存地址
这个咱们下节课会详细讲。简单说就是malloc函数——你让它给你分配一块内存,它返回这块内存的地址。这个地址就是个指针。
场景四:返回结构体指针
当你有一个结构体,想让函数返回这个结构体的地址时,也用到返回指针的函数。这个在链表里特别常见。
一个完整的例子:查找学生信息
咱们用一个学生信息的例子,把返回指针的函数彻底讲透。
#include <stdio.h> #include <string.h> typedef struct { int id; char name[20]; float score; } Student; // 按学号查找学生,返回学生结构体的指针 Student* find_student(Student students[], int n, int target_id) { for (int i = 0; i < n; i++) { if (students[i].id == target_id) { return &students[i]; // 找到了,返回这个学生的地址 } } return NULL; // 没找到 } int main() { Student list[3] = { {101, "张三", 85.5}, {102, "李四", 92.0}, {103, "王五", 78.5} }; Student *p = find_student(list, 3, 102); if (p != NULL) { printf("学号:%d\n", p->id); printf("姓名:%s\n", p->name); printf("成绩:%.1f\n", p->score); } else { printf("没找到这个学生\n"); } return 0; }
这个例子比数组那个更实用。find_student返回了一个Student*,通过它你能访问学生的所有信息。而且因为返回的是指针,你甚至可以通过它修改原数组里的数据:
Student *p = find_student(list, 3, 102); if (p != NULL) { p->score = 95.0; // 直接修改原数组里李四的成绩 }
你看,返回指针的函数威力就在这里——你拿到的不是一份拷贝,而是原数据的地址。想读就读,想改就改。
最大的坑:千万别返回局部变量的地址
这是返回指针的函数里最危险、最常见、最要命的错误。你一不小心就会踩进去。
看这个例子:
int* func() { int a = 10; return &a; // 危险!返回局部变量的地址 } int main() { int *p = func(); printf("%d\n", *p); // 输出什么?不确定! return 0; }
你可能会想:func返回了a的地址,p接住了它,那*p不就是10吗?
不是。 因为a是局部变量。函数func执行完的时候,a的内存就被释放了。你返回的是一个“已经不属于你的地址”。这块内存可能马上被别的函数用了,里面原来的10可能已经被覆盖成别的值了。
你再去访问*p,拿到的可能是10,可能是垃圾数据,也可能是程序直接崩溃。这叫“返回了悬空指针”——指向已释放内存的指针。
那怎么办?如果你真需要一个函数返回一个指针,而且这个指针指向的数据在函数结束后还要继续用,你有两种办法:
办法一:用static变量
int* func() { static int a = 10; // static变量,函数结束也不释放 return &a; }
static变量生命周期是整个程序,函数结束它还在。所以返回它的地址是安全的。但这个方法有个问题——每次调用func返回的都是同一个地址,如果你调用两次,第二次会把第一次的结果覆盖掉。适合简单场景,复杂的就不行了。
办法二:动态分配内存
int* func() { int *p = (int*)malloc(sizeof(int)); *p = 10; return p; // 返回堆上的地址,安全 }
用malloc从堆上申请内存,这块内存在你手动free之前一直有效。所以你可以安全地返回它的地址。但记住:谁申请,谁释放,调用者用完得负责free。
办法三:返回传入的地址
如果函数接收了一个指针参数,你可以把那个指针返回回去,或者返回基于它计算出来的地址。这个地址本来就不属于函数内部,所以安全。
咱们前面的find_max和find_student就是这种——返回的是传入数组里某个元素的地址,不是局部变量的地址,所以没问题。
另一个容易犯的错:返回值类型忘了写*
int func() { // 返回int,不是int* int a = 10; return a; // 返回的是值,不是地址 } int main() { int *p = func(); // 类型不匹配,编译器会报错 return 0; }
函数返回指针,返回值类型必须是指针类型。int func()返回的是int,int* func()返回的才是int*。少写一个*,意思完全变了。
返回指针的函数 vs 函数指针,别再搞混了
这俩名字长得像,我再说一遍区别:
返回指针的函数:int* func(int a);
-
意思是:func是一个函数,它返回一个int*。
-
重点在“函数”两个字上,它是个函数。
函数指针:int (*p)(int);
-
意思是:p是一个指针,它指向一个返回int的函数。
-
重点在“指针”两个字上,它是个指针。
怎么区分?看*跟谁挨着。int* func——*跟int挨着,说明返回类型是int*。int (*p)——*跟p挨着,说明p是指针。
总结一下
返回指针的函数,核心就几点:
格式:返回类型 *函数名(参数列表),比如int* find(int arr[], int n)。
作用:返回一个地址,而不是一个值。适合需要知道“在哪儿”的场景。
最典型的用法:从数组里找元素返回地址、从字符串里找字符返回地址、动态内存分配返回地址。
最大的坑:绝对不要返回局部变量的地址。局部变量函数结束就没了,你返回的地址指向一块已经不属于你的内存。用static变量、动态分配的内存、或者传入的地址,才是安全的。
回去自己动手写几个例子:
-
写一个函数,从整型数组里找第一个负数,返回它的地址。
-
写一个函数,从字符串里找第一个空格,返回它的地址。
-
写一个函数,用static变量实现一个计数器,返回计数器的地址。
写完跑一跑,看看结果对不对。再故意写一个返回局部变量地址的函数,看看编译器给什么警告,运行结果是不是不可预测。亲手踩一次坑,比看十遍都管用。
下节课咱们讲动态内存分配,那是指针真正的用武之地。