版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第2章类(class)2.1类的概念 2.2隐藏实现
2.3访问控制
2.4访问控制 本章小结
习题
2.1类的概念面向对象的程序设计是一种程序设计技术。面向对象的程序设计语言的意义是:某种程序设计语言特别提供了一些机制,以很好地支持使用该语言进行面向对象的程序设计。我们能在C中写出面向对象的程序,但这会非常的困难,因为它不直接支持这些机制。设计C++就是为了支持数据抽象、面向对象的程序设计和通用型程序设计,以及在这些风格约束下的传统的C程序设计技术。从本章开始,我们将介绍这些风格。本章介绍类—— C++里的数据抽象,它与第1章介绍的内部类型同样方便。2.1.1面向对象思想面向对象语言中的类就是对真实世界中的种类的抽象描述。例如,“人类”就是真实世界中的一个种类,那么,什么是人类?怎么来描述人类?首先来看看人类所具有的一些特征,这个特征包括属性(一些参数、数值,也就是后面要介绍的类的成员变量)以及方法(一些行为,他能干什么,也就是后面要介绍的类的方法)。每个人都具有身高、体重、年龄、血型等“属性”以及会劳动、会直立行走、会用自己的头脑去创造工具等“方法”。人之所以能区别于其他类型的动物,是因为每个人都具有人这个群体的属性与方法。“人类”只是一个抽象的概念,它仅仅是一个概念,它是不存在的实体,但是所有具备“人类”这个群体的属性与方法的对象都叫人。这个对象“人”是实际存在的实体。每个人都是人这个群体的一个对象。老虎为什么不是人?因为它不具备人这个群体的基本特征(属性与方法) ——老虎不会直立行走、不会使用工具等等,所以说老虎不是人。由此可见,类描述了一组有相同特性和相同行为的对象。在面向对象程序中,类实际上就是数据类型,如整数、小数等。整数也有一组特性和行为:整数的特性为int,整数的行为有 +、-、×、/ 等事件行为。面向过程的语言与面向对象的语言的区别就在于,面向过程的语言不允许程序员自己定义数据类型,而只能使用程序中内置的(已经定义好的)数据类型。为了模拟真实世界,为了更好地解决问题,程序员往往需要自己创建解决问题所必需的数据类型,比如前面讲的“人”这个数据类型。2.1.2类与对象的定义抽象数据类型就是将类以属性和方法组合的方式进行的抽象描述。面向对象的编程语言最大的特色就是可以定义自己所需的抽象数据类型,也就是上一节提到的类,以更好地解决问题。下面回顾一下“类”、“对象”、“属性”、“方法”之间的关系。就像前面所说的,人这个“类”是什么也做不了的,因为“人类”只是一个抽象的概念,它不是实实在在的“东西”,而这个“东西”就是所谓的对象。只有人这个“对象”才能去工作。而类是对象的描述,对象从类中产生,对象具有类所描述的所有属性与方法。比如电视机都有工作原理图,那么什么叫电视机呢?只要能够实现电视工作原理图的所有功能的物体,都叫电视机。可是,电视机原理图是不能工作的,也即这个原理图不能收看节目,只有电视机这个“实体”,即所谓的“对象”才能收看节目。也就是说,从类生成对象之后才真正有意义,才能开始工作。此时,电视机拥有电视原理图所描述的所有属性与方法。每一个实体都是对象。有一些对象具有相同的结构和特性。每个对象都属于一个特定的类型。在C++中对象的类型称为类(class)。类代表了某一批对象的共性和特征。类是对象的抽象,而对象是类的具体实例(instance)。
C语言中的结构体struct是类的基础,类就是由struct发展而来的。例如,C++中用户可以这样定义“人”(person)这个类://Person.husingnamespacestd;
classPerson{stringname; //姓名
intage; //年龄
charsex; //性别
//…};
可以看到“人”这个类是由多种内部类型的数据组成的。在这个例子中,它包括stringname(姓名,字符型)、intage(年龄,整数型)和charsex(性别,字符型)三个数据类型。这个类是内部类型的数据的集合,它和C语言中的结构体struct很相似。但并不是所有的类都如此,某些类中可能会包含自定义的数据类型,并且那些包含自定义的数据类型的类在C++编程中是很常见的。
注意:类定义后面要加分号(;),而C里的struct定义后面不加。类和对象的关系就如同结构体类型和结构体变量的关系,结构体中需要先声明一个结构体类型,然后用它去定义结构体变量。在C++中也是先声明一个类类型,然后用它去定义若干个同类型的对象。对象就是类类型的一个变量。
类定义的一般语法格式如下:
class类名
{public:
数据成员,成员函数的说明;
prutected:
数据成员,成员函数的说明;
Private:
数据成员,成员函数的说明;
};
先定义类,然后就可以定义对象。对象定义的一般语法格式如下:类名对象1,对象2;2.1.3成员变量及成员函数类的成员函数是函数的一种,它的用法和作用与C语言中的函数基本上是一样的,它与一般函数的区别是:它属于一个类的成员,出现在类体中;它可以被指定为private(私有的)、public(公用的)或protected(受保护的)。在使用类函数时,要注意调用它的权限以及它的作用域。下面来看看写的类能够干什么以及是怎么工作的。例如,有一个员工叫“peter”,修改他的名字后对其进行输出。
【程序2.1】//Person.cpp#include<iostream>;#include<string>;usingnamespacestd;classPerson{ stringname; //姓名
intage; //年龄
charsex; //性别
};intmain(){ Personpeter; ="peter"; cout<<<<endl; return0;}
首先看看自定义的数据类型Person,在应用时它和int类型的数据相似,两者都需要创建变量(对象peter),只不过前者是自己定义的,而后者是基本数据类型。main()函数是整个函数的入口,int表示main()函数将要返回一个整型值,一般0表示程序正常结束,非0表示程序异常结束;也可以分别用EXIT_SUCCESS和EXIT_FAILURE表示0和非0值,这样更易读。“Personpeter;”定义一个Person类型的对象peter;“="peter";”试图修改peter的name属性,将其赋值为"peter";“cout<<<<endl;”试图输出peter的name属性,endl表示换行。
编译这段代码,发现编译器提示以下信息:
errorC2248:'name':cannotaccessprivatememberdeclaredinclass'Person'errorC2248:'name':cannotaccessprivatememberdeclaredinclass'Person'
定位出错的语句,发现错误产自以下语句:
="peter";cout<<<<endl;
提示信息的意思是,程序无法访问私有(private)变量name。上述第一句试图修改name属性,第二句试图获得name属性并输出。因此,错误信息是两条。产生错误的原因是:类的属性包括私有属性和公有属性。私有属性是无法通过对象名直接获得的,即类似于这种访问方式是无效的。而公有属性可以通过对象名直接访问。那么,Person类中的属性是私有的还是公有的呢?似乎没有特别说明过。此处需要注意的是,在C++中,默认的类的属性的修饰符是private。如何修改这个错误呢?只需要将属性定义为公有属性就可以了。修改后的代码如下:【程序2.2】classPerson{public: stringname; //姓名
intage; //年龄
charsex; //性别
};
这样错误就被改正了。重新编译程序并运行,运行结果如下:
由程序2.2可以看出,只是在属性前加了语句“public:”,这样它后面的属性就被定义为公有属性。前面介绍过,类是属性与方法的集合。为了实现数据的封装,提高数据的安全性,一般应该把类的属性声明为私有的,而把类的方法声明为公有的。这样,对象能够直接调用类中定义的所有方法,当对象想要修改或得到自己的属性时就必须调用已定义的专用的方法才能够实现。因此,我们提倡“对象调用方法,方法改变属性”。也许有人会说,直接定义属性为公有的,就可以省去写公有方法的麻烦了。乍一看似乎有道理,其实不然。这样的设计原则是有好处的,比如,类的属性一般都有自己的取值范围或变化规则,用成员方法来访问就可以在成员方法里面加入这些限制和规则,使程序更加健壮。更极端的情况是某一属性允许被访问但不允许被修改,这样的属性声明为私有属性,只提供一个访问该属性的成员函数即可。例如上一节定义的Person类,将其中所有的属性都修改为private,即定义为私有属性,对这些属性的读取和修改只能用用户提供的方法。对Person类的修改如下:
【程序2.3】//Person.husingnamespacestd;classPerson{private: stringname; //姓名 intage; //年龄
charsex; //性别
public: voidsetName(stringiniName) {//这个方法是修改员工的姓名
name=iniName; } stringgetName(){//这个方法是得到员工的姓名
returnname; } intgetAge(){//只允许访问,提供get方法。而不允许修改,不提供set方法。
returnage; }};//Person.cpp#include<iostream>;#include"person.h";usingnamespacestd;intmain(){Personpeter; //定义一个Person的对象(最好用中文)peter.setName("peter"); //将对象名赋给perterstringp=peter.getName(); //获取对象名
cout<<p<<endl;return0;}
运行结果如下:
现在,Person这个类中包含许多属性和方法。此时,不能直接用创建好的对象调用它的属性来进行修改。因为它的属性是private类型的,是不能够被直接引用从而进行修改的。要想修改姓名就要用对象调用setName()方法,而要得到姓名就要调用getName()方法。这就是“对象调用方法,方法改变属性”。下面来看看修改后的person.cpp文件。“#include<iostream>;”的意思是说下面的程序要用到iostream中已经定义好的类来工作,比如屏幕输出类的对象cout;“#include"person.h"”的意思是下面要用到定义的Person类了;“Personpeter;”定义一个Person类型的对象peter,“peter.setName("peter");”设定了peter这个对象的属性name的值为"peter";“stringp=peter.getName();”获得peter的属性name的值,并赋给p变量,最后用“cout<<p<<endl;”打印出这个名字。
程序中对peter这个对象进行操作才会有实际意义。千万不要有这种想法:“试图对类进行操作!”这是毫无意义的。对象peter拥有了类所描述的所有的属性及方法,下面一一列举:
/*所有的Person对象都拥有这些属性。每创建一个对象就会重新分配一块内存来存放相应对象的这些属性。每个对象都有自己“独特”的一份。*/
private:stringname; //员工姓名
intage; //员工年龄
charsex; //员工性别/*所有的Person对象都拥有这些方法。但在内存中只有一份*/public:voidsetName(stringiniName){//这个方法修改姓名
name=iniName;}stringgetName(){//这个方法得到姓名
returnname;}intgetAge(){//这个方法得到年龄
returnage;}
实际上在创建peter这个对象时计算机只给这个对象的所有的属性分配了内存,而并没有给方法分配内存。方法只有一个,是属于所有的对象的,所以无论创建了多少个对象,计算机只会为一个方法分配一块内存。所有的方法其实都是一样的,不必为每个对象都拷贝方法,但不同对象的相同属性的值是不一样的,所以必须给每个对象的属性都分配内存来保存这些不同的数据。
对于上面的实例,它已经能完成绝大部分工作了,但它还是不完善的,还有许多的细节需要去完善。也许有的读者已经注意到了,当创建完“peter”这个对象时,这个对象的所有属性都是空的,也就是说,这个对象的姓名是未定的、年龄是未定的、性别是未定的。而想把这些属性都添加上去,就还要用对象调用相应的方法,去一个一个地修改。有没有什么好方法能够在创建对象的同时完成对属性的初始化呢?答案是肯定的,这就需要所谓的构造函数。构造函数是类中最特殊的函数,它与析构函数的功能正好相反。构造函数具有以下特征:
(1)它没有返回值类型;(2)它的名称与类的名称必须完全相同;
(3)可以对构造函数进行重载;
(4)它在创建对象时自动被调用。构造函数可对类中的属性进行初始化工作。上面的程序没有自己定义构造函数,在这种情况下,系统会自动定义一个“默认构造函数”。默认构造函数是没有参数的构造函数。如果程序员定义了构造函数,那么系统就不会再为你的程序添加一个默认构造函数了。(通常提倡自己定义构造函数,而不用系统的默认构造函数。)【程序2.4】//Person.hclassPerson{private: stringname; //姓名
intage; //年龄
charsex; //员工性别
public: Person(){//这个就是默认构造函数
name="peter"; //设置姓名 age=20; //设置年龄
sex="M"; //设置性别
} voidsetName(stringiniName){//这个方法是修改员工的姓名
name=iniName; } stringgetName(){//这个方法是得到员工的姓名
returnname; }intgetAge(){//只允许访问,提供get方法。而不允许修改,不提供set方法
returnage;} //…};
在创建“peter”这个对象的同时,它所有的属性也被初始化了。显然,这大大地提高了工作效率,但是,它还是不符合要求。想想看,如果现在创建这个类型的第二个对象,会发生什么事情?实际上,除了对象的“名”(这个名称不是对象属性中的name属性,而是对象本身的名称)不一样外,其所有的“属性值”都一样。比如,现在创建第二个对象john,会发现这个对象的所有属性和peter这个对象的所有属性完全相同。因此只能再用对象的方法去改变写属性了。很显然,这种方法不太好,需要一种在创建对象时为对象的属性赋予“想要的值”的方法。默认构造函数就显得无能为力了。需要的是带参数的构造函数,在创建对象时,把参数传给构造函数,这样就能完成上述功能。【程序2.5】//Person.hclassPerson{private: stringname; //姓名
intage; //年龄
charsex; //员工性别
//…public:Person(stringiniName,intiniAge,chariniSex){//看这个构造函数
name=iniName; //设置姓名
age=iniAge; //设置年龄
sex=iniSex; //设置性别
} //…};
这样一来,在创建对象的同时就可以赋予想要的值,很显然,这是方便而有效的。例如,“peter=Person("peter",20,'M');”,这样,所有的工作都完成了(在创建对象的同时赋予了想要的“初值”)。2.2隐藏实现在整个设计和编码过程中,都应该贯穿着隐藏实现的思想,这是一个很重要的概念。将程序员分成类的创建者(classcreator,即创建新的数据类型的人)和客户程序员(clientprogrammer,即使用这些类编程的人)能帮助更好地理解这种思想。客户程序员最需要的是直接使用一个已经实现功能的类,对他们来讲,也许不知道这个类是如何实现的,但是这个类为他们提供了使用方法,使得他们能够简洁地去使用一个现成的东西。而对于创建者来说,源程序一般是不会向客户提供的,他们只给客户提供使用方法。就像手机一样,他们不会给客户说明这部手机是如何制造出来的,而客户也只是关心这部手机该如何使用,创建者可以将它们的实现隐藏起来。这样,创建者就有了自己的权利,也不用担心程序会被恶意破坏和修改。当客户使用创建者的类时,客户的许多功能也许就是基于创建者的功能之上的,也许还会创建出更多的类,而这些类又可能会被其他人所使用。而最基本的东西,就是创建者原先提供给客户的类。创建者必须去保护它,维护它的正确性,这样才不会造成更大的错误。“接口”(Interface)是已创建好的类和客户程序员的桥梁,客户程序员必须遵照接口的规定去使用它。在实现的背后必然存放着某些代码和数据,这些代码与那些隐藏起来的数据叫做“隐藏的实现”。客户程序员只需使用接口去发送请求,隐藏起来的这些程序会帮他完成剩下的问题,而客户不用去关心程序是如何实现的。在程序内部,有许多数据是不想让客户使用的,可能是因为这个数据是一个辅助作用,只是为了完成自己的功能而创建的,它们没有功能可以向客户提供,所以也不必要让客户知道。也可能是因为这个数据是不应该被使用的,使用它会造成错误,可能程序提供了其他的函数来使用它以避免错误,这样它更不应该对客户开放。在这里举个简单的例子,让读者初步体会隐藏实现的作用和意义。在这里,对Person类做了简化。先看程序2.6。【程序2.6】#include<iostream>;#include<string>;usingnamespacestd;classPerson{private: intage; //年龄
public: voidsetAge(intinitAge){ age=initAge; }};intmain(){ Personpeter; Person*p=&peter; peter.setAge(21); void*add1=(void*)p; int*add2=(int*)add1; *add2=33; cout<<*add2<<endl; return0;}
运行结果如下:
可以看到,在这个例子中,Person类只有setAge()成员方法,而没有getAge()成员方法。这样设计的目的是不希望类的使用者获得成员属性age。然而,通过一种特殊的方法(指针),不但获得了私有属性age的值,而且还将age的属性值修改为33。这种特殊的方法将在下一节“访问控制”中详细讨论。这种指针的方法之所以能够成功,很大程度上是因为类的使用者可以了解类的详细的定义内容。在这个例子中,使用者知道Person类中含有一个int型的age属性,所以才能够通过整型的指针(int*)访问和修改私有属性age。为了避免这种情况出现,可以将实现部分与接口部分分开,并将实现部分尽可能地隐藏起来。将程序2.6修改如下:【程序2.7】//Person.h#ifndefPERSON_H#definePERSON_HclassPerson{ structInformation; Information*p;public: voidinitialize(); intgetAge();voidsetAge(int);};#endif//PERSON_H//Person.cpp#include"Person.h";structPerson::Information{ intage;};voidPerson::initialize(){ p=newInformation; p->age=31;}intPerson::getAge(){ returnp->age;}voidPerson::setAge(intinitAge){ p->age=initAge;}
//UsePerson.cpp#include"Person.h"intmain(){ Personpeter; peter.initialize(); peter.setAge(10); peter.getAge(); return0;}
在这个例子中,将Person类分成了两部分,分别是接口部分即Person.h和实现部分即Person.cpp。在头文件Person.h中,只包含公共的接口和一个指针,这个指针指向一个没有完全定义的类。所有程序员都能看到下面这行:
structInformation;
这只是一个不完全的类型说明或类声明,它没有提供有关该Information的任何细节。而类的实现部分被隐藏在Person.cpp中,并且Person.cpp不需要向程序的使用者提供。这样使得安全性和可扩展性都得到了提高。在任何项目中,都应该尽可能地从隐藏实现的角度去考虑问题。C++语言采用三个显式(明确)关键字即public、private和protected来设置类边界。这样能很好地保护和开放资源。这些关键字的使用和含义都是相当直观的,它们决定了谁能使用后续的定义内容。“public”(公共)意味着后续的定义能被任何人所使用,将其开放出去。“private”(私有)意味着除类的创建者以及类的内部函数成员,其他任何方式都不能访问后续的定义信息。private很好的保护了私有变量,使得它们不被其他类所访问。“protected”(受保护的)与“private”相似,不同的是一个继承的类可访问受保护的成员,但不能访问私有成员。有关继承的详细内容将在第9章介绍。2.3访问控制在C++中,用户可以说明成员数据和成员函数的访问级别。共有三种访问级别:公共的(public)、保护的(protected)和私有的(private)。它们用于在结构中设置边界,被称为访问说明符(accessspecifier)。无论什么时候使用访问说明符,后面都必须加上一个冒号。那么,设置这些访问控制的理由是什么?理由有两点:一是信息的隐蔽,即以后将要提到的封装,将类的内部实现和外部接口分开,这样当客户程序员要使用这个类时不需要关心类的内部实现而只需要了解该类的接口。而且对类进行维护时只需修改类的实现部分,基本上不用修改接口。二是数据的保护,即将程序员认为的类中的重要的信息保护起来,以免其他程序的不恰当的访问或不恰当的修改。2.3.1private
具有private访问控制级别的成员是完全保密的,即只能通过指向当前类(不包括派生类)的this指针才可以访问,其他环境均无法直接访问这个成员。如果有人企图访问一个私有成员,就会产生一个编译错误。
例如下面的Person类。
【程序2.8】classPerson{private:stringname;public: stringgetName(){ returnname; //类的成员可以访问类的私有成员
} voidsetName(stringinitName){//类的成员可以访问类的私有成员
name=initName; }};
下面定义该类的一个实例:
Personperson;
其成员访问的合法性如下:
; //非法,name为Person的私有成员类Person的私有成员name可以通过提供给外部访问的接口getName()和setName()来访问。2.3.2protected
具有protected访问控制级别的成员是半公开的,外界无法直接访问这个控制级别的成员,但是派生类的this指针可以获得访问能力。protected与private基本相似,只有在继承时有较大的区别。继承的类可以访问protected成员,但是不能访问private成员。有关继承的详细内容,将在第9章介绍。2.3.3public
具有public访问控制级别的成员是完全公开的,任何环境下都可以通过对象访问这个成员。同样稍微更改一下程序2.8中的Person类,改动后如程序2.9所示。
【程序2.9】classPerson{public: stringsex;public: stringgetSex(){ returnsex;//类的成员可以访问类的公有成员
} voidsetSex(stringinitSex){//类的成员可以访问类的公有成员
If(initSex!="male"&&initSex!="female"){ cout<<"Wronginput,Pleaseinputmaleorfemale!"<<endl; }else{sex=initSex; } }};
同样定义该类的一个实例:
Personperson;
其成员访问的合法性如下:
person.sex; //合法,sex为Person的公有成员类Person的公有成员sex还可以通过提供给外部访问的接口getSex()和setSex()来访问。
综合以上三个例子可以看出,访问指示符决定了跟在它后面的名称的访问级别,一直影响到下一个访问指示符出现或类说明结束为止。现在可以用一段简单的并且完整的程序来体现访问控制的作用。如下面的Person.cpp,为了避免累赘和重复,只注释了main函数中的语句。
【程序2.10】//Person.cpp//展示访问控制的作用
#include<iostream>#include<string>usingnamespacestd;classPerson{public: stringsex;protected: intage;private: stringname;public: Person(){}stringgetSex() { returnsex; } intgetAge() { returnage; } stringgetName() { returnname; }voidsetSex(stringinitSex) {//类的成员可以访问类的公有成员
if(initSex!="male"&&initSex!="female") { cout<<"Wronginput,Pleaseinputmaleorfemale!"<<endl; }else{ sex=initSex; } }voidsetAge(intinitAge) { age=initAge; } voidsetName(stringinitName) { name=initName; }};intmain(){ Personperson; //定义Person的一个对象(实例) person.sex="male"; //Person的实例person可以直接访问Person中的公有成员sex //person.age=20; //Person的实例person不能直接访问Person中的保护成员age person.setAge(20);//Person的实例person可以通过setAge()方法访问私有成员age//="Tom"; //Person的实例person不能直接访问Person中的私有成员name person.setName("Tom");//Person的实例person可以通过setName()方法访问私有成员name cout<<"Thevalueofsexis"<<person.getSex()<<endl; cout<<"Thevalueofageis"<<person.getAge()<<endl;//Person的实例person可以通过getAge()方法访问保护成员agecout<<"Thevalueofnameis"<<person.getName()<<endl;//Person的实例person可以通过getName()方法访问私有成员name return0;}
运行结果如下:
值得注意的是,访问控制可以阻止用户对对象进行的无规划的使用。但是在显式地使用类型转换时,这种保护也会失去作用。如在上面的Person.cpp中假设没有提供getAge()这个方法时,也可以通过以下的方式得到Age的值。同理,name的值也可以用这种方法得到。由于这种方法用到了强制类型转换,因此要谨慎使用。
Person*personPointer=&person;//取得Person实例在内存中的地址
void*add1=(void*)personPointer;//先将地址强制转换为void*型int*add2=(int*)add1;//先将地址强制转换为int*型
cout<<"Nowthevalueofageis"<<*(add2+4)<<endl;//即使age是Person中的保护成员,仍然可以得到age的值以上程序片段在MicrosoftVisualC++中完全可以替代Person.cpp中的语句:
cout<<"Thevalueofageis"<<person.getAge()<<endl;
如果读者使用的是其他的编译器如GNU的gcc编译器或者其他的操作系统如Linux,指针add2的偏移也许会不同,在调试时需要做相应的改动。
一般来说,公有的成员是类的对外接口,而私有和保护成员是类的内部实现并且不希望外部了解和访问的。需要区分类的成员对类的实例(instant)的可见性和对类的成员函数的可见性的不同。类的成员函数可以访问类的所有的成员,不存在任何限制。而其实例对类的成员的访问则要受到上述所说的访问控制符的制约。2.4访问控制出于信息隐藏的目的,类的成员一般被定义为私有的,并通过公有的成员函数提供对类私有数据的访问。但是有些时候,可能希望类外的函数可以访问类的保护成员。为了解决这个问题,可以将这些函数定义为友元(friend)函数。友元包括友元函数和友元类。友元可以提高类的访问效率,但会破坏类的封装性。2.4.1友元函数有时候,希望通过类外部的某个函数去操作类的属性。比如希望在这个函数中修改Person类的属性值,并以特定的格式输出Person类的信息。不希望在Person类中定义这样的输出函数,而导致类变得臃肿和不易维护。【程序2.11】#include<iostream>#include<string>usingnamespacestd;classPerson{private: stringname; intage;public: Person(stringinitName,intinitAge){ name=initName; age=initAge; } stringgetName(){ returnname; } intgetAge(){ returnage; }};//函数定义在类的外部
voidinformation(Personp){ p.age=20; cout<<<<"'sageis"<<p.age<<endl;}intmain(){ Personp("WangWei",23); cout<<p.getName()<<"'sageis"<<p.getAge()<<endl; information(p); return0;}
编译这段代码,出现下面的错误提示:
errorC2248:'age':cannotaccessprivatememberdeclaredinclass'Person'
错误产生在语句“p.age=20;”,原因是函数information不能访问Person类中的私有成员age。在C++的设计规则中,类外部的函数是无法对类的属性进行操作的。但是,友元函数是一个例外。友元函数可以超脱类的访问控制,具有访问类中所有数据成员的能力。友元通过关键字friend声明。它们不受其在类体中被声明的public、private和protected区的影响。修改上面的代码,把友元函数的声明放在了类定义的末尾部分。【程序2.12】classPerson{private: stringname; intage;public: Person(stringinitName,intinitAge){ name=initName; age=initAge; }stringgetName(){ returnname; } intgetAge(){ returnage; } friendvoidinformation(Personp);//友元函数的声明
};
运行结果如下:
可以看到,程序中通过两种方式对Person类的私有成员name和age进行了访问。第一种通过成员函数访问成员变量,由语句“cout<<p.getName()<<"'sageis"<<p.getAge()<<endl;”完成。第二种通过友元函数修改成员变量并进行输出,由语句“information(p);”完成。函数information被类Person声明为友元函数,通过这种方式函数information获得了对类Person中所有成员的访问权限。注意,友元函数并不能看做是类的成员函数,它只是个被声明为类的友元的普通函数。2.4.2嵌套友元下面回顾一下2.2.3节关于隐藏实现的例子,Person类的定义如下:
classPerson{ structInformation; Information*p;public: voidinitialize(); intgetAge(); voidsetAge(int);};
仔细观察,在Person类中嵌套了一个结构体Information。但是,结构体不能封装方法,可操作性较低。然而,类可以定义自己的方法。如果把结构体Information改为类会如何呢?分析程序2.13的代码:
【程序2.13】#include<iostream>#include<string>usingnamespacestd;classPerson{private: stringname;intage; public: Person(stringinitName,intinitAge){ name=initName; age=initAge; }
stringgetName(){ returnname; }voidsetName(stringinitName){ name=initName; }
intgetAge(){ returnage; } voidsetAge(intinitAge){ age=initAge; }classInformation{ private: Person*p; public: Information(Person*initp){ p=initp; } voidinfor(){ cout<<p->name<<"'sageis"<<p->age<<endl;} };};
在分析这段程序之前,先介绍一些嵌套类的相关知识。在一个类中定义的类称为嵌套类,包含嵌套类的类称为外围类。例如,类B定义在类A的内部,则类B为嵌套类,类A为外部类。这样做的好处是隐藏类名,提高类的抽象能力,并且强调了两个类的主从关系。
可以看到,类Information定义在类Person中。类Person是外围类,类Information是嵌套类。类Information拥有一个私有成员Person*p,p是指向Person对象的指针变量。函数infor试图通过指针p输出Person对象的私有成员变量name和age。编译这段代码,将会出现以下错误信息:
'name':cannotaccessprivatememberdeclaredinclass'Person''age':cannotaccessprivatememberdeclaredinclass'Person'
错误产生在语句“cout<<p->name<<"'sageis"<<p->age<<endl;”中,这里提示编程人员函数information不具有访问Person私有成员的权限。这是因为在C++中,嵌套类的成员函数对外围类的成员没有访问权限,反之亦然。
如果将嵌套类声明为外围类的友元类,结果会如何呢?将代码修改如下:
【程序2.14】classPerson{private: …public: … classInformation; friendInformation; classInfo
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年度总经理职位聘请与保密协议合同
- 2025版美容机构美容师专业聘用及培训合同范本3篇
- 课题申报参考:南宋私家本朝史籍修撰及其家国书写研究
- 课题申报参考:民国时期六大疫灾的时空变迁规律、环境机理与社会影响对比研究
- 二零二五年度智慧城市规划设计咨询服务合同2篇
- 二零二五年度内衣品牌授权销售区域保护合同规范
- 2025版模板智慧农业解决方案合同2篇
- 2025年度卫星通信设备销售与维护合同4篇
- 2025年度智能零售店铺门面租赁与系统支持合同
- 2025年度个人买卖房屋贷款合同规范2篇
- 采购支出管理制度
- 儿科护理安全警示教育课件
- 三年级下册口算天天100题
- 国家中英文名称及代码缩写(三位)
- 人员密集场所消防安全培训
- 液晶高压芯片去保护方法
- 使用AVF血液透析患者的护理查房
- 拜太岁科仪文档
- 2021年高考山东卷化学试题(含答案解析)
- 2020新译林版高中英语选择性必修一重点短语归纳小结
- GB/T 19668.7-2022信息技术服务监理第7部分:监理工作量度量要求
评论
0/150
提交评论