1、第八章 多态性,静态成员 友元 友元应用实例,静态成员是指声明为static的类成员。在类的范围内所有对象共享某个数据。友元是一种定义在类外部的普通函数,但它需要在类的内部进行说明,为了与该类的成员函数加以区别,在说明时前面加上关键字friend。友元不是成员函数,但它可以访问类中的私有成员。其作用是提高程序的运行效率。C+语言中的友元函数为在类外访问类中的私有成员和保护成员提供了方便,但破坏了类的封装性和隐蔽性。友元可以是一个函数,称为友元函数,也可以是一个类,称为友元类。友元函数和友元类统称为友元。,静态成员,类相当于一个数据类型,当说明一个某类的对象时,系统就为该对象分配一块内存单元来存
2、放类中的所有成员。但在某些应用中,需要程序中属于某个类的所有对象共享某个数据。为此,一个解决的办法就是将所要共享的数据说明为全局变量,但这将破坏数据的封装性;较好的解决办法是将所要共享的数据说明为类的静态成员。,静态数据成员C+中,同一个类定义多个对象时,每个对象拥有各自的数据成员(不包括静态数据成员),而所有对象共享一份成员函数和一份静态数据成员。静态数据成员是类的所有对象中共享的成员,而不是某个对象的成员,因此可以实现多个对象间的数据共享。静态数据成员不属于任何对象, 它不因对象的建立而产生,也不因对象的析构而删除,它是类定义的一部分,所以使用静态数据成员不会破坏类的隐蔽性。对静态数据成员
3、的操作和一般数据成员一样,定义为私有的静态数据成员不能由外界访问。静态数据成员可由任意访问权限许可的函数访问。可以在类的成员函数中改变静态数据成员。,静态数据成员不从属于任何一个具体对象,所以必须对它初始化,且对它的初始化不能在构造函数中进行。类中用关键字static修饰的数据成员叫做静态数据成员。说明一个静态数据成员的方法与说明一个一般静态变量一样,只不过前者是在一个类中说明。静态数据成员的使用方法如下:(1)静态数据成员的定义与一般数据成员相似,但前面要加上static关键词。(2)静态数据成员的初始化与一般数据成员不同,静态数据成员初始化的格式如下::=;(3)在引用静态数据成员时采用格
4、式::,这表明: (1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。 (2) 初始化时不加该成员的访问权限控制符private,public等。 (3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。,class Class1 int a;static int b;/ c1,c2; int Class1:b;类Class1中包含两个数据成员a和b,其中a为一般数据成员,在对象c1和c2中都存在有各自的该数据成员的副本;而b是静态数据成员,所有类Class1的对象中的该成员实际上是同一个变量。C+编译器将静态数据成员存放
5、在静态存储区,该存储区中的所有数据为类的所有对象所共享。,#include class Myclass public: Myclass(int a, int b, int c); void GetNumber(); void GetSum(); private: int A, B, C; static int Sum; ; int Myclass:Sum = 0; Myclass:Myclass(int a, int b, int c) A = a; B = b; C = c; Sum += A+B+C; ,void Myclass:GetNumber() cout“Number=“A“,“B
6、“,“Cendl; void Myclass:GetSum() cout“Sum=“Sumendl; void main() Myclass M(3, 7, 10),N(14, 9, 11); M.GetNumber(); N.GetNumber(); M.GetSum(); N.GetSum(); ,从输出结果可以看到Sum的值对M对象和对N对象都是相等的。这是因为在初始化M对象时,将M对象的三个int型数据成员的值求和后赋给了Sum,于是Sum保存了该值。在初始化N对象时,对将N对象的三个int型数据成员的值求和后又加到Sum已有的值上,于是Sum将保存另后的值。所以,不论是通过对象M还是
7、通过对象N来引用的值都是一样的,即为54。,静态成员函数静态成员函数的定义和其他成员函数一样。但在说明时需注意静态成员函数不得说明为虚函数。静态成员函数与静态数据成员类似,也是从属于类,静态成员函数的定义是在一般函数定义前加上static关键字。调用静态成员函数的格式如下::();静态成员函数与静态数据成员一样,与类相联系,不与对象相联系,只要类存在,静态成员函数就可以使用,所以访问静态成员函数时不需要对象。如果用对象去调用静态成员函数,只是用其类型。,静态成员函数只能访问静态数据成员、静态成员函数和类以外的函数和数据,不能访问类中的非静态数据成员(因为非静态数据成员只有对象存在时才有意义)。
8、但静态数据成员和静态成员函数可由任意访问权限许可的函数访问。和一般成员函数类似,静态成员函数也有访问限制,私有静态成员函数不能由外界访问。静态成员函数没有this指针,因此,静态成员函数只能直接访问类中的静态成员,若要访问类中的非静态成员时,必须借助对象名或指向对象的指针。,#include class M public: M(int a) A=a; B+=a; static void f1(M m); private: int A; static int B; ; void M:f1(M m) cout“A=“m.Aendl; cout“B=“Bendl; ,int M:B=0; void
9、main() M P(5),Q(10); M:f1(P); /不用对象名 M:f1(Q); ,友元,友元提供了在不同类的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。通过友元,一个普通函数或另一个类中的成员函数可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率,但破坏了类的封装性和数据的隐蔽性。友元函数定义友元函数的方式是在类定义中用关键词friend说明该函数,其格式如下:friend ();,友元函数说明的位置可在类的任何部位,既可在public区,也可在protected区,意义完全一样。友元函数定义则在类的外部,一般与类的成员函数定义放在一起。类的友元函
10、数可以直接访问该类的所有成员,但它不是成员函数,可以像普通函数一样在任何地方调用。友员函数的定义方法是在类的任何地方象定义其他函数一样定义该函数,并在其前面加上关键字friend即可。 友员函数虽然在类内定义,但它不是这个类的成员函数,它可以是一个普通函数,也可以是其他类的成员函数,在其函数体中通过对象名访问这个类的私有或保护成员。,友元类C+允许说明一个类为另一个类的友元类(friend class)。如果A是B的友员类,则A中的所有成员函数可以像友员函数一样访问B类中的所有成员。定义格式如下: class B friend class A; /A的所有成员函数均为B的友员函数/,友元关系不
11、可以被继承。假设类A是类B的友元,而类C从类B派生,如果没有在类C中显式地使用下面的语句: friend class A; 那么,尽管类A是类B的友元,但这种关系不会被继承到类C,也就是说,类C和类A没有友元关系,类A的成员函数不可以直接访问类C的受保护成员和私有成员。 不存在“友元的友元”这种关系。假设类A是类B的友元,而类B是类C的友元,即是说类B的成员函数可以访问类C的受保护成员和私有成员,而类A的成员函数可以访问类B的受保护成员和私有成员;但是,类A的成员函数不可以直接访问类C的受保护成员和私有成员,即是说友元关系不存在传递性。,友元应用实例,例8-8:求两数的平方差。 #includ
12、e class Myclass private:int a,b,max,min; public:Myclass(int i,int j):a(i),b(j)max=(ab)?a:b;min=(ab)?a:b;friend int Result(Myclass,int Result(Myclass ,此程序的运行结果为:7,拷贝初始化构造函数,拷贝初始化构造函数是一种特殊的成员函数,它的功能是用一个已知的对象来初始化一个被创建的同类的对象。拷贝初始化构造函数实际上也是构造函数,它是在初始化时被调用来将一个已知对象的数据成员的值拷贝给正在创建的另一个同类的对象。,拷贝初始化构造函数的特点如下:,1
13、、该函数名同类名,因为它也是一种构造函数,并且该函数也不被指定返回类型。 2、该函数只有一个参数,并且是对某个对象的引用。 3、每个类都必须有一个拷贝初始化构造函数,其格式如下: :(const&),其中,是与该类名相同的。const是一个类型修饰符,被它修饰的对象是一个不能被更新的常量。 如果类中没有说明拷贝初始化构造函数,则编译系统自动生成一个具有上术形式的缺省拷贝初始化构造函数。作为该类的公有成员。,缺省拷贝构造函数,如果没有创建拷贝构造函数, C + +编译器也将自动地为我们创建拷贝构造函数。 如果我们加了一个拷贝构造函数,我们就告诉了编译器我们将自己处理构造函数的创建,编译器将不再为
14、我们创建缺省的构造函数。,所以除非我们打算通过地址修改外部对象(这个地址通过非c o n s t指针传递),要不然都用c o n s t引用传递地址。,拷贝构造函数采用相同类型的对象引用作为它的参数,它可以被用来从现有的类创建新类。 当用传值方式传递或返回一个对象时,编译器自动调用这个拷贝构造函数。,下面举一例子说明拷贝初始化构造函数,class TPoint public: TPoint(int x, int y) X=x; Y=y; TPoint(TPoint ,TPoint:TPoint(TPoint ,#include void main() TPoint P1(5, 7); TPoi
15、nt P2(P1); cout“P2=“P2.Xcoord()“,“P2.Ycoord()endl; ,该程序的输出结果为: 拷贝初始化构造函数被调用。 P2=5,7 析构函数被调用。 析构函数被调用。,总结:拷贝初始化构造函数的功能就是用一个已知的对象来初始化另一个对象。在下述三种情况下,需要用拷贝初始化构造函数来用一个对象初始化另一个对象。 1、明确表示由一个对象初始化另一个对象时,如:TPoint P2(P1); 2、当对象作为函数实参传递给函数形参时,如: P = f(N); 3、当对象用为函数返回值时,如:上例 return R;,引用,int x; int 这里,编译器分派了一个存
16、储单元,它的值被初始化为1 2,这样这个引用就和这个存储单元联系上了。要点是任何引用必须和存储单元联系。但访问引用时,就是在访问那个存储单元。,使用引用时有一定的规则: 1) 当引用被创建时,它必须被初始化。(指针则可以在任何时候被初始化。) 2) 一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。(指 针则可以在任何时候指向另一个对象。) 3) 不可能有N U L L引用。必须确保引用是和一块合法的存储单元关连。,函数中的引用,最经常看见引用的地方是在函数参数和返回值中。当引用被用作函数参数时,函数内任何对引用的更改将对函数外的参数改变。当然,可以通过传递一个指针来做相同的事情,但引用具有更清晰的语法。(如果愿意的话,可以把引用看作一个使语法更加便利的工具。) 如果从函数中返回一个引用,必须像从函数中返回一个指针一样对待。当函数返回时,无论引用关连的是什么都不应该离开,否则,将不知道指向哪一个内存区域。,