大规模程序设计的代码重用、组织与管理方式

当写小规模程序时,采取何种代码组织方式,何种编程范式无伤大雅。

当代码达到几万行,函数达以几百个时,考虑采取何种代码组织方式,采用何种编程范式就很重要了。

需要知道的是,编程语言中的很多语法机制都是为大规模代码而准备的,但在学习阶段,能够完成的只能是小规律的demo代码。

编写大型程序的代码重用、组织与管理方式:

结构化程序设计方法:自顶向下,逐步求精(函数分解与调用方式)。

面向对象程序设计方法:自底向上,逐步抽象(继承和组合方式)。

模块化与多文件组织方式。

不同上下文中关键字、运算符、函数、对象、参数的多态。

程序设计可能会面临比较复杂的数据,这时程序员需要基于基本数据类型来自己定义新的数据类型,这就是自定义数据类型。数组、结构体、类都属于自定义数据类型。

1 结构化程序设计(面向过程程序设计)

程序描述了某种数据处理的过程和步骤,数据是程序处理的对象,一个复杂的程序任务,可能要处理大量数据,为此结构化程序设计语言提供了数组、结构体等复合变量的语法形式。

将数据处理的过程,细分成一组严格的操作步骤,这组操作步骤就叫做算法。

如果数据处理的算法很长很复杂,结构性程序设计方法,就是将一个很长很复杂的程序设计任务分解成多个简单的模块,分而治之,然后将这些模块组装起来,最终完成数据处理算法。

c++支持结构化程序设计的方法,以函数的语法形式来描述和组装模块,这就是函数的定义和调用。

将一个数据处理过程,分解成多个模块,各模块之间需要共享数据,c++语言提供了分散管理和集中管理这两种数据管理策略

1.1 自顶向下,逐步细化

将一个求解复杂问题的过程划分为若干个子过程,每个子过程完成一个独立的、相对简单的功能;用算法描述各个过程的操作步骤,每个算法称为一个模块;采用“自顶向下,逐步细化”的方法逐步分解和设计算法模块,再通过调用关系将各个模块组织起来,最终形成一个完整的数据处理算法。

采用结构性程序设计方法,程序员重点考虑的是如何分解和设计算法。

1.1.1 基于模块的协助开发的特点

① 自顶向下,逐步细化。

将复杂的算法(函数)逐步分解成功能单一的,可重复调用的算法(函数)。

② 模块重用。

结构化程序设计,应当合并重复模块,合并后的模块,可以被不同的上层模块调用,同一模块可以被多个模块调用,或被一个上层模块调用多次,这称为模块的重用或共用。

主模块和被调模块算法被划分成多个模块之后,调用其他模块的模块,称为主调模块,而被其他模块调用的模块称为被调模块,可以将不同的模块设计任务,交给不同程序员去完成。模块之间应当事先协调好被调函数的调用接口。被调模块的名称被调模块需要输入参数被调模块应该返回什么样的结果,又称返回值。

1.1.2 结构化程序设计方法

模块化是团队开发的基础。

模块接口是团队协助的基础。

模块重用影响到大型软件开发的组织与管理方式。

模块(函数)主要使用三种控制结构:

① 顺序结构;

② 选择结构(例如,if-else,switch);

③ 循环结构(例如,whuile,for,do)。

每种结构都只有一个入口,一个出口,是对goto随意跳转的约束。1966 年 Bohm和Jacpini用数学方法证明了只用三个结构和任意数量的布尔型标志就能表示任何算法。

只采用三种控制结构的模块相对于随意使用goto而言,具有极好的阅读性和可维护性情。

1.1.3 代码重用:

现在的软件开发项目可以重用以前项目所开发的代码。

一个软件项目可以重用另一个项目中的代码,即跨项目重用。

可以重用本单位已有的代码,亦可以购买外单位已有的代码,或委托外单位开发所需的子模块,即跨组织机构重用。

1.1.4 模块的4大要素

模块名称 输入参数 返回值 算法

1.1.5 模块设计者和模块调用者

