标题:[连载]《易学C++》初稿完成,请发e-mail索取部分样稿
只看楼主
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

  这次写的是一个短章节。终于开始面向对象啦!其实我觉得我认识的对象还是从VB开始的。一上来就说什么是对象,越说越不清楚。但是一旦先学会了使用对象,那么在使用中就会慢慢体会到什么是对象了。所以本章主要让读者学会使用字符串和向量。只要大家能够看懂程序,会自己改写就行了,不必去深究一些概念。下一章会从概念上更加详细地阐述什么是对象,什么是类。
  字符串和向量的使用中有出现迭代器的概念,虽然它是个非常重要而常用的东西,但是为了不增加大家的负担,我还是避开了这方面的知识。我会根据本章发布后的反馈情况来决定是否以后再补充这个知识点。

本次内容节选:

  其实单词Object更直观的翻译应该是物体。世界就是由各种物体组成的,比如某一辆汽车、某一个人、某一个杯子等等,这些都可以看作对象。
  任何一个对象往往有一些具体的属性,比如某汽车的品牌、型号、排量,某人的性别、身高、体重,某杯子的口径,材质等等。任何一个对象往往能进行一些操作,比如汽车可以开动、拐弯,人可以走路、吃饭,杯子可以被打破等等。
  所以,对象就是任何我们可以想象出来的具体的物体。

  在使用字符串类的时候,我们发现它和字符数组一个很明显的不同就是,我们无法对数据进行直接的修改和操作。如果有一个char a[]="Hello";,那么我们可以直接用a[0]= 'h';来修改存储在内存中的字符,甚至我们可以输出数组的首地址来了解这个数组到底存放在什么位置。而对于一个string a("Hello");,我们却无法直接修改它的数据,因为所有对a的操作都是由成员函数所定义的。我们只能了解这个字符串的存在,但它具体存储在于内存的什么位置,我们无法通过除了对应操作以外的简单方法得知。(如使用取地址操作符)
  由于我们不是字符串类的设计者,当我们对string进行种种操作时,我们只能了解到它的操作结果,而对它的操作原理和操作实现过程却无法得知。
  我们把类的数据不可知性和操作实现过程不可知性称为类的封装性。
  不难理解,作为使用者,我们不需要对数据和操作实现过程感兴趣。就好像买一个手机,我们只关心它是否能够正常通话,正常发短消息,却对它如何接通电话,如何把信号发送出去等等不感兴趣。类的封装性把类的设计者和类的使用者分隔开,使他们在设计程序时互不干扰,责任明确。

  在编写链表程序的时候,我们一定有这样的困惑:链表里面存储的数据类型可能是各种各样的,难道我们要为各种数据类型都写一个链表程序么?我们能不能写一个万用的链表程序呢?
  在PowerPoint之类的软件中,有一种模板功能。模板提供的文档框架是基本完整的,我们只需要在一些地方填写上自己需要的内容,就是一个完整的文档。在C++中,也有这么一种模板(Template),我们只需要在使用之前填写自己需要的数据类型,就是一个完整的程序。我们把具有模板功能的类称为模板类,向量就是一个模板类。在这一节,我们只需要了解如何使用模板类。关于更多模板的知识,将在后面的章节再作介绍。

2006-08-31 21:15
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

这一章竟然写了三个月,看来效率有点低下了。好在自己把一些主要的问题都讲清楚了,至于要想写得更好,还是以后再不断改进吧。我在面向对象的开头写了两章短篇,就是为了将最简单的问题讲清楚,而不给读者产生压力。试想如果学习一个完全不懂的概念,前两章就有半本书那么厚,读者一定会以为这个内容很难了。另外最近在高中里教书的时候发现一些初学者常见的问题,于是对前面的章节有所修改和加强。可见,实践是很重要的。
接下来写的思路是这样,面向对象的基本知识主要是围绕一个链表类的程序来说,比如什么构造函数,析构函数,静态成员数据,类模版等等;继承,多态,友元主要围绕一个文字RPG游戏来说。

本次内容节选:

我们已经了解了数据类型和变量的关系。数据类型是各个变量的归类,而变量则是某个数据类型的具体表现,我们称变量为数据类型的一个实例(Instance)。各个变量都有他们的属性:内容和占用内存空间等等;各个变量都有他们的操作:算术运算或逻辑运算等等。从这个角度来看,类和对象的关系就如同数据类型和变量的关系。我们不妨将类看作一种自定义的数据类型,那么对象就是一种属于该类型的变量。

