定义和使用 C++ 函数

C++Beginner
立即练习

介绍

在本实验中,你将深入 C++ 函数的世界。函数是编程的基石,它允许你将代码组织成易于管理、可重用的模块。本实验涵盖了广泛的与函数相关的课题,从创建具有不同返回类型的函数到利用递归和函数重载等高级概念。你将学习如何定义函数、按值和按引用传递参数、设置默认参数值,并理解函数原型。此外,你还将了解如何使用结构体返回多个值。完成本实验后,你将为编写模块化、高效且结构良好的 C++ 代码打下坚实的基础。

我们将首先探索具有不同返回类型的函数,例如整数、浮点数、字符和字符串。然后,你将学习按值传递参数和按引用传递参数之间的关键区别,以及默认参数值如何为你的函数增加灵活性。我们还将介绍函数重载,它允许你定义多个同名但参数列表不同的函数。此外,你还将探索递归函数,这是一种通过调用自身来解决问题的强大工具。你将了解如何在头文件中使用函数原型来创建更有条理的代码。最后,你将发现如何使用结构体从函数中返回多个值。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 初级 级别的实验,完成率为 84%。获得了学习者 97% 的好评率。

创建具有不同返回类型的函数

函数是编程中的一个基础概念。它们允许你将大型程序分解成更小、更易于管理的部分,使你的代码更易于编写、理解和维护。函数还可以返回值,从而将数据传递回程序的调用部分。

以下是 C++ 中函数的基本语法:

return_type function_name(parameters) {
    // Function body
    return value;
}
  • return_type: 指定函数将返回的值的数据类型。如果函数不返回任何值,则使用关键字 void
  • function_name: 这是你调用函数时使用的名称。选择一个能清晰描述函数用途的名称。
  • parameters: 这是你传递给函数的输入值。它们是可选的,你可以有零个或多个参数。
  • value: 这是函数返回的值。它应该与函数的 return_type 匹配。如果 return_typevoid,则省略 return 语句,或者在 void 函数末尾直接使用 return; 而不带值。

在本步骤中,你将学习创建返回不同数据类型的函数。这种能力对于构建通用且功能强大的程序至关重要。

打开 WebIDE,在 ~/project 目录中创建一个名为 function_types.cpp 的新文件:

touch ~/project/function_types.cpp

在编辑器中打开 function_types.cpp 文件,并添加以下代码:

#include <iostream>
#include <string>

// 返回整数的函数
int addNumbers(int a, int b) {
    return a + b;
}

// 返回 double(浮点数)的函数
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// 返回字符的函数
char getGrade(int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
}

// 返回字符串的函数
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled ? "Active" : "Inactive";
}

int main() {
    // 演示不同的返回类型
    int sum = addNumbers(5, 3);
    std::cout << "Sum: " << sum << std::endl;

    double average = calculateAverage(10.5, 20.5);
    std::cout << "Average: " << average << std::endl;

    char grade = getGrade(85);
    std::cout << "Grade: " << grade << std::endl;

    std::string status = getStudentStatus(true);
    std::cout << "Student Status: " << status << std::endl;

    return 0;
}

编译并运行程序:

g++ function_types.cpp -o function_types
./function_types

示例输出:

Sum: 8
Average: 15.5
Grade: B
Student Status: Active

关于函数返回类型的要点:

  • 函数可以返回不同的数据类型,为代码提供了灵活性。
  • return_type 在函数名称之前指定,指示函数将返回哪种类型的数据。
  • return 语句必须与声明的 return_type 匹配,否则编译器将报告错误。
  • void 函数不返回值,在 void 函数的末尾可以使用 return; 或直接省略 return 语句。
  • 你可以返回基本类型(如 intdoublechar)、字符串,甚至用户定义类型。
  • 将函数返回类型视为不同类型的容器。就像你会使用盒子、袋子或篮子来携带不同的物品一样,函数使用不同的返回类型来发送回不同类型的数据。

通过值传递参数给函数

在本步骤中,你将学习如何在 C++ 中按值将参数传递给函数。当你按值传递参数时,会创建一个原始参数的副本,并在函数中使用该副本。在函数内部对参数所做的任何更改都不会影响函数外部的原始变量。