模块的设计者和调用者具有不同的角色,不同角色对模块及其4大要素的理解是不一样的。

站在模块设计者的角度,他要思考的是接收输入参数,将输入参数作为原始数据进行处理,得到计算结果,并返回该结果,模块设计者,重点考虑处理算法,该算法因当能够按照模块的功能要求返回正确的计算结果,就是返回正确的返回值,模块算法是属于模块设计者的知识产权。而模块名称、输入参数、返回值是模块设计者为他人使用算法提供的开发接口,通称为模块的调用接口。调用接口必须是公开的,否则他人无法使用,算法是模块内部的实现细节可以不对外公开,在模块设计中,只有拿到模块设计的源程序,才能了解模块算法的实现细节,通常模块经常是以编译后的机器语言提供给他人使用的。

模块其实可以看做一个函数f(x),调用函数f(x)就是给出某个具体的x的值,就能得到对应的函数结果,模块名称相当于是函数名,输入参数是原始数据相当于自变量x,模块的计算结果就是返回值,相当于函数值,调用者通过模块名称调用模块,调用时按要求给定具体的输入参数值,然后接收返回值,得到所需的计算结果,调用者只需了解调用接口,即可调用模块,无需了解模块内部的算法。

1.1.6 主模块与子模块

将一个复杂的算法分解成多个模块,其中一个为主模块,由它负责调用其他模块,主模块不能被其他模块调用,除了子模块之外的其他模块统称为子模块,子模块可以被调用,也可以调用其他子模块。

c++语言支持结构化程序设计方法,以函数的语法形式来描述和组装模块,即函数的定义和调用f(x)。

1.2 数据的分散管理与集中管理

结构化程序设计,将一个数据处理过程,分解成多个算法模块,模块之间需要共享数据,c++语言以函数的形式来描述模块,每个模块被定义成一个函数,函数之间需要共享数据,才能完成规定的数据处理任务,c++语言为程序员提供了两种数据管理策略。分别是分散管理和集中管理

数据分散管理,将数据分散交由各个函数管理,函数各自定义变量申请自己所需的内存空间,其他函数不能直接访问其中的数据,需要时可通过数据传递来实现共享。采用分散管理策略时,程序员应当定义变量语句在函数的函数体中,这样定义的变量称为局部变量,局部变量属本函数所有,其他函数不能直接访问。 数据集中管理,将数据集中管理,统一定义公共的变量来存放共享数据,所有函数都可以访问,采用集中管理策略时,程序员应当将定义变量语句放在函数体外面(不在任何函数的函数体中),这样所定义的变量称为全局变量,全局变量不属于任何函数,是公共的,所有函数均可访问。 1.2.1 数据分散管理策略

核心思想就是分散管理,按需传递。

采用分散管理策略时,主调函数和被调函数互相不能访问彼此内部的局部变量,无法共享数据,c++语言通过形参实参结合,和函数返回值,这两个数据传递机制,实现了主调函数和被调函数间的数据共享。主调函数的函数体中含有对被调函数的调用语句,当执行对被调函数的调用语句时,计算机将自动执行两次数据传递操作,就是形实结合和返回值return。

1.2.2 数据的集中管理策略

数据集中管理,全局共享。

数据集中管理策略就是将数据集中管理,统一定义公共的变量来存放共享数据,所有函数都可以访问,这样可以减少函数间的数据传递。采用集中管理策略时,程序员应当将定义变量语句放在函数外面(不在任何函数的函数体中),这样所定义的变量称为全局变量。全局变量不属于任何函数,是公共的,所有函数都可以访问。

通常一个c++程序既有全局共享使用的数据,也有仅供局部使用的数据,程序员应合理的决定管理策略,将需要全局共享的数据定义成全局变量,集中管理,供所有函数访问,这样可以有效降低函数间的数据传递,而将仅供局部使用的数据,定义成局部变量,分散交由各个函数自己管理,这样可以降低管理的复杂性。

程序加载后立即为其中的全局变量或静态变量分配内存,全局变量将一直占用所分配的内存,直到程序执行结束退出时才被释放,这种内存分配方法称为静态分配

