CSP-J教程——第一阶段第十一课:函数与递归初步

课程目标

  • 理解函数的概念和优势
  • 掌握函数的定义、声明和调用
  • 理解参数传递(值传递)的机制
  • 理解返回值的意义和使用
  • 初步了解递归的概念
  • 学会将复杂问题模块化

第一部分:函数的基本概念(40分钟)

1.1 什么是函数?

生活比喻:

  • 厨房电器:微波炉、烤箱 - 输入食材,输出熟食,我们不需要知道内部原理
  • 快递员:把包裹(参数)交给快递员,他负责送到目的地(返回值)
  • 工厂生产线:每个工人(函数)负责特定的工序

1.2 为什么需要函数?

没有函数的困境:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    // 计算圆的面积(重复代码)
    double radius1 = 5.0;
    double area1 = 3.14159 * radius1 * radius1;
    cout << "圆1的面积: " << area1 << endl;
    
    double radius2 = 3.0;
    double area2 = 3.14159 * radius2 * radius2;
    cout << "圆2的面积: " << area2 << endl;
    
    double radius3 = 7.0;
    double area3 = 3.14159 * radius3 * radius3;
    cout << "圆3的面积: " << area3 << endl;
    
    return 0;
}

使用函数的便利:

cpp 复制代码
#include <iostream>
using namespace std;

// 定义计算圆面积的函数
double calculateCircleArea(double radius) {
    return 3.14159 * radius * radius;
}

int main() {
    // 使用函数计算圆的面积
    cout << "圆1的面积: " << calculateCircleArea(5.0) << endl;
    cout << "圆2的面积: " << calculateCircleArea(3.0) << endl;
    cout << "圆3的面积: " << calculateCircleArea(7.0) << endl;
    
    return 0;
}

1.3 函数的优势

  1. 代码复用:避免重复编写相同代码
  2. 模块化:将复杂问题分解为小问题
  3. 可读性:函数名可以描述功能,代码更易理解
  4. 易于维护:修改功能只需修改一个地方
  5. 团队协作:不同程序员可以负责不同函数

第二部分:函数的定义和调用(60分钟)

2.1 函数的组成部分

函数定义语法:

cpp 复制代码
返回类型 函数名(参数列表) {
    // 函数体
    return 返回值;  // 如果返回类型不是void
}

2.2 无参数无返回值的函数

cpp 复制代码
#include <iostream>
using namespace std;

// 函数定义:显示欢迎信息
void showWelcome() {
    cout << "****************" << endl;
    cout << "*  欢迎使用!  *" << endl;
    cout << "****************" << endl;
}

// 函数定义:显示菜单
void showMenu() {
    cout << "\n=== 菜单 ===" << endl;
    cout << "1. 开始游戏" << endl;
    cout << "2. 设置" << endl;
    cout << "3. 退出" << endl;
    cout << "============" << endl;
}

int main() {
    // 函数调用
    showWelcome();
    showMenu();
    
    cout << "程序继续执行..." << endl;
    
    // 可以多次调用同一个函数
    showMenu();
    
    return 0;
}

2.3 带参数无返回值的函数

cpp 复制代码
#include <iostream>
using namespace std;

// 函数定义:显示指定次数的消息
void showMessage(string message, int times) {
    for (int i = 0; i < times; i++) {
        cout << message << endl;
    }
}

// 函数定义:显示个人信息
void showPersonInfo(string name, int age, double height) {
    cout << "\n=== 个人信息 ===" << endl;
    cout << "姓名: " << name << endl;
    cout << "年龄: " << age << "岁" << endl;
    cout << "身高: " << height << "米" << endl;
}

// 函数定义:打印指定行数和列数的图案
void printPattern(char symbol, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << symbol << " ";
        }
        cout << endl;
    }
}

