C++一些注意点之静态成员

本文详细介绍了C++中静态成员的特点,包括静态数据成员和静态成员函数。静态数据成员在类的所有对象间共享,静态成员函数不依赖于特定的对象实例,可以直接通过类名访问。此外,文章还讨论了单例模式的应用,这是静态成员常用于的一种设计模式,确保类只有一个实例,并提供全局访问点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      定义类时实际上是定义了一种数据类型,编译程序并不为数据类型分配存储空间。只有在说明对象时才为对象的每个成员分配空间,并把占有的空间当一个整体来看待。当我们将类的一个成员存储类型指定为静态类型时,则由该类所产生的所有对象均共享为其静态成员所分配的一个存储空间。换言之,在说明对象时,并不为静态类型的成员分配空间。

1.静态数据成员

      静态数据成员注意一下几点:

    (1)类的静态成员数据时静态分配存储空间的,而其他成员是动态分配空间的。当类中没有静态成员时,在程序执行期间遇到对象时为其分配空间。当有静态成员时,编译时就为其分配了空间。

    (2)必须在文件作用中,对静态成员数据做一次且只能一次定义性的说明(说明的时候加上作用域)。类中的静态成员空间分配在定义说明时分配的。C++中静态变量的缺省初值为0,当然在定义性说明时也可以指定一个初值,不指定的话就取默认值。为了保持一致性,通常在构造函数中不给静态成员赋初值,而在定义性说明时赋初值。

class A{
	static int x,y;
}

int A::X=0;//定义性说明
int A::y=0;//定义性说明

    (3)静态成员数据与全局变量一样其内存空间是静态分配的。但是静态成员受到类中访问权限的控制。

    (4)const static成员可以在类体中初始化,这是一个例外。但是,还是必须在类的定义外进行定义。

class A{
public:
   static const int p = 30;//没有问题
}
const int A::p;//在类外进行定义

    (5) 所有对象共享静态数据成员。可以通过类直接访问静态成员(因为跟具体对象没有关系),但是条件是静态成员一定要是公有成员。

#include<iostream>

using namespace std;

class A{
   int i;
   static int count;
public:
   static int num;
   A(int a,int b){
	   i = a;
	   num = b;
	   count++;
   }
   void show(void)
   {
	   cout<<"i="<<i<<'\t'<<"num="<<num<<endl;
	   cout<<"count="<<count<<endl;
   }
};

int A::count=0;
int A::num=0;

int main()
{
	cout<<"-----------a-------------"<<endl;
	A a(2,3);
	a.show();
	cout<<"-----------a1------------"<<endl;
	A a1(1,5);
    a1.show();
	cout<<"-----------a-------------"<<endl;
	a.show();//a和a1共享了静态成员
	cout<<"-----------num-----------"<<endl;
	cout<<"num="<<A::num<<endl;//可以通过类直接访问
	return 0;
}

运行结果:


2.静态成员函数

       静态成员函数需要以下注意点:

     (1)在类外的代码中,可以通过类直接访问类的静态函数。

class A{
public:
     static show();
}
main(){
      A::show();
}

     (2)静态成员函数只能直接使用(可以间接使用其他成员)本类的静态成员数据或静态成员函数。不能直接使用非静态的成员数据。因为成员函数可被其他程序代码直接调用,所以不包含this指针

class A{
public:
     int i;
     static int count;
     static show(A& r){
     	  cout<<"i="<<r.i<<endl;//间接使用
          cout<<"count"<<count<<endl;//直接使用
     }
}

    (3)静态成员函数在类外定义时,前面不能加static修饰符,static不是数据类型的组成部分。

    (4)可以将静态成员函数声明为inline函数,但是不能把静态成员函数定义为虚函数。静态成员函数时在编译时分配空间,所以在运行时不能提供多态性。

   

3.应用---单例模式

       参考:http://blog.youkuaiyun.com/boyhailong/article/details/6645681(但是上面好多有错误的地方)

       单例模式也称为单件模式、单子模式,意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。

     《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

      单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。

