C++与面向对象程序设计(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-27 20:18:46

点击下载

作者:王新房,李成武,黄元(编著)

出版社:高等教育出版分社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

C++与面向对象程序设计

C++与面向对象程序设计试读:

前言

软件技术是计算机科学技术中发展最快的技术之一,在各行各业得到了广泛的应用。在软件的发展过程中,程序设计方法一直是研究的关键技术之一。程序设计方法是指导程序设计工作的思想方法,主要包括程序设计的原理和所应遵循的基本原则,帮助人们从不同的角度描述问题。选用合适的程序设计方法,对于开发满足用户需求的高质量应用软件至关重要。

普通高校绝大多数专业都开设了程序设计课程,但是,不同学校、不同专业的要求是不相同的。本书是主要针对普通高等学校电子信息大类专业(电子信息、自动化、通信、信息管理、电气、计算机等专业)而编写的一本面向对象程序设计教材。本书将程序设计的基本概念、原理、技术和方法作为重点,同时,通过具体的实例引导学生自己动手编写实用的应用程序,以达到学用结合、学以致用的目的。

如何把握理工科电类专业对面向对象程序设计课程的要求,教材内容应该如何选取和组织,是一个很值得探讨的课题。在以往的教学过程中,语法方面的内容占的比例较高,其实,要学会编程,良好的习惯、编程理念和基本方法是最核心的内容。我们希望本书在这方面能对读者有所启发。

本书需要40左右的学时,重点学习第2章、第3章、第4章、第5章和第6章,其他章节可作为自学内容来安排。为了帮助高校教师使用此书,我们同时提供本书的电子教案和程序源代码,读者可在人民邮电出版社教学服务与资源网免费下载,也可直接与作者联系索取(wangxf@xaut.edu.cn)。本书的代码是在微软公司的Visual C++6.0 和Visual Studio 2005 环境下调试通过的。

在本书的写作过程中,我们通过与实际应用密切相关的例子来说明有关的基本概念和方法,力求深入浅出。由于我们学识浅陋,书中难免存在错漏之处,希望读者提出宝贵的批评意见,更希望学术同仁不吝赐教。

西安理工大学计算机学院的邓亚玲老师对全书进行了细致的审阅,提出了很多宝贵意见,并为本书的部分习题提供了参考答案,制作了本书的电子教案,在此表示衷心的感谢。编者2012年9月第1章概述1.1 程序设计语言

自1946年第一台计算机问世到现在计算机的广泛普及,可以说计算机已应用到国民经济和人类生活的各个领域。不管计算机有多么神通广大,实际上,计算机本身并不知道如何解决一个问题,它都是按照人们事先设计好的步骤来解决问题的,也就是说计算机解决问题的方法和步骤是人们设计好的,计算机只是按照所规定的方法和步骤去完成相应的工作。为了让计算机能解决某个特定的问题,需要将我们的意图准确地告知计算机,这项工作是通过所谓的程序实现的。

程序(program)是为实现特定目标或解决特定问题而用计算机语言编写的指令序列的集合。计算机懂哪些语言呢?其实,它只懂得由0和1序列构成的指令语言(机器指令),我们称其为机器语言。人类通过学习可以掌握多种语言,当然也可以通过机器语言与计算机交流(通信),但问题在于这种机器语言对人类来说太难记忆,不便于推广。因此,便出现了各种类型的高级语言,学习高级语言,可以很方便地表达人的意图,也就是说用高级语言可以很容易地编写程序,但用高级语言编写的程序计算机是读不懂的,怎么办?解决的办法就是做一个翻译程序,人们首先用高级语言编写程序,然后用翻译程序将其转换为机器语言表达的程序,只有这样的程序计算机才可以理解运行。高级语言与机器语言的关系如图1-1所示。图1-1 高级语言与机器语言的关系

翻译程序用什么语言来写?这个问题由你来回答(见习题)。高级语言对人类来讲是比较容易掌握的一种语言,当前,有各种各样的高级语言,我们将其称为程序设计语言,如Fortran、Basic、Pascal、C、C++、Java、C#等,都是常用的高级程序设计语言。

高级语言的特点是很接近于自然语言,可用人们更易理解的方式编写程序,与机器的硬件系统无关。尽管每一种高级语言都有它自己的语法规范,但概念上它们大同小异。学习并掌握一门高级语言是很容易的事情,难点在于程序设计方法。我们通常讲程序等于算法加语言,语言不是核心,真正的核心是算法(程序设计方法)。如果你觉得编写程序比较难的话,问题应该在算法上,而不在程序设计语言上。例如,假设要编写一个求解一元二次方程解的程序,试设想一下,如果你不知道求解公式:,那么,语言学得再好,也无法完成这个求解一元二次方程的程序。因此,程序设计方法是本课程的重点,而不是语法规范。当然,编写程序的过程中,必须遵循语法规范。1.2 软件开发与程序设计方法

一个规范的软件开发过程需要经历系统分析、系统设计、编码、测试和维护几个阶段。软件开发方法是指导软件开发各个阶段工作的理论和方法,它决定了审视问题域的角度、各个开发阶段的工作任务以及最终软件系统的构成方式。其中,编码阶段的主要任务是按照系统设计的要求编制最终的程序代码,即程序设计。它是软件开发过程的一个重要阶段,是软件系统的最终实现。

程序设计是指设计、编写和调试程序的方法和过程。由于程序是应用系统的本体,是软件质量的具体体现,因此,研究程序设计中涉及的基本概念、描述工具和所采用的方法就显得格外重要。

基本概念主要包括程序、数据、子程序、模块,以及顺序性、并发性、并行性、分布性等。其中程序是程序设计中的核心,子程序是为了便于程序设计而建立的程序基本单位,也是模块的具体体现,而顺序性、并发性、并行性和分布性则反应了程序内在的特性。

描述工具主要是指编写程序的语言和为了便于调试程序而提供的各种开发环境。从某种意义上讲,它们决定了应用系统的最终功效,直接影响着软件产品的可靠性、易读性、易维护性及开发效率。

程序设计方法是指导程序设计工作的思想方法,它主要包括程序设计的原理和所应遵循的基本原则,帮助人们从不同的角度描述问题域。选用合适的程序设计方法,对于开发满足用户需求的高质量应用软件至关重要。

在程序设计过程中,选择一种良好的程序设计方法将有助于提高程序的设计效率、保证程序的可靠性、增强程序的可扩展性、改进程序的可维护性。当今,用于指导程序设计的方法有多种,它们各自有各自的特点,其中结构化和面向对象是两种最为成熟、应用最为广泛的程序设计方法。1.2.1 结构化程序设计方法

结构化程序设计是由迪克斯特拉(E.W.Dijkstra)在20世纪60年代提出的,是以模块化设计为中心,将待开发的软件系统划分为若干个相互独立的模块,这样,使完成每一个模块的工作变得单纯而明确。结构化程序设计主要有以下3个特征:

• 自顶向下,逐步求精;

• 模块化;

• 语句结构化。

自顶向下,逐步求精,即将编写程序看成是一个逐步演化的过程。自顶向下是指将分析问题的过程划分成若干个层次,每一个新的层次都是上一个层次的细化,即步步深入,逐层细分。

模块化是将整个系统分解成若干个模块,每个模块实现特定的功能,最终的系统将由这些模块组装而成。模块之间通过接口传递信息,力求模块具有良好的独立性。实际上,往往可以将模块看做是对欲解决的应用系统实施自顶向下、逐步求精后形成的各子系统的具体实现。

语句结构化要求支持结构化程序设计方法的语言都应该提供过程实现模块概念。结构化程序设计要求,在每一个模块中只允许出现3种流程结构的语句,即顺序结构、分支结构和循环结构。如图1-2所示。这3种流程结构的语句有一个共同的特点,即每种语句只有一个入口,一个出口,这对于保证程序的良好结构、检验程序的正确性十分重要。图1-2 结构化程序设计的3种流程结构语句

Pascal和C语言是支持结构化程序设计的典型代表。它们以过程或函数作为程序的基本单元,在每一个过程中仅使用顺序结构、分支结构和循环结构3种流程结构的语句,因此,人们又将这类程序设计语言称为过程式语言。用过程式语言编写的程序其主要特征可以用下列公式形象地表达出来:

程序=过程+过程调用

采用结构化程序设计方法,可以提高编写程序的效率及质量。自顶向下、逐步求精有利于在每一个抽象级别上尽可能地保证设计过程的正确性及最终程序的正确性。规范模块组装的策略及限定模块中只允许出现3种流程结构的语句,可以使得程序具有良好的结构,改善程序的可读性、可理解性和可维护性。利用结构化程序设计方法实现程序设计需要经过两个基本过程:分解和组装。

所谓分解是指通过对初始问题域的详细分析,不断地将其进行模块分解,每分解一次都是对问题的进一步细化。模块是求解问题域的一种描述,如图1-3所示。图1-3 模块M可分解为更小的模块

例如,设计一个程序,将从键盘上输入的100个整数重新按从小到大的顺序排序,并输出重新排序后的结果。分解成3个子模块:输入、排序和输出,如图1-4所示。图1-4 程序由3个模块构成

利用结构化程序设计方法求解问题的基本策略是从功能的角度审视问题域。它将应用程序看成是一个能够完成某项特定任务的功能模块,其中的每个子过程是实现某项具体操作的底层功能模块。在每个功能模块中,用数据结构描述待处理数据的组织形式,用算法描述具体的操作过程。1.2.2 面向对象程序设计方法

面向对象的概念起源于20世纪60年代后期出现的编程语言Simula 67。直到20世纪80年代,人们才开始注重面向对象分析和设计的研究,逐步形成了面向对象程序设计方法。今天,面向对象程序设计方法已成为人们在开发软件时首选的程序设计方法,可以说,面向对象技术是当今最好的软件开发技术。

结构化程序设计方法属于面向过程的程序设计方法。它根据计算机的要求,围绕算法进行程序设计。它所采取的方法与过程明显不同于人类认识世界解决问题时习惯采用的方法与过程,因此,使得描述问题的问题空间与实现解法的解空间在结构上明显不同。我们来看一个例子。

一位厨师的头发长了,需要理发,他会走进理发店,告诉理发师要理什么样的发式,也就是说,为了解决头发过长的问题,厨师只需向理发师提出要求,告诉他“做什么”(即理什么发式),并不需要告诉理发师“怎样做”,理发师自己就知道第一步做什么,第二步做什么。类似地,理发师肚子饿了,只需走进餐馆点好自己要吃的饭菜,厨师就知道该怎样做菜,并不需要顾客告诉他做菜的具体步骤,事实上,顾客也无须知道做菜的具体步骤。

实际上,人类解决问题往往采用的习惯模式是“客户/服务提供者”模式。人类社会中不同职业的人有不同的技能,当需要完成一项复杂的任务时,就把具有完成这项任务所需的各类人找来,向每个人提出具体要求,至于每个人如何完成自己所承担的任务,并不需要在布置任务时详细说明,因为他们具备完成自己所承担任务所需的技能。

面向对象程序设计方法模拟人类习惯解决问题的方法,用对象分解取代功能分解,也就是把程序分解成许多对象,不同对象之间通过发送消息向对方提出服务要求,接收消息的对象主动完成指定功能,程序中的所有对象分工协作,共同完成整个程序的功能。

面向对象方法学的基本原则是尽可能模拟人类习惯的思维方式,使开发软件的方法与过程尽可能接近人类认识世界、解决问题的方法与过程,也就是使描述问题的问题空间(也称为问题域)与实现解法的解空间(也称求解域)在结构上尽可能一致。1.3 面向对象基本概念

面向对象方法具有下述4个要点。

• 认为客观世界是由各种对象组成的,任何事物都是对象,复杂的对象可以由比较简单的对象以某种方式组合而成。

• 所有对象都可被划分成各种对象类(简称为类,Class),每个对象类都定义有一组属性和一组行为。

• 按照子类(或称为派生类)与父类(或称为基本类)的关系,把若干个对象类组成一个层次结构的系统(也称为类等级)。

• 对象彼此之间仅能通过传递消息互相联系。1.3.1 对象

现实世界中,到处都是对象,如计算机、手机、课本,甚至你自己都是对象(现实世界中的对象)。不管什么类型的对象,它们都有共性,这就是属性和行为。例如,你的性别、身高、皮肤颜色、爱好等等都是你的属性,除这些静态的属性外,你还有各种行为能力,比如你会编程,你会歌唱等,这些是你的行为。再比如汽车对象,一辆汽车除了有重量、速度、生产厂家等属性外,也有加速、换挡等行为。本书中的对象指的是软件对象,是在编程过程中对客观世界中的真实对象的模拟,我们用变量或者数据成员表示对象的属性,用函数表示对象的行为。可以说,对象是紧密联系在一起的一组变量和函数,或者说,对象包含了一组数据成员和一些成员函数。

定义:对象是紧密联系在一起的一组变量和函数,或者说,对象包含了一组数据成员和一些成员函数。

相同类型的对象之间的本质区别在哪里呢?比如,你和我(相同类型的对象)的本质区别在哪里呢?我有身高,你也有身高,我有我的爱好,你有你的爱好,区别在于我的身高和你的身高不一样,我的爱好也可能不是你的爱好。当然这是属性方面,再来看看行为方面,我会编程,你一定也会编程,但你我编程质量可能不一样。

这里涉及对象实例的概念,相同类的对象的结构是相同的,如果将同类的对象抽象为一个数据类型,那么定义该类型的变量就是对象的实例。看下面的代码:

int x1,y1;

float x2,y2;

用C语言的术语来说,上面定义了两个整型变量和两个浮点型变量。现在用面向对象的概念看这个问题,我们可以把整型看成一类对象,把浮点型数据看成另外一类对象,上面代码中的 x1 和y1是整型对象的实例,x1和y1具有相同的结构,也就是说它们在内存中占有相同大小的存储空间,但它们的值一般是不同的;同样,x2和y2都是浮点型对象的实例,x2和y2在内存中也占有相同大小的存储空间,但x2的值和y2的值一般不同。而这里的x1和x2却是两个完全不同类型的对象。当然,这个问题比较简单,现实世界中的对象是多种多样的,有些比较简单,有些可能会很复杂。一辆汽车是一个对象,或许你认为这个对象比较复杂,但汽车是由很多较简单的对象组成的。

对象与对象实例是有区别的,可以将相同类的对象理解成数据类型,而将对象实例理解为定义的变量。在不导致混淆的情况下,本书并不严格去区分这两个概念。1.3.2 消息

通常情况下,一个单一的对象并没有多少价值,比如一辆自行车,如果没有人去使用它,它能有什么价值呢?事实上,一个程序是由很多对象构成的,在对象之间相互交互的基础上,程序才表现出它的各种功能。对象之间通过互相发送消息实现它们之间的交互或通信。例如,当对象A需要对象B完成某个行为时,就可以给B发送消息,如图1-5所示。图1-5 对象A向对象B发送消息

这里,对象A向对象B发送的消息包含3个方面的内容:

• 接收消息的对象;

• 需要的行为;

• 完成行为所需的一些参数。

一个对象的行为表现为一组成员函数,因此,A向B发送消息其实就意味着调用对象B的成员函数。消息机制提供了如下两个优点:

• 两个对象可以用各种方式进行交互;

• 两个交互的对象并不需要在同一进程,甚至可以在不同的机器上。

从客户/服务器的观点看,A是客户,B是服务提供者。A通过向B发送消息请求所需的服务, B完成相关操作后将结果返回A。在这个过程中,A不必知道B完成操作的过程,它只对B的结果感兴趣。1.3.3 类

客观世界中的对象是多种多样的。分类是人们认识客观世界时经常采用的思维方法。分类所依据的原则是抽象,其具体过程是忽略事物非本质的特征,只注意那些与当前目标有关的本质特征,从而找出事物的共性,并把具有共同性质的事物划为一类,得到一个抽象的概念。例如,动物、人、企业等都是一些抽象的概念,它们是一些具有共同特征的事物的集合。类的概念使我们能对属于该类的全部事物进行统一描述,而不再对每个具体的事物进行同样的重复表述。可以说,类是人类对客观世界抽象的具体表现,是方法论应用于程序设计的有效手段。

定义:类是具有相同属性和行为的一组对象实例的集合,它为属于该类的对象实例提供了统一的抽象描述,包含属性和行为。

在面向对象程序设计中,类实际上是一个用户自定义数据类型,而对象是我们定义的属于某类的对象实例,简称对象。类与对象的关系如同一个模具与用这个模具铸造出来的铸件之间的关系。类是对象的抽象描述,它给出了属于该类的全部对象的抽象定义,而对象则是符合这种定义的一个实例,也就是说,一个对象是类的一个实例。1.3.4 封装

所谓封装就是把某个事物包装起来,使外界不知道该事物内部的具体内容。当然,我们也可以把数据和实现对数据操作的代码集中封装起来放在对象内部。一个对象就好比一个黑盒子,表示对象属性的数据和实现各个行为的操作代码封装在这个黑盒子里面,外界是看不见的,更不能从外界直接访问这些数据和行为。也就是说,在使用一个对象的时候,只需知道它向外界提供的接口形式而无须知道它的数据结构细节和实现行为操作的具体算法。实现封装应满足如下3个条件。

• 有一个清楚的边界。

• 有确定的接口(这些接口就是对象可以接收的消息,用户只能通过向对象发送消息来使用它)。

• 受保护的内部实现。封装就是信息隐藏,把对象的实现细节对外界隐藏起来。

封装在程序设计过程中具有重要的意义。封装的信息隐藏作用反映了事物的相对独立性,使我们只关心它对外所提供的接口,即能做什么,而不注意它的内部细节, 既怎样提供这些服务。封装的效果使对象以外的部分不能随意访问对象的内部数据,从而避免了外部错误对它的影响,大大减少了查错和排错的难度。另一方面,当对象内部进行修改时,只要对外的接口没有改变,对象内部的修改不会对外界产生任何影响。

封装是面向对象程序设计的第一大特征,我们将在第3章详细讨论它。1.3.5 继承

客观世界的事物既有共性也有个性。如果只考虑事物的共性,而不考虑事物的个性,就不能反映出现实世界中事物之间的层次关系,也就不能完整地、正确地对现实世界进行描述。运用抽象的原则就是舍弃对象的特殊性,提取它们的共性,从而得到一个适合一个对象集的类,如果在这个类的基础上,再考虑在抽象过程中舍弃的那部分对象的特殊性,则可形成一个新类。这个类具有前一个类的全部特性,也就是说一个类上面可以有父类,下面可以有子类,形成一种层次结构,这种层次结构的一个重要特点就是继承性。例如,动物这个类,可以有哺乳动物和非哺乳动物,哺乳动物下面可以有老虎、狗等,老虎有老虎的特点,狗有狗的特点。继承有如下优点:

• 子类可以通过继承自动具有父类的属性和行为,提高代码的可重用性;

• 可以设计一个抽象类,这个抽象类并不需要提供全部属性和行为,那些没有定义的属性和行为可以在子类中实现,避免重复工作。

继承是面向对象程序设计的第二大特征,我们将在第5章详细讨论它。1.3.6 多态性

多态性是指在继承系中,子类对象可以像父类对象那样使用。同样的消息既可以发送给父类对象也可以发送给子类对象。在类等级的不同层次中,相同的消息,被不同类(属同一簇)的对象接收,会产生不同的行为。

多态性机制不仅增加了程序设计的灵活性,而且可以显著提高软件的可重用性和可扩展性。当扩充系统(程序)功能需要增加新的实体类型时,只需派生出相对应的子类,并在子类中重新定义它的一些行为,完全不必修改原有的程序代码。

多态性是面向对象程序设计的第三大特征,我们将在第6章详细讨论它。1.4 面向对象程序设计方法的主要优点

面向对象程序设计方法之所以成为主流的程序设计方法,就是因为它具有很多其他方法无可比拟的优点,主要体现在如下几个方面。

1.与人类习惯的思维方法比较一致

面向对象方法学的基本原则是按照人们习惯的思维方式建立问题域的模型,开发出尽可能直观、自然地表现求解方法的软件系统。面向对象的软件系统中广泛使用的对象,是对客观世界中实体的抽象。

2.稳定性好

面向对象方法基于构造问题领域的对象模型,以对象为中心构造软件系统。它的基本做法是用对象模拟问题领域中的实体,以对象间的联系刻画实体间的联系。因此,以对象为中心构造的软件系统是比较稳定的。

3.可重用性好

面向对象的软件技术在利用可重用的软件成分构造新的软件系统时,有很大的灵活性。其中,有两种方法可以重复使用一个对象类:一种方法是创建该类的实例,从而直接使用它;另一种方法是从它派生出一个满足当前需要的新类。

4.较易开发大型软件产品

用面向对象方法开发大型软件产品时,构成软件系统的每一个对象就像一个微型程序,有自己的数据、操作、功能和用途。因此,可以把一个大型软件产品分解成一系列本质上相互独立的小产品来处理,这不仅降低了开发的技术难度,而且也使得对开发工作的管理变得容易。

5.可维护性好

采用面向对象思想设计的类,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来非常方便,成本也低。1.5 面向对象程序设计语言与开发工具

面向对象是一种程序设计理念、设计方法,它与程序设计语言是两个不同的概念,在实现所设计的程序(编写代码)的时候,语言的选取非常重要。概念上,不管选用什么程序设计语言,都可以实现设计,但实现的难度可能有很大的差异。早期的大多数程序设计语言并不是为实现面向对象的程序设计而提供的,比如Fortran、Basic、Pascal、C等。如果用这些语言去实现设计,代码量可能是巨大的。

面向对象程序设计方法已成为当前人们在开发软件时首选的程序设计方法,它是当今最好的软件开发技术,由此也出现了专门针对面向对象这种程序设计方法的程序设计语言,如C++、Java、C#等,我们称这样的程序设计语言为面向对象的程序设计语言,但并不意味着采用这样的程序设计语言编写出来的程序就一定具有面向对象的特征。

面向对象程序设计语言为我们提供了实现面向对象程序设计的特定方法,可以大大减少代码量。面向对象程序设计语言提供了实现类、数据封装、继承和多态性的有效手段,作为程序员,只要遵循它的语法规范就可以很容易地实现具有面向对象特征的程序设计。另外,面向对象程序设计语言的最大特点之一就是允许重载,不仅可以对函数重载,也可以对运算符重载,重载使我们可以设计实现与内部预定义数据类型没有本质区别的自定义数据类型。

在软件开发时,需要选择一个最适合的开发工具。程序设计语言与开发工具有紧密的联系,但概念上,两者是不同的。建议读者去网上搜索一下,看看当前流行的开发工具有哪些?

编写在微软操作系统上运行的程序,Visual Studio 应该是首选的开发工具。实际工作中,应根据具体情况选择最适合的开发工具。本书的所有代码是在Visual Studio 2005环境下调试通过的。1.6 Visual Studio 2005简介

Visual Studio是微软公司提供的开发工具,它的版本在不断地升级,当前最新的版本是Visual Studio 2012。学习本书Visual Studio 2005完全可以满足需求,甚至还可以选用Visual C++ 6.0。本节将简单介绍Visual Studio 2005的使用方法,其基本概念也适用于其他版本的Visual Studio。图1-6所示为运行Visual Studio 2005界面的一部分。图1-6 Visual Studio 2005的运行界面

Visual Studio 提供了许多模板,利用这些模板可以很方便地搭建程序框架。为了编写一个程序,需要创建一个项目(Project),为此,选择“File/New/Project”菜单命令,会出现如图1-7所示的界面。左上部分显示的是Visual Studio 所支持的编程语言,它支持Visual Basic、Visual C#、Visual J#、Visual C++等编程语言,这里选择Visual C++编程语言。右上方是Visual Studio提供的应用程序模板。我们在学习 C 语言的时候,编写的程序都是控制台(Console)程序,这类程序的入口点是main函数。本书主要采用这类程序,因此,当创建一个项目时,请选择“Win32 ConsoleApplication”模板搭建程序框架。下面部分用来命名项目(Name)、设置项目的存储位置(Location)及解名称(Solution Name)。为项目(Project)和解(Solution)各起一个有意义的名字,并选择存储的目录位置。Solution相当于Visual C++ 6.0中的工作空间的概念。在一个“Solution”中可以创建多个Project。

我们来创建第一个项目,项目名为“MyFirstApp”,解名称为“VS2005Code”,将其存储在“F:\”下。如果输入

Name:  MyFirstApp

Location:  F:\

Solution Name: VS2005Code

项目向导将会在“F:\”下创建一个VS2005Code目录,在该目录下生成两个文件( VS2005Code.ncb和VS2005Code.sln),并创建一个MyFirstApp目录。MyFirstApp目录存放MyFirstApp这个项目的所有文件。最后单击“OK”按钮,即可出现应用程序向导界面。如果选择“Win32 Console Application”模板,则会出现如图1-8所示的向导界面。图1-7 选择“File/New/Project”菜单命令后出现的界面图1-8 选择“Win32 Console Application”模板后出现的向导界面

在图1-8所示的界面中,可以选择应用程序类型(Windows Application、ConsoleApplication、Dll和Static Library),是否使用微软公司的MFC(微软基本类库)。本书的例子采用图1-8中的默认选择,即应用类型为Console application,选中“Precompiled header”(在所有的cpp文件中都需要包含 stdafx.h)。最后,单击Finish按钮,向导就会生成基本的程序框架,在Solution Explorer中可以看到向导生成的文件,如图1-9所示。

随后我们就可以在main函数体中添加代码,或者建立自己的源文件和头文件,在其中编写代码。也可以在“View”菜单下选择“Class View”,打开类视图窗口,如图1-10所示。图1-9 Solution Explorer图1-10 Class View

为了在 VS2005Code 下创建第二个项目,比如 MySecondApp,在图 1-9 中选中 Solution 'VS2005Code',单击鼠标右键,在出现的快捷菜单中选择“Add/New Project”,如图 1-11 所示。

这时,会再次出现如图 1-7 所示的界面。在 Name 中输入 MySecondApp,Location 中默认为VS2005Code,采用默认位置。

创建完第二个项目后,在F:/VS2005Code目录下会创建一个MySecondApp子目录,在该目下存放第二个项目所需的所有文件。最后,在Solution Explorer和ClassView中看到的内容如图1-12所示。

编译时,可以选择“Build/Build Solution”编译 Solution 下的所有项目,也可以在 Solution Explorer或Class View中选中一个项目,然后单击鼠标右键,在出现的快捷菜单中选择“Build”命令来编译所选中的项目。比如,要单独编译项目MySecondApp,则先选择该项目,然后单击鼠标右键,出现如图1-13所示的快捷菜单,选择“Build”菜单项即可。

编译完成后,就可以运行程序了。可以通过图1-13快捷菜单中的“Set as StartUp Project”将一个项目设置为当前运行的项目,然后通过快捷按钮运行该项目所生成的程序,也可以通过“Debug”菜单下的有关菜单项运行当前项目生成的程序。图1-11 在Solution 'VS2005Code'下创建第二个项目“MySecondApp”图1-12 创建第二个项目后,Solution Explorer和Class View中的内容图1-13 单独编译项目MySecondApp小结

本章属于概述,在学习本章时,不必对这些内容深刻地去理解,等学完本书各章节内容后,再回过头来理解相关内容或许更好。本章要求读者对下面的内容有印象即可。

1.程序设计语言与程序设计方法是两个不同的概念。本书的重点是程序设计方法,而不是程序设计语言。

2.在已有的程序设计方法中,面向对象程序设计方法已成为人们的首选,因为它有很多优点,了解这些优点。

3.记住面向对象程序设计的三大特征:数据封装、继承和多态性。

4.面向对象程序设计语言(比如C++)更容易实现采用面向对象理念设计的程序。

5.熟悉Visual Studio 2005开发环境。习题

1.1 翻译程序应该用什么语言编写?为什么?

1.2 看下面的代码:

int x1,y1;

float x2,y2;

谈谈4个变量之间的本质区别。

1.3 对象实例与变量有什么本质的区别?

1.4 简述软件开发过程中“设计”与“实现”的本质区别。

1.5 去网上搜索一下,列出当前流行的软件开发工具及支持的程序设计语言。

1.6 用Visual Studio 2005创建两个项目,一个在屏幕上显示“Hello World!”,另一个在屏幕上显示“读者,你好!”,最后编译并运行这两个程序。第2章C++的特性

本章将介绍C++中几个最重要的基本概念,这些概念对理解后续各章的内容至关重要。2.1 变量的定义与说明

首先看下面的例子:

int x;

extern int y;

其中,第1行定义了一个变量x。定义一个变量的本质其实就是当我们引入一个变量时,也同时为该变量在内存中分配一块存储空间。内存空间是有限的,单位为字节,没有被使用的内存空间为空闲空间。定义一个变量,即告诉编译器从空闲内存空间中拿出一块空间用于该变量,一个变量到底需要多少字节的存储空间,取决于编译器。

第2行是说明一个变量y。说明一个变量并不需要为变量分配存储空间,仅告诉编译器该变量为外部变量(这里用了关键字extern),该变量在其他地方已经定义过了。

C++中的变量定义和说明可以出现在任何地方,不一定非要在程序的最开始处,只要在使用变量前确保已定义或说明了该变量即可。

通过上面的例子,读者应深刻理解定义与说明的本质区别。2.2 C++标准库概览

C++的标准库定义在一个称为std的名字空间里。C++标准库的所有头文件都没有扩展名。C++标准库的内容总共在50多个标准头文件中定义,其中18个提供了C运行库的功能。C中那些标准库函数对应的<X.h>头文件,在C++中与之对应的头文件是<cX>。表2-1给出这些头文件的对应关系。

<cX>形式的标准头文件其内容除与ISO标准C的<X.h>头文件相同外,还容纳了C++扩展的功能。在<cX>形式标准的头文件中,与宏相关的名称在全局作用域中定义,其他名称在std命名空间中声明。

在C++中可以使用<X.h>形式的标准C运行库头文件名,也可以使用<cX>形式的C++标准库头文件名,当然我们建议采用后者。表2-1 C中的头文件与对应的C++头文件

C++标准库的50多个标准头文件内容分为10类:语言支持、输入/输出、诊断、一般工具、字符串、容器、迭代器支持、算法、数值操作和本地化。这里不一一介绍,读者可参考文献[7]。

需要说明的是,最初的C++标准是1998年制定的,2003年对1998年的标准进行了更新,2011年再次被更新(C++11)。一些流行的开发工具,并不是100%地支持C++标准,如VC++6.0,它是 1998 年的 C++标准之前的产品,因此,如果读者使用 VC++6.0,那么包含头文件时可能会有小小的差异。例如,对于I/O流库,如果使用VC++6.0,那么应该包含:

#include<iostream.h>

但在Visual Studio 2005下,应采用如下的方式包含头文件:

#include<iostream>

using namespace std;

后面的这种方式是C++标准推荐的。2.3 C++的简单输入与输出

在C中,为了从键盘输入,可调用scanf、getch、gets等函数,为了在屏幕上输出,用得最多的是printf函数。而在C++中,对于输入与输出除了用C中旧有的库函数外,还提供了一个相当方便的I/O类库iostream,我们将在第7章详细介绍,这里只简单介绍cin和cout两个流对象的使用。

cin 和 cout 是两个用于标准输入/输出的预定义流对象,cin 与运算符“>>”一起可完成标准输入,cout与运算符“<<”一起用于标准输出。“>>”和“<<”是右移和左移运算符,这里用于输入/输出是因为C++允许运算符重载,运算符重载是C++的一大特色,在后面的章节会详细讨论。cin和cout的使用方法很简单,我们看下面的例子。

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

void main()

{

char Name[10];

int Age;

cin>>Name;  //从键盘输入字符串,然后按回车键

cout>>Name<<endl; //输出Name

cin>>Age;  //从键盘输入一个整数,然后按回车键

Cout<<Age<<endl; //输出Age

cout<<"Hello World!"<<endl; //输出一个字符串

cout<<23<<endl;    //输出整数

cout<<28.9<<endl;   //输出一个浮点数

cout<<"名字:"<<Name<<",年龄:"<<Age<<"岁,工资:"<<3860.5<<"元"<<endl;

//输出混合数据

}

///////////////////////////////////////////////////////////

这里使用了C++中提供的I/O类库iostream来实现输入和输出。上面代码中的endl等效于换行符。

可以看出,cin和cout的使用方法很简单,cout将欲输出的数据借输出运算符“<<”送到标准输出,而cin借输入运算符“>>”将数据由标准输入读入。

一般来说,用cout来实现输出比printf要方便得多,由上面的代码可以看出,cout可以自动判别我们要输出的数据为何种类型而自动调整输出的格式,不必像 printf 函数那样一个一个都要由用户指定;cout不但方便,而且减少错误的机会,连续的输出也可以像printf一样连接起来。cin也一样,比scanf更方便,连续的输入也可以连接起来,比如:

cin>>Name>>Age; //注意输入时,每一个>>对应输入一个回车符或者空格2.4 数组与指针

数组中的元素在内存中是连续存放的,编译器并不负责访问数组时的越界问题,这是程序员的责任。所以,在访问数组元素时,一定不要越界,否则会产生不可预料的后果。程序设计时,语法问题编译器会发现,重要的是不要有逻辑上的问题,否则结果是可怕的(请思考为什么可怕)。

指针是C及C++中的特色,使用指针会使程序效率很高,特别是指针作为函数参数时体现的尤为明显,这一点我们在函数一节再说明。

数组和指针没有本质的区别。在处理很多相同类型的数据时,我们自然会想到用数组,比如下面的语句:

int x[1000];

这里定义了一个变量x,它是一个数组,可以存放1000个整数。当有1000个整数需要存储在变量中的时候,自然不会一个一个地去定义1000个变量,而会定义一个数组。但利用指针也可完成上述操作,其代码如下:

int *x;  //定义一个指针

x=new int[1000]; //申请可存储1000个整数的内存空间

if(x==NULL)

{

cout<<"申请内存失败"<<endl; //显示错误信息

exit(0);  //退出

}

这里使用了指针,在概念上等同于一个数组。比如,要给数组各元素赋初值 0,那么可以用下面的语句完成:

for(int i=0;i<1000;i++)

x[i]=0;

使用指针虽然没有定义数组那样简洁,但当定义像上面那样的数组时,如果系统没有那么大的连续自由空间(可以存放1000个整数),则程序运行结果不会正确。当然,对于简单的数据类型来说,像上面那样定义一个数组一般不会出现问题,因为一个简单类型的变量只需要几个字节的内存空间。但是,如果是一个自定义的数据类型,如一个结构体或一个对象,它可能需要很大的存储空间,这时使用数组要特别小心。

在编程的过程中,动态存储分配是一个重要的技巧。在C中,可以调用malloc函数向系统申请一块适当大小的存储空间,等到使用结束,再调用free函数将之释放,归还给系统。而C++中还提供了两个运算符new和delete,使用它们可以更方便地实现内存空间的动态分配。new和delete的使用方法如下:

数据类型 *指针变量 = new 数据类型[说明需求空间大小的一个整数];

delete 指针变量;

其中,数据类型可以是任何数据类型,包括用户自定义数据类型。【例 2-1】 用键盘输入一个整数 n,用以说明后面要输入的浮点数的个数,将后续输入的 n个浮点数相加,将结果输出在屏幕上。

示例代码如下:

///////////////////////////////////////////////////////////

#include <iostream>

using namespace std;

void main()

{

int n;

cout<<"请输入需要输入的浮点数的个数:"<<endl;

cin>>n;

float *x=new float[n]; //申请能存放n个浮点数的存储空间

for(int i=0;i<n;i++) //用一个for循环语句输入n个浮点数

{

cout<<"请输入第"<<i<<"个浮点数" <<endl;

cin>>x[i];

}

float y=0.0;

for(i=0;i<n;i++) //用一个for循环语句计算n个浮点数之和

y=y+x[i];

cout<<"y="<<y<<endl; //在屏幕上输出计算结果

delete x;   //释放开始申请的存储空间

}

///////////////////////////////////////////////////////////

上面的代码中,如果直接定义数组会出现两种情况,一是定义一个容量很大的数组,但容量再大,也会存在输入的n大于数组容量的时候,这时就会出现问题;二是如果n小于数组的容量,虽不会出现大的问题,但确实存在浪费内存空间的问题。这里给出的建议是,在编写程序时,要么你的程序结果是正确的,要么你的程序会给你一个警告!强烈建议你遵循这样的原则!2.5 函数2.5.1 引用类型和const变量

引用类型(Reference type)和const 变量是C++新增的特性。引用类型允许定义一个变量,它不需要占用额外的存储空间,它是另外一个变量的别名。定义引用类型变量的语法格式如下:

数据类型 & 引用类型变量=另外一个已定义的变量;

例如:

int x=10; //定义一个整形变量x

int & y=x; //定义一个整形引用变量y,它是变量x的别名

上面的代码说明定义了一个整形引用变量y,它是变量x的别名,y并不需要占用额外的存储空间,它与x使用相同的存储空间,因此x变化时,也同时意味着y的变化,反过来也一样。

定义引用类型时,只要在类型保留字后面加上引用运算符&,且同时给定初值即可。引用变量有什么用途呢?它主要用于函数的参数,提高程序的效率。

当希望一个变量的值在程序中不能被改变时,可以将其定义为const变量。定义一个const变量时必须同时给定初值。变量一旦定义为 const 变量,以后不管用什么方式,都不允许再改变该变量的值。定义一个const变量的语法格式如下:

const 类型关键字 变量名=初值;

例如:

const int x=10;

这里定义了一个整形const变量x。x以后永远等于10,不允许再改变x的值,否则,编译器会产生一条编译错误。

const变量和使用宏#define定义的常数有什么区别呢?用宏#define定义的常数没有类型,而const变量有类型,这是它们之间的本质区别。C++对数据类型有很严格的要求,编程时,尽量使用类型明确的变量或常数。const变量主要作为函数参数使用。2.5.2 函数原型和函数实现

函数原型(Function Prototype)就是对函数的说明。当调用一个函数时,编译器怎么知道调用是否符合被调用函数的语法要求呢?其依据就是函数原型。函数原型说明了调用该函数时应遵循的语法规则,它告诉编译器有关函数名、函数的返回值类型、函数的参数个数、各参数的类型等信息。例如,下面的函数原型:

int MyFunc(int,float);

该函数原型说明该函名称为 MyFunc,它的返回值类型为整型,调用该函数时必须传递两个参数,第1个为整型数据,第2个为浮点型数据。将函数的返回值赋予一个整型变量是安全的,如果将该函数的返回值赋予除整型以外的其他类型都是不安全的。同样,调用该函数时,如果给第一个参数传送的不是整型(int 类型),比如说传送的是一个浮点数,那么函数运行的结果将是不可靠的。调用该函数时,必须严格遵循函数原形的语法规则。从现在起,应养成严格使用类型的习惯,这是C++的重点之一,C++对类型有非常严格的要求。函数原型的说明格式如下:

类型关键字 函数名(函数参数列表说明);

在上面的函数参数列表说明中,每个参数都需要类型关键字说明该参数的类型,有多个参数时,参数之间用逗号分开。

函数实现是实现函数功能的实际程序。函数的实现必须包含两部分,即说明符(declarator)和函数体(function body)。下面是MyFunc函数的实现:

int MyFunc(int x,float y)

{

}

说明符必须与当初的函数原型完全符合,所谓完全符合是指具有相同的返回值类型、函数名、参数类型和参数个数。

通常函数原型放在一个头文件中(.h文件),函数的实现放在源文件中(.cpp文件)。为了养成一个良好的编程习惯,建议每当创建一个源文件时,同时创建一个同名的头文件。将自己编写的函数原型放在头文件中,而在源文件中编写函数的功能代码(实现函数)。

在调用一个函数时,首先应包含被调用函数的函数原型所在的头文件,否则编译器将会给出一条编译错误信息。在编写自己的头文件时,假如要编写的头文件为MyFile.h,强烈建议采用如下的文件结构:

#ifndef __MYFILE_H__

#define __ MYFILE _H__

//这里放函数原型和其他说明

#endif

确保宏__ MYFILE _H__只在该文件内被定义。怎样做到这个唯一呢?有多个方案可供选择,实践中一个好的解决办法是用你的文件名。在团队合作完成一个项目时,这样的习惯会避免很多问题。

采用上面的头文件结构,可以避免头文件被多次包含,比如如下的代码:

#include"MyFile.h"

#include "MyFile.h"

//程序的其他代码

上面的代码中包含了两次MyFile.h,实际效果其实只包含了一次。你或许会问什么情况下才会发生像上面代码那样会将一个头文件“include”两次?你可能觉得你不会对同一个头文件像上面代码那样“include”两次。但实际中你可能经常会那么做,为什么呢?考考你的思维,认真想想吧。2.5.3 函数信息的传递

函数与函数之间的通信,是由参数的传递而实现的。C教科书中一般都会讨论函数信息传递的问题,我们知道函数有两种信息传递方式:传值和传址。C++提供了第3种函数间的通信方式:以引用类型数据实现函数之间的通信,即引用传递。

1.传值方式

传值方式是函数间最简单的通信方式。下面的代码是通过传值方式实现两个函数的通信的。

///////////////////////////////////////////////////////////

void Show(int x);

void main()

{

int x=10;

Show(x);

cout<<x;

}

///////////////////////////////////////////////////////////

上面的代码中,Show函数的功能是在屏幕上显示x,我们在main函数中调用了Show函数, 通过传值的方式实现了main与Show的通信。我们调用Show函数时,将main中的x传给Show,此时,编译器会将main中的x的值拷贝到栈中,并产生一个新的变量即Show中的x,也就是说Show中的x用来接收main函数给它传过来的值,这样便完成了两个函数之间的通信。当Show函数执行完毕后,会将Show中x所占用的存储空间释放回系统。从这个传值过程中可以看到, main中的x和Show中的x是独立的,也就是说,在Show执行完毕之前,它们的值是相同的,但它们占用不同的存储空间,这样,不管在Show的实现代码中怎样更改x,都不会影响到main中x的值。所以,main函数调用Show函数后,它的x的值不会发生变化。

通过以上分析可以得到这样的结论:通过传值方式去调用一个函数时,调用者是安全的。不管被调用函数是如何实现的,它都不会改变调用者自己定义的变量的值。这是传值方式的优点。在调用一个函数时,特别是别人编写的函数时,看看被调用函数的原型和功能说明(一般在程序员参考手册中),如果是传值方式,且可以提供你所需的功能,则可以放心大胆地去使用,它是安全的。

2.传址方式

传址方式可以解决拷贝过程对性能的影响。比如,编写一个函数,用来找一个整型数组中各元素的最大值。在这种情况下,选择传值明显不合适。这时,传址方式可能是最好的选择,这个函数的原型如下:

int FindMaxElementOfArray(int * Array,int Len);

调用该函数时,只需要将数组的起始地址和长度分别拷贝给对应的Array和Len即可,不需要进行数组各元素的拷贝,程序性能比传值方式要提高很多,这是传址方式的优点。由于传递的是数组的地址,这个函数完全可以改变数组各元素的值,因此,给程序员一种不安全的感觉。像下面的程序:

///////////////////////////////////////////////////////////

int FindMaxElementOfArray(int * Array,int Len);

void main()

{

int x[10];

for(int i=0;i<100;i++)

{

x[i]=rand(); //一个随机数

}

for(i=0;i<10;i++)

{

cout<<"x["<<i<<"]="<<x[i]<<endl;

}

int MaxElement=FindMaxElementOfArray(x,100);

for(i=0;i<10;i++)

{

cout<<"x["<<i<<"]="<<x[i]<<endl;

}

}

///////////////////////////////////////////////////////////

上面的代码在调用 FindMaxElementOfArray 前后在屏幕上输出数组,输出结果是否一定相同?回答是不一定。如果 FindMaxElementOfArray 的实现代码修改过数组的值,那么调用FindMaxElementOfArray前后的输出就会不一样。你或许会问,FindMaxElementOfArray不应该修改数组的值,你有道理,问题是你怎么知道实现者就一定不会修改数组的值?建议实践中最好避免这样善意的假设。

从上面的讨论可以看出,传址可减少拷贝的字节数,提高程序的性能(特别是对结构体或对象)。假定一个结构体需要1KB的存储空间,如果向被调用函数以传值的方式传递信息,则需要拷贝1KB的数据,但如果采用传址方式,在32位系统上,只需要拷贝4个字节的数据(地址)。但传址的最大缺点就是它的不安全性,这个问题可以通过 const 得到解决。比如,如果上面FindMaxElementOfArray函数的原型为如下形式:

int FindMaxElementOfArray(const int * Array,int Len);

那么对该函数的调用是安全的,就像传值方式一样,调用该函数不会改变调用者自己定义的变量的值。因此,当调用库中或别人编写的函数时,查看函数原型是重要的一步。

当const和指针联合使用时,必须注意const的位置。比如,const int *ptr;表示ptr所指向的内容不可更改,但ptr可更改;而int const *ptr表示ptr所指向的内容可更改,但ptr不可更改。const一般以第一种形式和指针结合来作为函数的参数,既提高程序性能,又给调用者安全的感觉。

3.引用方式

C++提供了第3种函数间的通信方式:引用方式。前面我们讲过,引用类型变量如同某个变量的别名。当调用函数时,若以引用类型数据为参数,则编译器会在该函数中产生一个形式参数,此形式参数即为实在参数的别名,如此在该函数中使用此形式变量便如同使用调用该函数程序中的实在参数一样,因此,以引用类型作为函数的参数来实现函数之间的通信没有拷贝过程的发生,一样可提高程序性能,且比传址方式更有效。

假定用一个结构体来表示一个学生:

struct STU

{

char No[11]; //学号

char Name[11]; //姓名

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载