zl程序教程

您现在的位置是:首页 >  后端

当前栏目

《C++面向对象高效编程(第2版)》——1.1 背景

C++编程 高效 面向对象 1.1 背景
2023-09-11 14:17:36 时间

本节书摘来自异步社区出版社《C++面向对象高效编程(第2版)》一书中的第1章,第1.1节,作者: 【美】Kayshav Dattatri,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.1 背景

C++面向对象高效编程(第2版)
程序员开发软件的历史已有数十年,他们使用各种不同的编程语言,如Algol、COBOL、Lisp、C、Pascal等,来实现各种规模的软件开发——从简单的小程序到复杂的大型系统。[我们把编写诸如汉诺塔的解决方法、纸牌游戏、简单的快速排序等小程序,仅作为课程学习的作业。虽然这些小程序没有任何商业价值,但能帮助我们理解新的概念和新的语言。相比之下,大型系统(如库存控制、字处理、医院患者管理、天气预报、个人资金管理等软件系统)涉及许多重大问题,这样的软件系统,需要由设计者和程序员组成的小组协同工作才能实现,然后由公司出售,赚取利润。我们从设计和实现小程序中学到的知识和经验,对解决大型问题会有很大帮助。] 日常生活中,我们使用的系统都由这些语言实现。在开发和使用它们的过程中,我们已获得许多知识和经验,为什么还要学习新的编程范式?继续阅读以下内容,答案就其中。

1.1.1 面向过程编程示例

如果给定一个问题(即问题的口头或书面说明),如何使用某种语言(如C)设计和实现解决这个问题?首先,我们将该问题分解为多个便于处理的部分,这些部分称为模块。然后,设计出许多数据结构保存数据,并实现一些函数(也称为过程、例程)来操作这些数据。函数可用于修改数据结构,将它们保存到文件中,或打印数据。在面向过程编程系统中,解决问题的办法就是将待解决之事转换成一组函数。也就是说,我们把注意力都集中在函数身上,没有函数就无法完成任何操作。这种以函数为主的编程方法称为面向过程编程(Procedure-Oriented Programming)。之所以称为面向过程,是因为它的重点在过程。这种编程方法从函数的角度来思考问题,因此也称为问题的功能分解。

注意:
在C和C++中,过程、函数、子程序和例程这些术语之间没有差别。然而,在Pascal、Modula-2和Eiffel中,函数是指返回计算值的例程,而过程是指接收某些参数并执行一项操作,但不向主调函数返回任何值的例程。在本书中,过程、函数和例程将互换使用,它们的含义相同。Algol、Fortran、Pascal和C等编程语言都称为过程语言(procedural language)。
然而,经过更深入地研究面向过程的实现发现,数据结构更重要。我们最感兴趣的是保存在数据结构中的值,而非过程本身。过程只是修改数据结构的简单工具,没有数据结构,过程什么也做不了。而且,在程序的运行过程中,数据结构中的数据不断改变,而过程的代码丝毫未变。从这个角度而言,过程是静态的。我们之前费尽心思设计这些过程,殊不知重点根本不在这里。举个简单的例子,假设一个银行系统,允许客户有不同类型的银行账户(如存款账户、支票账户和贷款账户),允许客户存款、取款以及在账户之间转账。如果该系统用C实现,可以看到以下一组过程1:

typedef unsigned long AccountNum;

typedef int bool;

bool MakeDeposit(AccountNum whichAccount, float amount);

float WithDraw(AccountNum whichAccount, float howmuch);

bool Transfer(AccountNum form, AccountNum to,

 float howmuch);```

我们可以规定AccontNum只为正整数,然后用一个简单的数据结构来管理账户:

// 这是一个极其普通的银行账户档案
struct Account {
char name; / 账户名 */
AccountNum accountId;
float balance;
float interestYTD; / 数据利息的年份/
char accountType; / 存款,支票,贷款等/
/ 其他细节 /
};`
可通过下面的函数创建一个Account结构的实例,为客户创建一个账户。

AccountNum CreateNewAccount(const char name[], char typeOfAccount);

1.1.2 银行账户的表示