打开 WebIDE,在 ~/project 目录中创建一个名为 pass_by_value.cpp 的新文件:

touch ~/project/pass_by_value.cpp

在编辑器中打开 pass_by_value.cpp 文件,并添加以下代码:

#include <iostream>

// 演示按值传递的函数
void modifyValue(int x) {
    // 此修改仅影响局部副本,不影响原始变量
    x = x * 2;
    std::cout << "Inside function - Value of x: " << x << std::endl;
}

int main() {
    // 原始变量
    int number = 10;

    // 打印原始值
    std::cout << "Before function call - Original value: " << number << std::endl;

    // 使用原始变量调用函数
    modifyValue(number);

    // 原始变量保持不变
    std::cout << "After function call - Original value: " << number << std::endl;

    return 0;
}

编译并运行程序:

g++ pass_by_value.cpp -o pass_by_value
./pass_by_value

示例输出:

Before function call - Original value: 10
Inside function - Value of x: 20
After function call - Original value: 10

让我们创建另一个示例来进一步说明按值传递:

touch ~/project/swap_values.cpp

将以下代码添加到 swap_values.cpp

#include <iostream>
#include <string>

// 交换值的函数(按值传递时不会按预期工作)
void swapValues(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    std::cout << "Inside function - a: " << a << ", b: " << b << std::endl;
}

int main() {
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // 调用 swap 函数
    swapValues(first, second);

    // 原始变量保持不变
    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    return 0;
}

编译并运行第二个程序:

g++ swap_values.cpp -o swap_values
./swap_values

示例输出:

Before swap - first: 5, second: 10
Inside function - a: 10, b: 5
After swap - first: 5, second: 10

关于按值传递的要点:

  • 函数接收的是参数的副本,原始值得以保留。
  • 在函数内部对参数所做的更改不会影响函数外部的原始变量。
  • 按值传递适用于简单的 C++ 数据类型,如 intcharfloat 等,当你不需要修改原始值时。
  • 按值传递大型对象时,复制会消耗更多内存和时间,与按引用传递相比效率较低。

你可以将按值传递想象成复印一份文件。原件保持不变,你可以修改副本而不影响原件。

使用 & 运算符实现引用传递

在本步骤中,你将学习如何在 C++ 中使用 & 运算符通过引用将参数传递给函数。按引用传递允许你为函数提供对原始变量的直接访问权限,这意味着在函数内对参数所做的任何更改都将直接修改函数外部的原始变量。

打开 WebIDE,在 ~/project 目录中创建一个名为 pass_by_reference.cpp 的新文件:

touch ~/project/pass_by_reference.cpp

在编辑器中打开 pass_by_reference.cpp 文件,并添加以下代码:

#include <iostream>

// 演示按引用传递的函数
void swapValues(int& a, int& b) {
    // 直接修改原始变量
    int temp = a;
    a = b;
    b = temp;
}

// 使用引用修改值的函数
void incrementValue(int& x) {
    // 直接增加原始变量的值
    x++;
}

int main() {
    // 交换示例
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // 使用引用调用 swap 函数
    swapValues(first, second);

    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    // 递增示例
    int number = 7;

    std::cout << "Before increment: " << number << std::endl;

    // 使用引用调用 increment 函数
    incrementValue(number);

    std::cout << "After increment: " << number << std::endl;

    return 0;
}

编译并运行程序:

g++ pass_by_reference.cpp -o pass_by_reference
./pass_by_reference

示例输出:

Before swap - first: 5, second: 10
After swap - first: 10, second: 5
Before increment: 7
After increment: 8

让我们创建另一个示例来演示按引用传递的强大功能:

touch ~/project/string_reference.cpp

将以下代码添加到 string_reference.cpp

#include <iostream>
#include <string>

// 使用引用修改字符串的函数
void appendText(std::string& text) {
    text += " - Modified";
}

int main() {
    std::string message = "Hello, World!";

    std::cout << "Original message: " << message << std::endl;

    // 使用引用直接修改字符串
    appendText(message);

    std::cout << "Modified message: " << message << std::endl;

    return 0;
}

编译并运行第二个程序:

g++ string_reference.cpp -o string_reference
./string_reference

示例输出:

