有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。
声明静态成员
我们通过在成员的声明之前加上关键字static使得其与类关联在一起,和其他成员一样,静态成员可以使public得或private的。静态数据成员的类型可以使常量、引用、指针、类类型等。
举个例子,我们定义一个类,用它表示银行的账户记录:
class Account{ public: void calculate() {amount+=amount*interestRate;} static double rate() {return interestRate;} static void rate(double); private: std::string owner; double amount; static double interestRate; static double initRate(); };
类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。因此,每个Account对象将包含两个数据成员:owner和amount。只存在一个interestRate对象而且它被所有所有Account对象共享。
类似的,静态成员函数也不与任何对象绑定在一起,它们不包含this指针。作为结果,静态成员函数不能声明成const的,而且我们也不能在static函数体内使用this指针,这一限制既适用于this的显式使用,也对调用非静态成员的隐式使用有效。静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。静态成员函数不可以同时声明为 virtual、const、volatile函数 。
class base{ virtual static void func1();//错误 static void func2() const;//错误 static void func3() volatile;//错误 };
使用类的静态成员
我们使用作用域运算符直接访问静态成员:
double r;
r=Account::rate(); //使用作用域运算符访问静态成员
虽然静态成员不属于类的某个对象,但是我们仍然可以使用类的对象、引用或者指针类访问静态成员:
Account ac1;
Account *ac2=&ac1;
//调用静态成员函数rate的等价形式
r=ac1.rate(); //通过Account的对象或引用
r=ac2->rate(); //通过指向Account对象的指针
成员函数不要通过作用域运算符就能直接使用静态成员。
class Account{
public:
void calculate() {amount+=amount*interestRate;}
private:
static double interestRate;
};
静态数据成员函数
静态成员函数:静态成员函数一般只能访问静态成员变量,如果要访问非静态成员变量的话,只能访问某一个对象的非静态成员变量和静态成员函数。可以传一个对象的指针,引用等参数给这个静态成员函数。
class a { public: a():m_ia(123) {} int getia() { return m_ia; } static int f(a &aa) { return aa.getia(); } private: int m_ia; }; void main() { a aa; cout<<a::f(aa); }
静态成员函数中是不能调用非静态成员的,包括非静态成员函数和非静态成员变量。那么在非静态成员函数中是否可以调用静态成员函数呢?答案是肯定的,因为静 态成员函数属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员函数的。其实,我们也可以以一个内存模型这个角度来考 虑,也就是说,无论采取什么样的操作,程序代码都是在内存中运行的,只有在内存中占有了一席之地,我们才能访问它。如果一个成员函数或成员变量还没有在内 存中产生,结果是无法访问它的。所有静态成员函数只能访问静态成员变量。
使用static关键字声明的函数成员是静态的,静态成员函数同样也属于整个类,由同一个类的所有对象共同维护,为这些对象所共享.
作为成员函数,它的访问属性可以受到类的严格控制,对于公有的静态函数成员函数,可以通过类名或对象名来调用,但一般情况下建议用对象名来引用静态函数成员.注意,一般的成员函数只能通过对象名来调用.
由于一个类的静态成员函数只有一个拷贝,因此它访问对象的数据和函数时受到了限制.静态成员函数可以直接访问该类的静态数据成员.而访问非静态数据成员, 必须通过参数传递方式得到对象名,然后通过对象名来访问.可以看到,通过静态函数成员访问非静态成员使相当麻烦的,一般的使用中,它主要用来访问全局变量 或同一个类中的静态数据成员,特别是和后者一起使用,达到对同一个类中对象之间共享的数据进行维护的目的.
构造函数和析构函数不可以定义为static,构造函数要给每一个对象一个this指针如果可以是静态的,它如何构造和访问this指针?
明显是不可以的!
静态成员变量一般要在.cpp文件里进行定义:
double Account::_interestRate = 0.0589;
静态成员函数的声明除了在类体中的函数声明前加上关键字static 以及不能声明为const 或volatile 之外与非静态成员函数相同,出现在类体外的函数定义不能指定关键字static.
const string str = "liangxueliang";
Account *Acc = new Account(222222,str);
Acc->getRate();
Account::getRate();
定义静态数据成员
和其他的成员函数一样,我们既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句:
void Account::rate(double newRate)
{
interestRate=newRate;
}
和类的所有成员一样,当我们指向类外部的静态成员时,必须指明成员所属的类名。static关键字则只出现在类内部的声明语句中。
因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的。而且一般来说,我们不能在类的内部初始化静态数据成员。相反的,必须在类的外部定义和初始化每个静态成员。和其他对象一样,一个静态数据成员只能定义一次。
类似于全局变量,静态数据成员定义在任何函数之外,因此一旦它被定义,就将一直存在于程序的整个生命周期中。
我们定义静态数据成员的方式和在类的外部定义成员函数差不多。我们需要指定对象的类型名,然后是类名、作用域运算符以及成员自己的名字:
//定义并初始化一个静态成员
double Account::interestRate=initRate();
这条语句定义了名为interestRate的对象,该对象是类Account的静态成员,其类型是double。从类名开始,这条定义语句的剩余部分就都位于类的作用域之内了。因此,我们可以直接使用initRate函数。注意,虽然initRate是私有的,我们也能使用它初始化interestRate。和其他成员的定义一样,interestRate的定义也可以访问类的私有成员。
要想确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一文件中。
静态成员的类内初始化
通常情况下,类的静态成员不应该在类的内部初始化。然而,我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr。初始值必须是常量表达式,因为这些成员本身就是常量表达式,所以它们能用在所有适用于常量表达式的地方。例如,我们可以用一个初始化了的静态数据成员执行数组成员的维度:
class Account{ public: static double rate() { return interestRate;} static void rate(double); private: static constexpr int period=30; double daily_tbl[period]; //period是常量表达式 };
如果在类的内部提供了一个初始值,则成员的定义不能再指定一个初始值了:
//一个不带初始值的静态成员的定义
constexpr int Account::period; //初始值在类的定义内提供
即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。
静态成员能用于某些场景,而普遍成员不能
如我们所见,静态成员独立于任何对象。因为,在某些非静态数据成员可能非法的场合,静态 成员却可以正常地使用。举个例子,静态数据成员可以是不完全类型。特别地,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用。
class Bar{ public: //...... private: static Bar mem1; //正确:静态成员可以是不完全类型 Bar *mem2; //正确:指针成员可以使不完全类型 Bar mem3; //错误:数据成员必须是完全类型 };
静态成员和普通成员的另一个区别是我们可以使用静态成员作为默认实参:
class Screen{ public: //bkground 不是一个在类中稍后定义的静态成员 Screen &clear(char =bkground); private: static const char bkground; };
非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以从中获取成员的值,最终引发错误。