局部变量是在计算机执行到其定义语句时才分配内存,到其所在代码块执行结束即被释放,这种内存分配方法称为自动分配。也叫动态分配。

程序员可以根据实际需要,在程序中使用malloc函数来分配内存,使用完之后使用free()将其释放,这种动态内存分配方法让程序员可以更主动、更直接的管理内存,根据需要分配尽可能少的内存,同时尽早释放以减少内存的占用时间。

1.3 结构化程序设计回顾

运用结构化程序设计,可以将一个大型功能,分解成多个模块,分而治之,c++语言以函数的形式来描述各个模块,这就是函数的定义。然后再通过调用,将各个算法模块组装起来,最终形成一个完整的算法流程。

一个编写好的c++函数可以被同一项目的多个程序员调用,也可以在今后的项目中继续使用,这就是函数代码的重用。

市场上还要很多厂家为程序员提供编写好的,各种不同功能的函数库。

2 面向过程程序设计

程序 = 数据 + 算法。

变量(包括复合数据类型的变量)是程序中的数据元素,函数是程序中的算法元素。

面向对象程序设计将程序中的数据元素和算法元素根据内在关联关系进行分类管理,这就形成了”类“的概念,分类可以更好地管理数据和函数。

类相当于是一种自定义数据类型,用类所定义的变量称为”对象“。

基于类编写程序可以更好地组织管理程序代码,也更便于代码重用,在团队分工开发模式中,某些程序员定义类,编写类代码,某些程序员使用类,定义对象,然后通过对象,重用别人的类代码。

类代码更便于管理,在大型程序开发中,程序员需要定义大量变量和函数,这时用类来组织代码的优势也更加明显,例如,为了开发Windows应用程序,微软为程序员提供了2000多个函数,这些函数统称为win32 API,如何组织管理这些函数是非常麻烦的问题,甚至为这些函数命名也是一件麻烦事儿,因为这些函数不能重名,可以将这些函数按功能划分为不同的类,不同类中的函数可以重名,即便于组织代码,也便于程序员理解记忆。

使用其他文件中的类,需要先声明再使用,类的声明形式,与定义类时的声明部分相同,可以直接复制过来。声明类的代码同样可以写入头文件当中,以简化代码,在类的实现和使用时,直接使用#include指令包含对应的头文件即可。

2.1 类的封装

封装是与代码重用相关的一个概念,代码重用是提高软件开发效率的重要手段,也是软件技术不断进步的主要动力,代码重用过程中,程序员有两类角色,一类是提供代码的程序员,一类是使用代码的程序员。可重用的代码相当于是一种程序零件,可提供给不同的程序员组装出不同的产品,提供程序代码的程序员相当于是程序供应商。

提供代码的程序员将相对独立、能广泛使用的程序功能提炼出来,编写成函数或类等形式的可重用代码。可重用代码的特点是”一次开发,长期使用“。编写重用代码程序员的性格是”幸苦自己,方便别人“,因此只幸苦一次。该角色设计的程序代码越强大越好,越通用越好,这样才能被更多的人使用。

使用代码的程序员站在使用代码的程序员的角度,他希望能够使用别人提供的程序零件,组装自己的程序,这样能够站在更高的起点上,开发功能更强的产品,他希望程序零件能够承担更多的功能,从而降低开发工作量,另外希望使用越方便越好,图省事儿,图方便,例如,头文件就是为了方便程序员声明别人编写的函数或类而引入的语法形式。

c++语言可以将只在内部使用的成员封装起来,以防止它们被误访问,类的封装有两层含义。

开放,在定义类的时候,将需要被外部访问的成员开放出来,以保证类的功能,可以被正常使用。

隐藏,在定义类的时候,将不需要被外部访问的成员隐藏起来,以防止它们被误访问,被隐藏的成员只在内部使用,能被类里的其他成员访问。但不能被外部其他函数访问。

面向对象与面向过程在代码组织方面的区别,可以参照以下实例去理解:

