【C++高级编程】(二)设计专业的C++程序
本章内容:
- 程序设计的定义
- 程序设计的重要性
- C++程序设计的特点
- 高效C++程序设计的两个基本主题:抽象以及重用
- 不同类型的重用代码
- 代码重用的优缺点
- 重用代码的常用策略及指导原则
- 开放源代码库
- C++标准库
- C++程序设计的特定组件
(主要讲述如何利用专业的C++方法进行C++设计,磨刀不误砍柴工,在项目开始时清晰的规划设计实际上可以大幅缩短项目周期)
2.1 程序设计概述
- 程序设计(软件设计):为满足程序的功能及性能要求,而实现的结构规范
- 设计就是规划如何编写程序
- 通常以设计文档的形式写出设计,大多数设计文档的常见布局类似,包括两个主要的部分:
-
- 将总的程序分为子系统,包括子系统间的界面及依赖关系、子系统间的数据流、每个子系统的输入输出及通用线程模型;
- 每个子系统的详情,包括类的细分、类的层次结构、数据结构、算法、具体的线程模型及错误处理的细节
4. 设计文档通常包括图及表格,以显示子系统交互关系及类层次结构
-
- 设计的关键是在写程序前进行思考,而不要一头扎进应用程序
- 如在写代码时遇到问题,修改设计是不可避免的
2.2 程序设计的重要性
- 很重要
- 程序的子系统并不是孤立存在的——子系统彼此有联系;大部分的设计工作都用来判断和定义这些关系
2.3 C++设计的特点
- 具有庞大的功能集
- 是C语言的超集,此外还有类和对象、运算符重载、异常、模板及其他功能
- 是一门面向对象语言
- 有许多设计通用的、可重复使用代码的工具
- 提供了有用的标准库
- 提供了许多设计模式或解决问题的通用方法
2.4 C++设计的两个原则
C++设计的两个基本原则:抽象、重用,这两个原则几乎贯穿高效C++程序设计所有领域
2.4.1 抽象
1. 抽象的作用:可以直接使用代码而不必了解底层实现
- 如:直接调用在 <cmath> 头文件中声明的 sqrt() 函数,而不需要知道该函数求平方根的实际所用算法
- 如:直接使用 cout 插入运算符 (<<) 已经写好的公有接口进行输出,而不用了解 cout 是怎么将文本输出到屏幕的; cout 的底层实现可以随意改动,只有公开的行为及接口保持不变即可
2. 在设计中使用抽象
- 例如,一个国际象棋的程序:
//使用一个指向 ChessPiece 对象的二维指针数组,实现象棋的棋盘 ChessPiece* chessBoard[8][8]; ChessBoard[0][0] = new Rook();
- 但上面这种方法并没有用到抽象概念,更好的方法是用类建立棋盘模型(这样就可以公开接口,并隐藏底层的实现细节):
//建立 ChessBoard 类 class ChessBoard { public: // 这里放方法 void setPieceAt(ChessPiece* piece, int x, int y); ChessPiece& getPieceAt(int x, int y); //getPieceAt()函数返回了一个引用,建议底层实现使用指向对象的指针或智能指针,而不要将对象直接存在集合中,以避免一些奇奇怪怪的bug bool isEmpty(int x, int y); protected: // 这里放属性 }
注意:接口并不决定底层实现方式,改变实现并不需要改变接口(实现还可以根据需求提供更多的功能)
2.4.2 重用
- 不必重新造轮子,只需要修改这些轮子并添加必要的功能
1. 重用代码
- 使用已有的代码
- 如:使用 cout 输出的时候就已经在重用代码了。我们没有编写真正将数据输出到屏幕上的代码,而是使用了已有的 ostream 实现输出
2. 编写可供重用的代码
- 使用类、算法、数据结构...以供重用
- 在C++中,模板是一种编写多用途代码的语言技术:
//在此编写了一个可以用于任何二维棋盘游戏类型的泛型GameBoard: template <typename PieceType> class GameBoard { public: // 这里放方法 void setPieceAt(PieceType* piece, int x, int y); PieceType& getPieceAt(int x, int y); bool isEmpty(int x, int y); protected: // 这里放属性 }
注意:只需要修改类的声明,在接口中将棋子作为模板参数而不是固定类型
2.5 重用代码
- 好的 C++ 程序员不会从零开始启动一个项目,而是利用各种资源提供的代码(如:标准模板库、开放源代码库、公司专用代码、以前的项目),大胆地重用代码
2.5.1 关于术语的说明
- 有三种可以重用的代码:
- 过去编写的代码
- 同事编写的代码
- 所在公司以外的第三方编写的代码
- 可以通过以下几种方式来构建:
- 独立的函数 / 类
- 库
- 框架
- 程序使用库,但会适应框架。库提供了特定功能,而框架是程序设计和结构构建的基础
- 应用程序编程接口(API)是库或代码为特定目的提供的接口(并非库本身,库指的是实现,而 API 指的是库的公开接口)
2.5.2 决定是否重用代码
- 重用代码的优点:
- 简化设计(被重用的应用程序组件不需要进行设计)
- 不需要测试(一般认为库代码是没有bug的)
- 库可以检测用户的错误输入(给出提示)
- 安全性更高(某领域大佬编写的代码更安全)
- 库代码会持续改进更新
- 重用代码的缺点:
- 需要花时间理解接口及其正确用法
- 库代码提供的功能与需求功能未必完全吻合
- 一般来说,库代码的性能可能不会太好,不适应于特定场合,或根本没有对应文档记录
- 库版本的升级可能会引发新的bug
- 当产品过于依赖第三方库,而库的开发人员又停止了对库的支持
- 库还涉及许可证发放问题
- 还需要考虑跨平台的可移植性(跨平台的应用程序要使用可移植的库)
2.5.3 重用代码的策略
- 理解功能及限制因素
- 可以从文档、公开的接口或 API 开始理解代码的运行方式,如果库没有将接口及实现明确分离,可能还要研究源代码
- 首先应该理解基本功能:
- 如果是库,该库可以提供哪些行?
- 如果是框架,代码如何适应这个框架?
- 应该编写哪些类的子类?需要自己编写哪些代码
- 对于任何库或框架:
- 库或框架需要什么样的初始化调用?需要什么样的清理?
- 库或框架依赖于其他哪些库?
- 对于多线程程序而言,代码是否安全?
- 对于任何库调用:
- 如果某个调用返回一个内存指针,谁负责内存的释放(调用者还是库)?如果库对此负责,什么时候释放内存?是否可以使用智能指针管理由库分配的内存?
- 库调用检查哪些错误情况?此时做出了什么假定?如何处理错误?如何提醒客户端程序发生了错误?应避免使用弹出消息对话框、将消息传递到 stderr / cerr 或者 stdout / cout 以及终止程序的库
- 某个调用的全部返回值(按值或按引用)有哪些?所有可能抛出的异常有哪些?
- 关于框架:
- 如果从某个类继承,应该调用哪个构造函数?应该重写哪些虚方法?
- 程序员负责释放哪些内存?框架负责释放哪些内存?
- 理解性能
- 对代码提供性能保障
- 大O表示法
- 大O表示相对性能而不是绝对性能,将算法的运行时间表示为输入量的函数,就是常说的算法复杂度
算法复杂度 大O表示法 说明 举例 常数 O(1) 运行时间与输入量无关 访问数组中的某个元素 对数 O(logn) 运行时间是输入量对数的函数 使用二分法查找有序列表中的元素 线性 O(n) 运行时间与输入量成正比 在未排序的列表中查找元素 线性对数 O(n logn) 运行时间是输入量对数函数的线性倍函数 归并排序 二次方 O(n2) 运行时间是输入量的平方函数 较慢的排序算法(如选择排序法) - 用输入量的函数(而非绝对数字)表示性能的好处:
- 独立于平台(在不同计算机平台上加载相同负荷也可以显而易见的比较算法性能)
- 可以用一种方法表示算法所有可能的输入(如果用秒来表示算法所需特定时间,那么该时间只针对于特定输入,对于另外的输入则毫无意义)
- 大O表示相对性能而不是绝对性能,将算法的运行时间表示为输入量的函数,就是常说的算法复杂度
- 理解性能的几点提示
- 使用大O表示法表示性能时,需要考虑以下问题:
- 当数据量加倍时,算法所需的时间也会加倍,如果算法访问了不必要的磁盘,虽然不会影响大O表示法,但性能变得一塌糊涂
- 因此也很难比较出两个相同大O运行时间的算法孰优孰劣
- 而对于小规模的输入量,O(n2)算法的实际执行性能可能要优于O(logn)
- 除了考虑算法的大O特性外,还需要考虑算法性能的其他方面:
- 重点考虑某段特定库代码的使用频率(90/10法则:90%运行时间花费在10%的代码上)
- 不要信任文档,一定要运行性能测试来判断库代码是否提供了可接受的性能
- 使用大O表示法表示性能时,需要考虑以下问题:
- 理解平台限制
- 平台不仅包括不同的操作系统,还包括同一操作系统的不同版本(如:能在8.0版本上运行的库未必能在9.0上运行)
- 理解许可证及支持
- 有时使用第三方供应商提供的库必须支付许可证费用,可能还有其他许可限制
- 寻求帮助
- 首先参考库自带的文档,相关书籍,web(但不一定对)
- 原型
- 首次使用某个新库或框架时,可以编写一个快速原型测试库,以最快熟悉库功能及限制
2.5.4 开放源代码库
- 开放源代码库是一种日益流行的可重用代码类型
- 开放源代码(open-source)通常意味着任何人都可以查看源代码
2.5.5 C++标准库
- 标准库并不是整体式库的,它分为几个完全不同的组件
- C标准库:由于C++是C的超集,因此一些C库的工具在C++中依然有效,包括:
- 数学函数:abs()、sqrt()、pow()
- 随机生成数函数:srand()、rand()
- 错误处理辅助程序:assert()、errno
- 将字符数组作为字符串操作:strlen()、strcpy()
- C风格的 I/O 函数:printf()、scanf()
- 此外还需要注意,C头文件名称与C++不同
- 判断是否使用STL
- 设计标准库时优先考虑的是:功能、性能、正交性(orthogonality)
分类:
技术栈 / C++学习笔记
标签:
C++学习笔记
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本