C++ 自定义函数详解

返回值 · 参数 · 作用域 · 全局变量

函数定义与返回值类型

返回值类型 函数名 (参数列表) {
    // 函数体:执行具体任务的代码
    return 返回值; // 如果返回值类型不是 void
}

函数是 C++ 中的基本构建块,它允许我们将代码组织成可重用的模块。每个函数(除了 `void` 函数)都有一个明确的 返回值类型,它规定了函数执行完毕后将返回何种类型的数据。

常见返回值类型

  • int, float, double, long long - 返回基本数值类型
  • char, std::string - 返回单个字符或字符串 (需 #include )
  • bool - 返回逻辑真 (true) 或假 (false)
  • void - 不返回 任何值
  • int&, double& - 返回一个变量的 引用 (别名)
  • int*, char* - 返回一个指向某种类型数据的 指针 (内存地址)
  • 自定义类型 - 如结构体 (struct)、类 (class) 对象

参数类型与传递方式

函数参数是函数与外部世界沟通的桥梁。C++ 提供了多种参数传递方式,主要有以下三种:

按值传递 (Pass by Value)

函数接收的是实参的 副本。在函数内部修改形参,不会 影响函数外部的原始实参。

void func(int x);

按引用传递 (Pass by Reference)

函数接收的是实参的 别名。在函数内部修改形参,会直接 影响函数外部的原始实参。

void func(int &x);

指针传递 (Pass by Pointer)

函数接收的是实参的 内存地址。通过解引用指针 (*),函数内部可以访问和修改原始实参。

void func(int *x);
点击上面的 "演示效果" 按钮查看不同传递方式下内存的变化。

函数作用域与局部变量

作用域 (Scope) 是指标识符(如变量名、函数名)在程序代码中有效的区域。

局部变量 (Local Variables)

  • 在函数内部或代码块 ({...}) 内部声明的变量。
  • 生命周期:从声明处开始,到包含它的函数或代码块结束时销毁。
  • 可见性:仅在声明它的作用域内(及其嵌套作用域)可见。
  • 每次函数调用时,局部变量(非静态)都会重新创建和初始化(如果未显式初始化,其值未定义)。

变量遮蔽 (Shadowing)

如果在内部作用域(如函数内)声明了一个与外部作用域(如全局)同名的变量,内部变量会 遮蔽 外部变量。在该内部作用域中,直接使用该名称访问的是内部变量。

// 假设在全局作用域
int globalValue = 10; // 全局变量

void scopeDemo() {
    int localVar = 20;    // 局部变量,仅在 scopeDemo 内可见
    int globalValue = 30; // 局部变量,遮蔽了全局的 globalValue

    // 访问变量
    // cout << localVar << endl;     // 输出 20 (局部变量)
    // cout << globalValue << endl; // 输出 30 (访问的是被遮蔽的局部 globalValue)
    // cout << ::globalValue << endl; // 使用作用域解析符 :: 访问全局 globalValue, 输出 10
} // localVar 和 局部的 globalValue 在此销毁
                    

静态局部变量 (Static Local Variables)

使用 static 关键字修饰的局部变量:

  • 生命周期:从程序开始到程序结束,而不是函数调用期间。
  • 初始化:只在程序第一次执行到其声明时初始化一次。
  • 值保持:在函数多次调用之间保持其值。
  • 可见性仍然是局部的。
void countCalls() {
    static int callCount = 0; // 静态局部变量,只初始化一次为 0
    callCount++;
    // cout << "函数已被调用 " << callCount << " 次" << endl;
}
调用次数: 0

全局变量与函数

全局变量 (Global Variables) 在所有函数之外声明,其作用域从声明点开始,直到文件结束。默认情况下,它们在整个程序(包括其他文件,需配合 extern)中都是可见的。

全局变量状态
// 在某处声明
int globalCounter = 100;
全局变量: globalCounter
100
(全局存储区)
函数内部操作

一个函数可以访问和修改全局变量:

void modifyGlobal() {
    globalCounter += 10; // 直接访问并修改
    // cout << globalCounter;
}

跨文件访问: `extern` 关键字

如果想在一个 C++ 文件 (.cpp) 中使用另一个文件中定义的全局变量,需要在当前文件中使用 extern 关键字进行 声明(不是定义)。这告诉编译器该变量在别处定义。

// ---- file1.cpp ----
#include 
int sharedGlobalVar = 5; // 定义并初始化全局变量

// ---- file2.cpp ----
#include 
extern int sharedGlobalVar; // 声明 file1.cpp 中的全局变量

void printGlobal() {
    // std::cout << "来自 file1 的值: " << sharedGlobalVar << std::endl; // 可以访问
    sharedGlobalVar = 10; // 也可以修改
}

全局变量使用的注意事项 (风险)

  • 可维护性差:全局变量可以在程序的任何地方被修改,难以追踪其值的变化,增加调试难度。
  • 命名冲突:容易与其他文件中的全局变量或局部变量发生命名冲突。
  • 耦合性高:函数依赖于全局状态,使得代码模块化程度降低,不易复用和测试。
  • 并发问题:在多线程环境中,对全局变量的非同步访问可能导致数据竞争和不可预测的结果。
  • 建议:优先使用函数参数和返回值来传递数据,尽量避免或减少全局变量的使用。如果必须使用,考虑用命名空间或静态类成员来限制其作用域。

示例与练习

选择题测验
编程练习

测试一下你对本章内容的掌握程度吧!(点击选项查看答案和解析)

1. (单选) 下面哪个函数声明表示该函数不返回任何值?

A. int calculate();
B. void printMessage();
C. bool checkStatus();
D. char getInitial();
解析: 关键字 void 作为返回值类型表示函数不返回任何值。

2. (单选) 考虑函数 void modify(int x) { x = x + 5; } 和调用 int num = 10; modify(num);,调用后 num 的值是多少?

A. 5
B. 10
C. 15
D. 编译错误
解析: 函数 modify 使用的是按值传递 (int x)。形参 x 是实参 num 的一个副本。在函数内部修改 x 不会影响原始的 num。因此,num 的值仍然是 10。

3. (单选) 如果希望函数能够直接修改传入的整型变量,应该使用哪种参数类型?

A. 按值传递 (int x)
B. 按引用传递 (int &x)
C. 按常量引用传递 (const int &x)
D. 返回指针 (int* func())
解析: 按引用传递 (int &x) 允许函数通过形参直接操作原始实参。按指针传递 (int *x) 也可以达到修改目的,但选项中更直接的是引用传递。常量引用 (const int &x) 则禁止在函数内修改。

4. (单选) 关于函数作用域内的局部变量,以下说法错误的是?

A. 局部变量只在声明它的函数或代码块内可见。
B. 函数每次被调用时,非静态局部变量会被重新创建。
C. 局部变量的生命周期持续到整个程序结束。
D. 局部变量可以遮蔽同名的全局变量。
解析: 非静态局部变量的生命周期仅限于其所在的函数或代码块的执行期间,执行结束后即被销毁。静态局部变量的生命周期才持续到程序结束。

5. (单选) 在 fileA.cpp 中定义了全局变量 int count = 0;。要在 fileB.cpp 中访问并修改这个 count 变量,应该在 fileB.cpp 中如何做?

A. 直接使用 count,无需声明。
B. 使用 static int count; 声明。
C. 使用 extern int count; 声明。
D. 必须通过函数参数传递才能访问。
解析: extern 关键字用于向编译器声明一个变量或函数是在其他源文件中定义的,使得当前文件可以链接并使用它。

动手实践是最好的学习方式!尝试完成下面的编程任务。

练习 1:交换变量值

编写一个 C++ 函数 swapInts,该函数接收两个整型变量的引用作为参数,并交换它们的值。函数本身不返回值 (void)。

例如,调用前 a = 5, b = 10,调用 swapInts(a, b) 后,应使得 a = 10, b = 5

练习 2:统计数组元素

编写一个 C++ 函数 countGreaterThan,该函数接收一个整型数组、数组的大小和一个整数阈值 (threshold) 作为参数。函数应返回数组中有多少个元素严格大于该阈值。

例如,对于数组 {1, 5, 2, 8, 3},大小为 5,阈值为 4,函数应返回 2 (因为 5 和 8 大于 4)。

本章总结

恭喜你完成了 C++ 自定义函数的学习!回顾一下关键知识点:

  • 函数定义:使用 返回值类型 函数名(参数列表) { 函数体 } 的结构来创建可重用的代码块。
  • 返回值类型:可以是 void (无返回值),也可以是基本类型、引用、指针或自定义类型,通过 return 语句返回值。
  • 参数传递:按值传递(副本,不影响原值)、按引用传递(别名,影响原值)、按指针传递(地址,通过解引用影响原值)。
  • 作用域与变量:局部变量仅在函数内有效且生命周期短暂;静态局部变量生命周期长且保持值;全局变量在程序范围内可见(需注意风险和 `extern`)。
  • 全局变量使用:可以直接访问,但需谨慎,避免滥用以保持代码清晰和可维护性。

继续练习,加深理解!