趣味数学与编程|已知三角形的三条边或两边夹一角,求其它角或边

2.2 类的接口

公有成员是封装后类对外的接口,一个类必须有公有成员,否则这个类无法使用。

程序员设计类时应根据功能要求合理设定成员权限,一方面要开发用户正常使用所必须的成员,另一方面要尽可能隐藏不需要直接访问的成员。

2.3 面向对象程序的设计过程

面向对象的程序设计过程,可以简单的分为3个阶段,分别是:分析、抽象和组装,通常以uml统一建模语言来描述设计结果,并编写出书面的程序设计报告。

类的设计:将客观事物归纳,划分成不同的类,是人类解决客观事件,常用的方法,分类就是抓住主要特征,忽略次要特征,将具有功能性的事务,划分成一类,分类的过程,是一个不断抽象的过程,面向对象程序设计将分类称为抽象,计算机程序处理任何事务,都是将事务转换成数据模型,对事务的处理,就是对数据的处理。面向对象程序设计,将一个个具体的事物称为对象,对事物进行规划,抽象所划分成的类别,称为类;数据被称为事物的属性,在程序中,用变量存储数据;处理数据的算法,被称为方法,在程序中用函数来描述;同类对象具有相同的属性和方法,这些属性和方法被划分成类的成员,程序员需分析,提炼出类的属性成员和方法成员,并合理设定各成员的访问权限,这就是类的设计。

一个类要提炼几个属性和方法,要根据具体功能来定。面向对象的分类设计过程是一个自底向上,逐步抽象的过程。从一个个具体的对象,可以抽象出小类,从小类可以抽象出更大的类,越往上类越宽泛、越抽象。c++语言支持面向对象程序设计,用类的语法形式来描述类图。

2.4 类的定义

定义一个类,就是用c++语言描述该类包含哪些数据成员、函数成员、以及各成员的访问权限。

2.5 对象的定义和访问

类相当于时程序员自己定义的一种新的数据类型,可称为类类型,使用类通常是用类定义变量,用类定义的变量通常称为该类的对象或实例,使用类,需要先定义,再使用,或先声明再使用。

定义对象和定义变量的语法形式基本相同。类就是一张图纸,计算机执行定义对象语句就是按照图纸再内存中创建一个程序实例(即对象),计算机会严格按照类定义来创建对象,所创建的对象具有类所规定的数据成员、函数成员及访问权限。

定义好的对象可以访问,访问对象就是通过接口(即公有成员)操作内存中的对象,实现特定的程序功能,比如读写对象中的公有数据成员,或调用其中的公有函数成员。

对象中的公有成员可以访问,非公有成员(私有成员、保护成员)不能访问,这就是对象成员的访问权限控制。

访问对象中的公有成员需要使用成员运算符点“.”,以“对象名.数据成员名”的形式访问对象中的变量,以“对象名.函数成员名(实参列表)”的形式调用对象中的函数。

2.6 类和对象编程的主要内容

面向对象程序设计,主要包含2方面内容,即先定义类,然后用类定义对象,并访问对象及其成员

定义类:程序员再定义类时要考虑5大要素,即数据成员、函数成员、各个成员的权限、构造函数和析构函数。

定义对象:将类当作一种自定义数据类型来定义变量,所定义的变量就称为对象,计算机执行定义对象语句就是严格按照类所描述的5大要素在内存中创建该类的对象,所创建的对象具有类所规定的数据成员、函数成员及访问权限。

访问对象:访问对象就是通过接口(即公有成员)操作内存中的对象,实现特定的程序功能,比如读写对象中的公有数据成员,或调用其中的公有函数成员。

c++语言使用类的语法形式来描述类模型,将属性定义成类的数据成员,将方法定义成类的函数函数成员,通过为类成员设定访问权限来实现类的封装,为类添加构造函数可以为该类对象提供初始化方法,添加析构函数可以在销毁对象时做某些特定的善后处理。

2.7 类的组合和聚合

数据成员中包含对象成员的类称为组合类。

