第7章 函数 函数的基本概念 函数是C++程序的基本组成单位,它是一组执行特定任务的语句集合:
1 2 3 4 返回类型 函数名(参数列表) { return 返回值; }
函数的组成部分:
返回类型 :函数返回值的类型,可以是任何有效的C++类型,包括void(无返回值)函数名 :函数的标识符,遵循C++的命名规则参数列表 :函数接受的参数,每个参数由类型和名称组成,多个参数用逗号分隔函数体 :包含函数执行的语句,用大括号包围返回语句 :可选,用于返回函数值函数声明和定义 函数声明 函数声明告诉编译器函数的存在及其签名(返回类型、函数名和参数列表):
1 2 3 4 int add (int a, int b) ;double calculateArea (double radius) ;void printMessage () ;
函数声明的位置:
在函数使用前 :在调用函数之前声明在头文件中 :将函数声明放在头文件中,在需要使用的地方包含头文件函数定义 函数定义提供了函数的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 int add (int a, int b) { return a + b; } double calculateArea (double radius) { const double PI = 3.1415926535 ; return PI * radius * radius; } void printMessage () { std::cout << "Hello, function!" << std::endl; }
函数调用 函数调用是执行函数的过程:
1 2 3 4 5 6 7 8 int sum = add (5 , 3 ); std::cout << "Sum: " << sum << std::endl; double area = calculateArea (2.5 ); std::cout << "Area: " << area << std::endl; printMessage ();
函数调用的过程:
参数传递 :将实际参数传递给形式参数执行函数体 :执行函数体内的语句返回值 :将返回值传回调用点(如果有)参数传递 值传递 值传递是将实际参数的值复制给形式参数:
1 2 3 4 5 6 7 8 9 10 11 void increment (int x) { x++; std::cout << "Inside function: " << x << std::endl; } int main () { int a = 5 ; increment (a); std::cout << "Outside function: " << a << std::endl; return 0 ; }
引用传递 引用传递是将实际参数的引用传递给形式参数:
1 2 3 4 5 6 7 8 9 10 11 void increment (int & x) { x++; std::cout << "Inside function: " << x << std::endl; } int main () { int a = 5 ; increment (a); std::cout << "Outside function: " << a << std::endl; return 0 ; }
指针传递 指针传递是将实际参数的地址传递给形式参数:
1 2 3 4 5 6 7 8 9 10 11 void increment (int * x) { (*x)++; std::cout << "Inside function: " << *x << std::endl; } int main () { int a = 5 ; increment (&a); std::cout << "Outside function: " << a << std::endl; return 0 ; }
常量引用传递 常量引用传递用于避免复制大对象,同时防止修改原始对象:
1 2 3 4 5 6 7 8 9 10 void printLargeObject (const std::string& s) { std::cout << s << std::endl; } int main () { std::string largeString = "Hello, world!" ; printLargeObject (largeString); return 0 ; }
默认参数 默认参数是在函数声明中为参数指定默认值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void printMessage (std::string message = "Hello" , int count = 1 ) ;void printMessage (std::string message, int count) { for (int i = 0 ; i < count; i++) { std::cout << message << std::endl; } } printMessage (); printMessage ("Hi" ); printMessage ("Hey" , 3 );
默认参数的规则:
从右到左 :默认参数必须从右到左连续设置声明中指定 :默认参数只在函数声明中指定,定义中不指定同一作用域 :默认参数在同一作用域中只能指定一次可变参数 C++11引入了可变参数模板,可以接受任意数量的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 void print () { std::cout << std::endl; } template <typename T, typename ... Args>void print (T first, Args... rest) { std::cout << first << " " ; print (rest...); } print (1 , 2.5 , "Hello" , true );
返回值 基本返回类型 函数可以返回任何有效的C++类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int getInt () { return 42 ; } double getDouble () { return 3.14 ; } std::string getString () { return "Hello" ; } bool getBool () { return true ; }
无返回值 使用void作为返回类型表示函数不返回值:
1 2 3 4 5 void printHello () { std::cout << "Hello" << std::endl; return ; }
返回引用 函数可以返回引用,用于避免复制大对象:
1 2 3 4 5 6 7 8 9 10 11 12 int & getLargest (int & a, int & b) { return (a > b) ? a : b; } int main () { int x = 10 , y = 20 ; int & largest = getLargest (x, y); largest = 30 ; std::cout << "x: " << x << ", y: " << y << std::endl; return 0 ; }
返回指针 函数可以返回指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int * createArray (int size) { int * arr = new int [size]; for (int i = 0 ; i < size; i++) { arr[i] = i; } return arr; } int main () { int * arr = createArray (5 ); for (int i = 0 ; i < 5 ; i++) { std::cout << arr[i] << " " ; } delete [] arr; return 0 ; }
返回对象 函数可以返回类的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Point {public : Point (int x, int y) : x (x), y (y) {} int x, y; }; Point createPoint (int x, int y) { return Point (x, y); } int main () { Point p = createPoint (10 , 20 ); std::cout << "Point: (" << p.x << ", " << p.y << ")" << std::endl; return 0 ; }
函数重载 函数重载是指在同一作用域中定义多个同名函数,它们的参数列表不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int add (int a, int b) { return a + b; } double add (double a, double b) { return a + b; } std::string add (const std::string& a, const std::string& b) { return a + b; } int sum1 = add (1 , 2 ); int sum2 = add (1.5 , 2.5 ); std::string sum3 = add ("Hello" , " World" );
函数重载的规则 参数列表不同 :参数的数量、类型或顺序不同返回类型不同 :仅返回类型不同不能重载函数const修饰符 :成员函数的const修饰符不同可以重载引用修饰符 :成员函数的引用修饰符(&和&&)不同可以重载参数的cv限定符 :参数的const和volatile限定符不同可以重载函数重载的解析过程 名称查找 :找到所有同名函数可行函数筛选 :筛选出参数数量匹配的函数最佳匹配选择 :根据参数类型转换规则选择最佳匹配歧义处理 :如果有多个最佳匹配,编译错误函数重载与默认参数的交互 1 2 3 4 5 6 7 8 9 10 11 void print (int x, int y = 0 ) { std::cout << "Ints: " << x << ", " << y << std::endl; } void print (double x) { std::cout << "Double: " << x << std::endl; } print (5 );
函数重载的最佳实践 语义一致 :重载函数应该具有相似的语义避免歧义 :避免可能导致歧义的重载参数类型差异明显 :确保参数类型差异足够明显考虑模板 :对于多种类型的相似操作,考虑使用函数模板内联函数 内联函数是将函数体直接嵌入到调用点,减少函数调用的开销:
1 2 3 4 5 6 7 8 9 10 inline int max (int a, int b) { return (a > b) ? a : b; } int main () { int result = max (10 , 20 ); std::cout << "Max: " << result << std::endl; return 0 ; }
内联函数的工作机制 编译期处理 :编译器在编译期将内联函数的调用替换为函数体代码展开 :函数体直接展开到调用点,避免函数调用的开销无函数调用栈 :不需要创建函数调用栈,减少栈空间使用编译期优化 :编译器可以对展开后的代码进行更有效的优化内联函数的特点 关键字 :使用inline关键字声明编译器决定 :inline只是建议,编译器可以根据情况忽略定义在头文件 :内联函数通常定义在头文件中,便于多个编译单元使用链接期处理 :内联函数具有内部链接,避免多个编译单元的重复定义内联函数的优缺点 优点 减少函数调用开销 :避免函数调用的栈操作、参数传递等开销提高执行速度 :对于频繁调用的小函数,性能提升明显编译器优化 :展开后的代码可以进行更有效的优化避免函数指针歧义 :内联函数可以避免函数指针导致的优化障碍缺点 增加代码大小 :函数体展开会增加目标代码大小编译时间增加 :更多的代码需要编译,增加编译时间调试困难 :内联函数在调试时可能难以设置断点不适合大函数 :大函数展开会导致代码膨胀,反而降低性能内联函数的适用场景 频繁调用的小函数 :如数学运算、访问器方法等性能关键路径 :在性能关键的代码路径中使用类的成员函数 :类的小型成员函数,特别是访问器和修改器模板函数 :模板函数默认是内联的内联函数的最佳实践 只内联小函数 :函数体不超过10-15行避免递归 :递归函数不适合内联避免复杂控制流 :包含循环、switch等复杂控制流的函数不适合内联不要强制内联 :让编译器决定是否内联,过度使用inline可能适得其反在头文件中定义 :内联函数必须在使用它的每个编译单元中可见,因此通常在头文件中定义递归函数 递归函数是调用自身的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int factorial (int n) { if (n <= 1 ) { return 1 ; } return n * factorial (n - 1 ); } int fibonacci (int n) { if (n <= 1 ) { return n; } return fibonacci (n - 1 ) + fibonacci (n - 2 ); }
递归函数的特点:
基线条件 :递归必须有一个终止条件递归调用 :函数调用自身栈溢出 :递归深度过大可能导致栈溢出性能 :递归可能比迭代慢,因为有函数调用开销函数指针 函数指针是指向函数的指针变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int add (int a, int b) { return a + b; } int subtract (int a, int b) { return a - b; } int (*operation)(int , int );int main () { operation = add; std::cout << "Add: " << operation (5 , 3 ) << std::endl; operation = subtract; std::cout << "Subtract: " << operation (5 , 3 ) << std::endl; return 0 ; }
函数指针的应用:
回调函数 :将函数作为参数传递函数表 :使用函数指针数组实现函数表策略模式 :在运行时选择不同的算法lambda 表达式(C++11+) lambda表达式是C++11引入的匿名函数:
C++20 lambda表达式改进 C++20对lambda表达式进行了多项改进,包括模板lambda、consteval lambda等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 auto add = []<typename T>(T a, T b) { return a + b; };std::cout << "Add int: " << add (5 , 3 ) << std::endl; std::cout << "Add double: " << add (2.5 , 3.5 ) << std::endl; auto compileTimeAdd = []<typename T>(T a, T b) consteval { return a + b; };constexpr int result = compileTimeAdd (5 , 3 ); auto maxValue = []<typename T>(T a, T b) { return a > b ? a : b; };#include <concepts> auto addNumbers = []<std::integral T>(T a, T b) { return a + b; };
编译期函数:constexpr和consteval constexpr函数(C++11+) constexpr函数是C++11引入的,可以在编译期计算的函数:
1 2 3 4 5 6 7 8 9 10 11 constexpr int factorial (int n) { return n <= 1 ? 1 : n * factorial (n - 1 ); } constexpr int result1 = factorial (5 ); int n = 5 ;int result2 = factorial (n);
constexpr函数的发展 C++11 :引入constexpr函数,限制较多(只能有一个return语句)C++14 :放宽限制,允许多个return语句、局部变量等C++17 :进一步放宽限制,允许if/switch语句、循环等C++20 :支持constexpr lambda、constexpr虚函数等constexpr函数的规则 参数和返回类型 :必须是字面量类型函数体 :C++11中限制较多,C++14+中可以使用更多语言特性调用 :只能调用其他constexpr函数编译期计算 :当参数是编译期常量时,函数在编译期执行constexpr函数的适用场景 数学计算 :编译期计算数学常量和函数数组大小 :计算编译期数组大小模板参数 :作为模板的非类型参数常量表达式 :在需要常量表达式的地方使用consteval函数(C++20+) consteval函数是C++20引入的强制编译时计算函数,确保函数在编译期执行:
1 2 3 4 5 6 7 8 9 10 11 consteval int factorial (int n) { return n <= 1 ? 1 : n * factorial (n - 1 ); } constexpr int result = factorial (5 );
consteval函数的特点 强制编译期执行 :必须在编译期计算,否则编译错误返回值 :总是编译期常量参数 :必须是编译期常量表达式与constexpr的区别 :constexpr可以在运行期执行,consteval必须在编译期执行constinit变量(C++20+) constinit变量是C++20引入的常量初始化变量,确保变量在编译期初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 constinit int global_value = 42 ;void function () { static constinit int static_value = 100 ; } constinit int counter = 0 ;void increment () { counter++; }
constinit变量的特点 编译期初始化 :变量在编译期完成初始化静态存储期 :只能用于静态存储期的变量非const :变量本身可以是非常量与constexpr的区别 :constexpr变量是常量,constinit变量可以是变量编译期函数的最佳实践 优先使用constexpr :对于既可以在编译期又可以在运行期执行的函数使用consteval :对于必须在编译期执行的函数合理使用constinit :对于需要编译期初始化但运行期修改的变量注意编译时间 :复杂的编译期计算可能增加编译时间测试编译期执行 :确保函数在编译期正确执行C++20新特性:协程 C++20引入了协程(Coroutines),用于简化异步编程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <coroutine> #include <iostream> #include <future> struct Task { struct promise_type { Task get_return_object () { return Task{std::coroutine_handle<promise_type>::from_promise (*this )}; } std::suspend_never initial_suspend () { return {}; } std::suspend_never final_suspend () noexcept { return {}; } void return_void () {} void unhandled_exception () {} }; std::coroutine_handle<promise_type> handle; }; Task simpleCoroutine () { std::cout << "Coroutine started" << std::endl; co_return ; } int main () { simpleCoroutine (); std::cout << "Main function" << std::endl; return 0 ; }
// lambda表达式 auto add = [](int a, int b) { return a + b; }; std::cout << “Add: “ << add(5, 3) << std::endl;
// 带捕获的lambda int x = 10; auto addX = [x](int a) { return a + x; }; std::cout << “Add X: “ << addX(5) << std::endl;
// 引用捕获 auto addXRef = [&x](int a) { return a + x; };
// 捕获所有变量 auto addAll = [=](int a) { return a + x; };
// 可变lambda auto increment = x mutable { return ++x; };
lambda表达式的语法:
1 2 3 [capture](parameters) mutable -> return_type { }
函数的存储类别 外部函数 默认情况下,函数是外部的,可以在其他文件中使用:
1 2 3 4 5 6 7 8 9 10 11 12 extern int add (int a, int b) ; int main () { int sum = add (1 , 2 ); return 0 ; } int add (int a, int b) { return a + b; }
静态函数 静态函数只在定义它的文件中可见:
1 2 3 4 5 6 7 8 9 static int helper () { return 42 ; } int main () { int result = helper (); return 0 ; }
主函数 主函数是C++程序的入口点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { return 0 ; } int main (int argc, char * argv[]) { for (int i = 0 ; i < argc; i++) { std::cout << "Argument " << i << ": " << argv[i] << std::endl; } return 0 ; }
主函数的特点:
返回类型 :必须是int参数 :可选,可以带命令行参数返回值 :0表示成功,非0表示失败唯一入口 :每个C++程序只能有一个主函数函数的最佳实践 1. 函数设计 单一职责 :每个函数只做一件事函数名清晰 :函数名应该清晰地表达函数的功能参数数量 :函数参数不宜过多,一般不超过5个参数顺序 :将最常用的参数放在前面返回值明确 :返回值应该明确表达函数的结果2. 代码风格 缩进 :使用一致的缩进风格注释 :为复杂函数添加注释,说明功能、参数和返回值命名规范 :使用有意义的函数名和参数名空行 :在函数定义之间添加空行3. 性能考虑 避免不必要的复制 :对于大对象,使用引用或指针传递内联小函数 :对于频繁调用的小函数,考虑使用内联避免深度递归 :对于深度递归,考虑使用迭代函数开销 :了解函数调用的开销,合理使用函数4. 错误处理 返回错误码 :对于简单错误,返回错误码抛出异常 :对于严重错误,抛出异常断言 :对于逻辑错误,使用断言参数验证 :在函数开始时验证参数的有效性常见错误和陷阱 1. 函数声明和定义不匹配 1 2 3 4 5 6 7 8 int add (int a, int b) ;int add (int a, int b, int c) { return a + b + c; }
2. 忘记返回值 3. 栈溢出 1 2 3 4 int infiniteRecursion () { return infiniteRecursion (); }
4. 参数传递错误 1 2 3 4 5 6 7 8 9 10 11 void modify (int x) { x = 100 ; } int main () { int a = 10 ; modify (a); std::cout << a; return 0 ; }
5. 函数重载歧义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void print (int x) { std::cout << "Int: " << x << std::endl; } void print (double x) { std::cout << "Double: " << x << std::endl; } int main () { print (5 ); print (5.5 ); print (5.0f ); return 0 ; }
小结 本章介绍了C++中函数的基本概念、声明和定义、参数传递、返回值、函数重载、内联函数、递归函数、函数指针和lambda表达式等内容。通过本章的学习,你应该能够:
掌握函数的声明和定义方法 理解不同的参数传递方式(值传递、引用传递、指针传递) 掌握函数重载的规则和应用 理解内联函数、递归函数和函数指针的使用 了解C++11引入的lambda表达式 遵循函数设计的最佳实践 函数是C++程序的基本组成单位,合理使用函数可以提高代码的可读性、可维护性和可重用性。在后续章节中,我们将学习数组、指针、类等更高级的C++特性,这些特性将与函数结合使用,帮助我们构建更复杂、更强大的程序。