[C++]类的其它特性

来源:互联网 发布:苗族服装批发软件 编辑:程序博客网 时间:2024/05/19 08:23

类的其它特性

友元函数

类中私有和保护的成员在类外不能被访问。

友元函数是一种定义在类外部的普通函数,其特点是能够访问类中私有成员和保护成员,即类的访问权限的限制对其不起作用。

友元函数需要在类体内进行说明,在前面加上关键字friend。

一般格式为:

friend  <type> FuncName(<args>);

friend(关键字)   float(返回值类型) Volume(A &a)(函数参数);

友元函数不是成员函数,用法也与普通的函数完全一致,只不过它能访问类中所有的数据。友元函数破坏了类的封装性和隐蔽性,使得非成员函数可以访问类的私有成员。

一个类的友元可以自由地用该类中的所有成员。

有关友元函数的使用,说明如下:

友元函数不是类的成员函数

友元函数近似于普通的函数,它不带有this指针,因此必须将对象名或对象的引用作为友元函数的参数,这样才能访问到对象的成员。

友元函数与一般函数的不同点在于:

1.友元函数必须在类的定义中说明,其函数体可在类内定义,也可在类外定义;

2.它可以访问该类中的所有成员(公有的、私有的和保护的),而一般函数只能访问类中的公有成员。

 

友元函数不受类中访问权限关键字的限制,可以把它放在类的私有部分,放在类的公有部分或放在类的保护部分,其作用都是一样的。换言之,在类中对友元函数指定访问权限是不起作用的。

友元函数的作用域与一般函数的作用域相同。

谨慎使用友元函数

通常使用友元函数来取对象中的数据成员值,而不修改对象中的成员值,则肯定是安全的。

大多数情况是友元函数是某个类的成员函数,即A类中的某个成员函数是B类中的友元函数,这个成员函数可以直接访问B类中的私有数据。这就实现了类与类之间的沟通。

class A{

...

void fun( B &);  既是类A的成员函数

};

class B{

...

friend void fun( B &);   又是类B的友元函数

};

注意:一个类的成员函数作为另一个类的友元函数时,应先定义友元函数所在的类。

class   B  ;//先定义类A,则首先对类B作引用性说明

class   A{

   ......//类A的成员定义

   public:

   void  fun( B & );//函数的原型说明

    };

class  B{......

    friend  void  A::fun( B & );//定义友元函数

};

    void  A::fun ( B  &b)   //函数的完整定义