数据成员中包含对象指针的类称为聚合类,聚合类是一种特殊形式的组合类。

聚合类的对象成员是独立创建的,聚合类对象只包含指向对象成员的指针。

聚合类对象可以公用对象成员。

类的组合和聚合的两个特点:

代码重用:组合类是一种有效的代码重用形式,程序员在设计新类的时候,应先去了解有哪些可以重用的类,这些类可以是自己以前编写的,可以是集成开发环境IDE提供的,也可以是从市场上购买的,根据功能选择自己需要的类,然后用组合的方法定义新的类。

自底向上:类可以多级组合,用零件定义组合类,组合类可以继续作为零件类去定义更大的组合类,这就是类的多级组合,多级组合是一种自底向上的程序设计方法,类越往上组合,其功能就越有针对性,应用面也就越窄,多级组合过程中,每一级组合类都会根据自己的功能需要设定对象成员的访问权限。有多少级组合,就有多少层封装。

2.8 类的继承与多态

派生类是对基类成员的二次封装。

派生类通过继承方式,对向基类继承来的成员进行二次封装,如果采用公有继承的方式,则是将基类的成员中的公有成员全部开放,如果是私有或保护继承,者将基类的成员隐藏起来,在派生类外部是无法访问的,如果基类的成员本身就是私有或保护成员,派生类继承过后该成员的权限不变,只能通过基类的函数进行间接访问,不能再派生类中直接调用。

在类的继承与派生过程中,除构造函数和析构函数之外,派生类将继承所有基类中的数据成员和函数成员,派生类和基类之间存在着这样一种特殊的关系,就是:派生类是一种基类,具有基类的所有功能。

面向对象程序设计,利用派生类和基类之间的这种特殊关系,常常将派生类对象当做基类对象来使用(忽略继承体系中对象的差异性),或者用基类来代表派生类,其目的是为提高程序代码的可重用性。

C++语言对数据一致性的要求比较严格,属于强类型检查的计算机语言,C语言、java语言也属于强类型检查的语言,因为数据不一致,不能重用不同类型的函数去处理不同类型的数据。

在面向对象程序设计中,有一种特殊情况是可以的,这就是类B公有继承类A,即B是A的派生类,这时就能重用A类的代码来处理B类的数据。

在面向对象程序设计中,重用基类对象的程序代码来处理派生类对象,这是非常普遍的需求。如果派生类对象能够与基类对象一起共用程序代码,它将极大的提高程序的可扩展性,提高程序开发和维护的效率。面向对象程序设计方法,利用派生类和基类之间存在的特殊关系,提出了对象的替换与多态(一方面要体现继承关系中对象的差异性,另一方面也要体现他们的共性)。

为了让基类对象及派生类对象之间可以重用代码,c++语言指定了如下的类型兼容语法规则:

派生类对象可以赋值给基类对象。 派生类的对象可以初始化基类引用。 派生类对象的地址可以赋值给基类的对象指针,或者说基类的对象指针可以指向派生类对象。

应用类型兼容语法规则有1个前提条件和1个使用限制:

前提条件:派生类必须公有继承基类。 使用限制:通过基类对象、引用或对象指针访问派生类对象,只能访问其基类成员。

简单来说,就是将派生类对象,当做基类来使用。

可以将派生类对象当做基类来处理,即用基类对象替换派生类对象,将派生类对象当做基类对象处理的好处是,使某些处理基类对象的代码可以被派生类对象重用。这样,当需要扩充派生类时,不需要改写基类的代码,在做功能扩展时非常方便。

对象的多态:

面向对象程序设计,借用拟人的说法,将调用对象的某个函数成员称为向对象发生一条消息或指令。 将执行函数成员完成某种程序功能称为对象响应该消息所表现出的行为。 不同对象接收相同的消息,但会表现出不同的行为,这就称为对象的多态性,或称为对象具有多态。

从程序角度,对象多态性就是,调用不同对象的同名函数成员,但执行的函数不同,完成的程序功能不同,导致对象多态的同名函数成员有3种不同的形式:

