📔 Part III : Variables 学习笔记
Chapter 10 使用变量的一般事项¶
变量一词同时指代对象和内置数据类型,如整数和数组等。
- 数据类型一词通常是指内置数据类型。
- 数据一词则可以代表对象,也可能代表内置数据类型。
1. 数据认知¶
创建有效数据的第一步是了解所要创建数据的种类。积累大量的数据类型对程序员来说很重要。
关于数据类型的资源:
- 《Introduction to Algorithms》Cormen Charles
- 《Algorithmns in C++》part1-4 Sedgewick Robert
2. 轻松掌握变量的意义¶
隐式变量声明对于任何一种语言是最具有危险性的特性之一。
对于编程语言支持隐式声明时,给出如下建议参考:
- 关闭隐式声明
- 声明全部的变量
- 遵循某种命名规则
- 检查变量名
3. 变量初始化原则¶
不合理地初始化数据是产生编程错误的常见根源之一。
对于一些不恰当的变量初始化导致的问题:
- 从未对变量赋值
- 变量值已经过期。
- 变量的一部分被赋值,而另一部分没有。
避免产生初始化错误的建议:
-
在声明变量时就初始化。
- 防御式编程的一种方法。
-
在靠近变量第一次使用的位置初始化它。
- 就近原则
- 类似于让注释靠近它所描述的代码。
-
在理想情况下,在靠近第一次使用变量的位置声明和定义该变量。
- 声明的同时被定义。
-
在可能的情况下使用final 或者 const
- 防止变量在初始化之后再次被赋值。
-
特别注意计时器和累加器
- 在类构造函数里初始化该类型的数据成员
- 检查是否需要重新初始化
- 一次性初始化具名常量:用可执行代码来初始化变量。
-
使用编译器设置自动初始化所有变量。
- 代码进行平台移植时,需要注意确定编译器是否支持自动初始化所有变量的选项。
-
利用编译器的警告信息。
- 检查输入参数的合法性
- 使用内存访问检查工具来检查错误的指针
- 在程序开始时初始化工作内存
4. 作用域¶
作用域或者可见性指的是变量在程序内的可见和可引见的范围。
所有的变量都是全局变量,会无法控制变量的作用域,也会带来很多的问题。
所以有一些使用作用域的规则:
- 使用变量引用局部化
- 衡量一个变量的不同引用点的靠近程度的一种方法就是计算该变量的“跨度”。
-
尽可能缩短变量的“存活”时间
- “存活时间”即一个变量存在期间所跨越的语句总数。
- 变量的存活时间开始于引用它的第一条语句,结束于引用它的最后一条语句。
- “长存活时间”意味着一个变量历经了许多语句,而“短存活时间”意味着它只历经很少的语句。
- “跨度”则表明了对一个变量引用的集中程度。
- 短的变量存活时间的作用
- 使能对自己的代码更有准确的认识。
- 减少了初始化错误的可能。
- 使代码更具有可读性。
-
减小作用域的一般原则
- 在循环开始之前再去初始化该循环里使用的变量,而不是在该循环所属的子程序的开始处初始化该变量。
- 直到变量即将被使用时再为其赋值
- 把相关语句放到一起
- 把相关语句组提取成单独的子程序
- 开始时采用最严格的可见性,然后根据需要扩展变量的作用域
-
有关缩小变量作用域的说明
- 程序员采用哪种缩小变量作用域的方法,取决于他如何看待 “方便性” 和 “智力上的可管理性”。
- “方便性” 和 “智力可管理性” 两种理念之间的区别:在于写程序还是读程序之间的区别。
5. 持续性¶
“持续性” 是一项数据的生命期的另一种描述。持续性具有多种形态:
- 特定代码段或者子程序是的生命期。
- 只要你允许,它就会持续。
- 程序的生命期
- 永远持续
与持久性相关的最主要的问题是变量实际生命期比你想象的要短。为了避免上述问题,所以采取以下措施:
- 在程序中加入调试代码或者断言来检查那些关键变量的合理取值。
- 准备抛弃变量时给他们赋上 “不合理的数值”。
- 编写代码时要假设数据并没有持续性。
- 养成在使用所有数据之间声明和初始化的习惯。
6. 绑定时间¶
绑定时间:把变量和它的值绑定在一起的时间。对于绑定,发生在编写代码时还是编译程序时?是在程序加载还是运行时,抑或其它时间。
- 编码时(使用神秘数值)
- 编译时(使用具名常量)
- 加载时(从外部数据源中读取数据)
- 对象实例化
- 即时
采用越晚的绑定时间会越有利。绑定越晚,代码中就有越多的灵活性。
7. 数据类型和控制结构之间的关系¶
数据类型和控制结构之间以一种定义明确的方式相互结合。Jackson描绘出了三种类型的数据和相应控制结构之间的关系。 - 序列型数据翻译为程序中的顺序语句 - 序列型数据是由一些按特定顺序使用的数据组成的。 - 选择型数据翻译为程序中的if 和 case语句 - 选择型数据是任一特定时刻有且仅有一项被使用的数据。 - 迭代型数据翻译为程序中的for、repeat、while等循环结构 - 迭代型数据是需要反复进行操作的同类型的数据。
8. 为变量指定单一用途¶
通过使用一些巧妙的方法,可以给一个变量赋予多种职责。但是尽量避免这些奇技淫巧。
- 每个变量只用于单一用途。
- 避免让代码具有隐含含义。
- 确保使用了所有已声明的变量。
9. 核对表:使用数据的一般事项¶
-
初始化变量
- 每一个子程序都检查其输入参数的正确性吗?
- 变量声明位置靠近第一次使用的位置吗?
- 尽可能在声明变量的同时初始化变量吗?
- 如果无法同时声明和初始化变量,有没有在靠近第一次使用变量的位置声明变量?
- 计数器和累加器经过了适当的初始化吗?如果需要再一次使用,之前重新初始化了吗?
- 适当地重新初始化”需要重复执行的代码里的变量“了吗?
- 代码在通过编译器编译的时候是不是没有警告信息?
- 如果你用的语言允许隐式声明,你为由此可能引发的问题做好补偿措施了吗?
-
使用数据的其他事项
- 如果可能,所有变量都被定义为具有最小作用域了吗?
- 各变量的引用点都尽可能集中在一点吗?对同一变量的再次相邻引用,或者变量的整个生命期都这样做了吗?
- 控制结构符合数据类型吗?
- 所有声明的变量都用到了吗?
- 变量在合适的时间绑定了吗?——也就是说,你有意识地在晚期绑定所带来的灵活性和增加的复杂度之间做出了平衡了吗?
- 每个变量都有且仅有一项用途吗?
- 每个变量的含义都很明确且没有隐含含义吗?
Chapter 11 变量名的力量¶
1. 选择好变量名的注意事项¶
变量的好坏在很大程度上取决于它的命名的好坏。所以给变量命名时要小心谨慎。一个好的变量名是可读、易记的和恰如其分的。可以使用如下原则来实现目标: - 最重要的命名注意事项 - 为变量命名时最重要的考虑事项是,该名字要完全、准确地描述出该变量所代表的事物。 - 变量的描述就是最佳的变量名。名字应该尽可能地明确。
-
以问题为导向
- 一个好记的名字反映的通常都是问题,而不是解决方案。
-
最适当的名字长度
- 变量名的平均长度在10到16个字符,调试程序所需花费的气力是最小的。
- 平均名字长度在8到20个字符的程序也几乎同样容易调试。
-
变量名字的效果范围
- 较长的名字适用于很少用到的变量或者全局变量,而较短的名字则用于局部变量或者循环变量。
- 对位于全局命名空间中的名字加以限定词。
-
变量名字中的计算值限定词
- 程序中也都有一些表示计算结果的变量:总额、平均值、最大值等。如果要使用类似Total、Sum、Average、Max、Min、Record等限定词来修改某个名字,则记得把限定词加到名字的最后。
- 规则一致性,可以提高可读性,简化维护工作。
-
变量名字中的常用反义词
- 对仗词的使用要准确。
- 通过应用规则来提高对仗词来提高对仗词使用的一致性,从而提高可读性。
2. 为特定类型的数据命名¶
- 为循环索引命名
- 为状态变量命名
- 为状态变量取一个比flag更好的名字
-
为临时变量命名
- 临时变量用于存储计算的中间结果,作为临时占位符,以及存储内务管理值。
- 警惕“临时”变量。
-
为布尔变量命名
- 遵循的几条原则:
- 谨记典型的布尔变量名
- Done、error、found、Success或 OK。
- 给布尔变量赋予隐含“真/假”含义的名字。
- 使用肯定的布尔变量名
- 谨记典型的布尔变量名
- 遵循的几条原则:
-
为枚举类型命名
- 在使用枚举类型时,可以通过使用组前前缀,如color_、Planet_或者 Month_来明确表示该类型的成员都同属于一个组。
-
为常量命名
- 在具名常量时,应该根据该常量所表示的含义,而不是该常量所具有的数值为该抽象事物命名。
3. 命名规则的力量¶
3.1 为什么要有规则?¶
命名规则带来的好处:
- 要求你更多按规矩办事
- 有助于在项目之间传递知识
- 有助于在新项目中更快速地学习代码
- 有助于减少名字增生
- 强调相关变量之间的关系
3.2 何时采用命名规则¶
- 当多个程序员合作开发一个项目时
- 当计划把一个程序交给另一个程序员来修改和维护时
- 当所在组织中的其他程序员评估你写的程序时
- 当你的程序规模太大,以致于无法在脑海中同时了解事情的全貌,而必须分而治之时
- 当写的程序生命周期足够长,长到可能会把你搁置几个星期或者几个月之后又重新启动有关程序的工作时
- 当在一个项目中存在一些不常见的术语,并希望你在编写代码阶段使用标准的术语或者缩写时
3.3 正式程度¶
所需的正式程序取决于为同一程序而工作的人员数量、程序的规模,以及程序预期的生命期。
4. 非正式命名规则¶
4.1 语言无关规则的指导原则¶
-
区分变量名和子程序名字
- 本书要求变量名和对象名以小写字母开始。子程序名字以大写字母开始。
-
区分类和对象
- 通过大写字母开头区分类型和变量
- 通过全部大写区分类型和变量
- 通过给类型加 “t_” 前缀区分类型和变量
- 通过给变量加 “a” 前缀区分类型和变量
- 通过对变量采用更明确的名字区分类型和变量
- 标识全局变量:
g_
前缀 - 标识成员变量:
m_
前缀 - 标识类型声明:
t_
前缀 - 标识具名常量:
c_
前缀 - 标识枚举类型的元素:
e_
前缀
- 标识全局变量:
-
在不能保证输入参数是只读的语言里标识只读参数
- 直接使用const来进行标识。
-
格式化命名以提高可读性
- 驼峰法
- 下划线分隔
- 不要混用,避免可读性降低。
4.2 语言相关规则的指导规则¶
-
C的命名规则
- c和ch是字符常量
- i和j是整数下标
- n表示某物的数量
- p是指针
- s是字符串
- 预处理宏全部大写,通常也包括typedef
- 变量名和子程序名全部小写
- 下划线
(_)
用作分隔符。
-
C++的命名规则
- i和j是整数下标
- p是指针
- 常量、typedef和预处理宏全部大写
- 类和其他类型的名字混合大小写。
- 变量名和函数名中的第一个单词小写,后续每个单词的首个字母大写。
- 不把下划线(_)用作名字中的分隔符,除非用于全部大写的名字以及特定的前缀中。
4.3 混合语言编程的注意事项¶
在混合语言环境中编程时,可以对命名规则作出优化以提高整体的一致性和可读性。
5. 标准前缀¶
对具有通用含义的前缀标准化,为数据命名提供了一种简洁、一致并且可读性的方法。
标准化的前缀主要有两部分组成:
- 用户自定义类型缩写(UDT)
- 语义前缀
- 描述了变量或者对象是如何使用。
- 可以全用小写,也可以混合使用大小写。
标准前缀
- 优点
- 标准前缀能够更为精确地描述一些含义比较模糊的名字。
- 标准化的前缀使名字变得更紧凑。
- 当编译器不能检查所用的抽象数据类型时,标准前缀能帮助你对类型作出判断。
- 缺点
- 程序员在使用前缀的同时忽略给变量起有意义的名字。
6. 创建具备可读性的短名称¶
通过消除冗余的单词、使用简短的同义词以及使用诸多缩写策略中的任意一种来创建更好的短变量名。
6.1 缩写的一般指导原则¶
如下的一些原则是互相冲突,所以不要试图同时应用所有的原则。
- 使用标准的缩写
- 去掉所有非前置元音
- 去掉虚词
- 使用每个单词的第一个或前几个字母
- 统一地在每个单词的第一、第二或者第三个字母后截断
- 保留每个单词的第一个和最后一个字母
- 使用名字中的每一个重要单词,最多不超过三个
- 去除无用的后缀
- 保留每个音节中最引人注意的发音
- 确保不要改变变量的含义
- 将每个变量名的长度缩短到8到20个字符或者达到所用编程语言对变量名的限制字符数。
6.3 避免犯错的规则¶
- 不要用从每个单词中删除一个字符的方式来缩写
- 缩写要一致
- 创建能读出的名字
- 避免使用容易看错或者读错的字符组合
- 使用辞典来解决命名冲突
- 在代码中用缩写对照表解释极短的名字的含义。
- 在一份项目级的“标准缩写” 文档中说明所有的缩写
- 记住:名字对于代码读者的意义要比作者更重要
7. 应该避免的名称¶
对于一些变量名应该避免的指导原则:
- 避免使用令人误解的名字或缩写
- 避免使用具有相似含义的名字
- 避免使用具有不同含义但却有相似名字的变量
- 避免使用发音相近的名字
- 避免在名字中使用数字
- 避免在名字中拼错单词
- 避免使用英语中常常拼错的单词
- 不要仅靠大小写来区分变量名
- 避免使用多种自然语言
- 避免使用标准类型、变量和子程序的名字
- 不要使用与变量含义完全无关的名字
- 避免在名字中包含易混淆的字符
8. 核对表:变量命名¶
- 命名的一般注意事项
- 名字完整并准确表达了变量所代表的含义了吗?
- 名字反映了现实世界的问题而不是编程语言方案吗?
- 名字足够长,可以让你无须苦苦思索吗?
- 如果有计算值限定符,它被放在了名字的最后吗?
- 名字中用Count或者Index来代替Num了吗?
-
为特定类型的数据命名
- 循环下标的名字有意义吗?
- 所有的”临时“变量都重新命以更有意义的名字了吗?
- 当布尔变量的值为真时,变量名能准确表达其含义吗?
- 枚举类型的名字中含有能够表示其类别的前缀或者后缀了吗?
- 具名常量是根据它所代表的抽象实体而不是它所代表的数字来命名的吗?
-
命名规则
- 规则能够区分局部数据、类的数据和全局数据吗?
- 规则能够区分类型名、具名常量、枚举类型和变量名吗?
- 规则能够在编译器不强制检测只读参数的语言里标识出子程序中的输入参数吗?
- 规则尽可能地与语言的标准规则兼容吗?
- 名字为了可读性而加以格式化吗?
-
短名字
- 代码使用了长名字吗?
- 是否避免只为了省一个字符而缩写名字的情况?
- 所有单词的缩写方式都一致吗?
- 名字能够读出来吗?
- 避免使用容易被看错或者读错的名字吗?
- 在缩写对照里对短名字做出说明了吗?
Chapter 12 基本数据类型¶
基本数据类型是构建其他所有数据类型的构造块的。
1. 数值概论¶
避免使用数值犯错的一些建议: - 避免使用 “神秘数值” - 有三个好处: - 修改会变得可靠。 - 修改会变得容易。 - 代码可读性好 - 如果需要,可以使用硬编码的0和1 - 预防除零错误 - 除法问题需要注意 - 使类型转换变得明显 - 避免混合类型的比较 - 注意编译器的警告
2. 整数¶
在使用整数时,需要注意:
- 检查整数除法
-
检查整数溢出
- 考虑清楚算术表达式中的每个项可能达到的最大值。
-
检查中间结果溢出
3. 浮点数¶
在使用浮点数时主要考虑:十进制小数不能精确地用数字计算机中的1和0来表示。
使用浮点数时应该遵循的指导原则: - 避免数量级相差巨大的数之间的加减运算 - 避免等量判断 - 处理舍入的问题 - 常见方案: - 换用一种精确度更高的变量类型 - 换用二进制编码的十进制(BCD)变量。 - 把浮点变量变成整型变量
- 检查语言和函数库对特定数据类型的支持
4. 字符和字符串¶
以下是一些字符串的使用技巧:
-
避免使用神秘字符和神秘字符串
- 神秘字符:程序中随处可见的字面形式表示的字符。
- 神秘字符串:字面形式表示的字符串。
- 字符串的字面表示形式通常都会占用较多的存储空间。
- 字面形式的表示是模糊的。
-
避免off-by-one(偏差一)错误
- 读写操作超出了字符串末尾而导致的偏差一错误。
-
了解语言和开发环境如何支持Unicode
- 在程序生命期今早决定国际化/本地化策略
- 如果知道只需要支持一种文字的语言,请考虑使用ISO 8859 字符集。
- 需要支持多种语言,则使用Unicode
- 采用某种一致的字符串类型转换策略
在C语言中的字符串,提供了一些直接处理C字符串时,避免常见错误的方法: - 注意字符串指针和字符串数组之间的差异。 - 主要关注两个差异点: - 警惕任何包含字符串和等号的表达式 - 通过命名规则区分变量是字符数组还是字符串指针。 - 字符串指针;ps前缀 - 字符数组:ach前缀
-
把
C-style 字符串
的长度声明为CONSTANT + 1
- 避免off-by-one错误
-
用
null初始化字符串
以避免没有终端的字符串- C通过查找空结束符,即字符串末尾取值为0的字符串,来判断字符串的末尾
-
用
字符数组
取代C中的指针 - 用
strncpy()
取代strcpy()
避免无终端的字符串
5. 布尔变量¶
- 用布尔变量对程序加以文档说明
- 用布尔变量来简化复杂的判断
- 如果有需要,创建自定义的布尔类型
6. 枚举类型¶
枚举类型是一种允许用英语来描述某一类对象中每一个成员的数据类型。
使用枚举类型的指导原则: - 用枚举类型来提高可读性 - 枚举类型特别适合定义子程序的参数 - 用枚举类型来提高可靠性 - 枚举类型会使编译器执行比整数和常量更为彻底的类型检查。 - 用枚举类型来简化修改。 - 将枚举类型作为布尔变量的替换方案。 - 检查非法数值 - 定义出枚举的第一项和最后一项,以便用于循环边界 - 把枚举类型的第一个元素留做非法值。 - 明确定义项目代码编写标准中第一个和最后一个元素的使用规则,并且在使用时保持一致。 - 警惕给枚举元素明确赋值而带来的失误
如果使用的语言中没有枚举类型,可以使用全局变量或者类来模拟使用它。使用特殊创建的枚举类型的方法,类型安全,声明为类后,编译器会检查非法的赋值。
7. 命名常量¶
具名常量类似变量,一旦赋值后不能修改。使用具名常量是一种将程序 “参数化” 的方法。类似宏定义,只要改动一个位置,其余位置全部均可改动。 - 在数组声明中使用具名常量 - 避免使用文字量,即使是 “安全”的
8. 数组¶
数组是最简单和最常用的结构化数据类型。在有些语言中,也是唯一的结构化数据类型。使用数组给出的一些建议:
- 确认所有的数组下标都没有超出数组的边界。
- 考虑用容器来取代数组,或者将数组作为顺序化结构来处理
- 检查数组的边界点
- 如果数组是多维的,确认下标的使用顺序是正确的
-
提防下标串话
- 主要出现在多维数组中。
-
在C中结合 ARRAY_LENGTH() 宏来使用数组
9. 创建自己的类型(类型别名)¶
在C/C++中可以使用 typedef
语句 来创建一个新的特殊类型。
对于创建自己类型的原因:
- 易于修改
- 避免过多的信息分支
- 增加可靠性
- 弥补语言的不足
在创建自定义数据类型的指导原则:
- 给所创建的类型取功能导向的名字
- 避免使用预定义类型
- 不要重定义一个预定义的类型
- 定义替代类型以便于移植
10. 核对表:基本数据类型¶
-
数值概论
- 代码中避免使用神秘数值吗?
- 代码考虑了除零错误吗?
- 类型转换很明显吗?
- 如果在一条语句中存在两种不同类型的变量,那么这条语句会像你期望的那样吗?
- 代码避免了混合类型的比较吗?
- 程序编译时没有警告信息吗?
-
整数
- 使用整数除法的表达式能按预期的那样工作吗?
- 整数表达式避免整数溢出问题吗?
-
浮点数
- 代码避免了对数量级相差巨大的数字做加减运算吗?
- 代码系统地阻止了舍入错误的发生吗?
- 代码避免对浮点数做等量的比较吗?
-
字符和字符串
- 代码避免使用神秘字符和神秘字符串吗?
- 使用字符串时避免了off-by-one错误吗?
- C代码把字符串指针和字符串数组区别对待了吗?
- C代码遵循了把字符串声明为CONSTANT + 1长度的规则了吗?
- C代码在适当的时候用字符数组来代替指针了吗?
- C代码把字符串初始化为NULL来避免无终端的字符串了吗?
- C代码用strncpy()代替strcpy()吗?strncat()和strncmp()呢?
-
布尔变量
- 程序用额外的布尔变量来说明条件判断了吗?
- 程序用额外的布尔变量来简化条件判断了吗?
-
枚举类型
- 程序用枚举类型而非具名常量来提高可读性、可靠性和可修改性吗?
- 当变量的用法不能仅用true和false表示的时候,程序用枚举类型来取代布尔变量吗?
- 针对枚举类型的测试检测了非法数值了吗?
- 把枚举类型的第一条目保留为“非法的”了吗?
-
具名常量
- 程序用具名常量而不是神秘数值来声明数据和表示循环界限吗?
- 具名常量的使用一致吗?——没有在有些位置使用具名常量又在其他位置使用文字量?
-
数组
- 所有数组下标都没有超出数组边界吗?
- 数组引用没有出现off-by-one错误吗?
- 所有多维数组的下标使用都正确吗?
- 在嵌套循环里,把正确的变量用于数组下标来避免循环下标串话了吗?
-
创建类型
- 程序对每一种可能变化的数据分别采用不同的类型吗?
- 类型名是以该类型所表示的现实世界实体为导向,而不是以编程语言类型为导向的吗?
- 类型名的描述性足够强,可以帮助解释数据声明吗?
- 你避免重新定义预定义类型吗?
- 与简单地重定义一个类型相比,你考虑过创建一个新类吗?
Chapter 13 不常见的数据类型¶
1. 结构体¶
创建类而非结构体,除了能使用结构体可以提供的公用数据成员外,还能利用类所提供的私密性和功能性。
使用结构体的理由:
- 用结构体来明确数据关系
- 用结构体来简化对数据块的操作
- 用结构体来简化参数列表
- 用结构体来减少维护
2. 指针¶
指针的使用是现代编程中最容易出错的领域之一。常见的安全问题就是缓冲区溢出。
2.1 用来理解指针的范例¶
每一个指针包含两个部分:
-
内存中的某处位置
- 内存中的一个位置就是一个地址,常用16进制形式表示。
- 要使用指针所指向的数据,就必须访问该地址,解释该位置的内存内容。
-
如何解释该位置中的位置
- 由指针的基类型决定。
2.2 使用指针的一般技巧¶
内存破坏
:通过一个坏了的指针变量赋值时,把数据写入本不该写值的内存区域。
正确使用指针要求程序员采用一种双向策略。
- 首先要避免造成指针错误。
- 在编写代码之后尽快地检测出指针错误来。
使用技巧:
-
把指针操作限制在子程序或者类里面
- 减少访问指针代码位置的数量,使得代码相对于独立于数据的实现细节,增大了在其他程序内重用代码的可能性。
-
同时声明和定义指针
- 在与指针分配相同的作用域中删除指针
-
在使用指针之前检查指针
- 使用指针之前,要确保所指向的内存位置是合理的。
-
见检查指针所引用的变量再使用它
- 对指针所指向的数据执行合理性检查。
-
用“标记字段”来检测损毁的内存
- 增加明显的冗余
-
用额外的指针变量来提高代码清晰度
- 链表中的双指针处理法
-
简化复杂的指针表达式
-
画一个图
- 通过指针帮助指针链接的步骤。
-
按照正确的顺序删除链表中的指针
- 分配一片保留的内存后备区域
- 粉碎垃圾数据
- 在删除或者释放指针之后把它们设为空值。
- 常见的指针错误:悬空指针(即使用一个已经被delete或者free的指针)。
- 在删除变量之前检查非法指针
- 跟踪指针分配情况
- 编写覆盖子程序,集中实现避免指针问题的策略
- 采用非指针的技术
2.3 C++指针¶
在C++中使用指针的指导原则:
- 理解指针和引用之间的区别
- 在C++中,指针(*)
和 引用(&)
都能间接地引用对象
。
- 本质区别:
- 引用
必须是引用一个对象
。
- 指针
则可以指向空值
。
- 引用所指向的对象
在该引用初始化之后不能改变
。
-
把
指针
用于“按引用传递”
参数,把const引用
用于“按值传递”
参数- C++中向子程序传递参数的默认方式是传递值而不是传递引用。
-
使用
auto_ptr
- 通过在离开作用域就会自动释放内存,auto_ptr 可以避免很多鱼常规指针相关的内存泄漏问题。
-
灵活运用
智能指针
2.4 C指针¶
C语言中的一些指针应用技巧
- 使用显式指针类型而不是默认类型
- 允许对任何类型的变量
使用char
或void指针
。
- 弊端:
- 编译器会对不相符的指针类型和不合适的解除引用发出警告。
- 在类型转换时,必须进行显式转换。
-
避免强制类型转换
-
遵循参数传递的星号规则
- 在内存中分配使用 sizeof() 确定变量的大小
- sizeof()是编译时计算,不会造成性能的损失。
- 可移植性较好,不同平台编译,会自动计算值。
3. 全局数据¶
全局数据可以在程序中任意一个位置访问。使用全局数据的风险比使用局部数据的大,所以一般都是通过一些子程序来访问数据。
3.1 与全局数据有关的常见问题¶
- 无意间修改了全局数据
- 与全局数据有关的奇异的和令人激动的别名问题
- 与全局数据有关的代码重入问题
- 全局数据阻碍代码重用
- 与全局数据有关的非确定的初始化顺序事宜
- 全局数据破坏了模块化和智力上的可管理性
3.2 使用全局数据的理由¶
具名常量也是全局数据,只不过是全局变量罢了。全局变量的一些使用场景:
- 保存全局数值
-
模拟具名常量
- 在不支持具名常量时,可以用全局变量来代替。
-
模拟枚举类型
- 在不支持枚举类型的语言里用全局变量来模拟枚举类型。
-
简化对极其常用的数据的使用
- 将经常性使用的参数,直接设置成全局变量。
-
消除流浪数据
3.3 只有万不得已时才使用全局数据¶
- 首先把一个变量设置为局部的,仅当需要时才把变量设置为全局的。
- 区分全局变量和类变量。
- 使用访问器子程序
3.4 用访问子程序来取代全局数据¶
使用全局数据可行的事情,都可以使用访问器子程序代替。
访问器子程序是实现抽象数据类型和信息隐藏的一种核心方法。
-
访问器子程序的优势
- 获得了对数据的集中控制
- 可以确保对变量的所有引用都得到了保护
- 可以自动获得信息隐藏的普遍益处。
- 访问器子程序可以很容易地转变为抽象数据类型。
-
如何使用访问器子程序
- 要求所有的代码通过访问器子程序来存取数据。
- 好习惯:所有的全局数据都加上
g_
前缀。
- 好习惯:所有的全局数据都加上
-
不要把你所有的全局数据都扔在一处。
-
用锁定来控制对全局变量的访问
- 在使用或者更新一个全局变量之前,变量必须被签出。如果在使用期间,有其他的程序尝试签出,那么锁定/解锁子程序就会报错或触发断言。
- 在你的访问子程序里构建一个抽象层
- 使得对一项数据的所有访问都发生在同一个抽象层上。
- 要求所有的代码通过访问器子程序来存取数据。
3.5 如何降低全局数据的风险¶
- 创建一种命名规则来突出全局变量
- 为全部的全局变量创建一份注释良好的清单
- 不要用全局变量来存放中间结果
- 不要把所有的数据都放在一个大对象中并到处传递,以说明你没有使用全局变量
4. 推荐资源¶
- 《Writing Solid Code》第3章讲述了使用指针的风险,以及如何避免与指针有关的问题。
- 《Effective C++》讲述了安全并有效使用指针的指导原则。也包括对内存管理的问题。
5. 核对表:使用不常见数据类型的注意事项¶
-
结构体
- 你使用结构体而不是使用单纯的变量来组织和操作相关的数据吗?
- 是否考虑一个类来代替使用结构体?
-
全局数据
- 所有的变量是否都是局部的或者是类范围的?除非绝对有必要才是全局的?
- 变量的命名规则能把局部数据、类数据和全局数据区分开吗?
- 你对所有的全局变量都加以文档说明吗?
- 避免使用伪全局数据,即被四处传递且包含有杂乱数据的巨大对象吗?
- 用访问器子程序来取代全局数据吗?
- 把访问器子程序和数据组织到类里面吗?
- 访问器子程序提供了一个在底层数据类型实现之上的抽象层吗?
- 所有相关的访问器子程序都位于同一抽象层吗?
-
指针
- 把指针操作隔离在子程序里吗?
- 指针引用合法吗?或者说指针有可能成为空悬指针吗?
- 代码在使用指针之前检查它的有效性吗?
- 在使用指针所指向的变量之前检查其有效性吗?
- 指针用完后被设置为空值吗?
- 就可读性而言,代码用了所有需要使用的指针变量吗?
- 链表中的指针是按正确的顺序加以释放的吗?
- 程序分配了一片保留的内存后备区域,以便在耗尽内存的时候能够优雅地退出吗?
- 是不是在没有其他办法可用的情况下最终才使用指针的?