C++第六弹---静态成员与友元

静态成员

静态成员的定义或声明要加个关键static。静态成员可以通过双冒号来使用即<类名>::<静态成员名>。

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。
实例:

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
 
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
 
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
 
   // 输出对象的总数
   cout << "Total objects: " << Box::objectCount << endl;
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Constructor called.
Constructor called.
Total objects: 2

  1. 静态数据成员
  • 静态数据成员定义
    静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。 其定义方式与全局变量相同。举例如下:
    test.h文件 :
class   base
{   
       private:   
       static   const   int   _i;
      //声明,标准c++支持有序类型在类体中初始化,但vc6不支持。   
};  

test.cpp文件:

const   int   base::_i=10;//定义(初始化)时不受private和protected访问限制. 

注:不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。

  • 静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。举例如下:
class   base{   
              public   :   
              static   int   _num;//声明   
};   
        int   base::_num=0;//静态数据成员的真正定义   
        class   derived:public   base{   
};   
int main()   
{   
     base   a;   
     derived   b;   
     a._num++;   
     cout<<"base   class   static   data  number   _num   is"<<a._num<<endl;   
     b._num++;   
     cout<<"derived  class  static   data  number  _num   is"<<b._num<<endl;   
}   
//   结果为1,2;可见派生类与基类共用一个静态数据成员。 
  • 静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。举例如下:
class   base{   
          public   :   
          static   int   _staticVar;   
          int   _var;   
          void   foo1(int   i=_staticVar);//正确,_staticVar为静态数据成员   
          void   foo2(int   i=_var);//错误,_var为普通数据成员   
      }; 
  • 静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为所属类类型的指针或引用。举例如下:
class   base{   
          public   :   
          static   base   _object1;//正确,静态数据成员   
          base   _object2;//错误   
          base   *pObject;//正确,指针   
          base   &mObject;//正确,引用   
      };
  • 静态数据成员的值在const成员函数中可以被合法的改变。举例如下:
class   base{   
          public:   
          base(){_i=0;_val=0;}   
    
          mutable   int   _i;   
          static   int   _staticVal;     
          int   _val;   
          void   test()   const{//const   成员函数   
    
                _i++;//正确,mutable数据成员   
                _staticVal++;//正确,static数据成员   
                _val++;//错误   
    
          }   
      };   
      int   base::_staticVal=0;  
  • 公共静态数据成员可被类的外部访问,保护或私有静态数据成员只可被类的内部访问。

静态数据成员经常被用于以下几种场合:
(1)用来保存流动变化对象的个数;
(2)作为一个标志,指示一个特定的动作是否发生;
(3)一个指向链表第一成员或最后一个成员的指针。

  1. 静态成员函数
  • 静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数指针来储存。举例如下:
class   base{   
              static   int   func1();   
              int   func2();   
          };   
    
          int   (*pf1)()=&base::func1;//普通的函数指针   
          int   (base::*pf2)()=&base::func2;//成员函数指针  
  • 静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。
  • 静态成员函数不可以同时声明为 virtual、const、volatile函数。举例如下:
class   base{   
              virtual   static   void   func1();//错误   
              static   void   func2()   const;//错误   
              static   void   func3()   volatile;//错误   
        };  

最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。

友元

采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该函数的友元函数。除了友元函数外,还有友元类,两者统称为友元。友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

  1. 友元函数
    友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
    friend 类型 函数名(形式参数);

    友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。一个函数可以是多个类的友元函数,只需要在各个类中分别声明。友元函数的调用与一般函数的调用方式和原理一致。

  2. 友元类
    友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
    当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
    friend class 类名;
    其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。

    例如,以下语句说明类B是类A的友元类:

       class A
       {
              …
       public:
              friend class B;
              …
       };

经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。

注意:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的声明

友元示例:

#include <iostream> 
using namespace std; 
class Internet 
{ 
public: 
Internet(char *name,char *address) // 改为:internet(const char *name , const char *address)
{ 
strcpy(Internet::name,name); 
strcpy(Internet::address,address); 
} 
friend void ShowN(Internet &obj);   //友元函数的声明 
public:              // 改为:private
char name[20]; 
char address[20]; 
}; 
void ShowN(Internet &obj)        //函数定义,不能写成,void Internet::ShowN(Internet &obj) 
{ 
cout<<obj.name<<endl;          //可访问internet类中的成员
} 
void main() 
{ 
Internet a("中国软件开发实验室","www.cndev-lab.com"); 
ShowN(a); 
cin.get(); 
} 

上面的代码通过友元函数的定义,我们成功的访问到了a对象的保护成员name,友元函数并不能看做是类的成员函数,它只是个被声明为类友元的普通函数,所以在类外部函数的定义部分不能够写成void Internet::ShowN(Internet &obj),这一点要注意。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容