全局变量:一个让千万程序员崩溃的致命错误

开发
全局变量就像是编程界的"外卖",看着特别方便,随叫随到,但吃多了绝对会闹肚子!

嘿,小伙伴们! 今天我们来聊一个超级"诱人"但又特别"危险"的编程套路 —— 全局变量!

它就像是编程界的"外卖":

  • 看着特别方便
  • 随叫随到
  • 但吃多了绝对会闹肚子!

全局变量的"七宗罪"

来看看这个让人头秃的例子:

int userCount = 0;  // 这个变量就像个没人管的熊孩子 🐻
void addUser() { userCount++; }    // 谁都能来摸一把
void removeUser() { userCount--; }  // 谁动了我的数字?!

这代码有多可怕? 

  • 依赖全靠"缘分" - 谁用了都不知道
  • 状态像"过山车" - 想改就改,完全没规矩
  • 多线程直接变"蹦迪现场" 

拯救方案: 驯服野生变量!

想要管住调皮的全局变量,我们需要给它请个"保姆"! 

先看看这个小可爱:

class UserManager {  
    private:
        int userCount = 0;  // 乖乖呆在小笼子里 🐹
};

给它加上一些规矩:

public:
    void addUser() { 
        userCount++;  // 想动手先举手! 🙋♂️
    }

再来个安全门禁:

    int getCount() const { 
        return userCount;  // 只能看,不能摸! 🚫
    }

这样一来:

  • 数据乖乖待在私有区域
  • 想改变需要通过正门
  • 外面的人只能隔着玻璃看 

就像把野生小动物送进了动物园,既保护了它,也保护了其他人! 

记住: 驯服野生变量的第一步,就是给它一个温暖的家! 

这比直接把变量扔在大街上要安全多啦! 

生动有趣! 

全局变量的正确打开方式

嘿,想知道什么时候能用全局变量吗? 来看看这些"合法"的使用场景! 

首先是这位"常量天使" :

const int MAX_USERS = 1000;    // 乖乖不变的小宝贝 🎀
const double PI = 3.14159;     // 这个数字稳如老狗! 🐕

为啥它们这么乖? 因为戴上了const这顶"紧箍帽",谁都别想动它们一根毛! 

再来看看"单身贵族"单例模式,独一无二的存在 

class Logger {
private:
    Logger() = default;  // 悄悄把构造函数藏起来 🙈 - 外面的人别想new我!
    Logger(const Logger&) = delete;  // 禁止复制 📋❌ - 独一无二的存在
    Logger& operator=(const Logger&) = delete;  // 禁止赋值 ✍️❌ - 不许假冒伪劣!

public:
    static Logger& getInstance() {
        static Logger instance;  // 江湖只此一家,别无分店! 🏪
                                // 第一次调用才会创建 🐣
                                // C++11保证线程安全 🔒
        return instance;  // 永远返回同一个实例 💫
    }

    // 其他成员函数... 🛠️
};

它就像是皇帝一样,整个程序只能有一个! 想见它?必须通过getInstance()这个"御前大臣"!

最后是配置文件小管家:

namespace Config {
    const std::string APP_NAME = "超级无敌小火箭";  // 名字要起得帅气点! 🚀
    const int VERSION = 42;     // 这个数字充满智慧! 🧠
}

把它们放在namespace里,就像给小朋友们一个温暖的家! 

记住这个铁律哦:

全局变量就像辣条:

  • 偶尔吃一点点没问题
  • 但要是贪吃,绝对会闹肚子! 
  • 能不吃最好不吃! 

如果你突然想用全局变量,不妨先去喝杯奶茶冷静一下! 🧋 等喝完了再决定要不要用它! 

告别全局变量的绝招

嘿,想知道怎么优雅地跟全局变量说拜拜吗? 来看看这些法宝! 

第一招: 依赖注入大法 

class Service {
    Database& db_;  // 把数据库抱得紧紧的~ 🤗
                    // 使用引用避免拷贝开销 📦
                    // 保证数据库对象生命周期 ⏳
public:
    Service(Database& db) : db_(db) {
        // 通过构造函数注入依赖 💉
        // 比全局变量更容易测试和维护 ✅
        // 明确表达了类的依赖关系 🔍
    } 
};

就像点外卖一样,想要啥直接送到家! 🛵 不用自己到处找找找~

第二招: 全家福合照