Original message: Hello, World!
Modified message: Hello, World! - Modified

关于按引用传递的要点:

  • 在类型后使用 & 来创建引用参数(例如,int& a)。
  • 直接修改原始变量,与按值传递(使用副本)不同。
  • 避免了复制大型对象的开销,使其对于复杂数据类型更有效。
  • 当你需要修改原始值或处理复杂数据类型而不进行复制时非常有用。

你可以将按引用传递想象成将原始文档交给某人,而不是复印件。他们所做的任何更改都将直接影响原始文档。

为函数参数设置默认值

在本步骤中,你将学习如何在 C++ 中为函数参数设置默认值。默认参数允许你为函数参数分配默认值,从而使你的函数更加通用且易于使用。当调用者省略带有默认值的参数时,将自动使用该默认值。

打开 WebIDE,在 ~/project 目录中创建一个名为 default_parameters.cpp 的新文件:

touch ~/project/default_parameters.cpp

在编辑器中打开 default_parameters.cpp 文件,并添加以下代码:

#include <iostream>
#include <string>

// 带有默认参数的函数
void greetUser(std::string name = "Guest", int age = 0) {
    std::cout << "Hello, " << name << "!" << std::endl;

    if (age > 0) {
        std::cout << "You are " << age << " years old." << std::endl;
    }
}

// 另一个带有默认参数用于计算的示例
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // 调用不带参数的函数(使用默认值)
    greetUser();

    // 调用带有部分参数的函数
    greetUser("Alice");

    // 调用带有所有参数的函数
    greetUser("Bob", 25);

    // 演示使用默认参数进行面积计算
    std::cout << "\nArea calculations:" << std::endl;

    // 默认面积 (1x1)
    std::cout << "Default area: " << calculateArea() << std::endl;

    // 带一个参数的面积
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

    // 带有两个参数的面积
    std::cout << "Area with length 5 and width 3: " << calculateArea(5.0, 3.0) << std::endl;

    return 0;
}

编译并运行程序:

g++ default_parameters.cpp -o default_parameters
./default_parameters

示例输出:

Hello, Guest!
Hello, Alice!
Hello, Bob!
You are 25 years old.

Area calculations:
Default area: 1
Area with length 5: 5
Area with length 5 and width 3: 15

关于默认参数的要点:

  • 默认值在函数声明中指定,紧跟在参数类型之后(例如,int age = 0)。
  • 带有默认值的参数必须位于参数列表的末尾。你不能在默认参数之后放置非默认参数。
  • 你可以调用函数时提供较少的参数,缺失的参数将使用其默认值。
  • 默认值提供了灵活性,并减少了对执行相同操作但输入略有不同的多个函数重载的需求。

让我们创建另一个示例来演示更复杂的默认参数:

touch ~/project/student_info.cpp

将以下代码添加到 student_info.cpp

#include <iostream>
#include <string>

// 带有多个默认参数的函数
void printStudentInfo(
    std::string name = "Unknown",
    int age = 0,
    std::string grade = "N/A",
    bool isEnrolled = false
) {
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "Grade: " << grade << std::endl;
    std::cout << "Enrollment Status: " << (isEnrolled ? "Enrolled" : "Not Enrolled") << std::endl;
}

int main() {
    // 调用函数的不同方式
    printStudentInfo();  // 所有默认值
    std::cout << "\n";
    printStudentInfo("John");  // 仅姓名
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // 姓名和年龄
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // 所有参数

    return 0;
}

编译并运行第二个程序:

g++ student_info.cpp -o student_info
./student_info

示例输出:

Name: Unknown
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: John
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: Alice
Age: 20
Grade: N/A
Enrollment Status: Not Enrolled

Name: Bob
Age: 22
Grade: A
Enrollment Status: Enrolled

你可以将默认参数想象成自助餐,你可以选择拿所有菜品或只拿几样,如果你不指定,有些菜品会自动填入。

创建具有不同参数的函数重载

在本步骤中,你将学习 C++ 中的函数重载(function overloading)。函数重载允许你创建多个同名函数,只要它们的参数列表不同(无论是参数的数量还是参数的类型)。这项特性通过允许你使用相同的函数名对不同的数据类型或不同数量的输入执行类似的操作,使你的代码更具灵活性和可读性。

