高质量C语言编程指南

上传人:xgs****56 文档编号:13024184 上传时间:2020-06-05 格式:DOC 页数:101 大小:650.50KB
返回 下载 相关 举报
高质量C语言编程指南_第1页
第1页 / 共101页
高质量C语言编程指南_第2页
第2页 / 共101页
高质量C语言编程指南_第3页
第3页 / 共101页
点击查看更多>>
资源描述
高质量 C C 编程指南 文件标识 当前版本 1 0 作 者 林锐 博士 文件状态 草稿文件 正式文件 更改正式文件 完成日期 2001 年 7 月 24 日 高质量 C C 编程指南 v 1 0 2001 Page 2 of 101 版 本 历 史 版本 状态 作者 参与者 起止日期 备注 V 0 9 草稿文件 林锐 2001 7 1 至 2001 7 18 林锐起草 V 1 0 正式文件 林锐 2001 7 18 至 2001 7 24 朱洪海审查 V 0 9 林锐修正草稿中的错误 高质量 C C 编程指南 v 1 0 2001 Page 3 of 101 目目 录录 前 言 6 第 1 章 文件结构 11 1 1 版权和版本的声明 11 1 2 头文件的结构 12 1 3 定义文件的结构 13 1 4 头文件的作用 13 1 5 目录结构 14 第 2 章 程序的版式 15 2 1 空行 15 2 2 代码行 16 2 3 代码行内的空格 17 2 4 对齐 18 2 5 长行拆分 19 2 6 修饰符的位置 19 2 7 注释 20 2 8 类的版式 21 第 3 章 命名规则 22 3 1 共性规则 22 3 2 简单的 WINDOWS 应用程序命名规则 23 3 3 简单的 UNIX 应用程序命名规则 25 第 4 章 表达式和基本语句 26 4 1 运算符的优先级 26 4 2 复合表达式 27 4 3 IF 语句 27 4 4 循环语句的效率 29 4 5 FOR 语句的循环控制变量 30 4 6 SWITCH 语句 30 4 7 GOTO 语句 31 第 5 章 常量 33 5 1 为什么需要常量 33 5 2 CONST 与 DEFINE 的比较 33 5 3 常量定义规则 33 5 4 类中的常量 34 第 6 章 函数设计 36 高质量 C C 编程指南 v 1 0 2001 Page 4 of 101 6 1 参数的规则 36 6 2 返回值的规则 37 6 3 函数内部实现的规则 39 6 4 其它建议 40 6 5 使用断言 41 6 6 引用与指针的比较 42 第 7 章 内存管理 44 7 1 内存分配方式 44 7 2 常见的内存错误及其对策 44 7 3 指针与数组的对比 45 7 4 指针参数是如何传递内存的 47 7 5 FREE 和 DELETE 把指针怎么啦 50 7 6 动态内存会被自动释放吗 50 7 7 杜绝 野指针 51 7 8 有了 MALLOC FREE 为什么还要 NEW DELETE 52 7 9 内存耗尽怎么办 53 7 10 MALLOC FREE 的使用要点 54 7 11 NEW DELETE 的使用要点 55 7 12 一些心得体会 56 第 8 章 C 函数的高级特性 57 8 1 函数重载的概念 57 8 2 成员函数的重载 覆盖与隐藏 60 8 3 参数的缺省值 63 8 4 运算符重载 64 8 5 函数内联 65 8 6 一些心得体会 68 第 9 章 类的构造函数 析构函数与赋值函数 69 9 1 构造函数与析构函数的起源 69 9 2 构造函数的初始化表 70 9 3 构造和析构的次序 72 9 4 示例 类 STRING 的构造函数与析构函数 72 9 5 不要轻视拷贝构造函数与赋值函数 73 9 6 示例 类 STRING 的拷贝构造函数与赋值函数 73 9 7 偷懒的办法处理拷贝构造函数与赋值函数 75 9 8 如何在派生类中实现类的基本函数 75 9 9 一些心得体会 77 第 10 章 类的继承与组合 78 高质量 C C 编程指南 v 1 0 2001 Page 5 of 101 10 1 继承 78 10 2 组合 80 第 11 章 其它编程经验 82 11 1 使用 CONST 提高函数的健壮性 82 11 2 提高程序的效率 84 11 3 一些有益的建议 85 参考文献 87 附录 A C C 代码审查表 88 附录 B C C 试题 93 附录 C C C 试题的答案与评分标准 97 高质量 C C 编程指南 v 1 0 2001 Page 6 of 101 前 言 软件质量是被大多数程序员挂在嘴上而不是放在心上的东西 除了完全外行和真正的编程高手外 初读本书 你最先的感受将是惊慌 哇 我以前 捏造的 C C 程序怎么会有那么多的毛病 别难过 作者只不过比你早几年 多几次惊慌而已 请花一两个小时认真阅读这本百页经书 你将会获益匪浅 这是前面 N 1 个读者的建议 一 编程老手与高手的误区 自从计算机问世以来 程序设计就成了令人羡慕的职业 程序员在受人宠爱之后容易发 展成为毛病特多却常能自我臭美的群体 如今在 Internet 上流传的 真正 的程序员据说是这样的 1 真正的程序员没有进度表 只有讨好领导的马屁精才有进度表 真正的程序员会让领导 提心吊胆 2 真正的程序员不写使用说明书 用户应当自己去猜想程序的功能 3 真正的程序员几乎不写代码的注释 如果注释很难写 它理所当然也很难读 4 真正的程序员不画流程图 原始人和文盲才会干这事 5 真正的程序员不看参考手册 新手和胆小鬼才会看 6 真正的程序员不写文档也不需要文档 只有看不懂程序的笨蛋才用文档 7 真正的程序员认为自己比用户更明白用户需要什么 8 真正的程序员不接受团队开发的理念 除非他自己是头头 9 真正的程序员的程序不会在第一次就正确运行 但是他们愿意守着机器进行若干个 30 小 时的调试改错 10 真正的程序员不会在上午 9 00 到下午 5 00 之间工作 如果你看到他在上午 9 00 工作 这表明他从昨晚一直干到现在 具备上述特征越多 越显得水平高 资格老 所以别奇怪 程序员的很多缺点竟然可以 被当作优点来欣赏 就象在武侠小说中 那些独来独往 不受约束且带点邪气的高手最令人 崇拜 我曾经也这样信奉 并且希望自己成为那样的 真正 的程序员 结果没有得到好下 场 我从读大学到博士毕业十年来一直勤奋好学 累计编写了数十万行 C C 代码 有这样 的苦劳和疲劳 我应该称得上是编程老手了吧 我开发的软件都与科研相关 集成电路 CAD 和 3D 图形学领域 动辄数万行程序 技 术复杂 难度颇高 这些软件频频获奖 有一个软件获得首届中国大学生电脑大赛软件展示 高质量 C C 编程指南 v 1 0 2001 Page 7 of 101 一等奖 在 1995 年开发的一套图形软件库到 2000 年还有人买 罗列出这些 业绩 可以 说明我算得上是编程高手了吧 可惜这种个人感觉不等于事实 读博期间我曾用一年时间开发了一个近 10 万行 C 代码的 3D 图形软件产品 我内心得 意表面谦虚地向一位真正的软件高手请教 他虽然从未涉足过 3D 图形领域 却在几十分钟 内指出该软件多处重大设计错误 让人感觉那套软件是用纸糊的华丽衣服 扯一下掉一块 戳一下破个洞 我目瞪口呆地意识到这套软件毫无实用价值 一年的心血白化了 并且害死 了自己的软件公司 人的顿悟通常发生在最心痛的时刻 在沮丧和心痛之后 我作了深刻反省 面壁 半 年 重新温习软件设计的基础知识 补修 内功 之后 又觉得腰板硬了起来 博士毕业前 半年 我曾到微软中国研究院找工作 接受微软公司一位资深软件工程师的面试 他让我写 函数 strcpy 的代码 太容易了吧 错 这么一个小不点的函数 他从三个方面考查 1 编程风格 2 出错处理 3 算法复杂度分析 用于提高性能 在大学里从来没有人如此严格地考查过我的程序 我化了半个小时 修改了数次 他还 不尽满意 让我回家好好琢磨 我精神抖擞地进 考场 大汗淋漓地出 考场 这 高手 当得也太窝囊了 我又好好地反省了一次 我把反省后的心得体会写成文章放在网上传阅 引起了不少软件开发人员的共鸣 我因 此有幸和国产大型 IT 企业如华为 上海贝尔 中兴等公司的同志们广泛交流 大家认为提高 质量与生产率是软件工程要解决的核心问题 高质量程序设计是非常重要的环节 毕竟软件 是靠编程来实现的 我们心目中的老手们和高手们能否编写出高质量的程序来 不见得都能 就我的经历与阅历来看 国内大学的计算机教育压根就没有灌输高质量程序设计的观念 教师们和学生们也很少自觉关心软件的质量 勤奋好学的程序员长期在低质量的程序堆中滚 爬 吃尽苦头之后才有一些心得体会 长进极慢 我就是一例 现在国内 IT 企业拥有学士 硕士 博士文凭的软件开发人员比比皆是 但他们在接受大 学教育时就 先天不足 岂能一到企业就突然实现质的飞跃 试问有多少软件开发人员对 正确性 健壮性 可靠性 效率 易用性 可读性 可理解性 可扩展性 可复用性 兼 容性 可移植性等质量属性了如指掌 并且能在实践中运用自如 高质量 可不是干活 小心点就能实现的 高质量 C C 编程指南 v 1 0 2001 Page 8 of 101 我们有充分的理由疑虑 1 编程老手可能会长期用隐含错误的方式编程 习惯成自然 发现毛病后都不愿相信那 是真的 2 编程高手可以在某一领域写出极有水平的代码 但未必能从全局把握软件质量的方方 面面 事实证明如此 我到上海贝尔工作一年来 陆续面试或测试过近百名 新 老 程序 员的编程技能 质量合格率大约是 10 很少有人能够写出完全符合质量要求的 if 语句 很 多程序员对指针 内存管理一知半解 领导们不敢相信这是真的 我做过现场试验 有一次部门新进 14 名硕士生 在开欢迎 会之前对他们进行 C C 编程技能 摸底考试 我问大家试题难不难 所有的人都回答不 难 结果没有一个人及格 有半数人得零分 竞争对手公司的朋友们也做过试验 同样一败 涂地 真的不是我 心狠手辣 或者要求过高 而是很多软件开发人员对自己的要求不够高 要知道华为 上海贝尔 中兴等公司的员工素质在国内 IT 企业中是比较前列的 倘若他 们的编程质量都如此差的话 我们怎么敢期望中小公司拿出高质量的软件呢 连程序都编不 好 还谈什么振兴民族软件产业 岂不胡扯 我打算定义编程老手和编程高手 请您别见笑 定义 1 能长期稳定地编写出高质量程序的程序员称为编程老手 定义 2 能长期稳定地编写出高难度 高质量程序的程序员称为编程高手 根据上述定义 马上得到第一推论 我既不是高手也算不上是老手 在写此书前 我阅读了不少程序设计方面的英文著作 越看越羞惭 因为发现自己连编 程基本技能都未能全面掌握 顶多算是二流水平 还好意思谈什么老手和高手 希望和我一 样在国内土生土长的程序员朋友们能够做到 1 知错就改 2 经常温故而知新 3 坚持学习 天天向上 高质量 C C 编程指南 v 1 0 2001 Page 9 of 101 二 本书导读 首先请做附录 B 的 C C 试题 不要看答案 考查自己的编程质量究竟如何 然后参 照答案严格打分 1 如果你只得了几十分 请不要声张 也不要太难过 编程质量差往往是由于不良习惯 造成的 与人的智力 能力没有多大关系 还是有药可救的 成绩越差 可以进步的空间就 越大 中国不就是在落后中赶超发达资本主义国家吗 只要你能下决心改掉不良的编程习惯 第二次考试就能及格了 2 如果你考及格了 表明你的技术基础不错 希望你能虚心学习 不断进步 如果你还 没有找到合适的工作单位 不妨到上海贝尔试一试 3 如果你考出 85 分以上的好成绩 你有义务和资格为你所在的团队作 C C 编程 培 训 希望你能和我们多多交流 相互促进 半年前我曾经发现一颗好苗子 就把他挖到我们 小组来 4 如果你在没有任何提示的情况下考了满分 希望你能收我做你的徒弟 编程考试结束后 请阅读本书的正文 本书第一章至第六章主要论述 C C 编程风格 难度不高 但是细节比较多 别小看了 提高质量就是要从这些点点滴滴做起 世上不存在最好的编程风格 一切因需求而定 团队 开发讲究风格一致 如果制定了大家认可的编程风格 那么所有组员都要遵守 如果读者觉 得本书的编程风格比较合你的工作 那么就采用它 不要只看不做 人在小时候说话发音不 准 写字潦草 如果不改正 总有后悔的时候 编程也是同样道理 第七章至第十一章是专题论述 技术难度比较高 看书时要积极思考 特别是第七章 内存管理 读了并不表示懂了 懂了并不表示就能正确使用 有一位同事看了第七章后 觉得 野指针 写得不错 与我切磋了一把 可是过了两周 他告诉我 他忙了两天追查出 一个 Bug 想不到又是 野指针 出问题 只好重读第七章 光看本书对提高编程质量是有限的 建议大家阅读本书的参考文献 那些都是经典名著 如果你的编程质量已经过关了 不要就此满足 如果你想成为优秀的软件开发人员 建 议你阅读并按照 CMMI 规范做事 让自己的综合水平上升一个台阶 上海贝尔的员工可以向 网络应用事业部软件工程研究小组索取 CMMI 有关资料 最好能参加培训 高质量 C C 编程指南 v 1 0 2001 Page 10 of 101 三 版权声明 本书的大部分内容取材于作者一年前的书籍手稿 尚未出版 现整理汇编成为上海贝 尔网络应用事业部的一个规范化文件 同时作为培训教材 由于 C C 编程是众所周知的技术 没有秘密可言 编程的好经验应该大家共享 我们 自己也是这么学来的 作者愿意公开本书的电子文档 版权声明如下 1 读者可以任意拷贝 修改本书的内容 但不可以篡改作者及所属单位 2 未经作者许可 不得出版或大量印发本书 3 如果竞争对手公司的员工得到本书 请勿公开使用 以免发生纠纷 预计到 2002 年 7 月 我们将建立切合中国国情的 CMMI 3 级解决方案 届时 包括本 书在内的约 1000 页规范将严格受控 欢迎读者对本书提出批评建议 林锐 2001 年 7 月 高质量 C C 编程指南 v 1 0 2001 Page 11 of 101 第 1 章 文件结构 每个 C C 程序通常分为两个文件 一个文件用于保存程序的声明 declaration 称为 头文件 另一个文件用于保存程序的实现 implementation 称为定义 definition 文件 C C 程序的头文件以 h 为后缀 C 程序的定义文件以 c 为后缀 C 程序的定 义文件通常以 cpp 为后缀 也有一些系统以 cc 或 cxx 为后缀 1 1 版权和版本的声明 版权和版本的声明位于头文件和定义文件的开头 参见示例 1 1 主要内容有 1 版权信息 2 文件名称 标识符 摘要 3 当前版本号 作者 修改者 完成日期 4 版本历史信息 Copyright c 2001 上海贝尔有限公司网络应用事业部 All rights reserved 文件名称 filename h 文件标识 见配置管理计划书 摘 要 简要描述本文件的内容 当前版本 1 1 作 者 输入作者 或修改者 名字 完成日期 2001年 7月 20日 取代版本 1 0 原作者 输入原作者 或修改者 名字 完成日期 2001年 5月 10日 示例1 1 版权和版本的声明 1 2 头文件的结构 头文件由三部分内容组成 高质量 C C 编程指南 v 1 0 2001 Page 12 of 101 1 头文件开头处的版权和版本声明 参见示例 1 1 2 预处理块 3 函数和类结构声明等 假设头文件名称为 graphics h 头文件的结构参见示例 1 2 规则 1 2 1 为了防止头文件被重复引用 应当用 ifndef define endif 结构产生预处理块 规则 1 2 2 用 include 格式来引用标准库的头文件 编译器将从标准 库目录开始搜索 规则 1 2 3 用 include filename h 格式来引用非标准库的头文件 编译器将从用 户的工作目录开始搜索 建议 1 2 1 头文件中只存放 声明 而不存放 定义 在 C 语法中 类的成员函数可以在声明的同时被定义 并且自动成为内联函数 这 虽然会带来书写上的方便 但却造成了风格不一致 弊大于利 建议将成员函数的定义与声 明分开 不论该函数体有多么小 建议 1 2 2 不提倡使用全局变量 尽量不要在头文件中出现象 extern int value 这类声 明 版权和版本声明见示例 1 1 此处省略 ifndef GRAPHICS H 防止 graphics h 被重复引用 define GRAPHICS H include 引用标准库的头文件 include myheader h 引用非标准库的头文件 void Function1 全局函数声明 class Box 类结构声明 endif 示例 1 2 C C 头文件的结构 高质量 C C 编程指南 v 1 0 2001 Page 13 of 101 1 3 定义文件的结构 定义文件有三部分内容 1 定义文件开头处的版权和版本声明 参见示例 1 1 2 对一些头文件的引用 3 程序的实现体 包括数据和代码 假设定义文件的名称为 graphics cpp 定义文件的结构参见示例 1 3 版权和版本声明见示例 1 1 此处省略 include graphics h 引用头文件 全局函数的实现体 void Function1 类成员函数的实现体 void Box Draw 示例 1 3 C C 定义文件的结构 1 4 头文件的作用 早期的编程语言如 Basic Fortran 没有头文件的概念 C C 语言的初学者虽然会用使 用头文件 但常常不明其理 这里对头文件的作用略作解释 1 通过头文件来调用库功能 在很多场合 源代码不便 或不准 向用户公布 只要向 用户提供头文件和二进制的库即可 用户只需要按照头文件中的接口声明来调用库功能 而 不必关心接口怎么实现的 编译器会从库中提取相应的代码 2 头文件能加强类型安全检查 如果某个接口被实现或被使用时 其方式与头文件中的 声明不一致 编译器就会指出错误 这一简单的规则能大大减轻程序员调试 改错的负担 高质量 C C 编程指南 v 1 0 2001 Page 14 of 101 1 5 目录结构 如果一个软件的头文件数目比较多 如超过十个 通常应将头文件和定义文件分别保 存于不同的目录 以便于维护 例如可将头文件保存于 include 目录 将定义文件保存于 source 目录 可以是多级目录 如果某些头文件是私有的 它不会被用户的程序直接引用 则没有必要公开其 声明 为了加强信息隐藏 这些私有的头文件可以和定义文件存放于同一个目录 高质量 C C 编程指南 v 1 0 2001 Page 15 of 101 第 2 章 程序的版式 版式虽然不会影响程序的功能 但会影响可读性 程序的版式追求清晰 美观 是程序 风格的重要构成因素 可以把程序的版式比喻为 书法 好的 书法 可让人对程序一目了然 看得兴致勃 勃 差的程序 书法 如螃蟹爬行 让人看得索然无味 更令维护者烦恼有加 请程序员们 学习程序的 书法 弥补大学计算机教育的漏洞 实在很有必要 2 1 空行 空行起着分隔程序段落的作用 空行得体 不过多也不过少 将使程序的布局更加清晰 空行不会浪费内存 虽然打印含有空行的程序是会多消耗一些纸张 但是值得 所以不要舍 不得用空行 规则 2 1 1 在每个类声明之后 每个函数定义结束之后都要加空行 参见示例 2 1 a 规则 2 1 2 在一个函数体内 逻揖上密切相关的语句之间不加空行 其它地方应加空 行分隔 参见示例 2 1 b 空行 void Function1 空行 void Function2 空行 void Function3 空行 while condition statement1 空行 if condition statement2 else statement3 空行 statement4 示例 2 1 a 函数之间的空行 示例 2 1 b 函数内部的空行 高质量 C C 编程指南 v 1 0 2001 Page 16 of 101 2 2 代码行 规则 2 2 1 一行代码只做一件事情 如只定义一个变量 或只写一条语句 这样的代 码容易阅读 并且方便于写注释 规则 2 2 2 if for while do 等语句自占一行 执行语句不得紧跟其后 不论执行 语句有多少都要加 这样可以防止书写失误 示例 2 2 a 为风格良好的代码行 示例 2 2 b 为风格不良的代码行 int width 宽度 int height 高度 int depth 深度 int width height depth 宽度高度深度 x a b y c d z e f X a b y c d z e f if width height dosomething if width i 10 i 和 if a b i 10 i 良好的风格 for i 0 i 10 i 不良的风格 for i 0 I 10 i 过多的空格 x a b a b 良好的风格 x aFunction 不要写成 b Function 示例 2 3 代码行内的空格 高质量 C C 编程指南 v 1 0 2001 Page 18 of 101 2 4 对齐 规则 2 4 1 程序的分界符 和 应独占一行并且位于同一列 同时与引用它们 的语句左对齐 规则 2 4 2 之内的代码块在 右边数格处左对齐 示例 2 4 a 为风格良好的对齐 示例 2 4 b 为风格不良的对齐 void Function int x program code void Function int x program code if condition program code else program code if condition program code else program code for initialization condition update program code for initialization condition update program code While condition program code while condition program code 如果出现嵌套的 则使用缩进对齐 如 示例 2 4 a 风格良好的对齐 示例 2 4 b 风格不良的对齐 高质量 C C 编程指南 v 1 0 2001 Page 19 of 101 2 5 长行拆分 规则 2 5 1 代码行最大长度宜控制在 70 至 80 个字符以内 代码行不要过长 否则眼 睛看不过来 也不便于打印 规则 2 5 2 长表达式要在低优先级操作符处拆分成新行 操作符放在新行之首 以便 突出操作符 拆分出的新行要进行适当的缩进 使排版整齐 语句可读 if very longer variable1 very longer variable12 类的成员函数 规则 3 1 8 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等 例如 int minValue int maxValue int SetValue int GetValue 建议 3 1 1 尽量避免名字中出现数字编号 如 Value1 Value2 等 除非逻辑上的确需 要编号 这是为了防止程序员偷懒 不肯为命名动脑筋而导致产生无意义的名字 因为 用数字编号最省事 3 2 简单的 Windows 应用程序命名规则 作者对 匈牙利 命名规则做了合理的简化 下述的命名规则简单易用 比较适合于 Windows 应用软件的开发 规则 3 2 1 类名和函数名用大写字母开头的单词组合而成 高质量 C C 编程指南 v 1 0 2001 Page 24 of 101 例如 class Node 类名 class LeafNode 类名 void Draw void 函数名 void SetValue int value 函数名 规则 3 2 2 变量和参数用小写字母开头的单词组合而成 例如 BOOL flag int drawMode 规则 3 2 3 常量全用大写的字母 用下划线分割单词 例如 const int MAX 100 const int MAX LENGTH 100 规则 3 2 4 静态变量加前缀 s 表示 static 例如 void Init static int s initValue 静态变量 规则 3 2 5 如果不得已需要全局变量 则使全局变量加前缀 g 表示 global 例如 int g howManyPeople 全局变量 int g howMuchMoney 全局变量 规则 3 2 6 类的数据成员加前缀 m 表示 member 这样可以避免数据成员与成员 函数的参数同名 例如 void Object SetValue int width int height m width width m height height 规则 3 2 7 为了防止某一软件库中的一些标识符和其它软件库中的冲突 可以为各种 标识符加上能反映软件性质的前缀 例如三维图形标准 OpenGL 的所有库函数均以 gl 开 高质量 C C 编程指南 v 1 0 2001 Page 25 of 101 头 所有常量 或宏定义 均以 GL 开头 3 3 简单的 Unix 应用程序命名规则 高质量 C C 编程指南 v 1 0 2001 Page 26 of 101 第 4 章 表达式和基本语句 读者可能怀疑 连 if for while goto switch 这样简单的东西也要探讨编程风格 是不是小题大做 我真的发觉很多程序员用隐含错误的方式写表达式和基本语句 我自己也犯过类似的错 误 表达式和语句都属于 C C 的短语结构语法 它们看似简单 但使用时隐患比较多 本 章归纳了正确使用表达式和语句的一些规则与建议 4 1 运算符的优先级 C C 语言的运算符有数十个 运算符的优先级与结合律如表 4 1 所示 注意一元运算 符 的优先级高于对应的二元运算符 优先级 运算符 结合律 从左至右 类型 sizeof 复合表达式过于复杂 规则 4 2 2 不要有多用途的复合表达式 例如 d a b c r 该表达式既求 a 值又求 d 值 应该拆分为两个独立的语句 a b c d a r 规则 4 2 3 不要把程序中的复合表达式与 真正的数学表达式 混淆 例如 if a b c a b c 是数学表达式而不是程序表达式 并不表示 if a b return y 改写为 if condition return x else return y 或者改写成更加简练的 return condition x y 4 4 循环语句的效率 C C 循环语句中 for 语句使用频率最高 while 语句其次 do 语句很少用 本节重 点论述循环体的效率 提高循环体效率的基本办法是降低循环体的复杂性 建议 4 4 1 在多重循环中 如果有可能 应当将最长的循环放在最内层 最短的循环 放在最外层 以减少 CPU 跨切循环层的次数 例如示例 4 4 b 的效率比示例 4 4 a 的 高 for row 0 row 100 row for col 0 col 5 col sum sum a row col for col 0 col 5 col for row 0 row 100 row sum sum a row col 示例 4 4 a 低效率 长循环在最外层 示例 4 4 b 高效率 长循环在最内层 建议 4 4 2 如果循环体内存在逻辑判断 并且循环次数很大 宜将逻辑判断移到循环 高质量 C C 编程指南 v 1 0 2001 Page 30 of 101 体的外面 示例 4 4 c 的程序比示例 4 4 d 多执行了 N 1 次逻辑判断 并且由于前者 老要进行逻辑判断 打断了循环 流水线 作业 使得编译器不能对循环进行优化处理 降低了效率 如果 N 非常大 最好采用示例 4 4 d 的写法 可以提高效率 如果 N 非常 小 两者效率差别并不明显 采用示例 4 4 c 的写法比较好 因为程序更加简洁 for i 0 i N i if condition DoSomething else DoOtherthing if condition for i 0 i N i DoSomething else for i 0 i N i DoOtherthing 表 4 4 c 效率低但程序简洁 表 4 4 d 效率高但程序不简洁 4 5 for 语句的循环控制变量 规则 4 5 1 不可在 for 循环体内修改循环变量 防止 for 循环失去控制 建议 4 5 1 建议 for 语句的循环控制变量的取值采用 半开半闭区间 写法 示例 4 5 a 中的 x 值属于半开半闭区间 0 x N 起点到终点的间隔为 N 循环 次数为 N 示例 4 5 b 中的 x 值属于闭区间 0 x N 1 起点到终点的间隔为 N 1 循环次 数为 N 相比之下 示例 4 5 a 的写法更加直观 尽管两者的功能是相同的 for int x 0 x N x for int x 0 x 0 pbTo pbFrom return pvTo 示例 6 5 复制不重叠的内存块 assert 不是一个仓促拼凑起来的宏 为了不在程序的 Debug 版本和 Release 版本引起差别 assert 不应该产生任何副作用 所以 assert 不是函数 而是宏 程序员可以把 assert 看成一个 在任何系统状态下都可以安全使用的无害测试手段 如果程序在 assert 处终止了 并不是说 含有该 assert 的函数有错误 而是调用者出了差错 assert 可以帮助我们找到发生错误的原 因 很少有比跟踪到程序的断言 却不知道该断言的作用更让人沮丧的事了 你化了很多时 间 不是为了排除错误 而只是为了弄清楚这个错误到底是什么 有的时候 程序员偶尔还 会设计出有错误的断言 所以如果搞不清楚断言检查的是什么 就很难判断错误是出现在程 序中 还是出现在断言中 幸运的是这个问题很好解决 只要加上清晰的注释即可 这本是 显而易见的事情 可是很少有程序员这样做 这好比一个人在森林里 看到树上钉着一块 危险 的大牌子 但危险到底是什么 树要倒 有废井 有野兽 除非告诉人们 危险 是什么 否则这个警告牌难以起到积极有效的作用 难以理解的断言常常被程序员忽略 甚 至被删除 Maguire p8 p30 规则 6 5 1 使用断言捕捉不应该发生的非法情况 不要混淆非法情况与错误情况之间 的区别 后者是必然存在的并且是一定要作出处理的 高质量 C C 编程指南 v 1 0 2001 Page 42 of 101 规则 6 5 2 在函数的入口处 使用断言检查参数的有效性 合法性 建议 6 5 1 在编写函数时 要进行反复的考查 并且自问 我打算做哪些假定 一旦确定了的假定 就要使用断言对假定进行检查 建议 6 5 2 一般教科书都鼓励程序员们进行防错设计 但要记住这种编程风格可能会 隐瞒错误 当进行防错设计时 如果 不可能发生 的事情的确发生了 则要使用断言 进行报警 6 6 引用与指针的比较 引用是 C 中的概念 初学者容易把引用和指针混淆一起 一下程序中 n 是 m 的一个 引用 reference m 是被引用物 referent int m int n 相当于 m 的别名 绰号 对 n 的任何操作就是对 m 的操作 例如有人名叫王小毛 他 的绰号是 三毛 说 三毛 怎么怎么的 其实就是对王小毛说三道四 所以 n 既不是 m 的拷贝 也不是指向 m 的指针 其实 n 就是 m 它自己 引用的一些规则如下 1 引用被创建的同时必须被初始化 指针则可以在任何时候被初始化 2 不能有 NULL 引用 引用必须与合法的存储单元关联 指针则可以是 NULL 3 一旦引用被初始化 就不能改变引用的关系 指针则可以随时改变所指的对象 以下示例程序中 k 被初始化为 i 的引用 语句 k j 并不能将 k 修改成为 j 的引用 只是把 k 的值改变成为 6 由于 k 是 i 的引用 所以 i 的值也变成了 6 int i 5 int j 6 int k j k 和 i 的值都变成了 6 上面的程序看起来象在玩文字游戏 没有体现出引用的价值 引用的主要功能是传递函 数的参数和返回值 C 语言中 函数的参数和返回值的传递方式有三种 值传递 指针传 递和引用传递 以下是 值传递 的示例程序 由于 Func1 函数体内的 x 是外部变量 n 的一份拷贝 改 变 x 的值不会影响 n 所以 n 的值仍然是 0 void Func1 int x x x 10 int n 0 Func1 n cout n n endl n 0 高质量 C C 编程指南 v 1 0 2001 Page 43 of 101 以下是 指针传递 的示例程序 由于 Func2 函数体内的 x 是指向外部变量 n 的指针 改变该指针的内容将导致 n 的值改变 所以 n 的值成为 10 void Func2 int x x x 10 int n 0 Func2 cout n n endl n 10 以下是 引用传递 的示例程序 由于 Func3 函数体内的 x 是外部变量 n 的引用 x 和 n 是同一个东西 改变 x 等于改变 n 所以 n 的值成为 10 void Func3 int int n 0 Func3 n cout n n endl n 10 对比上述三个示例程序 会发现 引用传递 的性质象 指针传递 而书写方式象 值传递 实际上 引用 可以做的任何事情 指针 也都能够做 为什么还要 引用 这东西 答案是 用适当的工具做恰如其分的工作 指针能够毫无约束地操作内存中的如何东西 尽管指针功能强大 但是非常危险 就象 一把刀 它可以用来砍树 裁纸 修指甲 理发等等 谁敢这样用 如果的确只需要借用一下某个对象的 别名 那么就用 引用 而不要用 指针 以免发生意外 比如说 某人需要一份证明 本来在文件上盖上公章的印子就行了 如果把 取公章的钥匙交给他 那么他就获得了不该有的权利 高质量 C C 编程指南 v 1 0 2001 Page 44 of 101 第 7 章 内存管理 欢迎进入内存这片雷区 伟大的 Bill Gates 曾经失言 640K ought to be enough for everybody Bill Gates 1981 程序员们经常编写内存管理程序 往往提心吊胆 如果不想触雷 唯一的解决办法就是 发现所有潜伏的地雷并且排除它们 躲是躲不了的 本章的内容比一般教科书的要深入得多 读者需细心阅读 做到真正地通晓内存管理 7 1 内存分配方式 内存分配方式有三种 1 从静态存储区域分配 内存在程序编译的时候就已经分配好 这块内存在程序的整个 运行期间都存在 例如全局变量 static 变量 2 在栈上创建 在执行函数时 函数内局部变量的存储单元都可以在栈上创建 函数执 行结束时这些存储单元自动被释放 栈内存分配运算内置于处理器的指令集中 效率 很高 但是分配的内存容量有限 3 从堆上分配 亦称动态内存分配 程序在运行的时候用 malloc 或 new 申请任意多少 的内存 程序员自己负责在何时用 free 或 delete 释放内存 动态内存的生存期由我们 决定 使用非常灵活 但问题也最多 7 2 常见的内存错误及其对策 发生内存错误是件非常麻烦的事情 编译器不能自动发现这些错误 通常是在程序运行 时才能捕捉到 而这些错误大多没有明显的症状 时隐时现 增加了改错的难度 有时用户 怒气冲冲地把你找来 程序却没有发生任何问题 你一走 错误又发作了 常见的内存错误及其对策如下 内存分配未成功 却使用了它 编程新手常犯这种错误 因为他们没有意识到内存分配会不成功 常用解决办法是 在 使用内存之前检查指针是否为 NULL 如果指针 p 是函数的参数 那么在函数的入口处用 assert p NULL 进行检查 如果是用 malloc 或 new 来申请内存 应该用 if p NULL 或 if p NULL 进行防错处理 内存分配虽然成功 但是尚未初始化就引用它 犯这种错误主要有两个起因 一是没有初始化的观念 二是误以为内存的缺省初值全为 零 导致引用初值错误 例如数组 内存的缺省初值究竟是什么并没有统一的标准 尽管有些时候为零值 我们宁可信其无 高质量 C C 编程指南 v 1 0 2001 Page 45 of 101 不可信其有 所以无论用何种方式创建数组 都别忘了赋初值 即便是赋零值也不可省略 不要嫌麻烦 内存分配成功并且已经初始化 但操作越过了内存的边界 例如在使用数组时经常发生下标 多 1 或者 少 1 的操作 特别是在 for 循环语句中 循环次数很容易搞错 导致数组操作越界 忘记了释放内存 造成内存泄露 含有这种错误的函数每被调用一次就丢失一块内存 刚开始时系统的内存充足 你看不 到错误 终有一次程序突然死掉 系统出现提示 内存耗尽 动态内存的申请与释放必须配对 程序中 malloc 与 free 的使用次数一定要相同 否则肯 定有错误 new delete 同理 释放了内存却继续使用它 有三种情况 1 程序中的对象调用关系过于复杂 实在难以搞清楚某个对象究竟是否已经释放了内存 此时应该重新设计数据结构 从根本上解决对象管理的混乱局面 2 函数的 return 语句写错了 注意不要返回指向 栈内存 的 指针 或者 引用 因 为该内存在函数体结束时被自动销毁 3 使用 free 或 delete 释放了内存后 没有将指针设置为 NULL 导致产生 野指针 规则 7 2 1 用 malloc 或 new 申请内存之后 应该立即检查指针值是否为 NULL 防止 使用指针值为 NULL 的内存 规则 7 2 2 不要忘记为数组和动态内存赋初值 防止将未被初始化的内存作为右值使 用 规则 7 2 3 避免数组或指针的下标越界 特别要当心发生 多 1 或者 少 1 操作 规则 7 2 4 动态内存的申请与释放必须配对 防止内存泄漏 规则 7 2 5 用 free 或 delete 释放了内存之后 立即将指针设置为 NULL 防止产生 野指针 7 3 指针与数组的对比 C C 程序中 指针和数组在不少地方可以相互替换着用 让人产生一种错觉 以为两 者是等价的 数组要么在静态存储区被创建 如全局数组 要么在栈上被创建 数组名对应着 而 不是指向 一块内存 其地址与容量在生命期内保持不变 只有数组的内容可以改变 指针可以随时指向任意类型的内存块 它的特征是 可变 所以我们常用指针来操作 动态内存 指针远比数组灵活 但也更危险 高质量 C C 编程指南 v 1 0 2001 Page 46 of 101 下面以字符串为例比较指针与数组的特性 7 3 1 修改内容 示例 7 3 1 中 字符数组 a 的容量是 6 个字符 其内容为 hello 0 a 的内容可以改变 如 a 0 X 指针 p 指向常量字符串 world 位于静态存储区 内容为 world 0 常量字符 串的内容是不可以被修改的 从语法上看 编译器并不觉得语句 p 0 X 有什么不妥 但是 该语句企图修改常量字符串的内容而导致运行错误 char a hello a 0 X cout a endl char p world 注意 p 指向常量字符串 p 0 X 编译器不能发现该错误 cout p endl 示例 7 3 1 修改数组和指针的内容 7 3 2 内容复制与比较 不能对数组名进行直接复制与比较 示例 7 3 2 中 若想把数组 a 的内容复制给数组 b 不能用语句 b a 否则将产生编译错误 应该用标准库函数 strcpy 进行复制 同理 比较 b 和 a 的内容是否相同 不能用 if b a 来判断 应该用标准库函数 strcmp 进行比较 语句 p a 并不能把 a 的内容复制指针 p 而是把 a 的地址赋给了 p 要想复制 a 的内 容 可以先用库函数 malloc 为 p 申请一块容量为 strlen a 1 个字符的内存 再用 strcpy 进行字符串复制 同理 语句 if p a 比较的不是内容而是地址 应该用库函数 strcmp 来 比较 数组 char a hello char b 10 strcpy b a 不能用 b a if strcmp b a 0 不能用 if b a 指针 int len strlen a char p char malloc sizeof char len 1 strcpy p a 不要用 p a if strcmp p a 0 不要用 if p a 示例 7 3 2 数组和指针的内容复制与比较 高质量 C C 编程指南 v 1 0 2001 Page 47 of 101 7 3 3 计算内存容量 用运算符 sizeof 可以计算出数组的容量 字节数 示例 7 3 3 a 中 sizeof a 的 值是 12 注意别忘了 0 指针 p 指向 a 但是 sizeof p 的值却是 4 这是因为 sizeof p 得到的是一个指针变量的字节数 相当于 sizeof char 而不是 p 所指的内存容量 C C 语言没有办法知道指针所指的内存容量 除非在申请内存时记住它 注意当数组作为函数的参数进行传递时 该数组自动退化为同类型的指针 示例 7 3 3 b 中 不论数组 a 的容量是多少 sizeof a 始终等于 sizeof char char a hello world char p a cout sizeof a endl 12 字节 cout sizeof p endl 4 字节 示例 7 3 3 a 计算数组和指针的内存容量 void Func char a 100 cout sizeof a endl 4 字节而不是 100 字节 示例 7 3 3 b 数组退化为指针 7 4 指针参数是如何传递内存的 如果函数的参数是一个指针 不要指望用该指针去申请动态内存 示例 7 4 1 中 Test 函数的语句 GetMemory str 200 并没有使 str 获得期望的内存 str 依旧是 NULL 为什么 void GetMemory char p int num p char malloc sizeof char num void Test void char str NULL GetMemory str 100 str 仍然为 NULL strcpy str hello 运行错误 示例 7 4 1 试图用指针参数申请动态内存 高质量 C C 编程指南 v 1 0 2001 Page 48 of 101 毛病出在函数 GetMemory 中 编译器总是要为函数的每个参数制作临时副本 指针参数 p 的副本是 p 编译器使 p p 如果函数体内的程序修改了 p 的内容 就导致参数 p 的 内容作相应的修改 这就是指针可以用作输出参数的原因 在本例中 p 申请了新的内存 只是把 p 所指的内存地址改变了 但是 p 丝毫未变 所以函数 GetMemory 并不能输出任何东 西 事实上 每执行一次 GetMemory 就会泄露一块内存 因为没有用 free 释放内存 如果非得要用指针参数去申请内存 那么应该改用 指向指针的指针 见示例 7 4 2 void GetMemory2 char p int num p char malloc sizeof char num void Test2 void char str NULL GetMemory2 注意参数是 cout str endl free str 示例 7 4 2 用指向指针的指针申请动态内存 由于 指向指针的指针 这个概念不容易理解 我们可以用函数返回值来传递动态内存 这种方法更加简单 见示例 7 4 3 char GetMemory3 int num char p char malloc sizeof char num return p void Test3 void char str NULL str GetMemory3 100 strcpy str hello cout str endl free str 示例 7 4 3 用函数返回值来传递动态内存 高质量 C C 编程指南 v 1 0 2001 Page 49 of 101 用函数返回值来传递动态内存这种方法虽然好用 但是常常有人把 return 语句用错了 这里强调不要用 return 语句返回指向 栈内存 的指针 因为该内存在函数结束时自动消亡 见示例 7 4 4 char GetString void char p hello world return p 编译器将提出警告 void Test4 void char str NULL str GetString str 的内容是垃圾 cout str endl 示例 7 4 4 return 语句返回指向 栈内存 的指针 用调试器逐步跟踪 Test4 发现执行 str GetString 语句后 str 不再是 NULL 指针 但 是 str 的内容不是 hello world 而是垃圾 如果把示例 7 4 4 改写成示例 7 4 5 会怎么样 char GetString2 void char p hello world return p void Test5 void char str NULL str GetString2 cout str endl 示例 7 4 5 return 语句返回常量字符串 函数 Test5 运行虽然不会出错 但是函数 GetString2 的设计概念却是错误的 因为 GetString2 内的 hello world 是常量字符串 位于静态存储区 它在程序生命期内恒定 不变 无论什么时候调用 GetString2 它返回的始终是同一个 只读 的内存块 高质量 C C 编程指南 v 1 0 2001 Page 50 of 101 7 5 free 和 delete 把指针怎么啦 别看 free 和 delete 的名字恶狠狠的 尤其是 delete 它们只是把指针所指的内存给释放 掉 但并没有把指针本身干掉 用调试器跟踪示例 7 5 发现指针 p 被 free 以后其地址仍然不变 非 NULL 只是该地 址对应的内存是垃圾 p 成了 野指针 如果此时不把 p 设置为 NULL 会让人误以为 p 是 个合法的指针 如果程序比较长 我们有时记不住 p 所指的内存是否已经被释放 在继续使用 p 之前 通常会用语句 if p NULL 进行防错处理 很遗憾 此时 if 语句起不到防错作用 因为即 便 p 不是 NULL 指针 它也不指向合法的内存块 char p char malloc 100 strcpy p hello free p p 所指的内存被释放 但是 p 所指的地址仍然不变 if p NULL 没有起到防错作用 strcpy p world 出错 示例 7 5 p 成为野指针 7 6 动态内存会被自动释放吗 函数体内的局部变量在函数结束时自动消亡 很多人误以为示例 7 6 是正确的 理由是 p 是局部的指针变量 它消亡的时候会让它所指的动态内存一起完蛋 这是错觉 void Func void char p char malloc 100 动态内存会自动释放吗 示例 7 6 试图让动态内存自动释放 我们发现指针有一些 似是而非 的特征 1 指针消亡了 并不表示它所指的内存会被自动释放 2 内存被释放了 并不表示指针会消亡或者成了 NULL 指针 这表明释放内存并不是一件可以草率对待的事 也许有人不服气 一定要找出可以草率 行事的理由 如果程序终止了运行 一切指针都会消亡 动态内存会被操作系统回收 既然如此 在 高质量 C C 编程指南 v 1 0 2001 Page 51 of 101 程序临终前
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 办公文档 > 解决方案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!