搜索

C++ 教程

C++基础

第1章 搭建开发环境与第一个程序 第2章 基本数据类型与变量 第3章 运算符与表达式 第4章 流程控制语句 第5章 数组与字符串 第6章 函数基础 第7章 指针基础 第8章 引用与const限定 第9章 动态内存管理 第10章 结构体与联合体 第11章 函数进阶

面向对象

第12章 面向对象编程入门 第13章 封装与访问控制 第14章 运算符重载 第15章 继承与派生 第16章 多态与虚函数

STL与进阶

第17章 模板编程基础 第18章 标准模板库(STL)容器 第19章 STL算法与迭代器 第20章 异常处理机制 第21章 文件输入输出 第22章 字符串类string 第23章 命名空间与作用域 第24章 类型转换操作符

现代C++

第25章 智能指针 第26章 移动语义与右值引用 第27章 Lambda表达式 第28章 多线程编程基础 第29章 模板元编程入门 第30章 设计模式基础 第31章 C++11/14新特性 第32章 C++17/20核心特性

实战进阶

第33章 性能优化技巧 第34章 调试与测试 第35章 项目结构与构建 第36章 网络编程入门 第37章 数据库连接操作 第38章 图形界面开发基础 第39章 综合项目实战 第40章 进阶学习路线

第6章 函数基础

第6章 函数基础

入门 👤 已掌握基本流程控制和数组的初学者 🔧 将重复代码模块化,构建清晰程序结构
本章你将学到
  • 函数声明、定义与调用的完整流程
  • 理解参数传递与返回值的作用
  • 掌握函数作用域与局部变量的生命周期
核心概念函数定义参数传递

想象一下,你每次想喝咖啡,都需要亲自去咖啡豆产地采摘、烘焙、研磨、冲泡。这显然不现实。现实中,你只需要告诉咖啡师“一杯美式”,他就会调用一套封装好的“制作美式咖啡”流程。在C++中,函数就是这个“封装好的流程”,它把一系列操作打包成一个有名字的、可重复使用的代码块。

6.1 函数是什么?

生活类比:函数就像一个预设好程序的智能家电(比如洗衣机),你放入衣物(输入参数),选择模式(调用函数),它自动完成洗涤、漂洗、脱水(执行代码),最后把干净的衣物给你(返回结果)。
技术定义:函数是一段具有特定功能的、可重复使用的代码块,它通过一个名字来标识,可以接收输入(参数),并可能返回一个输出(返回值)。

一个函数主要包含四个部分:返回类型、函数名、参数列表和函数体。看看这个计算两个整数之和的函数:

int add(int a, int b) { // int是返回类型,add是函数名,int a, int b是参数列表
    int sum = a + b; // 函数体开始
    return sum;       // 使用return语句返回结果
} // 函数体结束

6.2 函数的声明、定义与调用

使用一个函数需要三步:先告诉编译器有这么一个函数(声明),再具体实现它(定义),最后在需要的地方使用它(调用)。

函数声明
→
函数定义
→
函数调用
↓
int add(int, int);
↗
int result = add(5, 3);
函数三要素对比
阶段目的代码示例位置要求
声明告知编译器函数的存在(名称、参数、返回类型)int add(int a, int b);通常在头文件(.h)或代码文件顶部
定义提供函数功能的具体实现int add(int a, int b) { return a+b; }在源文件(.cpp)中,每个函数只能定义一次
调用执行函数,并可能使用其返回值int sum = add(5, 3);在可执行语句中

在实际开发中,我习惯将函数声明放在头文件,定义放在对应的源文件。这样结构清晰,也方便多人协作。但为了本章学习方便,我们会把声明和定义写在一起。

关键点:理解函数调用过程

当执行int result = add(5, 3);时,CPU会:

  1. 跳转到add函数的代码位置。
  2. 为参数a和b创建临时的局部变量,并用5和3初始化它们。
  3. 顺序执行函数体内的语句,计算a + b得到8。
  4. 遇到return语句,将结果8返回给调用处。
  5. 跳回调用处,将返回值8赋值给变量result。

6.3 参数传递与返回值

参数是函数与外界沟通的桥梁,而返回值是函数工作成果的汇报。

无参无返回值

  • 函数:void printHello()
  • 作用:单纯执行操作,如打印菜单
  • 调用:printHello();

有参无返回值

  • 函数:void logError(string msg)
  • 作用:根据输入执行操作,不返回数据
  • 调用:logError(“文件打开失败”);

有参有返回值

  • 函数:float calculateBMI(float weight, float height)
  • 作用:最常见形式,输入数据,返回计算结果
  • 调用:bmi = calculateBMI(70.0, 1.75);

void是一个特殊的关键字。当它作为返回类型时,表示函数不返回任何值,这时return语句可以省略,或写成return;。当它作为参数时(如int func(void)),表示函数不接受任何参数,但C++中更推荐直接写空括号int func()。

⚠️ 常见误区

  • 混淆声明与定义:将带有函数体的代码(定义)多次包含在项目中,会导致“重复定义”链接错误。声明可以多次,定义只能有一次。
  • 忽略返回值:对于非void函数,必须确保所有执行路径都有返回值。编译器可能不会报错,但会导致未定义行为。我在排查一个随机崩溃的bug时,就曾发现某个if分支漏写了return。
  • 参数类型不匹配:调用函数时,传入的实参类型必须与形参类型兼容或能隐式转换。传递float给int参数会丢失精度,编译器通常会给出警告。

6.4 函数作用域与局部变量

在函数内部定义的变量称为局部变量。它们就像函数这个“房间”里的私人物品,只在函数执行期间存在,外部无法访问。

void myFunction() {
    int localVar = 42; // localVar是局部变量
    cout << localVar; // 正确,在函数内可访问
}

int main() {
    myFunction();
    // cout << localVar; // 错误!localVar在这里“不存在”
    return 0;
}

不同函数中的局部变量即使同名,也互不干扰。函数funcA中的int x和函数funcB中的int x占用不同的内存位置。

编者提示:关于变量在内存中更详细的生存周期和存储位置(如栈内存),将在后续关于内存管理的章节中深入探讨。理解局部变量的栈分配是理解函数调用机制和调试栈溢出错误的基础。

形参(函数定义中的参数)也是局部变量的一种特殊形式。在调用时,它们被实参初始化。

6.5 默认参数

C++允许为函数参数指定默认值。当调用函数时省略了该参数,编译器会自动使用默认值。这提高了函数的灵活性。

// 函数声明中指定默认参数
void connect(string ip, int port = 8080, int timeout = 10);

// 函数定义中通常不再重复指定(C++标准允许但易混淆)
void connect(string ip, int port, int timeout) {
    // ... 连接逻辑
}

int main() {
    connect("192.168.1.1"); // 使用默认port=8080, timeout=10
    connect("192.168.1.1", 9000); // port=9000, timeout使用默认值10
    connect("192.168.1.1", 9000, 30); // 提供所有参数,不使用默认值
    // connect(“192.168.1.1”, , 30); // 错误!不能跳过中间参数
}

默认参数规则

  • 默认参数必须从参数列表的最右边开始连续设置。
  • 默认参数通常在函数声明中指定,而非定义中(除非声明和定义合一)。
  • 调用时,只能省略从右往左连续的参数。

6.6 函数重载

生活类比:“打开”这个动作,根据对象不同,具体操作也不同:打开门(推/拉)、打开书(翻页)、打开电脑(按电源)。在C++中,函数重载允许你用同一个函数名,定义多个不同参数列表的函数,编译器会根据调用时传入的实参类型和数量来决定使用哪个版本。
技术定义:函数重载是指在同一作用域内,可以声明多个同名函数,但它们的参数列表(参数类型、数量或顺序)必须不同。返回类型不同不足以构成重载。

// 重载的print函数
void print(int num) {
    cout << "整数: " << num << endl;
}
void print(double num) {
    cout << "浮点数: " << fixed << setprecision(2) << num << endl;
}
void print(const string& text) {
    cout << "字符串: " << text << endl;
}

int main() {
    print(10);      // 调用 print(int)
    print(3.14159); // 调用 print(double)
    print("Hello");  // 调用 print(const string&)
    return 0;
}

重载解析是编译时完成的,不会带来运行时开销。这是C++支持多态性的一种重要方式(编译时多态)。

💡 工程实践建议

在项目初期设计函数时,我就有意识地为可能扩展的功能使用重载。例如,一个初始化设备的函数initDevice(),可以先有一个无参版本使用默认配置,再重载一个接受配置结构体或文件的版本。这样API对使用者更友好。

动手试一试

1. 编写一个函数isEven,接收一个整数参数,返回一个布尔值(bool),判断该数是否为偶数。
2. 编写一个重载函数max,分别实现比较两个整数、两个浮点数、三个整数,并返回最大值的功能。
3. 编写一个函数printRectangle,接收两个整数参数width和height(默认值都为5),在控制台打印出由星号*组成的对应长宽的矩形框。

检验你的理解

  1. 下面代码片段中,函数foo的声明和调用是否有错误?如果有,是什么?
    int foo(int x, int y = 10);
    int main() { int a = foo(5); }
  2. 局部变量在函数调用结束后,其值是否还能被访问?为什么?
  3. 能否仅通过不同的返回类型来重载函数?例如:int getValue(); 和 double getValue();。

本章小结

  • 函数是封装代码块的基本单元,通过声明、定义、调用三步使用,能极大提高代码复用性和可读性。
  • 参数传递与返回值是函数与外界交互的核心方式,void用于表示无返回或无参数。
  • 局部变量的生命周期仅限于函数执行期间,其作用域被限制在函数内部。
  • 默认参数提供了函数调用的灵活性,但必须从参数列表最右侧开始连续设置。
  • 函数重载允许同一函数名对应多个不同参数列表的实现,是C++编译时多态的关键特性。

意见反馈

微信二维码 微信扫码关注 抖音二维码 抖音扫码关注