C++编码实践,邪恶的非常量全局变量

在编程过程中,避免使用全局变量是一个良好的编程实践建议。当然,这里的全局变量主要是指 非常量全局变量;

尽管在小型项目中,这一点似乎看起来人畜无害,但是在大型项目中往往会出现很多问题。

新手程序员往往比较喜欢使用大量的全局变量,因为这样使用起来方便直接,特别是当设计到不同函数的多次调用传递参数时。

若无特别说明,本文后续内容中提到的全局变量均指 非常量全局变量


全局变量的潜在危险

到目前为止,全局变量危险的最大原因是因为他们的值可以在任何地方被任何调用的函数更改,并且程序员没有简单的方法知道这种情况的发生,考虑下面程序:

#include <iostream>int g_mode; // 声明全局变量(默认会被初始化为0)void doSomething()
{g_mode = 2; // 将全局变量 g_mode 设置为 2
}int main()
{g_mode = 1; // 注意:这是将全局变量 g_mode 设置为 1,而不是声明一个局部的 g_mode 变量!doSomething();// 程序员可能仍然期望 g_mode 的值是 1// 但是 doSomething 函数已经将其更改为 2!if (g_mode == 1){std::cout << "未检测到威胁。\n";}else{std::cout << "发射核导弹...\n";}return 0;
}

请注意,程序员将变量g_mode设置为1 ,然后调用doSomething() 。除非程序员明确知道doSomething()将更改g_mode的值,否则他或她可能不会期望doSomething()更改该值!因此, main()的其余部分并不像程序员期望的那样工作,这就导致一枚无情的核弹被发射,恭喜你喜提一份 公牢

简而言之,全局变量使得程序的状态不可预测,每一个函数调用都具有潜在的危险,并且程序员没有简单的方法来查看和防范这些危险。那么,有什么理由不使用局部变量呢?

除此之外,还有很多其他不推荐使用全局变量的充分理由。

对于全局变量,下面的代码并不罕见:

void someFunction()
{if (g_mode == 4){// do something good}
}

就短短的几行代码,假设此时你的程序出现错误无法工作,因为全局变量g_mode的值是3而不是4导致。你怎么解决?很好,你首先需要找到名为g_mode的全局变量可能存在的位置并追踪它的赋值等操作,这些工作不仅仅值涉及到你这点代码,很有可能存在于很多毫不相干的代码中!

将局部变量声明为尽可能靠近其使用位置的关键原因之一是,这样做可以最大限度地减少您需要查看以了解变量的作用的代码量。

全局变量处于相反的一端——因为它们可以在任何地方访问,您可能必须查看整个程序才能了解它们的用法。在小项目中,这可能不是问题。但在大项目中,谁说得准呢是吧?

例如,你发现某个全局变量在你的项目中被引用了545次。如果你一开始没有很好的文档跟踪记录,那么你可能必须仔细查看这个变量在每一个地方的使用,以了解它在不同情况下的使用方式、生效条件、影响的逻辑功能等等。

另外,全局变量还会降低程序模块化程度和灵活性 :

  • 当函数依赖全局变量时,函数的行为可能受到外部状态的影响,无法单独测试或理解其逻辑。

    例如,函数可能隐式依赖某个全局变量的值,而这个值又可能在其他地方被改变,导致函数的行为不可预测。

  • 如果函数与全局变量绑定过紧,这个函数就无法在其他场景中复用,除非那些场景中也包含相同的全局变量。


全局变量的初始化问题

静态变量(包括全局变量)的初始化时程序启动的一部分,在main函数执行之前,这分为两个阶段执行:

第一阶段称为静态初始化。静态初始化分为两个步骤:

  • 全局变量带有 constexpr 初始化器(包括字面值)的会被初始化为这些指定的值,这被称为常量初始化
  • 没有初始化器的全局变量会被初始化为零。由于零是一个 constexpr 值,因此零初始化也被认为是静态初始化的一种形式。

第二个阶段被称为动态初始化。这一阶段更为复杂和细致,但其核心是:带有非 constexpr 初始化器的全局变量会在此阶段被初始化

以下是一个非 constexpr 初始化器的示例:

int init()
{return 5;
}int g_something{ init() }; // non-constexpr initialization

在单个文件中,对于每个阶段,全局变量通常按定义顺序进行初始化(对于动态初始化阶段,此规则有一些例外)。鉴于此,需要小心,不要让变量依赖于稍后才会初始化的其他变量的初始化值。例如:

#include <iostream>int initX();  // forward declaration
int initY();  // forward declarationint g_x{ initX() }; // g_x is initialized first
int g_y{ initY() };int initX()
{return g_y; // g_y isn't initialized when this is called
}int initY()
{return 5;
}int main()
{std::cout << g_x << ' ' << g_y << '\n';
}

更严重的问题是,静态对象在不同翻译单元之间的初始化顺序是不明确的。

给定两个.cpp文件,任意一个文件都可以首先初始化全局变量。如果其中a.cpp中某个具有静态持续时间的变量使用b.cpp中定义的静态持续时间进行变量初始化,则b.cpp中的变量有50%的几率尚未初始化。


使用全局变量的一些建议

  • 首先,在全局变量名加上g_前缀,或者更好的做法是将他们放在命名空间中,以减少出现命名冲突的可能性:

例如,下面的代码;

#include <iostream>constexpr double gravity { 9.8 }; int main()
{std::cout << gravity << '\n'; return 0;
}

可以调整为:

#include <iostream>namespace constants
{constexpr double gravity { 9.8 }; 
}int main()
{std::cout << constants::gravity << '\n'; are global)return 0;
}
  • 其次,一个不错的做法是对变量进行“封装化”,而不是允许直接访问全局变量。

