C++函数全解析:从基础三要素到高级应用实战

发布时间:2026/8/26 9:43:51
C++函数全解析:从基础三要素到高级应用实战 1. 从“hello world”到函数为什么函数是C的基石如果你刚开始学C可能还沉浸在cout Hello, World!的喜悦中或者正被指针和引用搞得晕头转向。但很快你就会发现当你想写一个计算器或者处理一段用户输入时把所有的代码都堆在main函数里很快就会变成一团乱麻。代码重复、逻辑混乱、改一处而动全身——这就是没有函数的世界。函数就是解决这个问题的钥匙。它远不止是教科书里“完成特定功能的一段代码”这么简单。在我看来函数是C乃至所有编程语言中将复杂问题模块化、将具体操作抽象化的最基本、最核心的单元。它让你能从“写代码”进化到“设计程序”。你写的每一个sqrt()平方根计算、每一次sort()排序背后都是函数在默默工作。那些让你头疼的“无法识别为cmdlet、函数…”的错误本质上也是系统在它的“函数库”里找不到对应的指令。所以这个“函数模块”的学习绝不是死记硬背语法。我们要搞明白三件事第一函数如何把大任务拆成小零件声明、定义、调用第二这些零件之间如何精准传递数据和状态参数、返回值、作用域第三如何用好C提供的特殊“高级零件”函数重载、内联函数、lambda表达式。理解了这些你才能看懂别人的代码写出自己能维护的代码。2. 函数的基本三要素声明、定义与调用写一个函数就像工厂里生产一个标准零件。首先你得有这张零件的设计图纸声明然后得有生产线按照图纸把它造出来定义最后才能在组装产品时使用它调用。这三步少了任何一步你的程序都跑不起来。2.1 函数声明告诉编译器“我要什么”函数声明也叫函数原型。它的核心作用是在函数定义出现之前先告诉编译器这个函数的存在、它的名字、它需要什么原料参数、以及它会产出什么产品返回类型。编译器看到声明就知道“哦后面会有个叫calculateSum的函数它需要两个整数最后会还我一个整数。我先记下来看到别人调用它的时候我就不报错了。”声明的标准格式是返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...);例如int calculateSum(int a, int b); // 声明一个计算两数之和的函数 void printMessage(const std::string msg); // 声明一个打印信息的函数无返回值这里有个关键点参数名在声明中是可选的。int calculateSum(int, int);这样写也是合法的声明。但为了代码可读性我强烈建议加上有意义的参数名这相当于给图纸上的零件接口做了标注。注意声明通常放在头文件.h或.hpp里。这样任何想使用这个函数的源文件只需要包含这个头文件即可实现了接口的公开和定义的隐藏。2.2 函数定义亲手把零件造出来定义是声明的具体实现。这里你要用代码写出这个函数到底怎么做。定义的格式和声明几乎一样只是去掉了末尾的分号换成了由花括号{}包裹的函数体。// 函数定义 int calculateSum(int a, int b) { int result a b; // 在函数体内进行计算 return result; // 使用return语句返回结果 } void printMessage(const std::string msg) { std::cout Message: msg std::endl; // 无返回值函数可以没有return语句或使用 return; }在定义里参数名是必须的因为你要在函数体内使用它们。函数体就像一个小型的独立程序它拥有自己的变量局部变量执行一系列语句并通过return将结果送出去对于非void类型。2.3 函数调用让零件开始工作调用函数就是使用它。你需要在代码中写下函数名并提供与声明匹配的实际参数实参。int main() { int x 5, y 3; // 调用calculateSum函数x和y是实参 int sum calculateSum(x, y); std::cout Sum is: sum std::endl; // 输出Sum is: 8 // 调用printMessage函数 printMessage(Hello from function!); return 0; }调用发生时程序会跳转到函数定义处执行执行完毕后再返回调用点继续。实参的值会传递给形参定义中的a和b。这里就引出了C中一个至关重要的概念参数传递方式。3. 参数传递的三种方式值、指针与引用参数如何从调用者“进入”函数决定了函数内部的操作是否会影响到外部的原始数据。这是理解函数行为的关键也是初学者最容易混淆和出错的地方。3.1 传值最安全但也最“费劲”这是默认的方式。形参是实参的一个副本。函数内部对形参的任何修改都不会影响外部的实参。void swapByValue(int a, int b) { int temp a; a b; b temp; std::cout Inside function: a a , b b std::endl; } int main() { int x 1, y 2; swapByValue(x, y); std::cout Outside function: x x , y y std::endl; // 输出 // Inside function: a2, b1 // Outside function: x1, y2 x和y并未交换 }为什么选择传值当你明确不需要修改实参或者实参是像int、double这样的小型数据时传值简单安全。但注意如果实参是一个庞大的结构体或类对象复制整个副本的代价时间和内存会很高。3.2 传指针操作地址间接修改传递的是实参的内存地址。函数通过这个地址可以找到并修改原始数据。void swapByPointer(int* a, int* b) { // 形参是指针类型 int temp *a; // 通过解引用操作符*来访问指针指向的值 *a *b; *b temp; } int main() { int x 1, y 2; swapByPointer(x, y); // 调用时传递x和y的地址 std::cout x x , y y std::endl; // 输出x2, y1 交换成功 }为什么选择传指针一是为了在函数内部修改外部变量如上面的交换二是为了避免大型对象复制的开销只传递一个地址通常4或8字节。但指针语法稍显复杂*和且存在空指针、野指针的风险。3.3 传引用指针的“语法糖”更直观引用是变量的一个别名。传引用时形参直接绑定到实参操作形参就是直接操作实参语法上却像传值一样简洁。void swapByReference(int a, int b) { // 形参是引用类型 int temp a; // 直接使用无需解引用 a b; b temp; } int main() { int x 1, y 2; swapByReference(x, y); // 调用时直接传递变量无需取地址 std::cout x x , y y std::endl; // 输出x2, y1 }为什么选择传引用这是C中更受推崇的方式。它既有指针直接修改原数据的能力又拥有传值般的简洁语法。对于不想被修改的输入参数使用const引用如const std::string既能避免复制又能防止意外修改是传递大型对象的首选方式。实操心得我个人的选择习惯是——内置简单类型int, double等且不需修改用传值需要修改或对象较大用传引用只有在需要处理“可能不存在”的对象即可以为nullptr时或者与C语言接口交互时才使用指针。const引用是函数参数设计的“万金油”多多使用。4. 返回机制与作用域数据如何“回家”与变量的一生函数执行完毕后如何把结果送回来函数内部创建的变量它们的生命有多长理解返回机制和作用域才能避免返回局部变量地址这类经典错误。4.1 返回值函数的产出使用return语句来返回值。返回值的类型必须与函数声明中的返回类型一致或能隐式转换。double calculateAverage(int arr[], int size) { if (size 0) { // 可以返回一个特殊值或使用异常处理。这里简单返回0。 return 0.0; } int sum 0; for (int i 0; i size; i) { sum arr[i]; } return static_castdouble(sum) / size; // 返回计算出的平均值 }对于void函数return;语句是可选的用于提前结束函数执行。关于返回引用和指针你可以返回引用或指针但绝对不要返回局部变量的引用或指针因为局部变量在函数结束时就被销毁了返回它的地址或引用将导致“悬垂引用/指针”访问结果是未定义的程序崩溃或得到垃圾值。// 错误示范 int dangerousFunction() { int localVar 42; return localVar; // 函数结束localVar内存被释放返回的引用无效 }安全的做法是返回静态局部变量、全局变量、动态分配的内存需调用者管理释放或传入的引用参数的别名。4.2 作用域与生命周期变量在哪里“活”什么时候“死”局部作用域块作用域在函数内部或任何{}内定义的变量。它们从定义处诞生到所在块结束时死亡。其他函数无法访问它们。全局作用域在所有函数和类之外定义的变量。它们在程序启动时创建结束时销毁。任何函数都可以访问但需注意命名污染和线程安全问题。静态局部变量在函数内部用static关键字声明的变量。它在程序首次执行到其声明时初始化且只初始化一次。函数调用结束后它并不销毁而是保持其值下次进入函数时继续使用。int globalVar 100; // 全局变量 void function() { int localVar 10; // 局部变量每次调用都会重新创建和初始化 static int staticVar 0; // 静态局部变量 staticVar; localVar; std::cout local: localVar , static: staticVar std::endl; } int main() { function(); // 输出local: 11, static: 1 function(); // 输出local: 11, static: 2 (localVar重新创建staticVar保持) // std::cout localVar; // 错误main函数无法访问function的局部变量 std::cout globalVar; // 正确可以访问全局变量 }理解作用域是管理内存和避免命名冲突的基础。尽量将变量的作用域限制在最小必要范围多用局部变量这是写出清晰、安全代码的原则之一。5. 函数重载与默认参数让接口更灵活C允许你给同一个函数名“赋予”多个定义只要它们的参数列表参数类型、数量或顺序不同。这叫做函数重载。编译器会根据你调用时提供的实参来决定具体使用哪个版本。// 重载示例同一个函数名处理不同类型/数量的参数 void print(int value) { std::cout Integer: value std::endl; } void print(double value) { // 参数类型不同 std::cout Double: value std::endl; } void print(const std::string label, int value) { // 参数数量不同 std::cout label : value std::endl; } int main() { print(42); // 调用 print(int) print(3.14); // 调用 print(double) print(Age, 25); // 调用 print(const string, int) }为什么需要重载它为功能相似但操作对象不同的函数提供了统一的接口名提高了代码的可读性和易用性。例如sqrt函数可以对int、float、double等类型进行重载。默认参数则是另一种便利特性。你可以在函数声明中为参数指定一个默认值。调用时如果省略该参数则使用默认值。void createWindow(const std::string title, int width 800, int height 600) { std::cout Creating window: title ( width x height ) std::endl; } int main() { createWindow(My App); // 使用默认宽高800x600 createWindow(My App, 1024); // 宽度1024高度使用默认600 createWindow(My App, 1024, 768); // 指定所有参数 }注意默认参数必须从参数列表的最右边开始连续设置。即一个有默认值的参数右边不能有不带默认值的参数。通常默认参数在函数声明中指定而非定义除非声明和定义合一。6. 内联函数与Lambda表达式性能利器与现代风格6.1 内联函数用空间换时间函数调用是有开销的需要压栈、跳转、执行、返回。对于非常短小、频繁调用的函数比如一个简单的max比较这种开销可能比函数体本身执行时间还长。inline关键字就是给编译器的建议“请尝试把这个函数的代码直接嵌入到每个调用点省去调用的开销。”inline int max(int a, int b) { return (a b) ? a : b; } int main() { int x 5, y 10; int m max(x, y); // 编译器可能会将此处直接替换为 int m (x y) ? x : y; }需要注意inline只是一个建议编译器最终决定是否内联。函数体复杂、递归或包含循环的函数编译器通常会忽略inline建议。滥用inline可能导致代码体积膨胀因为同一段代码被复制多份反而降低缓存命中率影响性能。现代编译器优化能力很强很多时候会自动内联简单的函数所以不必过度使用。6.2 Lambda表达式匿名函数对象这是C11引入的现代特性允许你在需要函数的地方就地定义一个匿名函数。它特别适用于一次性、简单的回调操作比如作为算法如std::sort的参数。一个Lambda表达式的基本语法是[捕获列表] (参数列表) - 返回类型 { 函数体 }捕获列表[]决定了Lambda可以访问其外部作用域中的哪些变量以及如何访问值捕获[]、引用捕获[]、或指定变量[x, y]。参数列表()和普通函数一样。返回类型-可以省略编译器会根据函数体中的return语句自动推导。函数体{}实现逻辑。#include iostream #include vector #include algorithm int main() { std::vectorint numbers {5, 2, 8, 1, 9}; int threshold 5; // 使用Lambda表达式作为std::count_if的条件 int count std::count_if(numbers.begin(), numbers.end(), [threshold](int num) { // 值捕获threshold return num threshold; }); std::cout Count of numbers threshold : count std::endl; // 一个更复杂的Lambda对vector排序按绝对值大小 std::sort(numbers.begin(), numbers.end(), [](int a, int b) { // 无捕获 return std::abs(a) std::abs(b); }); for (int n : numbers) std::cout n ; }Lambda的优势代码紧凑将定义和使用放在一起逻辑清晰。它本质上是一个编译器生成的、匿名的函数对象仿函数。对于来自JavaScript或Python的开发者这类似于箭头函数但C的Lambda功能更强大特别是捕获列表。实操心得在STL算法中Lambda几乎已经完全取代了传统的函数指针和自定义仿函数。对于简单的比较、谓词逻辑用Lambda能让代码意图一目了然。记住如果函数体超过三五行或者需要重复使用还是老老实实写个命名函数更好维护。7. 函数指针与回调将函数作为数据传递在C中函数不是变量但指向函数的指针是变量。这意味着你可以把函数像数据一样存储、传递和调用。这是实现回调机制和策略模式等高级技巧的基础。7.1 函数指针的基础语法声明一个函数指针需要指定它指向的函数的返回类型和参数类型。// 声明一个函数类型别名让代码更清晰 using ArithmeticFunc int (*)(int, int); // 或者传统的typedef方式 // typedef int (*ArithmeticFunc)(int, int); // 几个具体的函数 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; } int main() { // 声明一个函数指针变量pFunc并让它指向add函数 ArithmeticFunc pFunc add; // 可省略函数名本身会退化为指针 std::cout 5 3 pFunc(5, 3) std::endl; // 通过指针调用函数 pFunc subtract; // 让指针指向subtract函数 std::cout 5 - 3 pFunc(5, 3) std::endl; // 函数指针数组实现“跳转表”或“命令模式” ArithmeticFunc funcArray[] {add, subtract, multiply}; char opSymbols[] {, -, *}; for (int i 0; i 3; i) { std::cout 5 opSymbols[i] 3 funcArray[i](5, 3) std::endl; } }7.2 回调函数的实际应用回调函数的核心思想是“当你做完某件事或者某个事件发生时请调用我给你的这个函数。” 这在事件驱动编程、异步操作和定制化算法行为中非常常见。一个经典的例子是排序算法的比较函数#include algorithm #include vector #include string // 一个自定义的比较函数用于std::sort bool compareByLength(const std::string a, const std::string b) { return a.length() b.length(); // 按字符串长度升序排序 } int main() { std::vectorstd::string words {apple, banana, cherry, date}; // std::sort的第三个参数就是一个函数指针或函数对象、Lambda // 这里我们传递自定义的比较函数 std::sort(words.begin(), words.end(), compareByLength); for (const auto w : words) { std::cout w ; // 输出date apple cherry banana } std::cout std::endl; }在这个例子里std::sort算法的通用排序逻辑是固定的但“如何比较两个元素”这个策略是通过函数指针compareByLength传递给它的。这就是回调——sort函数在需要比较的时候会“回调”我们提供的函数。现代C中的演进虽然函数指针仍然有效但在现代C中我们更常使用函数对象仿函数、std::function和Lambda表达式来实现回调因为它们更灵活、更安全可以捕获状态。std::function是一个通用的可调用对象包装器可以存储任何可调用的实体函数、Lambda、函数对象等。#include functional #include iostream void process(int value, const std::functionvoid(int) callback) { std::cout Processing value ...\n; callback(value * 2); // 调用回调函数 } int main() { // 使用Lambda作为回调 process(10, [](int result) { std::cout Callback received: result std::endl; }); }这种方式比裸函数指针更强大也是现代C代码中的主流做法。8. 函数模板编写通用算法当你发现你为int写了一个max函数又为double写了一个几乎一样的max函数为string又写了一个……这时你就需要函数模板了。模板允许你编写不依赖于具体类型的代码编译器会在你使用时根据提供的类型自动生成对应的函数版本这个过程叫实例化。8.1 基本函数模板// 定义一个函数模板 template typename T // T 是一个类型参数 T myMax(T a, T b) { return (a b) ? a : b; } int main() { int i myMax(3, 7); // 编译器生成并调用 myMaxint(int, int) double d myMax(6.28, 3.14); // 生成并调用 myMaxdouble(double, double) char c myMax(a, z); // 生成并调用 myMaxchar(char, char) // 甚至可以用在自定义类型上只要该类型支持 操作符 // std::string s myMax(std::string(hello), std::string(world)); }template typename T告诉编译器接下来的函数定义中T是一个占位符代表某种类型。当你用具体类型调用myMax时编译器会用该类型替换所有的T生成一个具体的函数。8.2 多类型参数与特化模板可以有多个类型参数也可以为非类型参数如整型常量。// 多个类型参数 template typename T1, typename T2 void printPair(const T1 first, const T2 second) { std::cout ( first , second )\n; } // 非类型模板参数 template typename T, int Size class FixedArray { T data[Size]; // ... }; int main() { printPair(42, Answer); // T1int, T2const char* printPair(3.14, true); // T1double, T2bool }有时对于特定的类型组合通用模板可能不是最优的甚至无法编译。这时可以使用模板特化为特定类型提供定制化的实现。// 通用模板 template typename T bool isEqual(T a, T b) { return a b; } // 为const char* 类型提供特化版本比较字符串内容而非指针地址 template bool isEqualconst char*(const char* a, const char* b) { return strcmp(a, b) 0; } int main() { const char* s1 hello; const char* s2 hello; std::cout isEqual(s1, s2) std::endl; // 调用特化版本输出1 (true) }函数模板的意义它是C泛型编程的基石之一另一个是类模板。STL标准模板库中的绝大多数算法如std::sort,std::find都是函数模板。它们让代码极具复用性一份模板代码可以应对无数种数据类型同时保持类型安全编译时检查和高性能编译时生成特化代码。避坑指南模板代码通常需要放在头文件中因为编译器需要在每个使用它的编译单元中看到完整的定义才能实例化。这可能导致编译时间变长。另外模板错误信息往往冗长晦涩需要耐心阅读。使用conceptsC20可以极大地改善模板错误信息和约束检查。9. 递归函数自己调用自己的艺术递归是一种函数调用自身的编程技巧。它非常适合解决那些可以自然地分解为相同问题的更小版本的问题比如树的遍历、分治算法快速排序、归并排序、数学定义阶乘、斐波那契数列等。9.1 递归的基本结构与要点一个有效的递归函数必须包含两个部分基线条件一个或多个最简单、不可再分的情况直接返回结果不再递归。这是递归的出口防止无限调用。递归条件将原问题分解为一个或多个规模更小的同类子问题并调用自身来解决子问题。// 经典例子计算阶乘 n! n * (n-1) * ... * 1 unsigned long long factorial(int n) { // 1. 基线条件 if (n 1) { return 1; } // 2. 递归条件n! n * (n-1)! return n * factorial(n - 1); } // 经典例子计算斐波那契数列第n项 F(n) F(n-1) F(n-2) int fibonacci(int n) { // 基线条件 if (n 0) return 0; if (n 1) return 1; // 递归条件 return fibonacci(n - 1) fibonacci(n - 2); } int main() { std::cout 5! factorial(5) std::endl; // 输出 120 std::cout F(6) fibonacci(6) std::endl; // 输出 8 }9.2 递归的代价与优化递归的代码通常非常简洁优雅直击问题本质。但它也有明显的缺点性能开销每次函数调用都需要分配栈帧存储参数、返回地址和局部变量。递归深度过大如fibonacci(50)会导致巨大的调用开销甚至栈溢出。重复计算以朴素递归实现的斐波那契数列为例fibonacci(5)会计算fibonacci(4)和fibonacci(3)而fibonacci(4)又会计算fibonacci(3)和fibonacci(2)……fibonacci(3)被计算了多次。时间复杂度是指数级的。优化策略1记忆化搜索将已计算过的结果保存起来避免重复计算。#include unordered_map std::unordered_mapint, unsigned long long memo; unsigned long long fibonacci_memo(int n) { if (n 0) return 0; if (n 1) return 1; // 先查表如果计算过直接返回 if (memo.find(n) ! memo.end()) { return memo[n]; } // 没计算过递归计算并存入表 memo[n] fibonacci_memo(n - 1) fibonacci_memo(n - 2); return memo[n]; }这能将时间复杂度从指数级降为线性级但需要额外的存储空间。优化策略2迭代/动态规划很多递归问题可以转化为等价的迭代循环完全消除调用开销。unsigned long long fibonacci_iterative(int n) { if (n 0) return 0; if (n 1) return 1; unsigned long long prev 0, curr 1; for (int i 2; i n; i) { unsigned long long next prev curr; prev curr; curr next; } return curr; }何时使用递归当问题本身是递归定义的如树、图遍历且递归深度可控、不会导致栈溢出时递归是清晰的解决方案。对于性能敏感或深度可能很大的场景优先考虑迭代或记忆化递归。10. 实战构建一个简易数学工具库现在让我们把前面学的所有知识串起来动手写一个包含多种功能的小型数学工具库。这个库将包含函数声明、定义、重载、模板、默认参数等特性。10.1 设计头文件接口首先我们创建一个头文件math_utils.h声明我们的函数接口。这是库的“使用说明书”。// math_utils.h #ifndef MATH_UTILS_H // 防止头文件被重复包含 #define MATH_UTILS_H #include vector namespace math { // 将函数放入命名空间避免命名冲突 // 1. 基本运算使用默认参数 int power(int base, int exponent 2); // 计算幂默认平方 // 2. 函数重载求绝对值 int abs(int value); double abs(double value); long long abs(long long value); // 3. 函数模板求最大值和最小值 template typename T T max(const T a, const T b); template typename T T min(const T a, const T b); // 4. 处理数组的模板函数 template typename T T sum(const std::vectorT arr); template typename T double average(const std::vectorT arr); // 5. 递归函数计算最大公约数 (GCD) int gcd(int a, int b); // 6. 使用函数指针/回调的例子对数组应用一个操作 template typename T void transformArray(std::vectorT arr, T (*transformFunc)(T)); } // namespace math #endif // MATH_UTILS_H10.2 实现源文件具体实现然后创建源文件math_utils.cpp实现这些函数。注意模板函数的定义通常需要放在头文件里但为了演示我们将非模板函数放在cpp中。// math_utils.cpp #include math_utils.h #include cmath #include stdexcept namespace math { // 1. 幂运算实现使用循环 int power(int base, int exponent) { if (exponent 0) { // 简单处理实际库可能返回浮点数或报错 throw std::invalid_argument(Exponent must be non-negative for integer power.); } int result 1; for (int i 0; i exponent; i) { result * base; } return result; } // 2. 重载的abs函数实现 int abs(int value) { return (value 0) ? -value : value; } double abs(double value) { return std::fabs(value); // 调用标准库函数 } long long abs(long long value) { return (value 0) ? -value : value; } // 3. 模板函数max和min的实现放在头文件中更常见这里仅作示意 // 通常模板定义在头文件内 // template typename T // T max(const T a, const T b) { // return (a b) ? a : b; // } // 5. 递归求最大公约数 (Euclidean algorithm) int gcd(int a, int b) { // 基线条件 if (b 0) { return a; } // 递归条件 return gcd(b, a % b); } } // namespace math由于max,min,sum,average,transformArray是模板函数它们的完整定义必须对使用者可见。因此更标准的做法是将它们的定义也放在头文件math_utils.h中在声明之后。我们修改头文件补充这些定义// 在math_utils.h的命名空间内补充模板函数定义 namespace math { // ... 之前的声明 ... // 模板函数定义 template typename T T max(const T a, const T b) { return (a b) ? a : b; } template typename T T min(const T a, const T b) { return (a b) ? a : b; } template typename T T sum(const std::vectorT arr) { T total T(); // 值初始化对于数值类型是0 for (const T elem : arr) { total elem; } return total; } template typename T double average(const std::vectorT arr) { if (arr.empty()) { throw std::invalid_argument(Array must not be empty for average calculation.); } double total static_castdouble(sum(arr)); return total / arr.size(); } template typename T void transformArray(std::vectorT arr, T (*transformFunc)(T)) { for (T elem : arr) { elem transformFunc(elem); } } } // namespace math10.3 编写测试程序最后我们写一个main.cpp来测试我们的工具库。// main.cpp #include math_utils.h #include iostream #include vector // 一个简单的转换函数用于测试transformArray int square(int x) { return x * x; } double half(double x) { return x / 2.0; } int main() { using namespace math; // 使用math命名空间 // 测试基本运算和默认参数 std::cout power(5) power(5) std::endl; // 默认平方25 std::cout power(2, 3) power(2, 3) std::endl; // 8 // 测试函数重载 std::cout abs(-10) abs(-10) std::endl; // 10 std::cout abs(-3.14) abs(-3.14) std::endl; // 3.14 // 测试函数模板 std::cout max(3, 7) max(3, 7) std::endl; // 7 std::cout max(3.14, 2.71) max(3.14, 2.71) std::endl; // 3.14 std::cout min(a, z) min(a, z) std::endl; // a // 测试数组模板函数 std::vectorint intVec {1, 2, 3, 4, 5}; std::cout sum(intVec) sum(intVec) std::endl; // 15 std::cout average(intVec) average(intVec) std::endl; // 3.0 std::vectordouble doubleVec {1.1, 2.2, 3.3}; std::cout average(doubleVec) average(doubleVec) std::endl; // 2.2 // 测试递归函数 std::cout gcd(48, 18) gcd(48, 18) std::endl; // 6 // 测试回调函数 transformArray std::cout Original intVec: ; for (int n : intVec) std::cout n ; std::cout std::endl; transformArray(intVec, square); // 将每个元素平方 std::cout After squaring: ; for (int n : intVec) std::cout n ; // 1 4 9 16 25 std::cout std::endl; // 用Lambda测试transformArray更现代的方式 std::vectordouble anotherVec {10.0, 20.0, 30.0}; // 使用Lambda表达式作为转换函数每个元素加5 transformArray(anotherVec, [](double x) - double { return x 5.0; }); std::cout After adding 5: ; for (double d : anotherVec) std::cout d ; // 15 25 35 std::cout std::endl; return 0; }通过这个完整的实战项目你将函数声明与定义分离、命名空间管理、重载、模板、递归、回调等核心概念串联了起来。编译并运行这个程序观察输出能让你对函数模块的理解从理论真正落到实地。你可以继续扩展这个库加入统计函数方差、标准差、数值计算函数利用快速幂算法等把它变成你个人学习C的实用工具箱。

相关新闻