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 时都会多看一眼后面还有没有“幽灵代码”。

小技巧: 如果函数有多个 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 知识体系图

下面这张图帮你理清返回值的核心逻辑:

函数返回值知识体系 返回值 return 语句 结束函数执行 带回一个值 类型匹配 隐式类型转换 显式类型转换 指针类型匹配 void 无返回值 纯执行,不交差 return 提前退出 void* 通用指针 避坑指南 不要返回局部变量地址 | 每个分支都要有 return

3.6 实战建议

最后,分享几个我多年积累的经验:

  1. 函数返回值要单一职责。一个函数要么返回数据,要么返回状态,别混着来。
  2. void 函数不是“没有返回值”,而是“不返回有意义的值”。它仍然可以通过全局变量或指针参数“间接返回”。
  3. 写库函数时,返回值设计要统一。比如整个项目都约定:0 成功,负数错误。别这个模块用 1 表示成功,那个模块用 0 表示成功。
  4. 编译器的警告别忽略。如果编译器说“control reaches end of non-void function”,说明你漏了 return。别侥幸,补上。

我记得有一次评审代码,看到一个函数有 7 个 return 点,每个 return 还带着不同的计算逻辑。我建议重构,把计算逻辑提取成单独的函数,主函数只保留一个 return。代码可读性瞬间提升。

嗯,关于返回值,今天就聊这么多。记住一句话:return 是函数的“门面”,别让它丢人


公众号:蓝海资料掘金营,微信 deep3321