创建账户的函数将返回新账户的账号。我们在研究这种解决方案时发现,客户将银行账户视为他们血汗钱的安全天堂,他们感兴趣的是账户中的资金和获得的利息,而非存取款功能。事实上,客户并不关心在银行系统中存款或取款的过程如何实现,他们只需要一种简单方便的方法完成操作。但是,作为程序员,我们却冥思苦想如何编写MakeDeposit函数(以及其他函数),如何创建一个小型数据结构管理数据。换句话说,我们把注意力放在了客户毫不关心的问题上。正因如此,我们设计出来的银行系统导致客户与其银行账户间毫无关系,客户仅仅被看做是一系列的字符和数字。这样的系统甚至根本无需考虑核对账户持有人和账户中的内容,就可直接操控数据来操作账户。从函数角度看,银行账户仅仅是一串数字——账号。

1.1.3银行账户的安全

进一步分析可以注意到,任何人(或者其他程序或者程序员)都可以创建一个账户,并操作它。因为账户仅作为一段数据保存,任何可以访问银行账户档案的人都可以修改它(甚至非法的)并取款。这是账户被当做一系列字符和整数的后果,保存在客户银行账户中的值没有任何保护措施。而且,也没有任何条款规定银行账户必须只能由可信任的银行职员修改,即使有,又由谁来执行?语言(如C或者Pascal)不能做到这一点,因为它们并不知道银行账户和普通整数的区别。

如果要打印客户的账户,需要添加一个新函数:

PrintAccount(Account thisAccount);
该函数将执行打印功能。但是,函数必须知道正在打印何种类型的账户(支票账户还是存款账户?)。这很容易,只需查看accountType中的值即可。假设开始时我们有三种账户(支票账户、存款账户和贷款账户),PrintAccount()函数理解这些类型,它将对其中的代码进行硬编码(hard coded)。到目前为止,一切运行正常。现在再添加一个新的账户类型——retirement_account。如果传递retirement_account给PrintAccount函数会出现什么情况?函数将无法正常工作。我们会看见以下的错误信息:

“Unknown acount type- Cannot Print”(“未知账户类型-无法打印”)
或者更糟:

“Illegal account type – Call Supervisor”(“非法账户类型-联系主管”)
之所以会出现这种情况,是因为在该系统中对账户的类型采用了硬编码方式,除非修改源代码,重新编译并再次链接,否则无法通过编译。因此,如果添加一个新账户类型,我们需要修改与该信息有关的所有函数,并重新进行编译—链接—测试过程。这些过程都十分冗长,而且极易出错。怎么会出现这样的问题?答案是:函数和数据结构被当做是彼此脱节的实体,因此函数难以理解数据结构中的改动。也就是说,对现有的实现进行改进非常困难。这样看来,我们只能以另一种方式建立银行系统,使得该系统在添加新账户类型不会影响其他账户类型,而且不会引起代码的重新编译。

以上谈到的问题都源于在最初的解决方案中误入歧途。我们将重点错误地放在自认为很重要的函数上,彻底忽略了对客户(即银行客户)而言更为重要的数据。换言之,我们之前全神贯注于如何做,其实应该将重点放在做什么上。这就是面向对象编程和面向过程编程的区别。

1.1.4 用面向对象编程解决问题

如果用面向对象编程(object-oriented programming)技术解决此银行账户的问题,我们会把注意力放在银行账户上。首先分析客户想用这个账户进行什么操作,对他们而言什么最重要,诸如此类的问题。简而言之,在面向对象编程中,重点是正在被操作的数据,而不是实现这些操作的过程。我们应该先找出用户(该例中是银行客户)想用数据(即银行账户)进行什么操作,然后再为其提供必要的操作。而且,数据和操作不像以前那样被当作彼此孤立的实体,现在它们被看做是一个整体。数据带有一组操作,允许用户对数据执行一些有意义的操作。同时,任何外部程序或过程无法直接访问数据本身。修改银行账户内数据唯一的方法,就是使用为修改数据而提供的操作,这些操作为用户提供修改银行账户的行为。现在,我们可以说,银行账户是一个类(class),我们可以创建任意数量的银行账户实例,每个实例都是银行账户类的对象(object)。因此,面向对象编程是一种由合作对象(cooperating object)(就是类的实例)构成程序的编程方法。而且,多个类之间可通过继承关系相互关联2 。

