再上一篇:第十二章 类的其它特性
上一篇:12.2 虚函数
主页
下一篇:12.5 指向类成员的指针
再下一篇:第十三章 运算符重载
文章列表

12.3 静态成员

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

在定义一个类时,实际上是定义了一种数据类型,编译程序并不为数据类型分配存储空间。只有在说明类的对象时,才依次为对象的每一个成员分配存储空间,并把对象占用的存储空间作为一个整体来看待。通常,每当说明一个对象时,把该类中的有关成员拷贝到该对象中,即同一类的不同对象,其成员之间是互相独立的。当我们将类的某一个数据成员的存储类型指定为静态类型时,则由该类所产生的所有对象,均共享为静态成员所分配的一个存储空间,换言之,在说明对象时,并不为静态类型的成员分配空间。

12.3.1 静态数据成员

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

例 12.9 静态数据成员的说明与使用。

#include

class A{

int i,j;

static int x,y;

public:

A(int a=0,int b=0,int c=0, int d=0)

{ i=a;j=b;x=c;y=d;}

void Show()

{

cout << "i="<

cout << "x="<

}

};

int A::x=0; //D

int A::y=0; //E

void main(void )

{

A a(2,3,4,5);

a.Show();

A b(100,200,300,400);

b.Show();

a.Show();

}

类A中的成员x和y是静态数据成员。执行程序将输出:

i=2 j=3 x=4 y=5

i=100 j=100 x=300 y=400

i=2 j=3 x=300 y=400

首先输出对象a的数据成员,然后产生对象 b,并输出b的数据成员,由于产生对象b时改变了静态的成员x和y的值,所以接着输出对象a的成员时,其x和y与对象b的x和y的值相同,这说明同一个类的不同对象的静态数据成员使用相同存储空间。

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

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

2、必须在文件作用域中,对静态数据成员作一次且只能作一次定义性说明。由于类是一种数据结构,所以在定义类时,不为类分配存储空间,这种说明属于引用性的说明。只有遇到定义性说明时,编译程序才能为静态数据成员分配存储空间。如上例中D行和E行就是对静态数据成员x和y的定义性说明。正因为静态数据成员在定义性说明时已分配了存储空间,所以通过静态数据成员名前加上类名和作用域运算符,可直接使用静态数据成员。如:#include

class A{

int i,j;

public: //D

static int x;

public:

A(int a=0,int b=0)

{ i=a;j=b;}

void Show()

{

cout << "i="<

cout << "x="<

}

};

int A::x; //F

void main(void )

{

cout <<"A::x="<

}

程序中的G行,直接输出了类A的静态数据成员x的值。执行程序后的输出为:x=0

在C++中,静态变量缺省的初值为0,所以静态数据成员总有唯一的初值。当然,在对静态数据成员作定义性的说明时,也可以指定一个初值。如上例中的F行改为:

int A::x = 500;

则将x的初值置为500。这种置初值不受静态数据成员的访问权限的限制。将D行删除时,仍可将F行改为:

int A::x = 500;

3、静态数据成员具有全局变量和局部变量的一些特性。静态数据成员与全局变量一样都是静态分配存储空间的,但全局变量在程序中的任何位置都可以访问它,而静态数据成员受到访问权限的约束。如将上例中的D行删去,则在主函数中就不能输出A::x的值。

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

例 12.10 利用静态数据成员作为产生对象的计数器。

#include

class A{

int i;

static int count;

public:

A(int a=0)

{

i=a; count++;

cout <<"Number of Objects="<

}

~A()

{

count--;

cout <<"Number of Objects="<

}

void SetData(int a ){i=a;}

void Show()

{

cout << "i="<

cout << "count="<

}

};

int A::count;

void main(void )

{

A a1(100);

{

A b[2];

}

}

执行程序时的输出为:

Number of Objects=1

Number of Objects=2

Number of Objects=3

Number of Objects=2

Number of Objects=1

Number of Objects=0

读者可根据程序自行分析输出的结果。

12.3.2 静态成员函数

与静态的数据成员一样,可以将类的成员函数定义为静态的成员函数。方法也是使用关键字static来修饰成员函数。

例 12.11 定义和使用静态的成员函数。

#include

class A{

int i;

static int count;

public:

A(int a=0)

{

i=a; count++;

cout <<"Number of Objects="<

}

~A()

{

count--;

cout <<"Number of Objects="<

}

static void SetData(int ,A &);

static void Show( A &r)

{

cout << "i="<

cout << "count="<

}

};

void A::SetData(int a,A &r)

{

r.i=a;

}

int A::count;

void main(void )

{

A a1(100);

A::Show(a1);

A::SetData(300, a1);

A::Show(a1);

{

A b[2];

}

}

结合上例,对静态成员函数的用法说明以下几点:

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