打开 WebIDE,在 ~/project 目录中创建一个名为 function_overloads.cpp 的新文件:

touch ~/project/function_overloads.cpp

在编辑器中打开 function_overloads.cpp 文件,并添加以下代码:

#include <iostream>
#include <string>

// 用于相加不同类型数字的函数重载
int add(int a, int b) {
    std::cout << "Adding two integers" << std::endl;
    return a + b;
}

double add(double a, double b) {
    std::cout << "Adding two doubles" << std::endl;
    return a + b;
}

// 参数数量不同的函数重载
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

// 参数类型不同的函数重载
std::string add(std::string a, std::string b) {
    std::cout << "Concatenating strings" << std::endl;
    return a + b;
}

int main() {
    // 调用不同的重载函数
    std::cout << "Integer addition: " << add(5, 3) << std::endl;
    std::cout << "Double addition: " << add(5.5, 3.7) << std::endl;
    std::cout << "Three integer addition: " << add(1, 2, 3) << std::endl;
    std::cout << "String concatenation: " << add("Hello, ", "World!") << std::endl;

    return 0;
}

编译并运行程序:

g++ function_overloads.cpp -o function_overloads
./function_overloads

示例输出:

Adding two integers
Integer addition: 8
Adding two doubles
Double addition: 9.2
Adding three integers
Three integer addition: 6
Concatenating strings
String concatenation: Hello, World!

让我们创建另一个示例来演示更多的函数重载:

touch ~/project/overloading_examples.cpp

将以下代码添加到 overloading_examples.cpp

#include <iostream>
#include <string>

// 重载的 print 函数
void print(int value) {
    std::cout << "Printing integer: " << value << std::endl;
}

void print(double value) {
    std::cout << "Printing double: " << value << std::endl;
}

void print(std::string value) {
    std::cout << "Printing string: " << value << std::endl;
}

// 重载的 calculate 函数
int calculate(int a, int b) {
    return a + b;
}

double calculate(double a, double b) {
    return a * b;
}

int main() {
    // 演示函数重载
    print(42);
    print(3.14);
    print("Hello, Overloading!");

    std::cout << "Integer calculation: " << calculate(5, 3) << std::endl;
    std::cout << "Double calculation: " << calculate(5.5, 3.2) << std::endl;

    return 0;
}

编译并运行第二个程序:

g++ overloading_examples.cpp -o overloading_examples
./overloading_examples

示例输出:

Printing integer: 42
Printing double: 3.14
Printing string: Hello, Overloading!
Integer calculation: 8
Double calculation: 17.6

关于函数重载的要点:

  • 函数可以拥有相同的名称,只要它们的参数列表不同(参数类型或参数数量不同)。
  • C++ 编译器根据函数调用时提供的参数的类型和数量来决定调用哪个重载函数。
  • 函数重载使代码更具可读性和直观性,允许你为相似的操作重用名称。
  • 如果函数仅返回类型不同,则无法重载它们。参数必须不同才能使重载正常工作。

你可以将函数重载想象成一个万能遥控器,可以用于不同的设备。相同的按钮(函数名)会根据上下文(你提供的参数)执行不同的操作。

编写递归函数进行阶乘计算

在本步骤中,你将通过实现一个阶乘计算程序来学习递归函数。递归是一种编程技术,其中函数调用自身来解决问题。为了避免无限循环,递归函数必须有一个基本情况(base case),在达到该情况时停止递归调用,同时还需要一个递归情况(recursive case)来不断调用自身,朝着基本情况迈进。

打开 WebIDE,在 ~/project 目录中创建一个名为 factorial_recursive.cpp 的新文件:

touch ~/project/factorial_recursive.cpp

在编辑器中打开 factorial_recursive.cpp 文件,并添加以下代码:

#include <iostream>

// 用于计算阶乘的递归函数
unsigned long long calculateFactorial(int n) {
    // 基本情况:0 或 1 的阶乘是 1
    if (n == 0 || n == 1) {
        return 1;
    }

    // 递归情况:n! = n * (n-1)!
    return n * calculateFactorial(n - 1);
}