不同类之间的同名函数成员:类成员具有类作用域,不同类之间的函数成员可以重名,互不干扰。 类中的重载函数:类中的函数成员可以重名,只要他们的形参个数不同或类型不同,重载函数成员导致的多态本质上属于重载函数多态。 派生类中的同名覆盖:派生类中新增成员可以与从基类继承而来的函数成员重名,但它们不是重载函数。

对象的多态,重点是研究同名覆盖,为了扩展和修改基类的功能,类族中的派生类可能会定义新的函数成员,来覆盖同名的基类成员,这样同一个类族中的基类及各个派生类都具有各自的同名函数成员,这些函数成员虽然名字相同,但实现算法有所不同。

应用liskov替换准则,将派生类对象当作基类对象来处理,即用基类对象替换派生类对象,其目的是让某些处理基类对象的代码可以被派生类对象重用,这里“某些”代码的含义是:这些代码在通过基类的引用或对象指针访问派生类成员时,只能访问其基类成员。

对象的多态,可需要扩充派生类时,无须更改基类的代码,同样的接口便可以实现扩充。

应用对象多态性,相当于是用基类来代表派生类,通过基类引用或对象指针调用派生类对象的函数成员,应能够根据实际引用或指向的对象类型,自动调用该类同名函数成员中的新增成员。C++语言使用虚函数的语法形式来实现类族中对象的多态性。

实现对象的多态性:

首先在定义基类时使用“virtual”关键字将函数成员声明成虚函数。

然后通过公有继承定义派生类,并重写虚函数成员,也就是新增1个与虚函数同名的函数成员。

然后使用基类引用或对象指针,来调用函数成员。

实现基类对象与派生类对象之间的多态性要满足以下3个条件:

① 在基类中声明虚函数成员;

② 派生类需公有继承基类,并重写虚函数成员(属于新增成员);

③ 通过基类的引用或对象指针调用虚函数成员;

只有满足这3个条件,基类对象和派生类对象才会分别调用各自的虚函数成员,呈现出多态性。

为了让某些类族共用程序代码:

定义基类时首先需确定将哪些函数成员声明成虚函数,将需要自动调用派生类新增的同名函数成员定义成虚函数。 定义派生类时,公用继承基类,并重写那些从基类继承来的虚函数成员 编写类族共用的程序代码时,需定义基类引用或对象指针来访问该类族的对象(不管是基类对象,还是派生类对象),然后通过基类引用或对象指针来调用派生类对象的函数成员,调用虚函数将自动调用派生类中重写的虚函数,否则将调用从基类继承来的函数成员。 2.8 面向对象程序设计的总结

面向对象程序设计,希望用类管理所有的程序代码,程序中没有游离在类外的全局变量或外部函数,对于一些通用的全局变量和函数,可以将其定义成静态成员,从形式上归为一类进行同一管理。

面向对象设计如同面向过程一样,重用了函数模块,更进一步,也重用了封装的数据。

3 源文件的多文件结构

一个c++程序可以包含很多个函数,源代码可能会很长,可以将这些函数分散保存在不同的源文件中,以多文件结构的形式来组织和管理源代码。

结构化程序设计,将一个复杂任务分解成多个模块,编码时,将每个模块定义成一个函数,基于团队协作开发程序时,可以将不同模块的程序设计和编码任务交由不同的程序员去完成,程序员各自独立编程,将所编写的源程序代码保存在自己的源文件中,这样可以互不干扰,基于团队协模式开发的c++程序会自然形成多文件结构。

工程:一个程序开发项目又称为工程,一个工程通常包含多个源程序文件,c++语言的源程序文件名为.cpp。一个源程序文件可以包括多个函数,一个函数只能集中放在一个源程序文件中,不能将函数定义分开存放在不同文件中,一个程序工程中可以包括很多个函数,但是只有一个主函数,函数名必须为main()。