2、静态成员函数只能直接使用本类的静态数据成员或静态成员函数,但不能直接使用非静态的数据成员。这是因为静态成员函数可被其它程序代码直接调用,所以,它不包含对象地址的this指针。当在静态成员函数中直接使用非静态成员时,系统无法确定是属于哪一个类的对象成员。如本例中,函数Show( A &r),为了显示对象的非静态的数据成员,必须将要显示的对象作为函数的参数。

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

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

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

6、因C++在产生类的对象时,为了减少对象所占用的存储空间,将同一类的所有对象的成员函数只保存一个拷贝,在对象中是通过指向函数的指针来实现成员函数的调用。为此,在一般情况下定义静态函数并不能获得好处,在使用上还没有非静态成员函数方便,通常没有必要定义静态成员函数。只有在一些特殊的应用场合,才使用静态成员函数。

*12.4 const 、volatile对象和成员函数

可以用关键词const 和volatile来修饰类的成员函数和对象。当用这二个关键词修饰了成员函数时,则const 和volatile对类的成员函数具有特定的语义,用const修饰的对象,只能访问该类中用const修饰的成员函数,而其它的成员函数是不能访问的。用volatile修饰的对象,只能访问该类中用volatile 修饰的成员函数,不能访问其它的成员函数。当希望成员函数只能引用成员数据的值,而不允许成员函数修改数据成员的值时,可用关键词const修饰成员函数。一旦在用const修饰的成员函数中出现修改成员数据的值时,将导致编译错误。12.4.1 const 和volatile成员函数

在成员函数的前面加上关键字const,则表示这函数返回一个常量,其值不可改变。这里讲的const成员函数是指将const放在参数表之后,函数体之前,其一般格式为:

FuncName() const ;

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

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

FuncName() volatile;

其语义是指明成员函数具有一个易变的this指针,调用这个函数时,编译程序把属于此类的所有的数据成员都看作是易变的变量,编译器不要对这函数作优化工作。因此,这种成员函数的执行速度要慢一些,但可保证易变变量的值是正确的。

也可以用这二个关键字同时修饰一个成员函数,其格式为:

FuncName() const volatile;

这两个修饰关键字的顺序是无关紧要的,语义指明限定成员函数在其函数体内不能修改数据成员的值,同时也不要优化该函数,在函数体内把对象的数据成员作为易变变量来处理。

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

12.4.2 const 和volatile对象

说明const 或volatile对象的方法与说明一般变量的方法相同。说明const对象的一般格式为:

const ClassName ObjName;

表示对象ObjName的数据成员均是常量,不能改变其数据成员的值。它可以通过成员运算符“.”来访问const成员函数,但不能访问其它的成员函数。

说明volatile对象的一般格式为:

volatile ClassName Obj;

表示对象Obj中的数据成员都是易变的,它只能访问volatile成员函数,不能访问其它的成员函数。

例 12.12 用const ,volatile修饰的成员函数和对象。

#include

class A{

int i,j;

public:

A(int a=0,int b=0){i=a;j=b;}

void SetData(int a, int b ){i=a;j=b;}

int Geti() const { return i;}

void Show( ) volatile;

void GetData(int * a, int *b)const volatile {*a=i; *b=j;}

};

void A::Show( ) volatile

{

cout << "i="<

}

~String() {if (Str) delete [ ] Str;}

};

String::String(const String &s) //拷贝初始化构造函数{

Length = s.Length;

if(s.Str) {

Str=new char[strlen(s.Str)+1];

strcpy(Str,s.Str);

}

else Str=0;

}

String::String(const char *s)

{

Length = strlen(s);

if(*s) {

Str= new char[strlen(s)+1];

strcpy(Str,s);

}

else Str=0;

}

char * String::IsInString(char c) const

{

char *cp=Str;

while ( *cp)

if(*cp == c) return cp;else cp++;

return 0;

}

void main(void )

{

String s1("I am a stunent");

String s2;

char *s="You are a student too!";

char *sp,*sp1;

s1.Show();

s2.Show();

s2.SetString(s);

s2.Show();

sp=s1.IsInString('s');

cout << "sp="<

sp1=s2.GetString();

cout <<"sp1="<

}

执行程序后的输出为:

String= I am a stunent

String is Empty!

String= You are a student too!

Sp=student

Sp1= You are a student too!

程序中成员函数IsInString(char c)的功能是,判断对象中的字符串中是否包含字符c。若包含则返回指向这一字符的指针,否则返回空。其它函数的功能都比较简单,不作出说明了。

在上例中,成员函数凡是不修改对象的数据成员时,都定义为const成员函数,这样做的好处是:当在成员函数中发生误操作,出现修改成员数据时,编译系统将给出错误信息。另外将构造函数String(const String &s)的形参定义为常数型对象的引用,这将防止在这构造函数中修改对象s中的数据成员。