所谓公有,就是外部可以访问的数据或执行的操作。比如一个人的身高(数据)是可以较直接地获得的,一个人吃东西(操作)是可以受外部控制的。私有就是外部不能直接访问的数据或执行的操作。比如一个人的心跳次数(数据)和消化过程(操作),虽然他们都是客观存在,但我们却不能直接地获取心跳数据或控制消化过程。
如果一个类的所有数据和操作都是公有的,那么它将完全暴露在外,同结构一样没有安全性。如果一个类的所有数据和操作都是私有的,那么它将完全与外界隔绝,这样的类也没有存在的意义。

由于数据封装在类的内部,在处理一些问题的时候就需要小心翼翼,不能把成员数据破坏了。以前我们介绍使用const来保护变量(就变成了常量),或保护指针所指向的内容,那么在类中,我们也可以使用const这个关键字来保护成员数据不被成员函数改变。我们把这种成员函数称为常成员函数。

2006-11-27 08:47
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

这一章主要是讲构造函数和析构函数。因为我在学的时候就认为拷贝构造函数就是构造函数的一种重载,所以把它也拉到这个章节里面来了。至于什么无名对象,至少在大学阶段用的机会不会很多,所以干脆就不说了。这段时间又有好多老师、出版社和我联系出版事宜,我想等到下一章写完,差不多也就剩2-3章左右,到那时候可以好好考虑一下出版的问题。

本次内容节选:

既然init函数担负着初始化对象的重任,那么它就必须和对象的声明“出双入对”了。万一忘记对对象进行初始化,程序就可能会出错。这就像在病毒肆虐的今天,保证电脑安全的病毒防火墙必须在开机之后立刻运行一样。万一哪天开了电脑忘记运行病毒防火墙,那么后果可能很严重。不过,你使用的病毒防火墙是你每次开机以后自己去点击运行的么?那样岂不是很麻烦?你是否知道,我们使用的病毒防火墙往往是随系统一起启动的?这给了我们一些启示:有的程序能够随着系统的启动而自动运行,那么会不会有一种函数,随着对象的创建而自动被调用呢?有!那就是构造函数(Constructor)。

根据程序的运行结果,我们发现头结点的构造函数比链表的构造函数优先运行。这也不难理解:构造函数的目的是要初始化成员数据,初始化成员数据的时候这个成员数据是必须存在的。所以当一个成员数据是一个对象的时候,应当先产生这个子对象,于是就先调用了子对象的构造函数。

我们看到,现在即使运行a.Destroy()之后,链表b里面的数据仍然能够正常显示。这是因为深拷贝构造函数是真正意义上的复制了链表a,并且使得链表a和链表b各自独立,互不干扰。这才是自定义拷贝构造函数存在的重要意义。

在学习链表的时候,我们知道结点是动态生成的,如果在程序结束之前不释放内存,就会造成内存泄漏。虽然我们已经编写了成员函数Destroy来删除所有动态生成的结点,但是如果我们不知道这个链表对象何时不再使用,那么调用Destroy的时机对我们来说就是个麻烦了。如果过早地调用,则后面的程序可能会出错。既然有构造函数能随着对象的创建而自动被调用,那么有没有一种函数能随着对象的消亡而自动被调用呢?有!那就是析构函数(Destructor)。

2007-02-10 16:04
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

这一章是面向对象初级部分的大杂烩,什么静态成员、友元、操作符重载都在。在钱能的书里面,操作符重载独立成章的。但是我觉得操作符重载和友元隔开的话,友元容易被忘记。至于输入输出流是否独立成章,这个到时候再看。下一章将介绍继承和多态,主要围绕一个文字RPG游戏。相信这个游戏能比较好地解释类与类之间的继承关系,也能引起大家的学习兴趣。为了配合出版工作,下一章将会是网上发布的最后一章。如果某些读者有特殊的需要,可以和我联系索取之后章节的电子版。

本次内容节选:

该程序中出现了两种调用静态成员函数的方法,一种是类名::静态成员函数名(参数表),另一种是对象名.静态成员函数名(参数表),这两种调用方法的效果是相同的。由于静态成员函数是属于类的,不是属于某一个具体对象,所以它分不清到底是访问哪个对象的非静态成员数据,故而不能访问非静态成员数据。