确保变量只能从声明文件内访问,例如将其设置为static或者const,然后提供外部访问函数来处理该变量。这些功能可以确保维护正确的使用,例如进行输入验证、范围检测等。

  • 在编写使用全局变量的独立函数时,不要直接在函数体中使用该变量,相反,我们应该将其作为函数参数传递使用。这样一来,如果你的函数需要在某些情况下使用不同的值,只需要修改参数即可,这也是模块化编程的一种良好实践。

例如:

#include <iostream>namespace constants
{constexpr double gravity { 9.8 };
}double instantVelocity(int time)
{return constants::gravity * time;
}int main()
{std::cout << instantVelocity(5) << '\n';return 0;}

建议的写法是:

#include <iostream>namespace constants
{constexpr double gravity { 9.8 };
}double instantVelocity(int time, double gravity)
{return gravity * time;
}int main()
{std::cout << instantVelocity(5, constants::gravity) << '\n'; return 0;
}

感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/892534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV的对比度受限的自适应直方图均衡化算法

OpenCV的对比度受限的自适应直方图均衡化&#xff08;CLAHE&#xff09;算法是一种图像增强技术&#xff0c;旨在改善图像的局部对比度&#xff0c;同时避免噪声的过度放大。以下是CLAHE算法的原理、步骤以及示例代码。 1 原理 CLAHE是自适应直方图均衡化&#xff08;AHE&…

1.1.2 配置静态IP和远程SSH登录

一、开放22端口 方法一&#xff1a;开放SSH服务&#xff08;推荐&#xff0c;不需要改动&#xff09; 查看配置文件&#xff0c;已经默认开放ssh服务端口了&#xff0c;ssh默认为22端口&#xff0c;所以不需要改动文件 方法二&#xff1a;开放22端口 &#xff08;1&#xff0…

Soildworks的学习【2025/1/12】

右键空白处&#xff0c;点击选项卡&#xff0c;即可看到所有已调用的选项卡&#xff1a; 点击机械小齿轮选项卡&#xff0c;选择文档属性&#xff0c;选择GB国标&#xff1a; 之后点击单位&#xff0c;选择MMGS毫米单位&#xff1a; 窗口右下角有MMGS&#xff0c;这里也可以选择…

web前端第五次作业---制作菜单

制作菜单 代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style…

GAN的应用

5、GAN的应用 ​ GANs是一个强大的生成模型&#xff0c;它可以使用随机向量生成逼真的样本。我们既不需要知道明确的真实数据分布&#xff0c;也不需要任何数学假设。这些优点使得GANs被广泛应用于图像处理、计算机视觉、序列数据等领域。上图是基于GANs的实际应用场景对不同G…

分治算法——优选算法

本章我们要学习的是分治算法&#xff0c;顾名思义就是分而治之&#xff0c;把大问题分为多个相同的子问题进行处理&#xff0c;其中我们熟知的快速排序和归并排序用的就是分治算法&#xff0c;所以我们需要重新回顾一下这两个排序。 一、快速排序&#xff08;三路划分&#xf…

解决el-table表格数据量过大导致页面卡顿问题 又名《umy-ui---虚拟表格仅渲染可视区域dom的神》

后台管理系统的某个页面需要展示多个列表 数据量过多 页面渲染dom卡顿 经调研发现两个组件 pl-table和umy-ui &#xff08;也就是u-table&#xff09; 最终决定使用umy-ui 它是专门基于 Vue 2.0 的桌面端组件库 流畅渲染表格万级数据 而且他是对element-ui的表格做了二次优化…

单元测试概述入门

引入 什么是测试&#xff1f;测试的阶段划分&#xff1f; 测试方法有哪些&#xff1f; 1.什么是单元测试&#xff1f; 单元测试&#xff1a;就是针对最小的功能单元&#xff08;方法&#xff09;&#xff0c;编写测试代码对其正确性进行测试。 2.为什么要引入单元测试&#x…