class CSingleton
{
//其他成员
public:
    static CSingleton* GetInstance()//为什么要用static函数?因为这个类是不能生产对象的,所以第一次只能通过类来调用这个函数。(下面红色标记地方)
    {
        if ( m_pInstance == NULL )  //判断是否第一次调用
            m_pInstance = new CSingleton();
        return m_pInstance;
    }
private:
    CSingleton(){};
    static CSingleton * m_pInstance;
};
CSingleton * CSingleton::m_pInstance;//必须有一个定义性说明,否则报错

       用户访问唯一实例的方法只有GetInstance()成员函数。如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的。GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针:

         CSingleton* p1 = CSingleton :: GetInstance();//第一次只能通过这种类的public函数调用

         CSingleton* p2 = p1->GetInstance();

         CSingleton & ref = * CSingleton :: GetInstance();


       对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。单例类CSingleton有以下特征:

           (1)它有一个指向唯一实例的静态指针m_pInstance,并且是私有的;

           (2)它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;

           (3)它的构造函数是私有的,这样就不能从别处创建该类的实例。         

         m_pInstance指向的空间什么时候释放呢?如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。

        我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人)

class CSingleton
{
//其他成员
public:
    static CSingleton* GetInstance();
private:
    CSingleton(){};
    static CSingleton * m_pInstance;
    class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例
    {
     public:
         ~CGarbo()
         {
             if( CSingleton::m_pInstance )//这里会报错
                 delete CSingleton::m_pInstance;
          }
     }
     Static CGabor Garbo; //注意这里写法有讲就,先定义Garbo,再定义其变量
};


注:这里为什么不直接定义一个public的析构函数,因为返回m_pInstance是一个new出来的对象,这中对象必须通过显示的delete,才能调用析构函数,若不使用delete运算符来撤销动态产生的对象,程序结束时,空间仍然存在,并占用相应的存储空间,即系统不会自动调用析构函数来撤销动态产生对象。


       第二种方法:使用局部静态变量,非常强大的方法,完全实现了单例的特性,而且代码量更少,也不用担心单例销毁的问题。这里为什么能实现,因为这是一个静态变量,第一次调用这个静态函数会创建一个变量instance,后面将不会创建。(注意,我们需要禁止复制构造函数和操作符重载,而上面这种方法不需要

#include<iostream>
using namespace std;
class Singleton
{
    //其他成员
public:
    static Singleton &GetInstance(int num)
    {
        static Singleton instance(num);//第一次调用函数生成static对象,后面再调用这个函数就不创建对象了
        return instance;
    }
    int getNum()const{return num;}
private:
    int num;
    Singleton(int a)
    {
       num=a;
    } 
    Singleton(const Singleton&);//我们需要禁止这个,上面一种方法不需要
    Singleton& operator=(constSingleton&);//这里我们需要禁止这个
};
 
int  main()
{
     Singleton *p =&(Singleton::GetInstance(12));
     cout<<p->getNum()<<endl;
     Singleton* p1 =&(p->GetInstance(5));
     cout<<p1->getNum()<<endl;
     return 0;
}

            第三种方法:在对static对象进行定义性说明时,完成初始化工作。

#include<iostream>
 
using namespace std;
class Singleton    
{    
private:    
       intnum;
       Singleton(int a)                          //D
       {num= a;};//注意:构造方法私有     
       static Singleton* instance;//惟一实例    
       int var;//成员变量(用于测试)    
public:    
       staticSingleton* GetInstance()//工厂方法(用来获得实例)   
       {
              return instance;
       }
       intgetNum()const
       {
              return num;
       }
};
 
Singleton* Singleton::instance = new Singleton(15); //F
 
int main()
{
       Singleton*p = Singleton::GetInstance();
       cout<<p->getNum()<<endl;
       Singleton*p1 = p->GetInstance();
       cout<<p1->getNum()<<endl;
       system("pause");
       return0;
}


注:在这个例子中,何海涛用了一个叫内部类的东西来延迟静态对象的初始化。

          有些不解的地方:D行明明已经将构造函数声明为私有的,而F行却能调用,这是为什么呢,求大神。





                
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值