鸡啄米在上一讲数据和函数中讲到,函数之间共享数据也就是此函数访问彼函数的数据主要是通过局部变量、全局变量、类的数据成员、类的静态成员及友元实现的,前三个已经讲过了,这一讲鸡啄米来讲讲静态成员。静态成员包括静态数据成员和静态函数成员。
1.静态数据成员
之前讲到的类的数据成员都是一个对象一个拷贝,每个对象都有自己的数据成员的值,但有时候我们需要让某个类的所有对象共享某个数据成员,比如我们有一个学生类CStudent,其中有学号、姓名等数据成员,如果我们想要对学生人数进行统计,那么这个数据应该放在哪里呢?放在类外的某个地方的话数据的隐藏性就不好了。可能有朋友就说了,可以在类中增加一个数据成员存放学生人数,但这样就有两个坏处,第一,该类的每个对象都将有一个这个数据成员的副本,浪费内存;第二,学生人数可能在不同的对象中有不同的数,不一致了。这个时候就需要静态数据成员了。
静态数据成员存放的是类的所有对象的某个共同特征的数据,对于每个对象而言,该数据都是相同的,在内存中只存在一份。这与类的一般数据成员不同,一般数据成员会在每个对象中都有一个拷贝,每个拷贝的值可能都不一样。静态数据成员由类的所有对象共同维护和使用,这样就实现了类的对象间的数据共享。
声明静态数据成员的方式与一般数据成员不同的是,其前面要加static关键字,比如static int x;。静态数据成员具有静态生存期。它可以通过类名或对象名访问。用类名直接访问的方式为:类名::标识符。在类的声明中仅对静态数据成员进行引用性说明,就是带static的声明,必须在其文件作用域的其他地方进行定义性说明,也可以进行初始化,如果不显式初始化的话会将此静态数据成员初始化为0。
对于静态数据成员鸡啄米给大家一个例程:
#include <iostream> using namespace std; class CStudent // 学生类的声明 { public: CStudent(int nID) { m_nID = nID; m_nCount++; } // 构造函数 CStudent(CStudent &s); // 拷贝构造函数 int GetID(){ return m_nID;} void GetCount() { cout<<" 学生人数:"<<m_nCount<<endl; } // 输出静态数据成员 private: int m_nID; static int m_nCount; // 静态数据成员的引用性说明 }; CStudent::CStudent(CStudent &s) { m_nID = s.m_nID; m_nCount ++; } int CStudent::m_nCount=0; // 静态数据成员的定义性说明及初始化 int main() { CStudent A(6); // 定义对象A cout<<"学生A,"<<A.GetID(); A.GetCount(); // 输出此时学生个数 CStudent B(A); // 定义对象B,并用A初始化B cout<<"学生B,"<<B.GetID(); // 输出此时学生个数 B.GetCount(); return 0; }
当然学生的学号不可能一样,这里只是举个例子。类CStudent的静态数据成员m_nCount用来给学生人数计数,定义一个新的学生对象,它的值就加1。这里初始化的时候要注意,必须用类名来引用,还有就是此数据成员的访问控制属性,这里m_nCount声明为私有类型,初始化时可以直接访问,但是在主函数内就不能直接访问了。
在对象A声明时,调用构造函数,m_nCount加1,对象B声明时,调用拷贝构造函数,m_nCount又加1,两次都访问的A和B共同维护的静态数据成员,这样就实现了对象间的数据共享。上面的运行结果是:
学生A,6 学生人数:1
学生B,6 学生人数:2
2.静态函数成员
上面的学生类中,GetCount函数用来输出静态数据成员m_nCount,我们想输出m_nCount时就要通过学生类的某个对象调用GetCount函数来实现。但是,在定义任何对象之前,m_nCount是有初始值的,那我们怎样输出这个初始值呢?因为没有定义任何对象,就没法通过对象调用GetCount。m_nCount是私有的,也不能用类名直接引用。因为m_nCount是类的所有对象共有的,那我们希望不通过对象直接用类名调用函数来显示m_nCount,这就要用到静态成员函数了。
静态成员函数跟静态数据成员一样,也是由类的所有对象所共有,由他们共同维护和使用。声明时前面也要加static关键字,比如,static void fun(void);。我们可以通过类名或对象名调用公有类型的静态成员函数,而非静态成员函数只能由对象名调用。
静态成员函数可以访问该类的静态数据成员和其他静态成员函数,如果要访问非静态数据成员,则必须将对象作为参数传递进去,然后通过传进去的对象名来访问。鸡啄米给大家举个例子:
class A { public: static void f(A a); private: int x; }; void A::f(A a) { cout<<x; //对x的引用是错误的 cout<<a.x; //正确 }
所以静态成员函数访问非静态成员很麻烦,它一般用来访问全局变量和同一个类中的静态数据成员。
鸡啄米把上面学生类的GetCount函数改成静态成员函数大家看下:
#include <iostream> using namespace std; class CStudent // 学生类的声明 { public: CStudent(int nID) { m_nID = nID; m_nCount++; } // 构造函数 CStudent(CStudent &s); // 拷贝构造函数 int GetID() { return m_nID;} static void GetCount(){ cout<<" 学生人数:"<<m_nCount<<endl; } // 静态成员函数,输出静态数据成员 private: int m_nID; static int m_nCount; // 静态数据成员的引用性说明 }; CStudent::CStudent(CStudent &s) { m_nID = s.m_nID; m_nCount ++; } int CStudent::m_nCount=0; // 静态数据成员的定义性说明及初始化 int main() { CStudent A(6); // 定义对象A cout<<"学生A,"<<A.GetID(); A.GetCount(); // 输出此时学生个数 CStudent B(A); // 定义对象B,并用A初始化B cout<<"学生B,"<<B.GetID(); // 输出此时学生个数 CStudent::GetCount(); return 0; }
大家可以比较的看下两个例子,学生类的声明只有一点不同,就是GetCount前加了static将其改为了静态成员函数,主函数中就可以通过类名或对象名调用静态成员函数,就像上面的A.GetCount()和CStudent::GetCount(),大家也可以把A.GetCount()改成CStudent::GetCount(),或者把CStudent::GetCount()改成B.GetCount()试下,运行结果应该是一样的。
静态成员的好处就是内存中只有一份拷贝,可以直接通过类名访问。鸡啄米今天就讲到这了,下一讲给大家讲讲友元。