struct Context {
    // 🏰 这是所有重要组件的"豪华大宅"
    // 🔑 通过一个对象统一管理所有依赖
    // 🎯 避免全局变量到处飞
    
    Config config;   // 📝 配置管家,保管所有设置
    Logger logger;   // 📋 记录小助手,负责写日记
    Database db;     // 💾 数据管家,安全存储数据
    
    // 💡 好处都在这:
    // ✨ 依赖关系一目了然
    // 🔒 生命周期统一管理 
    // 🚀 测试替换超轻松
};

瞧,多温馨啊! 所有重要的东西都在一个相框里

第三招: 玩具工厂模式

class DatabaseFactory {
    // 🏭 数据库生产车间,专门制造数据库实例
    // 🎯 目的是集中管理数据库的创建逻辑
    // 🔒 static 方法确保不需要实例化工厂类
    static Database create() {
        // 🏠 "localhost" 表示连接本地数据库
        // 🔌 3306 是 MySQL 的默认端口号
        // 🎁 每次调用都返回全新的数据库连接
        // 🚀 避免了全局变量带来的各种问题
        return Database{"localhost", 3306}; 
    }
};

想要新玩具? 工厂分分钟造一个! 干净又卫生~

记住这个魔法口诀:

  • 全局变量就像熊孩子,到处惹事! 
  • 依赖注入像保姆,照顾得妥妥的! 
  • 工厂模式像玩具店,要啥有啥! 

最后的小提示:

  • 能传参就传参,别偷懒! 
  • 想用全局先冷静,喝口奶茶! 
  • 代码整洁最重要,保持优雅! 

这样的代码,不仅程序员喜欢,产品经理也开心! 因为bug少了,加班也少啦! 

全局变量的初始化小把戏

看看这群不听话的小家伙:

// 🚨 这是一个危险的全局变量初始化示例
Logger g_logger;    // 🏃 急着想当第一名的Logger
                   // ⚠️ 初始化顺序完全不确定
                   // 💥 可能导致严重的依赖问题

Config g_config;    // 🏃♀️ 和Logger抢着初始化
                   // 🎲 谁先谁后全靠运气
                   // 🔄 依赖Config的代码可能会崩溃

Database g_db;      // 🏃♂️ 最后一个不代表最安全
                   // 🌋 如果其他变量依赖数据库
                   // 💀 可能会引发灾难性后果

这就像是幼儿园抢玩具,谁先初始化完全靠运气啦! 

与其让它们打架,不如这样:

Logger& getLogger() {
    // 🦥 这是一个懒汉式单例模式的实现
    // 🛏️ static 保证 Logger 只会在第一次调用时才会被初始化
    // 🔒 C++11 之后保证这种初始化是线程安全的
    // 💤 不用的时候不会占用内存资源
    // 🎯 返回引用避免了不必要的拷贝
    // ⚡️ 后续每次调用都返回同一个实例
    static Logger log;  // 困了就睡,要用就醒~ 😴
    return log;
}

这就像个小懒虫,需要的时候才伸个懒腰起床! 

为什么这样更好? 

  • 用到才初始化,节省资源 
  • 初始化顺序明确,不会打架 
  • 线程安全有保障,放心用 

来看看实战示例:

// 坏孩子版本 ❌
extern Database g_db;  // 到处乱跑的熊孩子 🏃
                      // 谁都可以随意修改它 😱
                      // 多线程访问会出问题 💥
                      // 初始化顺序不确定 ⚠️
                      // 测试时难以替换 😫

// 好孩子版本 ✅
Database& getDB() {
    static Database db;  // 乖乖待在家里 🏠
                        // 懒汉式单例模式 🦥
                        // 用到才初始化,节省资源 💪
                        // C++11保证线程安全 🔒
                        // 返回引用避免拷贝 ⚡️
                        // 始终是同一个实例 💫
    return db;
}

记住: 与其让变量们在初始化时打群架,不如让它们按需登场! 

多线程下的全局变量那些事儿

嘿,小伙伴们! 今天来聊聊全局变量在多线程环境下有多"社死"! 

先看看这个"社恐"宝宝:

std::vector<User> g_users;  // 裸奔的小可怜 😱

这就像个无人管理的"公共厕所" :

  • 线程A: 我要进去! 
  • 线程B: 等等,我也要! 
  • 线程C: 借过借过! 结果? 当然是撞个满怀啦! 

来看看这个"乖宝宝"版本:

class UserManager {
    std::mutex mutex_;      // 门卫大爷驾到! 👮
    std::vector<User> users_;  // 乖乖待在保护圈内 🏰

有了门卫以后:

public:
    void addUser(User u) {
        std::lock_guard<std::mutex> lock(mutex_);  // 先登记再进门 📝
        users_.push_back(std::move(u));  // 安全入住啦 🏠
    }

这样一来:

  • 想进门要先报到
  • 一次只能进一个
  • 有序排队不拥挤

小贴士:

  • 全局变量就像没人看管的熊孩子
  • mutex就是贴心小保姆
  • lock_guard就是自动门禁卡

记住: 多线程面前,全局变量不加锁,分分钟社死现场! 要么不用,要用就保护好! 

命名空间 - 变量的豪华别墅

来看看这些可怜的"流浪变量":

int count;      // 无家可归的小可怜 😢
void reset();   // 漂泊的孤独灵魂 👻

给它们一个温馨小窝:

namespace GameEngine {    // 欢迎来到游戏城堡! 🎮
    int playerScore;     // 我是分数管家 📊
}

想要更多私密空间? 来个套娃! 

namespace Game {
    namespace Core {     // 我是豪华套房~ 🏯
        int secretData;  // 私密数据躲这里 🤫
    }
}

现代C++还能这样玩:

namespace Game::Utils {   // 无需套娃的新潮写法 ✨
    int helperCount;     // 整洁又时髦! 🌟
}

使用小技巧:

  • using namespace 要慎用,就像把门钥匙到处乱扔! 
  • 命名要有意义,不要整些"黑话" 
  • 层级别太深,自己都会迷路! 

记住: 给变量一个家,比让它流浪好一万倍! 

单元测试小魔法

嘿,看看这个让测试工程师抓狂的代码:

Database g_db;  // 这个全局变量就像个顽固的老顽固 👴
void processUser() {
    g_db.query();  // 死活不让人换掉,气死测试工程师啦! 😫
}

咱们来个华丽大变身:

class UserProcessor {
    Database& db_;  // 这个小可爱随时可以换衣服! 👗

瞧瞧这个构造函数,多么优雅:

public:
    UserProcessor(Database& db) : db_(db) {}  // 想穿啥穿啥,随你开心! 🎭

测试的时候就可以这样玩:

void process() {
    db_.query();  // 测试时换成MockDB,美滋滋~ 🍯
}

有了这个魔法加持:

  • 测试工程师笑开花
  • 代码质量大提升
  • Bug们都躲着走

记住这个小咒语:

  • 依赖注入是好朋友
  • 全局变量是坏蛋蛋
  • 可测试性是王道呀

这样的代码,不仅测试工程师爱了,产品经理也笑了! 因为bug少了,加班也少了! 

实用小贴士

(1) 看到全局变量先三问:

  • 真的需要吗? 
  • 会不会后悔? 
  • 有更好方案吗? 

(2) 非要用,请记得:

  • 写清楚注释
  • 放进命名空间
  • 保证线程安全

(3) 最后的忠告:

全局变量就像是恐怖片,一不小心就会吓死人! 

  • 能用局部就别用全局 
  • 能用const就别用变量 
  • 代码质量大于开发速度 

记住: 与其事后擦屁股,不如一开始就把代码写好! 

责任编辑:赵宁宁 来源: everystep
相关推荐

2018-12-24 09:22:39

2011-11-24 14:20:24

Java

2020-02-22 21:51:43

程序员Microsoft SServerSQL

2021-12-04 23:01:33

程序员开发互联网

2014-01-06 09:33:32

程序员管理

2020-10-05 21:13:37

程序员技能开发者

2020-07-10 09:55:15

程序员技能开发者

2015-06-16 10:31:36

程序员

2011-02-14 13:05:17

PythonWeb

2015-06-08 10:48:39

程序员程序员自白

2021-07-01 07:43:41

项目程序员代码

2014-09-01 09:50:58

程序员

2022-05-23 07:56:58

C语言嵌入式开发

2019-11-07 15:30:00

EmacsIDE

2010-10-18 11:39:41

程序员

2015-05-13 14:06:03

程序员糟糕的程序员

2020-01-06 09:53:29

程序员

2015-08-24 10:07:13

程序员bug

2009-02-12 15:07:57

程序员创业经验

2019-04-22 10:25:52

程序员技术职场
点赞
收藏

51CTO技术栈公众号