C++函数实战:从基础到递归的20道编程精解

发布时间:2026/7/16 23:38:41
C++函数实战:从基础到递归的20道编程精解 1. 函数基础入门从素数判断开始刚接触C函数时很多同学会觉得抽象。其实函数就像厨房里的料理机——你把食材参数丢进去它按照预设程序函数体处理最后给你成品返回值。我们先用最经典的素数判断来理解这个过程。判断素数的核心思路很简单一个大于1的自然数除了1和它本身外不能被其他数整除。比如5是素数而6可以被2或3整除就不是。来看具体实现bool isPrime(int n) { if (n 1) return false; // 处理边界情况 for (int i 2; i * i n; i) { // 优化只需检查到平方根 if (n % i 0) return false; } return true; }这个函数有几个关键点函数头bool isPrime(int n)声明返回类型、函数名和参数类型for循环中的i*i n是个重要优化减少不必要的检查遇到能整除的数立即返回false否则循环结束返回true实际使用时我们可以这样调用cout isPrime(17); // 输出1true cout isPrime(9); // 输出0false常见坑点忘记处理n1的情况循环条件写成in会导致效率低下没有及时返回false导致程序做无用功2. 函数参数传递实现两数交换参数传递是函数的核心机制。C中有三种传递方式值传递、引用传递和指针传递。我们通过交换两个数的例子来对比它们的区别。值传递版无效void swap(int a, int b) { int temp a; a b; b temp; } // 调用后x,y值不变引用传递版推荐void swap(int a, int b) { int temp a; a b; b temp; } // 调用后x,y值互换指针传递版void swap(int *a, int *b) { int temp *a; *a *b; *b temp; } // 调用时swap(x, y)为什么值传递不行因为传递的是副本函数内修改不影响原变量。引用和指针则直接操作内存地址。实际项目中引用传递更安全直观。比如实现数组元素交换void swapElements(int arr[], int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; }3. 返回多个值计算统计指标函数通常只能返回一个值但有时我们需要多个结果。有几种解决方案方法1使用结构体struct Stats { int min; int max; double avg; }; Stats calculateStats(int arr[], int size) { Stats result; result.min arr[0]; result.max arr[0]; double sum 0; for(int i0; isize; i) { if(arr[i] result.min) result.min arr[i]; if(arr[i] result.max) result.max arr[i]; sum arr[i]; } result.avg sum / size; return result; }方法2通过参数返回void calculateStats(int arr[], int size, int min, int max, double avg) { min max arr[0]; double sum 0; for(int i0; isize; i) { if(arr[i] min) min arr[i]; if(arr[i] max) max arr[i]; sum arr[i]; } avg sum / size; }应用场景比如需要同时获取数组的最大值、最小值和平均值时。第一种方法更面向对象第二种性能稍好。4. 递归函数从阶乘到斐波那契递归是函数调用自身的编程技巧适合解决可分治的问题。我们先看经典的阶乘实现int factorial(int n) { if(n 1) return 1; // 基线条件 return n * factorial(n-1); // 递归调用 }递归函数必须有两个部分基线条件终止条件递归条件调用自身斐波那契数列是另一个经典案例int fibonacci(int n) { if(n 0) return 0; if(n 1) return 1; return fibonacci(n-1) fibonacci(n-2); }递归的优缺点优点代码简洁直观反映数学定义缺点存在重复计算效率低斐波那契时间复杂度O(2^n)优化方法记忆化缓存已计算结果int fibMemo(int n, vectorint memo) { if(memo[n] ! -1) return memo[n]; if(n 1) return n; memo[n] fibMemo(n-1, memo) fibMemo(n-2, memo); return memo[n]; }5. 实战案例银行利息计算器综合运用函数知识我们实现一个复利计算器double calculateInterest(double principal, double rate, int years, bool compound) { if(compound) { return principal * pow(1 rate/100, years) - principal; } else { return principal * rate/100 * years; } } void printSchedule(double principal, double rate, int years) { cout fixed setprecision(2); cout Year | Balance endl; cout -----|-------- endl; for(int y1; yyears; y) { double interest calculateInterest(principal, rate, y, true); cout setw(4) y | principal interest endl; } }这个案例展示了带条件的函数逻辑数学函数pow的使用格式化输出控制函数组合调用6. 函数重载处理多种数据类型C允许同名函数处理不同类型参数称为函数重载。比如实现通用的绝对值函数int abs(int x) { return x 0 ? -x : x; } double abs(double x) { return x 0 ? -x : x; } long abs(long x) { return x 0 ? -x : x; }编译器会根据参数类型自动选择合适版本。这在数学运算、容器类等场景非常有用。注意事项仅返回值类型不同不算重载避免歧义性重载模板是更现代的解决方案7. 默认参数与内联函数默认参数void print(string message, bool newLine true) { cout message; if(newLine) cout endl; } // 可这样调用 print(Hello); // 自动补全默认参数 print(Hi, false); // 显式指定内联函数inline int max(int a, int b) { return a b ? a : b; }内联函数适合简单、频繁调用的函数能减少函数调用开销。但编译器可能忽略inline建议。8. 函数指针与回调机制函数指针允许我们将函数作为参数传递实现回调机制// 比较函数类型 typedef bool (*CompareFunc)(int, int); void sortArray(int arr[], int size, CompareFunc comp) { for(int i0; isize-1; i) { for(int ji1; jsize; j) { if(comp(arr[i], arr[j])) { swap(arr[i], arr[j]); } } } } // 比较函数实现 bool ascending(int a, int b) { return a b; } bool descending(int a, int b) { return a b; } // 使用 sortArray(nums, 10, ascending); // 升序排序 sortArray(nums, 10, descending); // 降序排序这种技术在GUI事件处理、算法策略模式中广泛应用。9. 递归实战汉诺塔问题汉诺塔是展示递归威力的经典案例void hanoi(int n, char from, char to, char aux) { if(n 1) { cout Move disk 1 from from to to endl; return; } hanoi(n-1, from, aux, to); cout Move disk n from from to to endl; hanoi(n-1, aux, to, from); }调用方式hanoi(3, A, C, B); // 将3个盘子从A移到C借助B递归思维要点将问题分解为更小的相同问题明确最小问题的解法基线条件相信递归调用能解决子问题10. 综合练习简易计算器最后我们实现一个支持四则运算的计算器double calculate(double a, double b, char op) { switch(op) { case : return a b; case -: return a - b; case *: return a * b; case /: if(b 0) { cout Error: Division by zero; return 0; } return a / b; default: cout Error: Invalid operator; return 0; } } void runCalculator() { double a, b; char op; cout Enter first number: ; cin a; cout Enter operator (, -, *, /): ; cin op; cout Enter second number: ; cin b; double result calculate(a, b, op); cout Result: result endl; }这个案例涵盖了多条件分支处理错误检查用户输入输出函数组合11. 调试技巧追踪函数调用调试递归或复杂函数时调用追踪非常有用int factorial(int n, int depth 0) { string indent(depth*2, ); cout indent Enter factorial( n )\n; if(n 1) { cout indent Base case: return 1\n; return 1; } int result n * factorial(n-1, depth1); cout indent Return result endl; return result; }输出示例Enter factorial(3) Enter factorial(2) Enter factorial(1) Base case: return 1 Return 2 Return 612. 性能优化避免重复计算递归函数常有重复计算问题。以斐波那契为例我们比较三种实现原始递归O(2^n)int fib(int n) { if(n 1) return n; return fib(n-1) fib(n-2); }记忆化递归O(n)int fibMemo(int n, vectorint memo) { if(memo[n] ! -1) return memo[n]; if(n 1) return n; memo[n] fibMemo(n-1, memo) fibMemo(n-2, memo); return memo[n]; }迭代法O(n)int fibIter(int n) { if(n 1) return n; int a 0, b 1; for(int i2; in; i) { int c a b; a b; b c; } return b; }选择建议小规模问题用原始递归最直观中等规模用记忆化大规模用迭代法13. 数学函数应用解二次方程实现解二次方程的函数展示数学库的使用#include cmath void solveQuadratic(double a, double b, double c) { double discriminant b*b - 4*a*c; if(discriminant 0) { double x1 (-b sqrt(discriminant)) / (2*a); double x2 (-b - sqrt(discriminant)) / (2*a); cout Two real roots: x1 and x2 endl; } else if(discriminant 0) { double x -b / (2*a); cout One real root: x endl; } else { double real -b / (2*a); double imag sqrt(-discriminant) / (2*a); cout Complex roots: real imag i and real - imag i endl; } }这个例子使用了cmath中的sqrt函数处理了三种不同情况并展示了复数解的表示方法。14. 字符串处理函数实现几个常用的字符串操作函数反转字符串void reverseString(char str[]) { int len strlen(str); for(int i0; ilen/2; i) { swap(str[i], str[len-1-i]); } }统计字符出现次数int countChar(const char str[], char ch) { int count 0; for(int i0; str[i]!\0; i) { if(str[i] ch) count; } return count; }判断回文bool isPalindrome(const char str[]) { int len strlen(str); for(int i0; ilen/2; i) { if(str[i] ! str[len-1-i]) return false; } return true; }这些函数展示了字符数组的基本操作注意字符串以\0结尾的特性。15. 随机数生成器实现一个灵活的随机数生成函数#include cstdlib #include ctime void initRandom() { srand(time(0)); // 用当前时间初始化随机种子 } int randomInt(int min, int max) { return min rand() % (max - min 1); } double randomDouble(double min, double max) { return min (max - min) * (rand() / (double)RAND_MAX); }使用示例initRandom(); cout randomInt(1, 100); // 1-100的随机整数 cout randomDouble(0.0, 1.0); // 0-1的随机浮点数注意事项必须先调用srand初始化rand()生成的随机数质量有限C11的 库更强大16. 文件操作函数实现文件复制功能展示文件I/O操作#include fstream bool copyFile(const string source, const string dest) { ifstream src(source, ios::binary); ofstream dst(dest, ios::binary); if(!src || !dst) return false; dst src.rdbuf(); return src dst; }这个函数以二进制模式打开源文件和目标文件检查文件是否成功打开使用rdbuf()高效复制文件内容返回操作是否成功17. 模板函数入门模板允许我们编写通用函数template typename T T getMax(T a, T b) { return a b ? a : b; } // 特化版本 template const char* getMax(const char* a, const char* b) { return strcmp(a, b) 0 ? a : b; }使用示例cout getMax(3, 5); // 5 cout getMax(3.14, 2.71); // 3.14 cout getMax(apple, zoo); // zoo模板是C泛型编程的基础STL库大量使用了模板技术。18. Lambda表达式C11引入的lambda表达式简化了匿名函数的定义void sortCustom(vectorint nums, bool ascending) { sort(nums.begin(), nums.end(), [ascending](int a, int b) { return ascending ? a b : a b; } ); }lambda的完整语法[capture](parameters) - return_type { // 函数体 }捕获列表可以捕获外部变量[]值捕获所有外部变量[]引用捕获所有外部变量[x, y]混合捕获19. 递归回溯八皇后问题八皇后问题展示递归在回溯算法中的应用bool isSafe(int board[8][8], int row, int col) { // 检查当前列 for(int i0; irow; i) { if(board[i][col]) return false; } // 检查左上对角线 for(int irow, jcol; i0 j0; i--, j--) { if(board[i][j]) return false; } // 检查右上对角线 for(int irow, jcol; i0 j8; i--, j) { if(board[i][j]) return false; } return true; } bool solveNQueens(int board[8][8], int row 0) { if(row 8) return true; for(int col0; col8; col) { if(isSafe(board, row, col)) { board[row][col] 1; if(solveNQueens(board, row1)) { return true; } board[row][col] 0; // 回溯 } } return false; }这个算法逐行放置皇后每步检查是否安全递归尝试下一行失败时回溯撤销选择20. 现代C特性constexpr函数C11引入的constexpr函数可以在编译期求值constexpr int factorial(int n) { return n 1 ? 1 : n * factorial(n-1); } constexpr int fib(int n) { return n 1 ? n : fib(n-1) fib(n-2); }使用示例int main() { constexpr int f5 factorial(5); // 编译期计算 int x fib(10); // 运行期计算 }constexpr的优势提升性能编译期计算可用于模板元编程C14放宽了限制允许局部变量和循环21. 函数式编程高阶函数高阶函数是指接受或返回函数的函数C中可以通过函数指针或std::function实现#include functional #include vector vectorint filter(const vectorint nums, functionbool(int) pred) { vectorint result; for(int n : nums) { if(pred(n)) { result.push_back(n); } } return result; } void demo() { vectorint numbers {1,2,3,4,5,6,7,8,9,10}; // 过滤偶数 auto evens filter(numbers, [](int n) { return n%2 0; }); // 过滤大于5的数 auto bigNums filter(numbers, [](int n) { return n 5; }); }这种风格在算法抽象、回调机制中非常有用是函数式编程的核心概念。22. 异常安全资源管理函数编写异常安全的资源管理函数class FileHandle { FILE* file; public: FileHandle(const char* filename, const char* mode) : file(fopen(filename, mode)) { if(!file) throw runtime_error(File open failed); } ~FileHandle() { if(file) fclose(file); } // 禁用拷贝 FileHandle(const FileHandle) delete; FileHandle operator(const FileHandle) delete; // 允许移动 FileHandle(FileHandle other) : file(other.file) { other.file nullptr; } void write(const string data) { if(fputs(data.c_str(), file) EOF) { throw runtime_error(Write failed); } } };这个类使用RAII资源获取即初始化模式构造函数获取资源析构函数释放禁用拷贝防止重复释放允许移动语义转移所有权提供安全的写入接口23. 多线程并行计算函数使用C11的线程库实现并行计算#include thread #include vector #include numeric void parallelSum(const vectorint data, int result, int start, int end) { result accumulate(data.begin()start, data.begin()end, 0); } int main() { vectorint bigData(1000000, 1); // 100万个1 int sum1 0, sum2 0; thread t1(parallelSum, ref(bigData), ref(sum1), 0, bigData.size()/2); thread t2(parallelSum, ref(bigData), ref(sum2), bigData.size()/2, bigData.size()); t1.join(); t2.join(); cout Total sum: sum1 sum2 endl; }注意事项使用ref传递引用注意数据竞争问题实际项目中应使用更高级的并行库如TBB24. 性能分析内联与尾递归优化内联函数inline int square(int x) { return x * x; }编译器会将函数调用替换为函数体消除调用开销。适合简单、频繁调用的函数。尾递归优化int factorial(int n, int acc 1) { if(n 1) return acc; return factorial(n-1, n*acc); // 尾递归 }某些编译器能将尾递归转换为循环避免栈溢出。关键点是递归调用必须是最后一步操作。25. 设计模式策略模式用函数对象实现策略模式class SortStrategy { public: virtual void sort(vectorint data) 0; virtual ~SortStrategy() {} }; class BubbleSort : public SortStrategy { public: void sort(vectorint data) override { // 实现冒泡排序 } }; class QuickSort : public SortStrategy { public: void sort(vectorint data) override { // 实现快速排序 } }; class Sorter { unique_ptrSortStrategy strategy; public: void setStrategy(unique_ptrSortStrategy s) { strategy move(s); } void execute(vectorint data) { if(strategy) strategy-sort(data); } };这种设计允许运行时切换算法符合开闭原则。现代C更常用std::function实现策略模式。26. 元编程编译期字符串处理C17引入的constexpr if支持更强大的元编程templatesize_t N constexpr auto reverseString(const char (str)[N]) { char result[N] {}; for(size_t i0; iN-1; i) { result[i] str[N-2-i]; } result[N-1] \0; return result; } void demo() { constexpr auto reversed reverseString(hello); static_assert(reversed[0] o, Compile-time check); }这个函数在编译期完成字符串反转结果可用于模板参数等需要编译期常量的场景。27. 函数组合与管道操作实现函数组合工具支持链式调用templatetypename T class Pipe { T value; public: explicit Pipe(T v) : value(v) {} templatetypename F auto then(F f) - Pipedecltype(f(value)) { return Pipedecltype(f(value))(f(value)); } T get() { return value; } }; templatetypename T PipeT make_pipe(T v) { return PipeT(v); } void demo() { auto result make_pipe(5) .then([](int x) { return x * 2; }) .then([](int x) { return x 3; }) .get(); cout result; // 输出13 }这种模式在数据处理流水线中非常有用可以创建清晰的操作链。28. 协程基础生成器函数C20引入了协程支持可以实现生成器模式#include coroutine Generatorint range(int start, int end) { for(int istart; iend; i) { co_yield i; // 暂停并返回值 } } void demo() { for(int n : range(1, 5)) { cout n ; // 输出1 2 3 4 5 } }协程允许函数在执行中暂停和恢复非常适合实现惰性求值、异步IO等场景。完整实现需要定义Generator模板类。29. 概念约束增强函数接口C20概念(concepts)可以更好地约束模板函数templatetypename T concept Addable requires(T a, T b) { { a b } - std::same_asT; }; templateAddable T T sum(T a, T b) { return a b; } void demo() { cout sum(3, 4); // OK cout sum(3.14, 2.0); // OK // sum(a, b); // 编译错误 }概念使模板错误更友好接口更清晰是现代C的重要特性。30. 实战总结函数设计原则根据多年经验我总结了这些函数设计原则单一职责一个函数只做一件事简短精炼理想长度在20行以内明确命名动词开头如calculateTax()避免副作用尽量减少修改外部状态合理参数3个以内为宜过多考虑封装对象完善文档注释说明用途、参数、返回值和异常错误处理明确错误处理策略异常/错误码性能考量高频调用函数要注意效率记住这些原则你的函数代码将更健壮、更易维护。编程实践中函数是构建复杂系统的基石掌握好函数设计是成为优秀程序员的关键一步。