int main() {
    // 计算不同数字的阶乘
    int numbers[] = {0, 1, 5, 7, 10};

    for (int num : numbers) {
        unsigned long long result = calculateFactorial(num);
        std::cout << "Factorial of " << num << " is: " << result << std::endl;
    }

    return 0;
}

编译并运行程序:

g++ factorial_recursive.cpp -o factorial_recursive
./factorial_recursive

示例输出:

Factorial of 0 is: 1
Factorial of 1 is: 1
Factorial of 5 is: 120
Factorial of 7 is: 5040
Factorial of 10 is: 3628800

让我们创建另一个示例,其中包含一个更详细的递归函数:

touch ~/project/factorial_steps.cpp

将以下代码添加到 factorial_steps.cpp

#include <iostream>

// 带有分步输出的递归函数
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // 添加缩进以进行可视化
    std::string indent(depth * 2, ' ');

    // 基本情况:0 或 1 的阶乘是 1
    if (n == 0 || n == 1) {
        std::cout << indent << "Base case: factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // 带有可视化的递归情况
    std::cout << indent << "Calculating factorial(" << n << ")" << std::endl;

    // 递归调用
    unsigned long long subResult = calculateFactorialWithSteps(n - 1, depth + 1);

    // 合并结果
    unsigned long long result = n * subResult;

    std::cout << indent << "factorial(" << n << ") = "
              << n << " * factorial(" << n-1 << ") = "
              << result << std::endl;

    return result;
}

int main() {
    int number = 5;

    std::cout << "Factorial calculation steps for " << number << ":" << std::endl;
    unsigned long long result = calculateFactorialWithSteps(number);

    std::cout << "\nFinal result: " << number << "! = " << result << std::endl;

    return 0;
}

编译并运行第二个程序:

g++ factorial_steps.cpp -o factorial_steps
./factorial_steps

示例输出:

Factorial calculation steps for 5:
Calculating factorial(5)
  Calculating factorial(4)
    Calculating factorial(3)
      Calculating factorial(2)
        Calculating factorial(1)
        Base case: factorial(1) = 1
      factorial(2) = 2 * factorial(1) = 2
    factorial(3) = 3 * factorial(2) = 6
  factorial(4) = 4 * factorial(3) = 24
factorial(5) = 5 * factorial(4) = 120

Final result: 5! = 120

关于递归函数的要点:

  • 递归函数调用自身:函数调用自身来解决同类型的更小的子问题。
  • 基本情况 (Base Case):递归函数必须有一个基本情况,这是一个停止递归调用的条件。没有基本情况,函数将无限调用自身,导致栈溢出错误(stack overflow error)。
  • 递归情况 (Recursive Case):递归情况是函数调用自身以朝着基本情况前进的地方。
  • 问题分解:递归将一个复杂问题分解为更小、更简单的子问题,使其更易于管理。
  • 栈使用:每次递归调用都会占用程序调用栈(call stack)的空间。深度递归可能导致栈溢出,因此要注意调用栈的限制。
  • 适用问题:递归特别适用于可以根据更小、相似的子问题自然定义的任务,例如树遍历、分治算法(divide-and-conquer algorithms)和分形生成。

你可以将递归想象成一套俄罗斯套娃,每个娃娃里面都包含一个更小的自身版本,而最里面的娃娃代表了停止嵌套过程的基本情况。

在头文件中使用函数原型

在本步骤中,你将学习函数原型(function prototypes)以及如何使用头文件(header files)来组织和声明 C++ 中的函数。函数原型声明了函数的名称、返回类型和参数,但不提供实现(函数体)。通过使用头文件,你可以将函数的接口(声明)与其实现分离开来。这种分离提高了代码的组织性,使得在程序的多个部分或多个程序中维护和重用函数更加容易。

打开 WebIDE,在 ~/project 目录中创建三个文件:

首先,创建 math_functions.h

touch ~/project/math_functions.h

将以下代码添加到 math_functions.h

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// 数学运算的函数原型
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);

#endif // MATH_FUNCTIONS_H

.h 文件用于声明,包含函数原型和其他声明,但不包含实现。这样,你就可以声明函数而无需实现它们。#ifndef#define#endif 指令称为包含保护(include guards),它们可以防止头文件被多次包含,从而避免错误。