int main() {
    // 调用带参数的函数
    showMessage("Hello, Function!", 3);
    showPersonInfo("小明", 12, 1.65);
    printPattern('*', 4, 6);
    
    return 0;
}

2.4 带返回值的函数

cpp 复制代码
#include <iostream>
using namespace std;

// 函数定义:计算两个数的和
int add(int a, int b) {
    int result = a + b;
    return result;  // 返回计算结果
}

// 函数定义:判断一个数是否为偶数
bool isEven(int number) {
    return number % 2 == 0;  // 直接返回布尔表达式的结果
}

// 函数定义:计算阶乘
int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; i++) {
        result *= i;
    }
    return result;
}

// 函数定义:获取成绩等级
char getGrade(int score) {
    if (score >= 90) return 'A';
    else if (score >= 80) return 'B';
    else if (score >= 70) return 'C';
    else if (score >= 60) return 'D';
    else return 'E';
}

int main() {
    // 使用带返回值的函数
    int sum = add(5, 3);
    cout << "5 + 3 = " << sum << endl;
    
    // 直接在表达式中使用函数调用
    cout << "10 + 15 = " << add(10, 15) << endl;
    
    // 使用判断函数
    int num = 7;
    if (isEven(num)) {
        cout << num << " 是偶数" << endl;
    } else {
        cout << num << " 是奇数" << endl;
    }
    
    // 使用计算函数
    cout << "5的阶乘 = " << factorial(5) << endl;
    
    // 使用等级函数
    int score = 85;
    cout << "成绩 " << score << " 的等级是: " << getGrade(score) << endl;
    
    return 0;
}

第三部分:参数传递机制(50分钟)

3.1 值传递(Pass by Value)

值传递的特点:

  • 函数接收参数的副本,不是原始变量
  • 在函数内修改参数不会影响原始变量
  • 就像复印文件,修改复印件不影响原件
cpp 复制代码
#include <iostream>
using namespace std;

// 值传递示例
void modifyValue(int x) {
    cout << "函数内修改前: x = " << x << endl;
    x = x * 2;  // 修改参数的值
    cout << "函数内修改后: x = " << x << endl;
}

// 交换两个数的值(错误版本)
void swapWrong(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    cout << "函数内: a = " << a << ", b = " << b << endl;
}

// 计算矩形面积和周长
void calculateRectangle(double length, double width, double &area, double &perimeter) {
    area = length * width;
    perimeter = 2 * (length + width);
}

int main() {
    // 值传递示例
    int num = 5;
    cout << "调用函数前: num = " << num << endl;
    modifyValue(num);
    cout << "调用函数后: num = " << num << endl;
    cout << "注意:num的值没有改变!" << endl;
    
    cout << "\n--- 交换函数测试 ---" << endl;
    int x = 10, y = 20;
    cout << "交换前: x = " << x << ", y = " << y << endl;
    swapWrong(x, y);
    cout << "交换后: x = " << x << ", y = " << y << endl;
    cout << "注意:x和y的值没有交换!" << endl;
    
    return 0;
}

3.2 多返回值问题

cpp 复制代码
#include <iostream>
using namespace std;

// 方法1:使用多个函数(不推荐)
double calculateArea(double length, double width) {
    return length * width;
}

double calculatePerimeter(double length, double width) {
    return 2 * (length + width);
}

// 方法2:使用引用参数(后续课程详细讲解)
void calculateRectangle(double length, double width, double &area, double &perimeter) {
    area = length * width;
    perimeter = 2 * (length + width);
}

// 方法3:使用数组或结构体(后续课程学习)

int main() {
    double len = 5.0, wid = 3.0;
    
    // 方法1:调用多个函数
    double area1 = calculateArea(len, wid);
    double perimeter1 = calculatePerimeter(len, wid);
    cout << "方法1 - 面积: " << area1 << ", 周长: " << perimeter1 << endl;
    
    // 方法2:使用引用参数
    double area2, perimeter2;
    calculateRectangle(len, wid, area2, perimeter2);
    cout << "方法2 - 面积: " << area2 << ", 周长: " << perimeter2 << endl;
    
    return 0;
}