Xcode 正则表达式实现查找替换

在软件开发过程中&#xff0c;查找和替换文本是一项常见的任务。正则表达式&#xff08;Regular Expressions&#xff09;是一种强大的工具&#xff0c;可以帮助我们在复杂的文本中进行精确的匹配和替换。Xcode 作为一款流行的开发工具&#xff0c;提供了对正则表达式的支持。本…

基于微信小程序的电影交流平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

GAMES101学习笔记(三):Rasterization 光栅化(三角形的离散化、抗锯齿、深度测试)

文章目录 视口变换 Viewport三角形网格 Triangle Mesh采样 Sampling走样/反走样 Aliasing/Antialiasing采样频率、空间域与频率域深入理解采样、走样、反走样反走样总结深度测试 Depth testing 课程资源&#xff1a;GAMES101-现代计算机图形学入门-闫令琪 Lec5 ~ Lec6 学习笔记…

《分布式光纤传感:架设于桥梁监测领域的 “智慧光网” 》

桥梁作为交通基础设施的重要组成部分&#xff0c;其结构健康状况直接关系到交通运输的安全和畅通。随着桥梁建设规模的不断扩大和服役年限的增长&#xff0c;桥梁结构的安全隐患日益凸显&#xff0c;传统的监测方法已难以满足对桥梁结构健康实时、全面、准确监测的需求。分布式…

BUUCTF:web刷题记录(1)

目录 [极客大挑战 2019]EasySQL1 [极客大挑战 2019]Havefun1 [极客大挑战 2019]EasySQL1 根据题目以及页面内容&#xff0c;这是一个sql注入的题目。 直接就套用万能密码试试。 admin or 1 # 轻松拿到flag 换种方式也可以轻松拿到flag 我们再看一下网页源码 这段 HTML 代码…

腾讯云AI代码助手编程挑战赛-知识百科AI

作品简介 知识百科AI这一编程主要用于对于小朋友的探索力的开发&#xff0c;让小朋友在一开始就对学习具有探索精神。在信息化时代下&#xff0c;会主动去学习自己认知以外的知识&#xff0c;同时丰富了眼界&#xff0c;开拓了新的知识。同时催生了在大数据时代下的信息共享化…

大语言模型预训练、微调、RLHF

转发&#xff0c;如有侵权&#xff0c;请联系删除&#xff1a; 1.【LLM】3&#xff1a;从零开始训练大语言模型&#xff08;预训练、微调、RLHF&#xff09; 2.老婆饼里没有老婆&#xff0c;RLHF里也没有真正的RL 3.【大模型微调】一文掌握7种大模型微调的方法 4.基于 Qwen2.…

【理论】测试框架体系TDD、BDD、ATDD、MBT、DDT介绍

一、测试框架是什么 测试框架是一组用于创建和设计测试用例的指南或规则。框架由旨在帮助 QA 专业人员更有效地测试的实践和工具的组合组成。 这些指南可能包括编码标准、测试数据处理方法、对象存储库、存储测试结果的过程或有关如何访问外部资源的信息。 A testing framewo…

20250112面试鸭特训营第20天

更多特训营笔记详见个人主页【面试鸭特训营】专栏 250112 1. TCP 和 UDP 有什么区别&#xff1f; 特性TCPUDP连接方式面向连接&#xff08;需要建立连接&#xff09;无连接&#xff08;无需建立连接&#xff09;可靠性可靠的&#xff0c;提供确认、重传机制不可靠&#xff0c…

linux--防火墙 iptables 双网卡 NAT 桥接

linux--防火墙 iptables 双网卡 NAT 桥接 1 介绍1.1 概述1.2 iptables 的结构 2 四表五链2.1 iptables 的四表filter 表&#xff1a;过滤规则表&#xff0c;默认表。nat 表&#xff1a;地址转换表。mangle 表&#xff1a;修改数据包内容。raw 表&#xff1a;原始数据包表。 2.2…

oracle闪回表

文章目录 闪回表案例1&#xff1a;&#xff08;未清理回收站时的闪回表--成功&#xff09;案例2&#xff08;清理回收站时的闪回表--失败&#xff09;案例3&#xff1a;彻底删除表&#xff08;不经过回收站--失败&#xff09;案例4&#xff1a;闪回表之后重新命名新表总结1、删…

202506读书笔记|《飞花令·江》——余霞散成绮,澄江静如练,江梅一夜落红雪,便有夭桃无数开

202506读书笔记|《飞花令江》——余霞散成绮&#xff0c;澄江静如练&#xff0c;江梅一夜落红雪&#xff0c;便有夭桃无数开 摘录 《飞花令江》素心落雪编著&#xff0c;飞花令得名于唐代诗人韩翃《寒食》中的名句“春城无处不飞花”&#xff0c;类似于行酒令&#xff0c;是文人…