类似于链表类和链表结点类的问题,我们可以用友元类来解决。即链表类是链表结点类的“朋友”,可以直接访问链表结点类的私有成员数据或私有成员函数。显然,要做链表结点类的“朋友”,必须要得到链表结点类的认可。所以我们必须在链表结点类的声明中告诉电脑,链表类是它认可的“朋友”,可以访问它的私有成员。

在声明和定义操作符重载时需要注意以下几点:
(1)操作符只能是C++中存在的一些操作符,自己编造的操作符是不能参与操作符重载的。另外,“::”(域解析操作符)、“.”(成员操作符)、“……?……:……”(条件操作符)和sizeof等操作符不允许重载。
(2)参数表中罗列的是操作符的各个操作数。重载后操作数的个数应该与原来相同。不过如果操作符作为成员函数,则调用者本身是一个操作数,故而参数表中会减少一个操作数。
(3)各个操作数至少要有一个是自定义类型的数据,如结构或类。
(4)尽量不要混乱操作符的含义。如果把加号用在减法上,会使程序的可读性大大下降。

前增量操作符是“先增后赋”,在操作符重载中我们理解为先做自增,然后把操作数本身返回。后增量操作符是“先赋后增”,在这里我们理解为先把操作数的值返回,然后操作数自增。所以,前增量操作返回的是操作数本身,而后增量操作返回的只是一个临时的值。

2007-03-09 21:08
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

终于将第17章写完了。这一章是整本书中内容最多的一章。虽然C++在继承这块的内容很多,但是我还是做了删减。比如多重继承这种常常被人批判的内容,我只简要介绍了一下概念。对于本科生来说,这些内容应该已经够用。特别是在企业面试的时候,常常是问一些很基础的概念。面向对象的精髓并不是靠看几本书就能了解的,而是要在实际的工程中才能领悟的。

看到众多网友来信与我交流,肯定我的写作风格、向我提出众多宝贵的建议,我感到非常高兴。本章将会是我公布在网上的最后一个章节。之后的2-3章或许大家以后可以在书店里看到。近期我会和各大出版社联系,讨论出版事宜,以便更多的读者能够快速踏入C++程序设计的大门。如果您有在出版社工作的朋友,并且看好我的作品,也欢迎您主动来和我联系!我的邮箱地址为tomatostudio@126.com

本次内容节选:

如果有一个类,我们可以将其实例化,成为若干个对象。另外,如果我们希望对这个类加以升级改造,我们可以将这个类继承,形成子类(或者称为派生类),被继承的类则称为父类(或者称为基类)。实例化和继承是一个类的两种发展方向。继承能够减少我们开发程序的工作量,提高类的重用性。

如果我们把编写一个类看作是一次生产,那么产品(即编写出来的类)可以有两种用途:一种是将产品直接使用,相当于将类实例化;另一种是将产品用于再生产,相当于将类继承。类在这种不断的“再生产”中变得更为强大、健全。

private是私有继承,或称为私有的实现继承。它主要体现的是父类成员的重用。父类所有的公有、保护成员继承到子类时,类型会发生改变。父类的公有成员在子类中变成了私有成员,父类的保护成员在子类中也变成了私有成员。这时,我们可以利用从父类继承而来的成员函数来实现子类的成员函数,并且不必担心外部直接访问父类的成员函数,破坏了子类的秩序。比如我们认为栈是一种特殊的链表,它只能从链表尾部添加或删除结点,栈的压栈和退栈功能可以方便地由链表类的成员函数实现。但是,如果外部还能直接访问从链表类继承而来的成员函数,那么就可以在栈的任何位置插入结点,栈就会被破坏。

在使用继承的时候,子类必然是在父类的基础上有所改变。如果两者完全相同,这样的继承就失去了意义。同时,不同子类之间具体实现也是有所区别的,否则就出现了一个多余的类。不同的类的同名成员函数有着不同的表现形式,称为多态性。多态性是符合人的认知规律的,即称呼相同,所指不同。比如,学生类及其子类都有学习这个成员函数,但本科生、中学生、小学生的学习内容并不相同;玩家类的子类都有攻击这项技能,但剑士、弓箭手和魔法师的攻击方法不同。

多态是在程序员没有指定调用父类还是某个子类的成员函数时,电脑根据程序员的要求,揣测并选择最合适的成员函数去执行。但是当成员函数的参数格式不同时,程序员在调用成员函数的各种参数无疑就是在暗示到底调用哪个成员函数。这时电脑岂敢自作主张揣测人类的心思?因此,要使用虚函数实现多态性,至少要使各个函数的参数格式也完全相同。

