C++编程基础(六):函数
文章目录
- 一、函数概述
- 1.函数优点
- 2.函数的类型
- 二、函数定义与声明
- 1.定义
- 2.声明
- 3.函数的四种基本形式
- 三、参数传递
- 1.形参与实参
- 2.传值参数
- 3.传引用
- 4.传指针
- 四、高级参数特性
- 1.默认参数
- 2.C 风格可变参数 (`...`)
- 五、函数返回值
- 1.`return` 语句
- 2.返回引用
- 六、函数重载
- 七、函数递归
- 递归条件
- 案例 1:斐波那契数列
- 案例 2:汉诺塔
- 八、内联函数
函数是 C++ 程序的基本构建块,用于执行特定的任务。在其他编程语言中,它们也可能被称为“过程”或“子例程”。函数提供了代码模块化和可重用性,是构建可维护、可扩展软件的核心。
一、函数概述
1.函数优点
函数有很多优点,这里主要介绍两点:
- 提高代码可重用性:定义一次,调用多次。实现相同的功能不需要重复编写相同的代码。
- 代码模块化与优化:将大型程序分解为多个小而专注的函数,使代码更易于理解、测试和维护。
2.函数的类型
C++编程语言中有两种类型的函数:
- 库函数:是在C++头文件中声明的函数,如
<cmath>中的sqrt(),<string>中的getline()。 - 用户定义的函数:是由程序员创建的函数,以便可以多次使用它。它降低了大程序的复杂性并优化了代码。
二、函数定义与声明
在使用函数之前,需要先“定义”它(提供实现)或“声明”它(承诺它存在)。
1.定义
函数定义提供了函数的完整实现,包括:返回类型、函数名、参数列表(形参)以及函数体。
// 语法:定义一个函数
返回类型 函数名(参数列表) {// 函数体:包含一系列语句return 返回值; // 如果返回类型不是 void
}// 示例:一个完整的函数定义
int add(int a, int b) {int sum = a + b;return sum;
}
2.声明
函数声明(也称为函数原型)只告诉编译器函数的名称、返回类型和参数类型,不包含函数体。这允许我们在定义函数之前就调用它。
// 语法:声明一个函数
返回类型 函数名(参数列表);// 示例
int add(int a, int b); // 声明,注意末尾的分号int main() {int result = add(5, 10); // 可以调用,因为已经声明了return 0;
}// 定义
int add(int a, int b) {return a + b;
}
最推荐的做法是将函数声明放在头文件(.h 或 .hpp)中,将函数定义放在源文件(.cpp)中。
3.函数的四种基本形式
根据是否有参数和返回值,函数可以分为四种基本类型:
#include <iostream>// 1. 无参数,无返回值
void sayHello() {std::cout << "Hello!" << std::endl;
}// 2. 无参数,有返回值
int getMagicNumber() {return 42;
}// 3. 有参数,无返回值
void printSum(int a, int b) {std::cout << "Sum is: " << a + b << std::endl;
}// 4. 有参数,有返回值
int multiply(int a, int b) {return a * b;
}
三、参数传递
C++ 中函数参数的传递方式主要有三种:传值、传引用、传指针。
1.形参与实参
- 形参:在函数定义时,括号内声明的变量。
- 实参:在函数调用时,传递给函数的实际值或变量。
int multiply(int a, int b); // a 和 b 是形参
multiply(10, 20); // 10 和 20 是实参
2.传值参数
函数会创建形参的一个副本。函数体内的所有操作都是针对这个副本进行的,不会影响原始的实参。
void changeValue(int x) {x = 100; // 只修改了副本 x
}int main() {int val = 10;changeValue(val);// val 仍然是 10std::cout << "val = " << val << std::endl; return 0;
}
优点:安全,不会意外修改调用者的数据。
缺点:对于大型对象(如 std::string 或 std::vector),拷贝操作的开销很大。
3.传引用
形参是实参的一个别名,它们共享同一块内存。函数体内对形参的操作会直接影响原始的实参。
通过在类型后添加 & 来定义引用形参。仅在定义时添加,使用时与普通变量一样,不含 &。
void swap(int& a, int& b) {int temp = a;a = b;b = temp;
}int main() {int x = 10, y = 20;swap(x, y);// x 和 y 的值被交换了std::cout << "x = " << x << ", y = " << y << std::endl; // x = 20, y = 10return 0;
}
优点:效率高(没有拷贝开销),并且能修改实参。
缺点:可能会意外修改数据。如果不想修改,可以使用 const 引用:
void printVector(const std::vector<int>& vec)
4.传指针
传递的是实参的内存地址。函数通过解引用指针(*)来访问和修改原始数据。
void swap_ptr(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}int main() {int x = 10, y = 20;swap_ptr(&x, &y); // 调用时传递地址std::cout << "x = " << x << ", y = " << y << std::endl; // x = 20, y = 10return 0;
}
说明:功能上与传引用类似,但语法更繁琐(调用时需 &,内部需 *)。在 C++ 中,优先推荐使用引用,除非需要处理“空”状态(指针可以为 nullptr,引用不行)。
四、高级参数特性
1.默认参数
在函数声明时,可以为一个或多个参数指定默认值。如果在调用函数时未提供该参数,编译器将自动使用默认值。
// 只能从右向左设置默认值
void greet(std::string name, std::string prefix = "Mr./Ms. ") {std::cout << "Hello, " << prefix << name << std::endl;
}int main() {greet("Smith"); // Hello, Mr./Ms. Smithgreet("Jones", "Dr. "); // Hello, Dr. Jonesreturn 0;
}
2.C 风格可变参数 (...)
C++ 继承自 C 语言的特性,允许函数接受不定数量的参数。这需要使用 <cstdarg> 头文件中的 va_list, va_start,va_arg, va_end 宏。
#include <cstdarg>
#include <iostream>// 第一个参数 n 必须指明后面有多少个可选参数
int sum_all(int n, ...) {int sum = 0;va_list args; // 1. 定义参数列表va_start(args, n); // 2. 初始化,从 n 之后开始for (int i = 0; i < n; ++i) {sum += va_arg(args, int); // 3. 按 int 类型获取下一个参数}va_end(args); // 4. 清理return sum;
}int main() {std::cout << sum_all(4, 1, 3, 5, 8) << std::endl; // 17return 0;
}
五、函数返回值
1.return 语句
void 函数可以没有 return 语句,或者使用 return; 提前退出。
有返回值的函数必须使用 return value; 返回一个类型匹配的值。
错误示例:返回多个值
C++ 函数不直接支持返回多个值。以下语法是错误的:
return num1, num2;// 错误!
(这在 C++ 中是逗号运算符,只会返回最后一个值num2)
2.返回引用
函数可以返回一个引用,允许函数调用本身成为一个“左值”(可以被赋值)。
int global_val = 10;int& getGlobal() {return global_val;
}int main() {getGlobal() = 100; // 返回的是引用,可以直接赋值std::cout << global_val << std::endl; // 100
}
警告:不要返回对局部变量的引用,因为局部变量在函数结束时会被销毁,返回它的引用会导致“悬挂引用”,引发未定义行为。
六、函数重载
C++ 允许在同一作用域中定义多个同名函数,只要它们的参数列表不同(参数的个数、类型或顺序不同)。
int sum(int a, int b) {return a + b;
}double sum(double a, double b) {return a + b;
}std::string sum(const std::string& a, const std::string& b) {return a + b;
}
注意:函数的返回类型不能作为重载的唯一依据。
int func()和double func()无法构成重载。
七、函数递归
递归是指一个函数在其函数体内直接或间接地调用自身。
递归条件
- 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)。
- 函数的局部变量是独立的,不会相互影响。
- 递归必须向退出递归的条件逼近,否则就是无限递归了。
- 当一个函数执行完毕,或者遇到
return,就会返回,遵守谁调用就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁。
案例 1:斐波那契数列
int fibo(int n) {if (n <= 1) { // 基准情况 (F0 = 0, F1 = 1 或 F1=1, F2=1)return n; } else {return fibo(n - 1) + fibo(n - 2); // 递归步骤}
}
案例 2:汉诺塔
#include <iostream>// 将 num 个圆盘从 sou (源) 借助 aux (辅助) 移动到 tar (目标)
void hanoi(int num, char sou, char tar, char aux) {if (num == 1) { // 1. 基准情况std::cout << "Move disk 1 from " << sou << " to " << tar << std::endl;} else {// 2. 将 n-1 个从 sou 移到 auxhanoi(num - 1, sou, aux, tar);// 3. 将 最大的 1 个从 sou 移到 tarstd::cout << "Move disk " << num << " from " << sou << " to " << tar << std::endl;// 4. 将 n-1 个从 aux 移到 tarhanoi(num - 1, aux, tar, sou);}
}int main() {hanoi(3, 'A', 'C', 'B'); // A->C via Breturn 0;
}
八、内联函数
在C语言 中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。在C++中,为了解决这个问题,特别的引入了 inline 修饰符,表示为内联函数。
inline 是一个关键字,用于向编译器建议将函数调用替换为函数体的实际代码,以减少函数调用的开销(压栈、跳转等)。
inline bool isEven(int num) {return num % 2 == 0;
}int main() {bool b1 = isEven(100); // 编译器可能将其展开为: bool b1 = (100 % 2 == 0);
}
注意:
inline只是一个建议,编译器可以忽略它(例如,对于大型函数或递归函数)。inline关键字必须与函数定义放在一起。- 现代编译器(如 GCC, Clang)在开启优化(
-O2,-O3)时,会自动内联许多小型函数,inline关键字的必要性已大大降低。
