006、C++函数:定义、调用、参数传递与函数重载
006、C函数定义、调用、参数传递与函数重载昨天review新人代码看到这么一段voidprocessData(intx){xx*2;// ... 后续处理}intmain(){intvalue42;processData(value);coutvalue;// 输出还是42新人在这里卡了半天}新手盯着调试器一脸困惑“明明函数里改了怎么出来还是原样” 这个经典的传值问题正好引出今天要聊的C函数核心机制。函数定义不只是写个模板定义函数时很多人只关注“能跑就行”但细节决定代码质量。看看这个常见的定义// 不好的写法 - 参数名毫无意义intcalc(inta,intb,intc){returna*bc;}// 好一点的写法intcalculateTotalPrice(intunitPrice,intquantity,inttax){returnunitPrice*quantitytax;}函数名和参数名就是最好的文档。我见过最离谱的代码里有个函数叫doStuff()三个月后原作者自己都看不懂这“stuff”到底是啥。返回值类型也容易踩坑。早期我写过一个返回局部变量引用的函数stringgetErrorMessage(){string msgSomething went wrong;returnmsg;// 灾难局部变量销毁后引用悬空}编译能过运行时随机崩溃这种bug最难查。函数调用栈帧里的秘密每次函数调用系统都会在栈上创建一个栈帧。理解这个对调试至关重要。当你看到调用栈深度几百层时大概率是递归没写好或者某个函数被意外循环调用。调用约定也值得注意。普通函数调用有开销如果函数特别小又频繁调用考虑inline提示编译器做内联优化inlineintsquare(intx){returnx*x;// 适合内联函数体小无复杂控制流}但别滥用inline编译器比你聪明它觉得不合适的内联提示会被忽略。参数传递值、指针和引用的选择开头的那个问题解决方案很简单// 方案1用指针voidprocessData(int*x){*x*x*2;// 注意解引用}// 方案2用引用更C的方式voidprocessData(intx){xx*2;// 直接操作原变量}引用和指针在底层都是传递地址但引用更安全——必须绑定到有效对象不存在空引用。不过引用也有坑voidbadPractice(conststringstr){string localstr;// 如果调用方传了个临时对象...// 临时对象可能很快销毁但我们已经复制了所以其实没问题// 这里想说的是注意引用的生命周期}对于内置类型int、double等传值通常更高效。对于大对象传const引用避免拷贝// 高效写法避免复制整个vectorvoidprocessVector(constvectorintdata){// 只能读不能修改data内容}// 需要修改时voidmodifyVector(vectorintdata){// 可以修改原vector}默认参数便利与陷阱并存默认参数让函数调用更简洁但有几个规则voidconnect(string host,intport3306,inttimeout5000);connect(localhost);// 使用默认端口和超时connect(localhost,3307);// 只覆盖端口默认参数必须从右向左连续设置。更重要的坑是默认参数在声明处指定不要在定义处重复指定否则容易不一致。头文件里声明时给默认值实现文件里就不要再写了这是链接器级别的坑我踩过。函数重载名字相同参数不同重载是C多态的一种形式编译器根据参数类型和数量决定调用哪个版本voidlog(constchar*message);// C风格字符串voidlog(conststringmessage);// C字符串voidlog(interrorCode);// 错误码voidlog(doublevalue);// 浮点数但重载分辨率有时让人意外voidtest(intx);voidtest(doublex);test(3.14f);// 调用哪个float会优先转double而不是int隐式转换可能引发歧义。更棘手的是const重载classData{public:voidprocess()const;// 常成员函数voidprocess();// 非常成员函数};const版本和non-const版本可以重载这是实现“读时不锁写时加锁”模式的基础。函数重载的边界情况有些看起来能重载的其实不行// 不能重载 - 仅返回类型不同intparse(conststringstr);doubleparse(conststringstr);// 编译错误// 这个可以 - 参数类型不同voidprocess(int*ptr);voidprocess(constint*ptr);// 这是不同的类型模板函数也能重载但匹配规则更复杂。当普通函数和模板函数都匹配时编译器优先选普通函数。实战建议参数顺序设计把最可能使用默认值的参数放最后把输出参数指针/引用放输入参数后面这是行业惯例。const正确性能加const就加上。const引用参数既避免拷贝又明确表示“我不改这个数据”对调用方是种承诺。单一职责一个函数只做一件事。我见过300行的函数里面嵌套5层if这种代码没人敢改。重载要谨慎参数数量相同但类型相近时重载可能让调用者困惑。如果逻辑完全不同不如起不同名字。小函数优先20行以内的函数容易测试、容易复用、容易优化。大函数往往藏着重复代码。最后分享个真实教训曾经为了“优化”把一个小函数的参数从传值改成传引用结果性能测试反而慢了。查汇编发现编译器原本能对传值做寄存器优化改引用后多了次内存访问。过早优化是万恶之源先写清晰正确的代码真有性能问题再profile。函数是代码的积木写好每个积木大厦才稳固。下次我们聊聊这些积木如何组织——命名空间和文件结构。