{

     ......//函数体的定义

类A中的成员函数fun()是类B的友元函数。即在fun()中可以直接引用类B的私有成员。

 

友元类

class A{

    .....

    friend class B;         类B是类A的友元

}

class B{

    .....

 }

类B可以自由使用类A中的成员

对于类B而言,类A是透明的

类B必须通过类A的对象使用类A的成员

不管是按哪一种方式派生,基类的私有成员在派生类中都是不可见的。

如果在一个派生类中要访问基类中的私有成员,可以将这个派生类声明为基类的友元。

 

虚函数

多态性是面向对象的程序设计的关键技术。

多态性:调用同一个函数名,可以根据需要但实现不同的功能。

 

运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定

可以将一个派生类对象的地址赋给基类的指针变量。

 

若要访问派生类中相同名字的函数,必须将基类中的同名函数定义为虚函数,这样,将不同的派生类对象的地址赋给基类的指针变量后,就可以动态地根据这种赋值语句调用不同类中的函数。

虚函数的定义和使用

   可以在程序运行时通过调用相同的函数名而实现不同功能的函数称为虚函数。定义格式为:

virtual  <type>  FuncName(<ArgList>);

一旦把基类的成员函数定义为虚函数,由基类所派生出来的所有派生类中,该函数均保持虚函数的特性。

在派生类中重新定义基类中的虚函数时,可以不用关键字virtual来修饰这个成员函数 。

虚函数是用关键字virtual修饰的某基类中的protected或public成员函数。它可以在派生类中重新定义,以形成不同版本。只有在程序的执行过程中,依据指针具体指向哪个类对象,或依据引用哪个类对象,才能确定激活哪一个版本,实现动态聚束。

 

关于虚函数,说明以下几点:

1、当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型、顺序、参数的个数必须一一对应,函数的返回的类型也相同。若函数名相同,但参数的个数不同或者参数的类型不同时,则属于函数的重载,而不是虚函数。若函数名不同,显然这是不同的成员函数。

2、实现这种动态的多态性时,必须使用基类类型的指针变量,并使该指针指向不同的派生类对象,并通过调用指针所指向的虚函数才能实现动态的多态性。

3、虚函数必须是类的一个成员函数,不能是友元函数,也不能是静态的成员函数。

4、在派生类中没有重新定义虚函数时,与一般的成员函数一样,当调用这种派生类对象的虚函数时,则调用其基类中的虚函数。

5、可把析构函数定义为虚函数,但是,不能将构造函数定义为虚函数。

6、虚函数与一般的成员函数相比较,调用时的执行速度要慢一些。为了实现多态性,在每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现的。因此,除了要编写一些通用的程序,并一定要使用虚函数才能完成其功能要求外,通常不必使用虚函数。

7、一个函数如果被定义成虚函数,则不管经历多少次派生,仍将保持其虚特性,以实现“一个接口,多个形态”。

 

虚函数的访问

用基指针访问与用对象名访问

用基指针访问虚函数时,指向其实际派生类对象重新定义的函数。实现动态聚束。

通过一个对象名访问时,只能静态聚束。即由编译器在编译的时候决定调用哪个函数。

 

纯虚函数

在基类中不对虚函数给出有意义的实现,它只是在派生类中有具体的意义。这时基类中的虚函数只是一个入口,具体的目的地由不同的派生类中的对象决定。这个虚函数称为纯虚函数。

class    <基类名>

{ virtual <类型><函数名>(<参数表>)=0;

......

};

1、在定义纯虚函数时,不能定义虚函数的实现部分。

2、把函数名赋于0,本质上是将指向函数体的指针值赋为初值0。与定义空函数不一样,空函数的函数体为空,即调用该函数时,不执行任何动作。在没有重新定义这种纯虚函数之前,是不能调用这种函数的。

3、把至少包含一个纯虚函数的类,称为抽象类。这种类只能作为派生类的基类,不能用来说明这种类的对象。

其理由是明显的:因为虚函数没有实现部分,所以不能产生对象。但可以定义指向抽象类的指针,即指向这种基类的指针。当用这种基类指针指向其派生类的对象时,必须在派生类中重载纯虚函数,否则会产生程序的运行错误。

4、在以抽象类作为基类的派生类中必须有纯虚函数的实现部分,即必须有重载纯虚函数的函数体。否则,这样的派生类也是不能产生对象的。

综上所述,可把纯虚函数归结为:抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。

 

虚基类

多基派生中的多条路径具有公共基类时,在这条路径的汇合处就会因对公共基类产生多个拷贝而产生同名函数调用的二义性。

解决这个问题的办法就是把公共基类定义为虚基类,使由它派生的多条路径的汇聚处只产生一个拷贝。

class Base{ };

class A : public Base{ };

class B:  public  Base{ };

class C: public A, public  B{ };

类C中继承了两个类Base,即有两个类Base的实现部分,在调用时产生了二义性。

由虚基类派生出的对象初始化时,直接调用虚基类的构造函数。因此,若将一个类定义为虚基类,则一定有正确的构造函数可供所有派生类调用。

用虚基类进行多重派生时,若虚基类没有缺省的构造函数,则在每一个派生类的构造函数中都必须有对虚基类构造函数的调用 (且首先调用)。

 

静态成员

通常,每当说明一个对象时,把该类中的有关成员数据拷贝到该对象中,即同一类的不同对象,其成员数据之间是互相独立的。

当我们将类的某一个数据成员的存储类型指定为静态类型时,则由该类所产生的所有对象,其静态成员均共享一个存储空间,这个空间是在编译的时候分配的。换言之,在说明对象时,并不为静态类型的成员分配空间。

在类定义中,用关键字static修饰的数据成员称为静态数据成员。

class A{

    int x,y;  static  int z;

    public:  

     void Setxy(int a, int b)

    {  x=a;   y=b;}

};

A   a1,  a2;

有关静态数据成员的使用,说明以下几点:

1、类的静态数据成员是静态分配存储空间的,而其它成员是动态分配存储空间的(全局变量除外)。当类中没有定义静态数据成员时,在程序执行期间遇到说明类的对象时,才为对象的所有成员依次分配存储空间,这种存储空间的分配是动态的;而当类中定义了静态数据成员时,在编译时,就要为类的静态数据成员分配存储空间。

2、必须在文件作用域中,对静态数据成员作一次且只能作一次定义性说明。因为静态数据成员在定义性说明时已分配了存储空间,所以通过静态数据成员名前加上类名和作用域运算符,可直接引用静态数据成员。在C++中,静态变量缺省的初值为0,所以静态数据成员总有唯一的初值。当然,在对静态数据成员作定义性的说明时,也可以指定一个初值。

3、静态数据成员具有全局变量和局部变量的一些特性。静态数据成员与全局变量一样都是静态分配存储空间的,但全局变量在程序中的任何位置都可以访问它,而静态数据成员受到访问权限的约束。必须是public权限时,才可能在类外进行访问。

4、为了保持静态数据成员取值的一致性,通常在构造函数中不给静态数据成员置初值,而是在对静态数据成员的定义性说明时指定初值。

 

静态成员函数

可以将类的成员函数定义为静态的成员函数。即使用关键字static来修饰成员函数 。

class A

{    float x, y;

public :

     A( ){  }

     static   void sum(void)  { ..... }

};

对静态成员函数的用法说明以下几点:

1、与静态数据成员一样,在类外的程序代码中,通过类名加上作用域操作符,可直接调用静态成员函数。

2、静态成员函数只能直接使用本类的静态数据成员或静态成员函数,但不能直接使用非静态的数据成员 (可以引用使用)。这是因为静态成员函数可被其它程序代码直接调用,所以,它不包含对象地址的this指针。

3、静态成员函数的实现部分在类定义之外定义时,其前面不能加修饰词static。这是由于关键字static不是数据类型的组成部分,因此,在类外定义静态成员函数的实现部分时,不能使用这个关键字

4、不能把静态成员函数定义为虚函数。静态成员函数也是在编译时分配存储空间,所以在程序的执行过程中不能提供多态性。

5、可将静态成员函数定义为内联的(inline),其定义方法与非静态成员函数完全相同。

 

const 、volatile对象和成员函数

用const修饰的对象,只能访问该类中用const修饰的成员函数,而其它的成员函数是不能访问的。用volatile修饰的对象,只能访问该类中用volatile修饰的成员函数,不能访问其它的成员函数。

当希望成员函数只能引用成员数据的值,而不允许成员函数修改数据成员的值时,可用关键词const修饰成员函数。一旦在用const修饰的成员函数中出现修改成员数据的值时,将导致编译错误。  

 

const和volatile成员函数

在成员函数的前面加上关键字const,则表示这函数返回一个常量,其值不可改变。

const成员函数则是指将const放在参数表之后,函数体之前,其一般格式为:

<type>  FuncName(<args>)  const ;

其语义是指明这函数的this指针所指向的对象是一个常量,即规定了const成员函数不能修改对象的数据成员,在函数体内只能调用const成员函数,不能调用其它的成员函数。

 

用volatile修饰一个成员函数时,其一般格式为:

<type>  FuncName(<args>)   volatile;

其语义是指明成员函数具有一个易变的this指针,调用这个函数时,编译程序把属于此类的所有的数据成员都看作是易变的变量,编译器不要对这函数作优化工作。

 

由于关键字const和volatile是属于数据类型的组成部分,因此,若在类定义之外定义const成员函数或volatile成员函数时,则必须用这二个关键字修饰,否则编译器认为是重载函数,而不是定义const成员函数或volatile成员函数。

 

指向类成员的指针

在C++中可以定义一种特殊的指针,它指向类中的成员函数或类中的数据成员。并可通过这样的指针来使用类中的数据成员或调用类中的成员函数。

指向类中数据成员的指针变量

定义一个指向类中数据成员的指针变量的一般格式为:

<type>  ClassName:: *PointName;

其中type是指针PointName所指向数据的类型,它必须是类ClassName中某一数据成员的类型

 

1、指向类中数据成员的指针变量不是类中的成员,这种指针变量应在类外定义。

2、与指向类中数据成员的指针变量同类型的任一数据成员,可将其地址赋给这种指针变量,赋值的一般格式为:

PointName = &ClassName::member;

这种赋值,是取该成员相对于该类的所在对象中的偏移量,即相对地址(距离开始位置的字节数)

    如:mptr = &S::y;  

 表示将数据成员y的相对起始地址赋给指针变量mptr。

3、用这种指针访问数据成员时,必须指明是使用那一个对象的数据成员。当与对象结合使用时,其用法为:

ObjectName.* PointName

4、由于这种指针变量并不是类的成员,所以使用它只能访问对象的公有数据成员。若要访问对象的私有数据成员,必须通过成员函数来实现。

 

指向类中成员函数的指针变量

定义一个指向类中成员函数的指针变量的一般格式为:

<type>  (ClassName:: *PointName)(<ArgsList>);

其中PointName是指向类中成员函数的指针变量;ClassName是已定义的类名;type是通过函数指针PointName调用类中的成员函数时所返回值的数据类型,它必须与类ClassName中某一成员函数的返回值的类型相一致;<ArgsList>是函数的形式参数表。

在使用这种指向成员函数的指针前,应先对其赋值

PointName= ClassName::FuncName;

同样地,只是将指定成员函数的相对地址赋给指向成员函数的指针。

在调用时,用(对象名.指针)( )的形式。

比较 :int max( int a,int b)

{return (a>b?a:b);}

若有:int  (*f)( int, int );f=max;

则调用时    (*f)(x,y);

所以:(s1.*mptr1)();         (s1.*mptr2)(100);

或:(ps->*mptr1)();(ps-*mptr2)(100);

 

对指向成员函数的指针变量的使用方法说明以下几点:

1、指向类中成员函数的指针变量不是类中的成员,这种指针变量应在类外定义。

2、不能将任一成员函数的地址赋给指向成员函数的指针变量,只有成员函数的参数个数、参数类型、参数的顺序和函数的类型均与这种指针变量相同时,才能将成员函数的指针赋给这种变量。

3、使用这种指针变量来调用成员函数时,必须指明调用那一个对象的成员函数,这种指针变量是不能单独使用的。用对象名引用。

4、由于这种指针变量不是类的成员,所以用它只能调用公有的成员函数。若要访问类中的私有成员函数,必须通过类中的其它的公有成员函数。

5、当一个成员函数的指针指向一个虚函数,且通过指向对象的基类指针或对象的引用来访问该成员函数指针时,同样地产生运行时的多态性。

6、当用这种指针指向静态的成员函数时,可直接使用类名而不要列举对象名。这是由静态成员函数的特性所确定的。

 

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 新加坡半年临时驾照过期后怎么办 北京怎么办残摩行驶证 报考驾照三年到期了怎么办 车辆违章扣3分怎么办 驾照过期忘审了怎么办 外省港澳证办了居住证怎么办 电动车交警罚单丢了怎么办 在外地开车违章扣分怎么办 郑州车在外地扣分违章怎么办 电工证掉了应该怎么办 毕业回国美国驾照过期了怎么办 a2驾驶证4年没交体检报告怎么办 中国驾照在美国丢了怎么办 拿了驾照不敢开车怎么办 刚拿驾照不敢上路怎么办 雅思考试作文格式写错了怎么办 英国银行卡注销后钱怎么办 本科毕业有毕业证没有学位证怎么办 莫名收到平安一账通验证码怎么办 新车没带行驶证怎么办 深圳行驶证副本丢了怎么办 新车行驶证是怎么办的 新车怎么办牌和行驶证 b本扣12分了怎么办 车的绿本丢了怎么办 车辆落户查不到购车发票怎么办 扣车凭证丢了怎么办 三轮车驾驶证被扣了怎么办 违章12分不够扣怎么办 忘记带行驶证了怎么办 开车不带行驶证怎么办 屏幕没碎黑屏了怎么办 三星s6屏幕不亮怎么办 苹果手机掉水里屏幕黑屏怎么办 三星s6电池不耐用怎么办 台式电脑显示屏坏了怎么办 三星笔记本电脑不显示韩文怎么办 三星s6开不开机怎么办 电脑显示屏显示检测信号线怎么办 三星s8屏幕坏了怎么办 手机挤压漏液了怎么办