本文将列举C++开发中常见的十种不良编程习惯,无论新手还是资深开发者都可能沾染,并提供相应的修正方法。


一、变量未初始化

不良表现:定义变量时不给初始值,直接使用未定义的变量。例如:

cpp

int a, b, c;
// 后续代码直接使用a、b、c

此时变量的值是不确定的(包含残留数据),程序行为不可预测。

正确做法:在变量定义时立即初始化。整数赋0,浮点数赋0.0,布尔赋false,指针赋nullptr。对于自定义类型,确保构造函数完成初始化。


二、命名不规范

不良表现:使用无意义的单字母命名(如a、b、c),或使用拼音、不准确的英文单词。例如用sexboy/girl表示性别及男女,而非更专业的gendermale/female

正确做法

  • 使用有意义的英文单词或词组

  • 遵循项目统一的命名风格(驼峰、下划线等)

  • 不熟悉的词汇借助翻译工具,但需确认准确性

  • 常见规范:变量名小写字母开头(如studentCount),类型名大写字母开头(如StudentInfo),常量全大写(如MAX_SIZE

命名准确性需要长期积累,但值得投入时间。


三、变量声明位置与使用范围不匹配

不良表现:变量在很外层作用域声明,却只在内部小范围使用。例如:

cpp

int temp;
while (condition) {
    temp = ...;
    // 使用temp
}

变量temp被声明在循环外,但其有效使用范围仅在循环体内。

正确做法:将变量的声明移至最接近其使用位置的地方。上述代码应将temp声明在while循环内部。这样做的好处是:

  • 提高代码可读性,读者无需上下查找变量定义

  • 缩小变量作用域,减少误用的风险

  • 便于重构和移动代码块


四、类成员数据未初始化

不良表现:类的默认构造函数未对成员变量进行初始化。例如:

cpp

class Point {
    int x, y;
public:
    Point() {}  // x和y未初始化
};

创建Point对象后,xy的值不确定,后续使用可能导致逻辑错误。

改进一:使用构造函数初始化列表

cpp

Point() : x(0), y(0) {}

但成员较多时容易遗漏。

正确做法(C++11起):在成员声明处直接赋予默认值

cpp

class Point {
    int x = 0;
    int y = 0;
};

这样无论调用哪个构造函数,成员都有确定的初始值,且不易遗漏。


五、使用裸指针管理动态内存

不良表现:手动newdelete动态对象,可能因提前返回、异常抛出等路径导致内存泄漏。例如:

cpp

void func() {
    Point* p = new Point();
    if (some_condition) return;  // 内存泄漏
    delete p;
}

正确做法:使用智能指针(C++11起)

  • std::unique_ptr:独占所有权,轻量高效

  • std::shared_ptr:共享所有权,引用计数

  • std::weak_ptr:配合shared_ptr打破循环引用

推荐使用std::make_unique(C++14)和std::make_shared创建智能指针,异常安全且更高效。


六、包含不必要的头文件

不良表现:代码中包含了从未使用的头文件,尤其常见的是图省事使用万能头文件#include <bits/stdc++.h>。这会引入大量标准库符号,显著增加编译时间。

正确做法

  • 只包含实际用到的头文件,例如使用std::vector则包含<vector>

  • 在头文件中尽量使用前置声明而非包含其他头文件,减少编译依赖

  • 使用工具(如Include What You Use)分析并清理冗余头文件


七、函数体过于庞大

不良表现:单个函数包含数百行甚至上千行代码,内部嵌套多层循环和条件分支,难以阅读和维护。

正确做法

  • 保持函数体在100行以内(理想情况50行)

  • 一个函数只做一件事(单一职责原则)

  • 将长函数拆分为多个小函数,每个小函数有清晰的名称和职责

  • 使用辅助函数提取重复代码块

大型函数往往是代码坏味道的信号,应主动重构。


八、命名空间污染

不良表现:在全局作用域使用using namespace std;,或在头文件中使用using namespace。这会将标准库中的所有名称引入当前作用域,容易与自定义名称冲突。

典型错误

cpp

#include <iostream>
using namespace std;  // 污染全局

另一个常见问题:自定义的变量、函数、类没有放入任何命名空间,全部暴露在全局。

正确做法

  • 去掉using namespace std;,改用std::coutstd::vector等前缀

  • 在实现文件(.cpp)的局部范围内,可谨慎使用using(如using std::cout;),但避免在头文件中使用

  • 将自己的代码放入独立的命名空间,例如namespace MyLib { ... }


九、滥用全局变量

不良表现:在程序中大量使用全局变量,用于在不同函数间传递状态。这种做法在C语言代码中常见,但C++中应极力避免。

问题

  • 全局变量破坏模块化,任何函数都可能修改它,导致程序状态难以追踪

  • 多线程环境下,全局变量需要复杂的同步机制

  • 代码可测试性差,单元测试难以隔离

正确做法

  • 优先使用局部变量和函数参数传递数据

  • 需要跨多个函数共享的状态,可封装在类中作为成员变量

  • 对于真正需要全局访问的配置信息,使用单例模式(但也要谨慎)或依赖注入

  • 考虑将全局变量改为静态成员变量并合理限制访问权限


十、缺少必要的注释

不良表现:代码没有任何注释,尤其是一些算法复杂、逻辑非直观的函数。例如下面的函数,短短三行却让人费解:

cpp

int f(int a, int b) {
    return (a & b) + ((a ^ b) >> 1);
}

很少有人一眼看出这是求两个整数的平均值(防止溢出的版本)。

正确做法

  • 为每个公开的函数添加注释:说明函数功能、参数含义、返回值、可能的异常

  • 对复杂的算法或业务逻辑,在代码块上方添加解释性注释

  • 不要注释“做了什么”,而是注释“为什么这么做”以及“注意事项”

  • 保持注释与代码同步更新,过时的注释比没有注释更糟

好的注释能让代码自解释,提高团队协作效率。


补充说明:坏习惯的改正需要刻意练习

以上十大坏习惯,很多开发者并非不知道,而是缺乏改正的意识和决心。以下是几点建议:

  • 使用静态分析工具:如Clang-Tidy、Cppcheck,能自动检测未初始化变量、未使用的头文件等问题。

  • 代码审查:请同事审查代码,他们更容易发现你习以为常的坏习惯。

  • 逐步改进:每次修改代码时,顺手把涉及到的坏习惯改正,不必一次性重写所有代码。

  • 学习优秀开源代码:阅读Boost、LLVM等高质量C++项目,观察其命名、注释、作用域控制等细节。


总结与自查清单

为了让读者更容易对照检查,以下列出十个坏习惯的速查表:

  1. 变量定义时是否给了初始值?

  2. 变量名是否有意义且准确(非拼音、非单字母)?

  3. 变量的作用域是否尽可能小?

  4. 类的成员变量是否在声明处有默认值?

  5. 动态内存管理是否使用了智能指针而非裸指针?

  6. 头文件是否只包含必要的内容?是否用了万能头文件?

  7. 函数体是否超过100行?是否需要拆分?

  8. 是否在头文件或全局使用了using namespace

  9. 是否存在不必要的全局变量?

  10. 关键函数和复杂逻辑是否有清晰的注释?

建议每完成一个模块的编码,对照此清单自查一次,逐步养成良好的编程习惯。


进阶思考:从坏习惯到代码质量文化

坏习惯往往是“省事”心态的产物。初学者为了快速看到效果而忽略规范,久而久之形成肌肉记忆。打破这些习惯,需要将编码规范内化为本能。

一些团队采用格式化工具(clang-format)和静态检查(pre-commit钩子)强制约束,例如:

  • 自动格式化代码风格

  • 禁止提交包含未初始化变量的代码

  • 检查头文件包含是否冗余

另外,可以尝试“童子军规则”:每次修改一个文件时,顺便改善其中的一两个小问题(如补充注释、缩小作用域)。随着时间推移,整个代码库的质量会逐步提升。


结语

C++是一门强大但复杂的语言,它给予程序员极大的控制权,同时也要求程序员承担相应的责任。上述十大坏习惯,有些是C/C++特有的(如裸指针、未初始化变量),有些是所有编程语言共通的(如命名、注释)。

改正坏习惯不是一蹴而就的。我曾经花数年时间才彻底改掉某个不良编码风格。但只要你有意识地每天改进一点点,代码质量就会稳步上升。希望这份清单能帮助你少走弯路,写出更健壮、更可维护的C++代码。


本教程纯技术分享,无任何广告或推广内容。

更多推荐