第四部分:函数声明和定义分离(30分钟)

4.1 为什么需要函数声明?

问题场景:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    // 错误:函数在调用之后定义
    int result = multiply(5, 3);  // 编译错误!
    cout << "结果: " << result << endl;
    return 0;
}

// 函数定义在main之后
int multiply(int a, int b) {
    return a * b;
}

4.2 函数声明(函数原型)

解决方案:函数声明 + 函数定义

cpp 复制代码
#include <iostream>
using namespace std;

// 函数声明(函数原型)
int multiply(int a, int b);
void showInfo(string name, int age);
double calculateBMI(double weight, double height);
bool isPrime(int number);

int main() {
    // 现在可以正确调用函数了
    cout << "5 × 3 = " << multiply(5, 3) << endl;
    showInfo("小明", 12);
    
    double bmi = calculateBMI(45.0, 1.65);
    cout << "BMI: " << bmi << endl;
    
    int num = 17;
    if (isPrime(num)) {
        cout << num << " 是质数" << endl;
    } else {
        cout << num << " 不是质数" << endl;
    }
    
    return 0;
}

// 函数定义
int multiply(int a, int b) {
    return a * b;
}

void showInfo(string name, int age) {
    cout << "姓名: " << name << ", 年龄: " << age << endl;
}

double calculateBMI(double weight, double height) {
    return weight / (height * height);
}

bool isPrime(int number) {
    if (number <= 1) return false;
    for (int i = 2; i * i <= number; i++) {
        if (number % i == 0) return false;
    }
    return true;
}

4.3 多文件组织(了解概念)

cpp 复制代码
// math_functions.h - 头文件(函数声明)
#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

int add(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);

#endif

// math_functions.cpp - 源文件(函数定义)
#include "math_functions.h"

int add(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 a / b;
    else return 0;
}

// main.cpp - 主程序
#include <iostream>
#include "math_functions.h"
using namespace std;

int main() {
    cout << "5 + 3 = " << add(5, 3) << endl;
    cout << "5 × 3 = " << multiply(5, 3) << endl;
    cout << "10 ÷ 3 = " << divide(10, 3) << endl;
    return 0;
}

第五部分:递归初步(50分钟)

5.1 什么是递归?

递归的概念:

  • 函数直接或间接调用自身
  • 像俄罗斯套娃,大娃娃里面有小娃娃
  • 像镜子对着镜子,无限反射

生活比喻:

  • 讲故事:"从前有座山,山里有座庙,庙里有个老和尚在讲故事:从前有座山..."
  • 查字典:查一个词,解释中又有不认识的词,继续查...

5.2 递归的基本结构

递归三要素:

  1. 基准情况:递归结束的条件
  2. 递归调用:函数调用自身
  3. 向基准情况推进:每次调用都更接近结束条件

5.3 递归示例:阶乘计算

数学定义:

  • 0! = 1(基准情况)
  • n! = n × (n-1)!(递归关系)
cpp 复制代码
#include <iostream>
using namespace std;

// 递归函数:计算阶乘
int factorial(int n) {
    // 基准情况
    if (n == 0 || n == 1) {
        cout << "到达基准情况: factorial(" << n << ") = 1" << endl;
        return 1;
    }
    
    // 递归调用
    cout << "计算 factorial(" << n << ") = " << n << " × factorial(" << n-1 << ")" << endl;
    int result = n * factorial(n - 1);
    cout << "返回 factorial(" << n << ") = " << result << endl;
    
    return result;
}

int main() {
    int number = 5;
    cout << "计算 " << number << " 的阶乘:" << endl;
    int result = factorial(number);
    cout << number << "! = " << result << endl;
    
    return 0;
}

5.4 递归示例:斐波那契数列

数学定义:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2)
cpp 复制代码
#include <iostream>
using namespace std;

// 递归函数:计算斐波那契数列
int fibonacci(int n) {
    // 基准情况
    if (n == 0) return 0;
    if (n == 1) return 1;
    
    // 递归调用
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 显示斐波那契数列
void showFibonacci(int count) {
    cout << "斐波那契数列前" << count << "项: ";
    for (int i = 0; i < count; i++) {
        cout << fibonacci(i) << " ";
    }
    cout << endl;
}

int main() {
    showFibonacci(10);
    
    // 计算单个斐波那契数
    int n = 6;
    cout << "斐波那契数列第" << n << "项: " << fibonacci(n) << endl;
    
    return 0;
}

5.5 递归的优缺点

优点:

  • 代码简洁,表达力强
  • 适合解决分治问题
  • 符合数学定义

缺点:

  • 可能效率较低(重复计算)
  • 可能栈溢出(递归深度太大)
  • 可能难以理解

第六部分:综合应用示例(60分钟)

6.1 数学工具包

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;

// 函数声明
void showMenu();
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
int power(int base, int exponent);
int factorial(int n);
int gcd(int a, int b);  // 最大公约数
bool isPrime(int number);

int main() {
    int choice;
    
    do {
        showMenu();
        cout << "请选择操作: ";
        cin >> choice;
        
        if (choice >= 1 && choice <= 4) {
            int a, b;
            cout << "请输入第一个数字: ";
            cin >> a;
            cout << "请输入第二个数字: ";
            cin >> b;
            
            switch (choice) {
                case 1:
                    cout << a << " + " << b << " = " << add(a, b) << endl;
                    break;
                case 2:
                    cout << a << " - " << b << " = " << subtract(a, b) << endl;
                    break;
                case 3:
                    cout << a << " × " << b << " = " << multiply(a, b) << endl;
                    break;
                case 4:
                    if (b != 0) {
                        cout << a << " ÷ " << b << " = " << divide(a, b) << endl;
                    } else {
                        cout << "错误:除数不能为0!" << endl;
                    }
                    break;
            }
        } else if (choice == 5) {
            int base, exp;
            cout << "请输入底数: ";
            cin >> base;
            cout << "请输入指数: ";
            cin >> exp;
            cout << base << "^" << exp << " = " << power(base, exp) << endl;
        } else if (choice == 6) {
            int n;
            cout << "请输入要计算阶乘的数: ";
            cin >> n;
            if (n >= 0) {
                cout << n << "! = " << factorial(n) << endl;
            } else {
                cout << "错误:阶乘只能计算非负整数!" << endl;
            }
        } else if (choice == 7) {
            int a, b;
            cout << "请输入第一个数: ";
            cin >> a;
            cout << "请输入第二个数: ";
            cin >> b;
            cout << a << "和" << b << "的最大公约数是: " << gcd(a, b) << endl;
        } else if (choice == 8) {
            int number;
            cout << "请输入要检查的数: ";
            cin >> number;
            if (isPrime(number)) {
                cout << number << " 是质数" << endl;
            } else {
                cout << number << " 不是质数" << endl;
            }
        } else if (choice != 0) {
            cout << "无效选择!" << endl;
        }
        
    } while (choice != 0);
    
    cout << "感谢使用数学工具包!" << endl;
    return 0;
}

// 函数定义
void showMenu() {
    cout << "\n=== 数学工具包 ===" << endl;
    cout << "1. 加法" << endl;
    cout << "2. 减法" << endl;
    cout << "3. 乘法" << endl;
    cout << "4. 除法" << endl;
    cout << "5. 幂运算" << endl;
    cout << "6. 阶乘" << endl;
    cout << "7. 最大公约数" << endl;
    cout << "8. 质数判断" << endl;
    cout << "0. 退出" << endl;
}

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(int a, int b) { return static_cast<double>(a) / b; }

int power(int base, int exponent) {
    int result = 1;
    for (int i = 0; i < exponent; i++) {
        result *= base;
    }
    return result;
}

int factorial(int n) {
    if (n == 0 || n == 1) return 1;
    return n * factorial(n - 1);
}

int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

bool isPrime(int number) {
    if (number <= 1) return false;
    for (int i = 2; i * i <= number; i++) {
        if (number % i == 0) return false;
    }
    return true;
}

6.2 图形绘制工具

cpp 复制代码
#include <iostream>
using namespace std;

// 函数声明
void drawRectangle(int width, int height, char symbol);
void drawTriangle(int height, char symbol);
void drawDiamond(int size, char symbol);
void drawChristmasTree(int levels);

int main() {
    int choice;
    
    cout << "=== 图形绘制工具 ===" << endl;
    
    // 绘制矩形
    cout << "\n1. 绘制矩形:" << endl;
    drawRectangle(8, 4, '*');
    
    // 绘制三角形
    cout << "\n2. 绘制三角形:" << endl;
    drawTriangle(5, '#');
    
    // 绘制菱形
    cout << "\n3. 绘制菱形:" << endl;
    drawDiamond(5, '+');
    
    // 绘制圣诞树
    cout << "\n4. 绘制圣诞树:" << endl;
    drawChristmasTree(4);
    
    return 0;
}

// 函数定义:绘制矩形
void drawRectangle(int width, int height, char symbol) {
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            cout << symbol;
        }
        cout << endl;
    }
}

// 函数定义:绘制三角形
void drawTriangle(int height, char symbol) {
    for (int i = 1; i <= height; i++) {
        // 打印空格
        for (int j = 1; j <= height - i; j++) {
            cout << " ";
        }
        // 打印符号
        for (int j = 1; j <= 2 * i - 1; j++) {
            cout << symbol;
        }
        cout << endl;
    }
}

// 函数定义:绘制菱形
void drawDiamond(int size, char symbol) {
    // 上半部分
    for (int i = 1; i <= size; i++) {
        for (int j = 1; j <= size - i; j++) cout << " ";
        for (int j = 1; j <= 2 * i - 1; j++) cout << symbol;
        cout << endl;
    }
    // 下半部分
    for (int i = size - 1; i >= 1; i--) {
        for (int j = 1; j <= size - i; j++) cout << " ";
        for (int j = 1; j <= 2 * i - 1; j++) cout << symbol;
        cout << endl;
    }
}

// 函数定义:绘制圣诞树
void drawChristmasTree(int levels) {
    for (int level = 1; level <= levels; level++) {
        int height = level + 1;
        for (int i = 1; i <= height; i++) {
            // 打印空格
            for (int j = 1; j <= levels - i + 1; j++) {
                cout << " ";
            }
            // 打印星号
            for (int j = 1; j <= 2 * i - 1; j++) {
                cout << "*";
            }
            cout << endl;
        }
    }
    // 树干
    for (int i = 0; i < 2; i++) {
        for (int j = 1; j <= levels; j++) {
            cout << " ";
        }
        cout << "|" << endl;
    }
}

练习与作业

基础练习(必做)

练习1:数学函数库

创建以下数学函数:

  1. square(int n) - 返回n的平方
  2. cube(int n) - 返回n的立方
  3. isEven(int n) - 判断n是否为偶数
  4. isPositive(int n) - 判断n是否为正数
  5. max(int a, int b) - 返回a和b中的较大值

练习2:温度转换器

编写温度转换函数:

  1. celsiusToFahrenheit(double celsius) - 摄氏转华氏
  2. fahrenheitToCelsius(double fahrenheit) - 华氏转摄氏
  3. 编写主程序让用户选择转换方向并输入温度

练习3:字符串处理函数

