3. 返回值:return语句,返回值的类型匹配,无返回值函数
好,咱们今天聊聊函数的返回值。这东西看着简单,但坑不少。我见过太多新手甚至工作一两年的工程师,在返回值上翻车。
说白了,函数就是一段代码,干完活总得给调用者一个交代。这个交代,就是返回值。
3.1 return 语句:函数怎么“交差”
return 的作用就两个:结束函数,带回一个值。
你看这个例子:
int add(int a, int b) {
int sum = a + b;
return sum; // 把结果带回去
}
调用的时候:
int result = add(3, 5);
// result 现在等于 8
嗯,这里要注意:return 一执行,函数立刻结束。后面的代码不会跑了。
int check_value(int x) {
if (x < 0) {
return -1; // 负数直接返回
}
// 只有 x >= 0 时才会走到这里
return x * 2;
}
我曾经在调试一个通信协议栈时,发现函数总是返回错误值。查了半天,原来是 return 之后还有一行代码在修改全局变量。那个变量刚好被另一个线程用了。嗯,从那以后我写 return 时都会多看一眼后面还有没有“幽灵代码”。
3.2 返回值的类型匹配:别让编译器替你“擦屁股”
函数的返回值类型,必须和 return 后面的表达式类型一致。或者至少能隐式转换。
看这个反面教材:
int get_percentage() {
float ratio = 0.753f;
return ratio; // 编译器会截断为 0!
}
你想想看,一个 0.753 被硬生生截成 0。调用者拿到 0%,一脸懵。
为什么会这样?因为 return 语句会把表达式的值转换成函数声明的返回值类型。float 转 int,直接丢掉小数部分。
我个人的习惯是:显式写清楚类型转换,哪怕编译器能自动做。
int get_percentage() {
float ratio = 0.753f;
return (int)(ratio * 100); // 返回 75,清晰明了
}
还有一种更隐蔽的坑:指针和整数的混用。
// 错误示范:返回局部变量的地址
int* get_value() {
int x = 42;
return &x; // x 是局部变量,函数结束就销毁了
}
这个指针指向的内存已经被回收了。你用这个指针去读数据,读到的是垃圾。我曾经在嵌入式项目中遇到过这种问题,现象是“有时候对,有时候错”,排查了两天才定位到是返回了局部变量的地址。
3.3 无返回值函数:void 的正确用法
有些函数就是“干活的”,不需要交差。这时候用 void 声明返回值类型。
void print_hello() {
printf("Hello, World!\n");
// 没有 return 语句
}
void 函数里也可以写 return,但后面不能跟表达式。作用仅仅是提前结束函数。
void process_data(int flag) {
if (flag == 0) {
return; // 条件不满足,提前退出
}
// 继续处理...
printf("Processing...\n");
}
我个人建议:void 函数中尽量少用 return。如果逻辑分支太多,说明函数可能太复杂了,该拆分了。
还有一种情况:void 指针。这个稍微高级一点,但值得提一下。
void* allocate_memory(size_t size) {
return malloc(size);
}
void* 是“通用指针”,可以指向任何类型。但使用时必须显式转换回原类型。
- 有返回值的函数,每个分支都要有 return
- 返回值类型要匹配,避免隐式截断
- void 函数不要返回具体值
- 不要返回局部变量的地址
3.4 返回值与错误处理
在实际项目中,返回值经常用来表示“操作是否成功”。
我常用的模式是:
- 返回 0 表示成功
- 返回负数 表示各种错误码
- 返回正数 表示实际数据或状态
int open_device() {
if (硬件初始化失败) {
return -1; // 设备不存在
}
if (资源被占用) {
return -2; // 设备忙
}
return 0; // 成功
}
调用方拿到返回值后,先检查再使用:
int ret = open_device();
if (ret != 0) {
printf("打开设备失败,错误码:%d\n", ret);
return;
}
// 正常使用设备...
嗯,这里要注意:不要用返回值既表示数据又表示错误。比如一个函数返回 -1 表示“没找到”,但 -1 也可能是有效数据。这种设计会让人崩溃。
3.5 知识体系图
下面这张图帮你理清返回值的核心逻辑:
3.6 实战建议
最后,分享几个我多年积累的经验:
- 函数返回值要单一职责。一个函数要么返回数据,要么返回状态,别混着来。
- void 函数不是“没有返回值”,而是“不返回有意义的值”。它仍然可以通过全局变量或指针参数“间接返回”。
- 写库函数时,返回值设计要统一。比如整个项目都约定:0 成功,负数错误。别这个模块用 1 表示成功,那个模块用 0 表示成功。
- 编译器的警告别忽略。如果编译器说“control reaches end of non-void function”,说明你漏了 return。别侥幸,补上。
我记得有一次评审代码,看到一个函数有 7 个 return 点,每个 return 还带着不同的计算逻辑。我建议重构,把计算逻辑提取成单独的函数,主函数只保留一个 return。代码可读性瞬间提升。
嗯,关于返回值,今天就聊这么多。记住一句话:return 是函数的“门面”,别让它丢人。
公众号:蓝海资料掘金营,微信 deep3321