编译器:使用编译器将c++语言翻译成机器语言,编译时,同一源程序文件中的所有函数,被统一编译,因此一个源程序文件被称为一个编译单元,每个源程序编译后都生成一个目标程序,目标程序的扩展名通常为.obj,目标程序是机器语言的程序,机器语言与cpu相关,相同的c++源程序,可以被不同编译器编译,生成不同机器语言的目标程序,从而运行于不同类型的cpu之上。

连接器:每个源程序文件编译后都生成一个目标程序文件,使用连接器,将多个目标程序连接在一起,最终生成一个可执行程序文件。在windows操作系统上,可执行操作文件的扩展名通常为.exe,可执行程序是最终的程序,可以被计算机硬件执行。软件产品销售的是可执行程序,而源程序则是软件厂家的机密。可执行程序文件可以复制、分发安装并执行,但很难被阅读修改。

3.1 外部全局变量和外部函数

在多文件程序中,一个文件定义的函数,可以被其他文件函数调用。可以被其他文件调用的函数称为外部函数,一个文件中定义的全局变量,也可以被其他文件中的函数访问,可以被其他文件访问的全局变量,被称为外部全局变量。换句话说,一个文件中的函数,可以调用其他文件中的外部函数, 也可以访问其他文件中的外部全局变量,调用外部函数时,需要先声明再调用,访问外部全局变量时,也需要先声明再访问,声明的作用,是将外部函数或外部全局变量的作用域,延伸到本程序文件中来。

源程序文件所定义的函数默认都是外部函数,可以被其他文件中的函数调用;所定义的全局变量默认都是外部全局变量,也可以被其他文件中的函数访问。

外部函数和外部全局变量被多文件结构中的所有文件共享,其他文件只要经过声明都可以使用这些函数和全局变量。

多文件结构中,所有的外部函数不能重名,所有的外部全局变量不能重名,所有的外部函数和外部全局变量之间也不能重名。

某些情况下,一个文件中定义的某些函数或全局变量只供文件内部的其他函数使用,c++语言可以将这些函数或全局变量定义成静态的,即静态函数或静态全局变量,另外c++语言还可以定义静态局部变量。

3.2 头文件

团队协作开发时,假想某个程序员编写了一个c++程序文件(假设为1.cpp),其中定义了一组函数,也定义了若干全局变量。其他程序员需要访问1.cpp中的函数或全局变量,就需要对这些函数或全局变量进行声明,访问多少个外部函数或全局变量,就需要写多少条声明语句。对于项目组的所有程序员,只要访问1.cpp中的函数或全局变量,就都需要在自己的程序文件中编写声明语句。编写这些声明语句时重复而枯燥的工作,为此c++语言引入了头文件(header)的概念。

程序员甲在编写好1.cpp源程序文件后,另外再编写一个头文件,其中包含1.cpp所有外部函数和全局变量的声明语句。习惯上将这个头文件命名为1.h(或1.hpp),即与源文件同名,扩展名为.h或.hpp,项目组的其他程序员需要访问1.cpp中的函数或全局变量,只要再自己的程序文件增加语句:#include "1.h",该语句的作用就是将头文件1.h中的所有声明语句自动插入到该语句位置,这就消除了以往一条一条手工编写声明语句的繁琐。

头文件的内容主要包含,外部函数的申明,外部全局变量的申明,还包含一些共用的符号常量定义等等。插入头所使用的include指令是一种特殊指令,被称为编译预处理指令。

头文件解决的是一个重复声明和接口的问题。一方面,每个编译单元在编译时,可能需要相同的函数与变量声明,另一方面,在链接时,这些些编译单元会整合到一起,又需要避免重复声明,所以头文件及其保护声明的机制就可以解决这一访问。另外,头文件还是函数或类的设计和实现者与类的使用者之间的接口。

3.3 多文件结构下程序员与函数的关系

为别人提供函数的程序员将常用功能或算法定义成函数,保存到源文件(扩展名为.cpp);将源程序文件编译成目标代码文件(扩展名为.obj);通常还会进一步将目标代码打包成一个函数库文件(扩展名通常为.lib)为函数库中的函数编写声明语句,集中保存在一个头文件中(扩展名为.h)发布或小数函数库产品,其中包含库文件和头文件,库文件为目标代码(即机器语言),其他程序员只能调用,无法阅读修改,而头文件是函数声明语句的源码,其它程序员可以阅读,以了解函数的功能与调用接口