创建字符串处理函数:

  1. stringLength(string str) - 返回字符串长度(不用内置函数)
  2. countVowels(string str) - 统计元音字母个数
  3. reverseString(string str) - 返回反转后的字符串

挑战练习(选做)

挑战1:递归深度探索

  1. 编写递归函数计算数字的各位数之和
  2. 编写递归函数判断字符串是否是回文
  3. 测试递归深度限制,找出你的计算机最多支持多深的递归

挑战2:函数组合应用

创建一个"数字分析器"程序,包含以下功能:

  1. 判断质数
  2. 计算所有因数
  3. 判断完美数(等于其所有真因数之和的数)
  4. 判断阿姆斯特朗数(各位数字立方和等于自身的数)

挑战3:模块化游戏

将之前学过的猜数字游戏重构为模块化程序:

  • generateRandomNumber() - 生成随机数
  • getUserGuess() - 获取用户猜测
  • giveHint(int guess, int target) - 给出提示
  • playGame() - 游戏主逻辑

实验任务

任务1:函数调用栈实验

通过调试或输出语句跟踪以下递归函数的执行过程:

cpp 复制代码
void countDown(int n) {
    if (n < 0) return;
    cout << "进入: countDown(" << n << ")" << endl;
    countDown(n - 1);
    cout << "离开: countDown(" << n << ")" << endl;
}

任务2:值传递验证

设计实验验证值传递机制:

cpp 复制代码
void testValuePassing(int x) {
    x = 100;
    cout << "函数内: x = " << x << endl;
}
// 调用后检查原始变量是否改变

任务3:函数性能测试

比较递归和迭代版本的阶乘函数:

  • 测试计算20!的时间
  • 测试能计算的最大阶乘值
  • 分析两种方法的优缺点

学习总结

今天学到了:

  • 函数概念:模块化编程的基本单元
  • 函数定义:返回类型、函数名、参数列表、函数体
  • 函数调用:使用函数执行特定任务
  • 参数传递:值传递机制和特点
  • 返回值:函数执行结果的返回
  • 函数声明:提前声明函数原型
  • 递归初步:函数调用自身的编程技巧

关键技能:

  • 模块化设计:将复杂问题分解为函数
  • 接口设计:设计合理的函数参数和返回值
  • 代码复用:通过函数避免重复代码
  • 递归思维:用递归方式解决自相似问题

下一课预告:

下一节课我们将学习排序与查找算法,包括选择排序、冒泡排序、顺序查找和二分查找,这是算法学习的重要基础!


相关推荐
长安er7 小时前
LeetCode 46/51 排列型回溯题笔记-全排列 / N 皇后
笔记·算法·leetcode·回溯·递归·n皇后
oscar9991 天前
CSP-J教程——第一阶段——第七课:程序流程控制 - 循环结构(二)while和do-while循环
循环·csp-j
鹿角片ljp3 天前
力扣104.求二叉树最大深度:递归和迭代
算法·leetcode·二叉树·递归
长安er3 天前
LeetCode 235 & 236 最近公共祖先(LCA)解题总结
算法·leetcode·二叉树·递归·lca
长安er5 天前
LeetCode 100/101/110/199 对称/平衡二叉树与右视图
算法·leetcode·二叉树·dfs·bfs·递归
BestOrNothing_20155 天前
C++ 函数类型大全:成员函数 / 非成员函数 / 全局函数 / 静态函数 / 特殊成员函数 / 虚函数 / 模板函数 全面总结
c++·面向对象·八股·函数·程序语言
xcLeigh11 天前
超全 Kingbase KES V9R3C15 JSON 函数指南:从基础操作到高级应用
json·函数·国产数据库·kingbase·金仓数据库
quan263112 天前
20251204,职级权限,开发实践分享
java·递归·java权限·职级架构
达不溜先生 ୧⍢⃝୨16 天前
循环赛日程表问题
c语言·算法·递归·分治·循环赛日程表·动态二维数组