接下来,创建 math_functions.cpp

touch ~/project/math_functions.cpp

将以下代码添加到 math_functions.cpp

#include "math_functions.h"

// 函数实现
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

double divide(double a, double b) {
    // 检查除以零的情况
    if (b == 0) {
        return 0;
    }
    return a / b;
}

这个 .cpp 文件包含了在头文件中声明的函数的实际实现。

最后,创建 main.cpp

touch ~/project/main.cpp

将以下代码添加到 main.cpp

#include <iostream>
#include "math_functions.h"

int main() {
    // 演示来自头文件的函数调用
    int x = 10, y = 5;

    std::cout << "Addition: " << x << " + " << y << " = " << add(x, y) << std::endl;
    std::cout << "Subtraction: " << x << " - " << y << " = " << subtract(x, y) << std::endl;
    std::cout << "Multiplication: " << x << " * " << y << " = " << multiply(x, y) << std::endl;
    std::cout << "Division: " << x << " / " << y << " = " << divide(x, y) << std::endl;

    return 0;
}

这个 main.cpp 文件包含了 math_functions.h 头文件,使得函数原型可用。然后它就可以使用 math_functions.cpp 中实现的函数了。

使用多个源文件编译程序:

g++ math_functions.cpp main.cpp -o math_operations
./math_operations

示例输出:

Addition: 10 + 5 = 15
Subtraction: 10 - 5 = 5
Multiplication: 10 * 5 = 50
Division: 10 / 5 = 2

让我们创建另一个示例,其中包含一个更复杂的头文件:

创建 calculator.h

touch ~/project/calculator.h

将以下代码添加到 calculator.h

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // 计算器运算的函数原型
    int add(int a, int b);
    int subtract(int a, int b);
    int calculate(int a, int b, char operation);
};

#endif // CALCULATOR_H

这个头文件声明了一个名为 Calculator 的类及其公共方法。

创建 calculator.cpp

touch ~/project/calculator.cpp

将以下代码添加到 calculator.cpp

#include "calculator.h"

int Calculator::add(int a, int b) {
    return a + b;
}

int Calculator::subtract(int a, int b) {
    return a - b;
}

int Calculator::calculate(int a, int b, char operation) {
    switch (operation) {
        case '+': return add(a, b);
        case '-': return subtract(a, b);
        default: return 0;
    }
}

这个 calculator.cpp 提供了在 calculator.h 头文件中声明的方法的实现。

创建 calculator_main.cpp

touch ~/project/calculator_main.cpp

将以下代码添加到 calculator_main.cpp

#include <iostream>
#include "calculator.h"

int main() {
    Calculator calc;

    std::cout << "Calculator Operations:" << std::endl;
    std::cout << "10 + 5 = " << calc.calculate(10, 5, '+') << std::endl;
    std::cout << "10 - 5 = " << calc.calculate(10, 5, '-') << std::endl;

    return 0;
}

这个主文件使用了 Calculator 类并执行了运算。

编译计算器程序:

g++ calculator.cpp calculator_main.cpp -o calculator
./calculator

示例输出:

Calculator Operations:
10 + 5 = 15
10 - 5 = 5

关于函数原型和头文件的要点:

  • 头文件 (.h) 声明函数原型、类和其他声明。它们充当接口,概述了可用的函数。
  • 源文件 (.cpp) 实现头文件中声明的实际函数。它们包含函数如何工作的代码。
  • #ifndef#define#endif(包含保护)可防止同一头文件被多次包含,从而避免潜在的编译错误。
  • 使用头文件可以促进模块化和代码重用。
  • 头文件允许你将“什么”(声明)与“如何”(实现)分离开来。
  • 它们有助于代码组织,使其更易于维护和理解。

你可以将头文件想象成餐厅菜单。菜单(头文件)列出了有什么菜品,而厨房(源文件)则准备了实际的菜肴。

使用结构体返回多个值

在本步骤中,你将学习如何使用结构体(structures)在 C++ 中从函数返回多个值。结构体是用户定义的数据类型,它允许你将不同类型的数据组合在一个名称下。它们非常适合以结构化和有组织的方式从函数返回多个相关值。

打开 WebIDE,更新 ~/project 目录中的 student_info.cpp 文件:

将现有代码替换为以下内容:

#include <iostream>
#include <string>

// 定义一个结构体来存储学生信息
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// 返回多个值的函数,使用结构体
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// 计算多个统计值的函数
struct MathStats {
    int sum;
    double average;
    int minimum;
    int maximum;
};

MathStats calculateArrayStats(int numbers[], int size) {
    MathStats stats;
    stats.sum = 0;
    stats.minimum = numbers[0];
    stats.maximum = numbers[0];

    for (int i = 0; i < size; i++) {
        stats.sum += numbers[i];

        if (numbers[i] < stats.minimum) {
            stats.minimum = numbers[i];
        }

        if (numbers[i] > stats.maximum) {
            stats.maximum = numbers[i];
        }
    }

    stats.average = static_cast<double>(stats.sum) / size;

    return stats;
}

int main() {
    // 演示学生评估
    StudentResult student1 = evaluateStudent("Alice", 75);
    StudentResult student2 = evaluateStudent("Bob", 45);

    std::cout << "Student Results:" << std::endl;
    std::cout << student1.name << " - Score: " << student1.score
              << ", Passed: " << (student1.passed ? "Yes" : "No") << std::endl;
    std::cout << student2.name << " - Score: " << student2.score
              << ", Passed: " << (student2.passed ? "Yes" : "No") << std::endl;

    // 演示数组统计
    int numbers[] = {5, 2, 9, 1, 7};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    MathStats stats = calculateArrayStats(numbers, size);

    std::cout << "\nArray Statistics:" << std::endl;
    std::cout << "Sum: " << stats.sum << std::endl;
    std::cout << "Average: " << stats.average << std::endl;
    std::cout << "Minimum: " << stats.minimum << std::endl;
    std::cout << "Maximum: " << stats.maximum << std::endl;

    return 0;
}

编译并运行程序:

g++ student_info.cpp -o student_info
./student_info

示例输出:

Student Results:
Alice - Score: 75, Passed: Yes
Bob - Score: 45, Passed: No

Array Statistics:
Sum: 24
Average: 4.8
Minimum: 1
Maximum: 9

关于结构体和返回多个值的要点:

  • 结构体将相关数据组合在一起:你可以将不同类型(例如 stringintbool)的变量组合成一个单元,这在你拥有相关信息时非常有用。
  • 返回复杂数据类型:使用结构体,你可以从函数返回复杂数据类型,从而轻松管理和传递相关数据集。
  • 有组织的返回值:返回结构体有助于组织多个相关返回值,使代码更整洁、更易于维护。
  • 提供传递结构化信息的清晰方式:结构体为返回多个相关值提供了一个清晰、命名的容器,提高了代码的可读性和可理解性。

你可以将结构体想象成一个多隔间的午餐盒,可以在不同的隔间里放不同种类的食物。

总结

在本实验中,你全面了解了如何在 C++ 中定义和使用函数。你学会了创建具有不同返回类型的函数、通过值和引用传递参数、设置默认参数值以及使用函数重载。此外,你还探索了递归函数、如何在头文件中使用函数原型以及如何使用结构体返回多个值。

涵盖的关键概念包括:

  • 函数返回类型:你了解到函数可以返回不同的数据类型,并且 return 语句必须与声明的返回类型匹配。
  • 传值和传引用:你理解了按值传递参数(创建副本)和按引用传递参数(使用原始变量)之间的区别。
  • 默认参数:你学会了如何通过为函数参数分配默认值来使函数更加通用。
  • 函数重载:你看到了如何定义具有相同名称但参数列表不同的多个函数,从而使代码更具可读性和直观性。
  • 递归函数:你探索了递归函数的强大功能,其中函数调用自身来解决更小的子问题,以及基线条件对于防止无限递归的重要性。
  • 函数原型和头文件:你学会了如何使用头文件来组织函数声明,从而促进模块化和代码重用。
  • 使用结构体返回多个值:你发现如何使用结构体从函数返回多个相关值,结构体为数据提供了有组织的容器。

通过掌握这些概念,你现在已经具备了编写更模块化、更高效、结构更良好的 C++ 代码的能力,并为你探索更高级的编程技术奠定了坚实的基础。