使用别人函数的程序员可以使用系统函数,也可以获得(比如购买)第三方开发的函数库编写程序时调用别人函数库中的函数,以实现某种特定的功能,调用前需要用#include指令包含相应的文件头连接时,连接器将函数库中被调函数代码与自己的程序连接到一起,形成最终语句。

3.4 面向过程程序设计的多文件结构

面向对象程序设计,可以使用上述结构化程序设计相同的多文件结构语法机制。此外,还可以将类的声明和定义放在头文件,类的成员函数放到源文件中去实现。

4 代码重用

程序 = 数据 + 算法,程序中的数据,包括原始数据,中间结果和最终结果,如何根据所处理的数据来合理的使用和管理内存,是编写程序的第一项工作内容,c++语言通过定义变量语句来申请内存空间,定义变量就是与数据相关的代码;将数据的处理过程,细分成一组严格的操作步骤,这组操作步骤就被称为算法,如何设计数据处理算法,是编写程序的第二项工作内容,c++语言通过定义函数来描述算法模块,函数是与算法相关的代码。

其中,代码重用非常重要。

结构化程序设计与面向对象程序设计在代码重用方面有所不同。

4.1 结构化程序设计中的代码重用

结构化程序设计重用的是函数代码,换句话说,结构化程序设计重用的是算法代码,没有重用数据代码。

4.2 面向对象程序设计中的代码重用

面向对象程序设计既重用数据代码,也重用函数代码,因此开发效率更高。

重用类代码有3种形式:

用类定义对象; 通过组合定义新的类(称为组合类); 通过继承定义新的类(称为派生类);

面向对象程序设计之所以能够有效提高程序开发效率是因为:

分类管理程序代码,即类与对象编程;

重用类代码;

使用类定义对象;

使用已有类定义新类;

组合;

继承;

5 多态性

源程序中相同的程序元素可能会在不同的上下文中具有不同的语法解释,C++称这些程序元素具有多态性。常见的有:

关键字多态 重载函数多态 运算符多态 对象多态 参数多态

对于源程序中具有多态性的程序元素,什么时候对他们做出最终明确的语法解释呢?任何下达给计算机的指令,必须在具有明确的语法解释后,才能被计算机执行,否则不能执行。对具有多态性的程序元素作出最终明确的语法解释,这称为多态的实现。

实现多态有两个时间点,分别是在程序编译的时候,或是在程序执行的时候。不同的多态形式具有不同的实现时间点,编译时实现的多态称为编译时多态,执行时实现的多态称为执行时多态。

C++中某些关键字是多义词,具有多态性,例如static、const、void、public等等,关键字多态,是由编译器在编译源程序文件时进行解释的,是一种编译时多态。

如果两个函数的形参个数不同,或者类型不同,那么这两个函数就可以重名,被称为重载函数。编译时,由编译器根据实参的个数和类型的不同,自动调用形参最匹配的那个重载函数,相同的重载函数名,调用时会调用不同的函数,这就是重载函数多态。这也是由编译器在编译时实现的,这是一种编译时多态。所谓实现重载函数多态,就是在编译时,将调用语句中的函数名,转换成某个重载函数的存储地址。将源程序中具有多态的函数名,转换成某个具体的特定的函数储存地址,这种函数名到存储地址的转换,被称为对函数的绑定。

而C++在继承关系中通过虚函数和基类对象指针或引用,通过虚函数表所实现的多态是执行时的多态。

版权申明:本站文章均来自网络,如有侵权,请联系01056159998 邮箱:itboby@foxmail.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

猜你还会喜欢下面的内容

    无相关信息

中国领先的互联网域名及云服务提供商

为您提供域名,比特币,P2P,大数据,云计算,虚拟主机,域名交易最新资讯报道

域名注册云服务器