在面向对象编程中,关键要理解类和对象的概念。类是一个实体(entity),它拥有一组对象的共同属性(特性)。对象是类的实例(instance),类的所有对象都具有相同的结构和行为。可以把类看做是切甜饼的工具,而甜饼就是切甜饼的工具所创建的实例。这个比喻可能有些粗浅,但类似于类创建对象的过程。切甜饼的工具决定了甜饼的大小和形状(不是味道)。与此类似,类决定了创建对象的大小和行为。在面向对象的解决方案中,一切皆为类的对象3。这样看来,在面向对象编程中,我们只需考虑类和对象、类之间的关系,以及对象之间的关系4。Shalaer-Mellor 5 以另一种方式解释了面向过程编程和OOP(面向对象编程)之间的区别——功能分解按顺序提出了系统设计中的三要素(算法、控制流和数据描述),而在OOP中,此三要素的顺序完全相反。

现在,重新回到银行账户的问题上。我们重点关注的是银行账号,将它设计成一个类。在C++中,BankAccount类的框架如下所示。这里再次提醒读者,请不要过多担心C++的语法。

class BankAccount {

 public:

 // 为简化起见,此处省略若干细节

 void MakeDeposit(float amount);

 float WithDraw();

 bool Transfer(BankAccount to, float amount);

 float GetBalance() const;

 private:

 // 供BankAccount类使用的私有数据实现 

 float balance;

 float interestYTD;

 char* owner;

 int account_number;

};```

对客户(即希望打开银行账户和使用它的人)而言,最重要的是该类中public区域内可访问的操作(在图1-1中以粗体表示)。而类中private区域内的声明只能在其内部使用,客户不可从外部访问。例如,MakeDeposit操作的实现如下所示。再次提醒读者,请不要担心C++语法。

// BankAccount类其中一项操作的实现
void BankAccount::MakeDeposit(float amount)
{
if (amount 0.0)
balance = balance + amount;
}`
只有在BankAccount类内部声明的操作,才能访问私有成员balance(和其他私有成员)。类的外部无法使用私有成员。我们将这种私有数据称为被封装的数据,以这种方式隐藏在类的内部称为数据封装(data encapsulation)。我们将在第2章中详细介绍数据封装。

image

1无需过多关注语法,只需注意过程和参数的概念。不同的语言都通过不同的语法进行声明。
2继承将在第5章中详细介绍。
3在C++中,如果将main()程序看做是根对象(root object),的确可以说一切皆为类对象。
4在某些语言中,对象可以创建其他类。
5译者注:Shalaer-Mellor软件设计方法,由Sally Shlaer和Stephen Mellor共同研发,是众多面向对象分析(OOA)/ 面向对象设计(OOD)方法之一。


【C++要笑着学】面向对象总结 | 瞎编的C++小故事 | 再次理解封装 | 再次理解面向对象 我是柠檬叶子C。本篇将对之前讲的面向对象的内容进行一个梳理,通过举一些例子去感受C和C++之间的区别和联系。举了一个比较有意思的胡编乱造的故事(bushi)。文章的最后会再次理解一些概念,强调封装的意义,加深对 面向对象 的理解。如果觉得文章不错,可以 一键三连 支持一下博主!你们的关注就是我更新的最大动力!
C++面向对象课程设计报告_快递系统 面向对象程序课程设计 题目:快件管理系统 该系统为两种角色的用户提供服务,一种是代收点服务人员,一种是收件人。代收点服务人员根据账号、密码登录系统。收件人无需登录即可使用系统。代收点服务人员可将快件信息录入系统,快件信息包括快递单号、快递公司、收件人、收件人联系电话、收件人地址、邮编、寄件人、寄件人联系电话、寄件...
从c++到Java,关于Java面向对象基础的学习(二) 继续补充完成java面向基础对象学习的第二部分,本章内容主要包括构造器和this以及javabean和封装等思想,感觉仍然需要更多练习才能帮助熟练java面向对象基础部分
从c++到Java,关于Java面向对象基础的学习(一) 一、设计对象并使用 1、学习获取已有对并使用学 2、学习如何自己设计对象并使用 3、定义类的补充注意事项 二、面向对象内存机制 1、多个对象内存图 2、两个变量指向同一个对象内存图 3、补充(垃圾回收)
C++面向对象友元,全局函数、类、成员函数做友元 在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术 友元的目的就是让一个函数或者类 访问另一个类中私有成员 友元的关键字为 ==friend== 友元的三种实现 • 全局函数做友元 • 类做友元 • 成员函数做友元 全局函数做友元
异步社区 异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。