C++中的确能实现多继承,但是在某些问题上并不是处理得很好。比如车和船同时具有长、宽、高等属性,要搞清水陆两用车的长、宽、高到底是从哪个类继承来的,着实要花费一些功夫。应该说,C++中多重继承的思想是优秀的,但是它的实现却是混乱的。有不少人都认为多重继承是C++的一个败笔,它把原本简单的单一继承复杂化了,使程序员很难再把思路理清。所以,即使是经验丰富的程序员,在大多数情况下也不会去使用多重继承。

2007-07-06 10:54
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
得分:0 
佩服楼主,连载了两年。

赞!!!

Fight  to win  or  die...
2007-07-06 11:53
孤魂居士
Rank: 2
来 自:老A(中国地大)
等 级:论坛游民
威 望:4
帖 子:1142
专家分:18
注 册:2007-5-21
得分:0 

准备用3年做个高级软件工程师 10年也做不成。准备用10年做成高级软件工程师 3年就成了QQ 群 45771086
欢迎版主...欢迎JAVA爱好者...
一起从深夜 到凌晨...
2007-07-06 15:30
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

最近更新节选:

cout和cin并不是语句,而是标准输入输出流的类对象。它们分别被定义在istream.h和ostream.h头文件中。那么什么是流呢?简单地说,数据如同流水线上的物品在电脑中传输。要读取流中的数据(把输入流中的数据读到内存中),就如同取下流水线上的物品,这就是抽取;要向流中写入数据(把数据放到输出流中输出),就如同往流水线上放东西,这就是插入。

cerr和cout类似,也是输出流对象。当然,它们也有区别。cout是平时常用的普通输出,可以被重定向。而cerr正如其名,通常是异常情况下输出重要的出错信息。它只会输出到屏幕上,无法被重定向。这样,用户才不会因为信息被重定向而错过了重要的出错提示信息。

为什么不能像标准输入输出流一样有个现成的fin或者fout对象呢?这也不难理解:文件输入输出流的输入输出设备是磁盘文件,但是磁盘上有那么多文件,到底应该使用哪一个呢?所以,C++干脆就把创建对象的任务交给用户了。我们可以在创建对象自动调用构造函数时,设定输入或输出到哪个文件。

我们用形如cin >>a;的方式输入数据,就像是按“个”为单位取下流水线上的物品。因为侧重点在于单个物品,空格就犹如分隔物品的纸箱格,与其他数据一起被丢弃。我们也可以使用cin的成员函数getline来获取数据,这时候就像按“箱”为单位取下流水线上的物品。纸箱格作为取下的东西的一部分,不会被丢弃。

我们已经知道,插入操作符应该有两个操作数。前者是ostream类的对象(引用),后者是需要输出的内容,两者的次序不应该颠倒。如果把插入操作符作为成员函数,那么就会影响了操作数的次序。因此,通常情况下,我们将插入操作符作为友元函数。

2007-07-28 23:14
野比
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:24
帖 子:1627
专家分:516
注 册:2007-5-24
得分:0 
不错..只是...可不可以在前面加个目录啥的呢? 那就更好了..

女侠,约吗?
2007-07-29 09:10
p1s
Rank: 4
等 级:贵宾
威 望:10
帖 子:454
专家分:3
注 册:2005-11-4
得分:0 

最近更新节选:

通常情况下,我们把实现某一个独立功能的模块编写为一个函数。在函数的参数列表中,需要说明各个参数分别是属于什么数据类型的。然而,有的时候函数的功能与参数的数据类型是完全无关的。最典型的功能就是变量数据的交换:不论变量a和变量b的数据类型是什么,只要它们两者数据类型相同,并且能交换,这个函数就完成了它的使命。

除了函数可能与参数的数据类型无关之外,类也可能与之服务的数据类型无关。比较典型的就是用于存储数据的类。数据的类型众多,为了让类能够存储各种各样的数据,不见得给每个数据类型都定义一个类吧?这时后,我们想到了模板。当我们要使用一个类时,可以根据需要在类模板中填写合适的数据类型,生成一个模板类。

从原理上来说,我们先给类模板填上数据类型,使其成为一个模板类。如Linklist<int>和Linklist<char>都是模板类,它们是类模板的一个类实例。然后,我们可以创建模板类对象。如链表a和b,他们都是模板类的对象实例。所以,从类模板到对象应该经过两个实例化的过程。

2007-08-16 15:23



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-37649-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.082567 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved