目录
前言
我们已经入门了C++的基础,那么我们就应该知道C++是如何设计程序的,这样才可以再后面深入学习的时候,给把知识运用到自己所思考的思路中
一,引言
在编写程序的时候,先不要具体的写代码,而是首先设计程序,你需要用到什么样的数据结构,要编写什么类等等,磨刀不误砍柴工,在项目初期的时候,我们花一些时间去做设计,会大大节省项目的生命周期
读完本次文章你该学习到的知识:
1,编程设计的定于
2,编程设计的重要性
3,C++所特有的一些设计问题
4,实现有效C++设计的两种基本原则:抽象与重用
5,C++程序设计的具体组件
二,什么是编程设计
程序设计和软件设计就是程序体系结构的规范,要实现这个规范来满足程序的功能和性能的需求,所谓设计就是你打算如何编写程序,一般需要以一份设计文档的形式完成,这个文档主要包括两个部分:
1,将程序分为多个子系统,包括子系统之间的接口和依赖关系,子系统之间的数据流,在各子系统之间来回的输入和输出,以及总的线程模型
2,各个子系统里面的具体细节,包括进一步细分的类,类层次体系,数据结构,算法,特定的模型和错误处理细节等
设计里面通常包含一些图表,用来显示子系统交互和类层次体系
我们在写程序的时候,我们总是要保留一些余地来为后续做准备,因为后续我们也不知道会出现什么bug,但是要预测出来留有余地,方便后续我们的更改代码
三,编程设计的重要性
由于专业的术语我们很难懂,所以我们以盖房子为例子,有一次你让一个工程师为你盖房子,你想看看盖房子的蓝图,好让自己有个预期,但是工程师说他盖过很多的房子,自己心里有数,你看这个工程师自信满满的样子,你也就交给他了,过来几天你发现房子外面出现了一些管线,一般来说墙内走线才正常,你去质疑工程师,工程师说他忘记在墙上留下管线的空间了,但是刚刚相出这个的净墙的技术,即使这个做错了,但是还是可以工作的,但是尽管已经对他的方法存在质疑,但是你还是选择了相信,到后面厨房居然没有水池,建筑工人发现在建厨房2/3的时候,没有给水池留空间,但是我们又不想返工,就在隔壁案例一个单独的水池间,这样也是可以的
当你去编写程序的时候是不是也是这样去解决你程序里面的问题呢,没有设计好,通过别的方法解决,即使怪怪的,但是还是有那样的功能,但是我们项目可不能这样马虎,因为我们是多人项目,不是个人项目
比如多个线程共享的队列的数据结构,你忘记了在其中设置加锁机制,你就用来别的方法,每个人在自己的程序进行加锁机制,这样也是可以,但是当你招募员工的时候,别的员工以为你的线程那里有加锁机制,这样就会出现很多很多的bug,所以整体图尤为的重要,这个就相当是程序编写的大脑,如果我们陷入代码编写,就无法得到最优的设计
比如我们编写一个象棋的程序
设计的:
没设计的:
虽然两个看似都差不多,但是设计的是找到了每个棋子的规律的,比如车,马这种是有特殊走法的,但是一般都是移动,所以我们只需要把相同的部分统一起来,然后提升功能即可,这样就可以减少代码行数
四,C++设计有什么不同之处
1,C++是拥有丰富的特性集的,几乎就是C语言的完备体,另外还要加上类和对象,操作符的重载,异常,模板和其他的一些特性,由于这个语言的规模如此之大,就使得设计成为一项很棘手的任务
2,C++是一个面向对象的语言,则于之前的C就有很大的差别,设计包含类,类接口和对象交互
3,C++还有一个独特的方面就是提供了大量工具来设计重用的代码
4,C++提供了一个有用的标准库,这个库里面有很多的工具,IO流,字符串,以及常用的数据结构和算法
五,C++设计的两个原则
原则有两个:抽象和重用
抽象
要理解抽象(abstraction)原则,最简单的方法是通过一个实际对照来说明。电视作为一个简单的技术,在大多数家庭中都已普及。你对电视的功能可能已经很熟悉了,可以打开和关掉电视,可以转换频道,可以调节音量,还可以增加一些外部部件,如喇叭、VCR和DVD机等。不过,你能解释在这个黑盒子里到底是怎么工作的吗?也就是说,你知道它是怎么通过大气或者通过线缆接收信号的,又是如何完成信号转换,并在屏幕上显示出来的?我们当然无法将电视如何工作解释清楚,不过对于怎么使用它却可能很精通。这是因为电视很清楚地将其内部实现与外部接口相分离。我们只是通过其接口与电视交互:包括电源开关、频道转换按钮和音量控制按钮等。我们不知道电视的工作原理,对此也不关心,我们不想知道它是怎样使用阴极管或另外某种技术在屏幕上生成图像的。这些并不重要,因为这一切不会对接口产生影响
总的来说不知道里面是怎么构造的,但是知道这些有什么功能的接口,我们利用这些接口来实现一些我们所想要的功能
抽象带来的好处
在软件领域中,抽象原则也是类似的。可以使用代码,而无需了解其底层实现。先举一个简单的例子,你的程序可能调用了一个 sqrt()函数(此函数在头文件<cmath>中声明),而无需知道这个函数具体使用了什么算法来计算平方根。实际上,对于不同版本的库,平方根计算的底层实现可以有所不同,只要接口保持不变,函数调用就仍能正常工作。抽象的原则还可以延伸至类。在第】章中曾介绍过,可以使用类 ostream的cout对象将数据作为流传送至标准输出,如
cout<<"I love you"<<endl;在以上代码行中,使用了cout插入(insertion)操作符(带一个字符数组)的公开接口。不过,cout是如何管理从而在用户界面上显示这行文本的呢?对此无需了解。只要知道公共接口就可以了。cout 的底层实现完全可以修改,只要保证所提供的行为和接口保持不变即可
在设计中运用抽象
在设计中纳入抽象
应当适当地设计函数和类,从而使你和其他程序员可以直接使用这些函数和类,而无需知道(或依赖于)函数和类的底层实现。作为一个将实现暴露在外的设计,与将实现隐藏在接口内的设计相比,要了解它们之间有什么区别,还是来考虑前面的象棋程序。你可能希望用一个二维的ChessPiece 对象指针数组来实现棋盘。可以如下声明和使用这个棋盘:
不过,这种方法没有用到抽象的概念。每个程序员要想使用这个棋盘,都会知道它实现为一个二维数组。要想对这个实现做某种修改,例如修改为一个向量数组,将会很困难,这是因为需要把整个程序中用到棋盘的地方都加以修改。在此接口与实现就未做分离。
还有一个更好的办法,就是将棋盘建模为一个类。这样就可以提供一个接口,而该接口隐藏了底层的实现细节。以下是一个ChessBoard类的例子:
注意,这个接口没有明确指定任何底层实现。ChessBoard可以是一个二维数组,不过接口对此并未做严格要求。修改实现时无需修改接口。另外,实现还可以提供额外的功能,如越界检查,对于这一点前面的第一种方法是无能为力的,因为我们只需加一个接口即可
通过这个例子,希望你能了解到抽象是C++编程中的一项重要技术。
重用
C++的第二个基本设计原则是重用(reuse)
同样地,通过做一个实际的对照分析将有助于理解这个概念。假设你放弃编程,成为了一个面包师。在第一天的工作中,大师傅让你烤些饼干。为了达到他的要求,你在烹饪书里找到了巧克力饼干的食谱,接下来把配料混合在一起,在饼干模子上做出饼干然后把饼干模子放到烤箱里烘烤。看到烤出来的饼干,大师傅可能会很满意。
下面我们要指出一些显而易见的情况,专门把它们指出来似乎有些小题大做,因为在我们看来,这实在再显然不过了。在此你没有自己造炉子来烤饼干,也没有自己搅奶油、自己磨面、自己做巧克力片。你肯定会说,“那是当然的了”。如果你是一个真正的厨师,这一点无庸置疑,但是如果你是一个程序员,要编写一个烤面包模拟游戏,又会怎样呢?倘若如此,你可能只好考虑自行编写程序的每个组件,从巧克力片到炉子都要亲力而为。不过,你可以看看周围有没有可以重用的代码来节省时间。也许你的某个同事写过一个烹饪模拟游戏,其中有一些很不错的炉子代码。尽管这些代码不完全满足要求,没有完成你需要的每一件工作,不过可以对它进行修改,并增加必要的功能。还有一样东西是直接拿来用的,你在这里参考了一个食谱,而不是自己研究出来一个食谱。同样的,这当然也无需多说。不过,在C++编程中可并不像这么显而易见。尽管C++中有许多标准方法可以用来解决一些反复出现的问题,但是许多程序员还是很固执,每次设计时都会把这些策略重新再设计一次,这是十分浪费时间的编写可重用代码
重用设计原则不仅适用于你编写的代码,还适用于你使用的代码。应当适当地编写程序,从而可以重用类、算法和数据结构。你和你的同事不仅能够在当前项目中利用这些组件,还能在将来的项目中使用这些可重用的代码。总的说来,应当避免设计过于特定的代码(仅适用于当前情况)。C++中提供了一个编写通用代码的技术,这就是模板
重用思想
如前面的烤饼干例子所示,如果每做一道菜(每烤一种饼干)都自造一个食谱,这是很可笑的。不过,程序员通常会在设计中犯这种错误。他们可能没有利用有的“食谱”(或模式)来设计程序,而是每设计一个程序都重新实现这些技术。在各种不同的C++应用中已经出现了许多设计模式(design pattern)。作为一个C++程序员,你应当熟悉这样一些模式,并在程序设计中有效地结合这些模式
六,设计的实例
以象棋为例子来讲述设计
需求
着手设计之前,有一点很重要,这就是要准确地把握程序的功能需求和性能需求。理想情况下,这些需求应当以需求规范的形式明确地给出。棋盘需求包含以下各类规范,不过实际的需求可能会更多,更为详细:
程序要支持象棋的标准规则(下法)。
程序要支持两个玩家(真正的人)。这个程序不支持人工能机器玩家。
程序要支持一个基于文本的界面:
程序要以ASCII文本方式显示棋盘和棋子玩家要输入数字(表示棋盘上的位置)来表示如何走棋
基于上述需求,可以确保你对程序做了正确的设计,从而得到用户所期望的表现。如果用户只需要一个基于文本的界面,你可能不会希望花时间为这个象棋游戏设计和编写一个图形用户界面。反过来。如果用户更喜欢图形用户界面,你也必须有所了解,这一点很重要,这样就不至于将程序设计为仅有基于文本的界面,而把基于图形的可能性完全排除在外
设计子系统
将程序划分为子系统
第一步是把程序划分为通用功能子系统,并明确子系统间的接口和交互。此时,不必心数据结构和算法(甚至类)的特定细节,只需对程序的各个部分及其交互有一个总的认识就可以了。可以把子系统列在一个表中,在这个表中表示出子系统的高层行为或功能、子系统向其他子系统提供的接口,以及其他子系统的哪些接口
接下来就是要绘制信息流了
选择线程模型
在这一步中,要选择程序中使用多少个线程,并明确线程的交互。还要为共享数据指定加锁机制如果你对多线程程序不熟悉,或者你的平台不支持多线程,就应该建立单线程程序。不过,如果程序有多项不同的任务,每个任务都应当并行进行,那么多线程则是一个不错的选择。例如,图形用户界面应用通常就有一个线程专门完成主要应用工作,而另一个线程用于等待用户按键或选择菜单项
为每个子系统指定类、数据结构、算法和模式
这一步要考虑更深层次的细节,并指定各个子系统的一些特定的内容,其中包括为每个子系统编写的特定类
正式设计文档的这一部分通常还会表示每个类的具体接口,不过,这个例子没有做到那么细。设计类、选择数据结构、算法和模式,这些工作可能很复杂。要时刻谨记本章前面讨论的抽象和重用这两个原则。对于抽象、关键是要分别考虑接口和实现。首先,要从用户角度指定接口,确定希望组件做些什么。再通过选择数据结构和算法确定组件如何达到目的。对于重用,要先熟悉标准的数据结构算法和模式。另外,还要确保自己了解C++中的标准库代码,以及可供使用的任何专用代码
为每个子系统指定错误处理
在这个设计步骤中,要明确每个子系统中的错误处理。错误处理应当包括系统错误(如内存分配失败)和用户错误(如非法输人)的处理。要指定各个子系统是否使用异常,这个是为异常所用的
错误控制的一般原则是一切都要处理。要仔细周全地考虑到所有可能的错误条件。如果掉了一种可能性,最后它就会在程序中作为一个bug出现在你面前!不要让问题变成“未预料到的”错误才暴出来。要考虑到所有可能性:内存分配失败,非法的用户输人,磁盘故障,网络故等等。不过,从针对象棋游戏的这个表可以看到,对用户错误的处理应当与内部错误的处理有所区别。例如,用户输入了一个非法的走法时,不应导致象棋程序终止
六,补充
向量数组与二维数组
1. 向量数组和二维数组的相似性
结构上:如果向量数组中的每个向量长度相同,那么它完全可以被看作是一个二维数组
vec_array = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]
这既是一个向量数组(包含3个向量,每个向量有3个元素),也是一个3×3的二维数组。
操作上:很多操作在两者之间是通用的,比如矩阵加法、转置等。
2. 向量数组和二维数组的区别
尽管它们在结构上可能相同,但在概念和语义上,它们的侧重点不同:
(1) 概念上的区别
向量数组:
更强调向量的性质。每个子数组(向量)可以被视为一个独立的数学对象,有自己的几何或代数意义。
例如,在机器学习中,每个向量可能表示一个数据样本的特征;在物理中,每个向量可能表示一个力或速度。
向量数组中的向量长度可以不同(虽然在实际应用中通常长度相同)。
二维数组:
更强调矩阵的性质。它是一个规则的矩形结构,通常用于表示矩阵运算、表格数据等。
二维数组的行和列数量是固定的,且每行的长度必须相同。
它更侧重于整体的矩阵操作,如矩阵乘法、行列式计算等。
(2) 语义上的区别
向量数组:
语义上更倾向于“一组向量的集合”,每个向量可以独立操作。
例如,你可以对每个向量分别进行归一化、计算模长等操作。
二维数组:
语义上更倾向于“一个整体的矩阵”,操作通常是针对整个矩阵的。
例如,矩阵乘法、矩阵转置等操作。
总结
我们今天学习了程序设计,当然即使里面有些不明白也没有关系,这些后面我们都是需要学习的,我们只是做了解和学习大概,为C++学习有一个大概的了解,也为自己程序设计多一些思路
编程设计就是相当于写出依赖关系和各个接口,然后画出线程图然后细节化子系统里的数据结构和算法,然后设置一些常见的异常,防止后续写程序出现bug
两个重要的原则:抽象与重用
设计程序的大致流程:需求,画图表来来设计子系统和接口与个数,画出信息流,画出线程图谱,设计出每个子系统所需要的数据结构和算法,设计出每一个子系统的异常,防止出现bug
补充也讲述了向量数组和二维数组,一个主要针对个体,细化向量,一个主要针对整体,整化矩阵



注意,这个接口没有明确指定任何底层实现。ChessBoard可以是一个二维数组,不过接口对此并未做严格要求。修改实现时无需修改接口。另外,实现还可以提供额外的功能,如越界检查,对于这一点前面的第一种方法是无能为力的,因为我们只需加一个接口即可





592

被折叠的 条评论
为什么被折叠?



