C++ class中的静态(static)成员
(1) 静态数据成员
①一般地静态数据成员在该类定义之外被初始化,如同一个成员函数被定义在类定义之外一样。在这种定义中的静态成员的名字必须被其类名限定修饰,例如下面是_interestRate的初始化
// 静态类成员的显式初始化
#include "account.h"
double Account::_interestRate = 0.0589;
②静态数据成员的初始化不应该被放在头文件中,而应该放在含有类的非inline 函数定义的文件中。静态数据成员可以被声明为任意类型。例如:
#include <string>
class Account
{
// ...
private:
static const string name;
};
const string Account::name( "Savings Account" );
作为特例,整型的const 静态数据成员可以在类体中用一常量值初始化。例如,如果决定用一个字符数组而不是string 来存储账户的姓名那么我们可以用int 型的const 数据成员指定该数组的长度,例如:
// 头文件
class Account {
// ...
private:
static const int nameSize = 16;
static const char name[nameSize];
};
// 文本文件
const int Account::nameSize; // 必需的成员定义
const char Account::name[nameSize] = "Savings Account";
用常量值作初始化整型的const 静态数据成员是一个常量表达式(constant expression)。如果需要在类体中使用这个被命名的值,那么,类设计者可声明这样的静态数据成员。例如,因为const 静态数据成员nameSize是一个常量表达式,所以类的设计者可以用它来指定数组数据成员name 的长度。在类体内初始化一个const 静态数据成员时,该成员必须仍然要被定义在类定义之外。但是因为这个静态数据成员的初始值是在类体中指定的所以在类定义之外的定义不能指定初始值。
因为name 是一个数组,不是整型,所以它不能在类体内被初始化。任何试图这么做的行为都会导致编译时刻错误,例如:
class Account {
// ...
private:
static const int nameSize = 16; // ok: 整型
static const char name[nameSize] =
"Savings Account"; // 错误
};
name 必须在类定义之外被初始化。
这个例子还说明了一点我们注意到成员nameSize 指定了数组name 的长度,而数组name的定义出现在类定义之外
const char Account::name[nameSize] = "Savings Account";
nameSize 没有被类名Account 限定修饰。尽管nameSize 是私有成员,但是name的定义仍没有错。如同类成员函数的定义可以引用类的私有成员一样,静态数据成员的定义也可以。静态数据成员name的定义是在它的类的域内,当限定修饰名Account::name被看到之后,它就可以引用Account 的私有数据。
③静态数据成员的访问
1. 在类的成员函数中可以直接访问该类的静态数据成员,而不必使用成员访问操作符:
inline double Account::dailyReturn()
{
return( _interestRate / 365 * _amount );
}
2. 在非成员函数中,我们必须以如下两种方式访问静态数据成员:
I. 使用成员访问操作符
class Account {
// ...
private:
friend int compareRevenue( Account& , Account* );
// 余下部分未变
};
// 引用和指针参数来说明对象和指针访问
int compareRevenue( Account &ac1, Account *ac2 )
{
double ret1, ret2;
ret1 = ac1._interestRate * ac1._amount;
ret2 = ac2->_interestRate * ac2->_amount;
// ...
}
ac1._interestRate 和ac2->_interestRate都引用静态成员Account::_interestRate
II. 访问静态数据成员的另一种方法,是用被类名限定修饰的名字直接访问它
// 用限定修饰名访问静态成员
if ( Account:_interestRate < 0.05 )
当我们不通过类的成员访问操作符访问静态数据成员时必须指定类名以及紧跟其后的域操作符 Account:: 。
因为静态成员不是全局对象所以我们不能在全局域中找到它下面的friend 函数compareRevenue()的定义与刚刚给出的等价
int compareRevenue( Account &ac1, Account *ac2 )
{
double ret1, ret2;
ret1 = Account::_interestRate * ac1._amount; //因为是friend函数,才可以访
//私有的静态成员变量_interestRate
ret2 = Account::_interestRate * ac2->_amount;
// ...
}
注意:静态数据成员的“惟一性”本质(独立于类的任何对象而存在的惟一实例),使它能够以独特的方式被使用,这些方式对于非static 数据成员来说是非法的
①静态数据成员的类型可以是其所属类,而非static 数据成员只能被声明为该类的对象的指针或引用。例如:
class Bar {
public:
// ...
private:
static Bar mem1; // ok
Bar *mem2; // ok
Bar mem3; // 错误
};
2 静态数据成员可以被作为类成员函数的缺省实参,而非static 成员不能。例如:
extern int var;
class Foo {
private:
int var;
static int stcvar;
public:
// 错误: 被解析为非 static 的 Foo::var
// 没有相关的类对象
int mem1( int = var );
// ok: 解析为 static 的 Foo::stcvar
// 无需相关的类对象
int mem2( int = stcvar );
// ok: int var 的全局实例
int mem3( int = ::var );
};
(2) 静态成员函数
使用类的静态成员函数来访问类的私有静态数据成员。
静态成员函数的声明除了在类体中的函数声明前加上关键字static, 以及不能声明为const 或volatile之外,与非静态成员函数相同。出现在类体外的函数定义不能指定关键字static。
静态成员函数没有this 指针,因此在静态成员函数中,隐式或显式地引用这个指针都将导致编译时刻错误。试图访问隐式引用this 指针的非静态数据成员也会导致编译时刻错误。例如前面给出的成员函数dailyReturn()就不能被声明为静态成员函数,因为它访问了非静态数据成员amount。
我们可以用成员访问操作符点. 和箭头-> 为一个类对象或指向类对象的指针调用静态成员函数,也可以用限定修饰名直接访问或调用静态成员函数,而无需声明类对象。
(3)在派生类中的静态数据成员与静态成员函数
class的static data member只有一份实例,被class和class的派生类的所有实例共享。class和class的派生类共用同一块内存中的静态数据成员。
一个简单的例子:
#include <iostream>
using namespace std;
class A{
public:
A(){}
~A(){}
static void SetA(int b){a=b;}
static int GetA(){return a;}
private:
static int a;
};
int A::a = 5;
class B : public A{
public:
B(){}
~B(){}
};
int main(){
cout<< "a=" << A::GetA() <<'/n'; //输出5
cout<< "a=" << B::GetA() <<'/n'; //输出5
B::SetA(4);
cout<< "a=" << B::GetA() <<'/n'; //输出4
cout<< "a=" << A::GetA() <<'/n'; //输出4
return 0;
}
类的静态函数与函数中的静态数据成员在编译期绑定!