C++Primer学习(7.6 类的静态成员)

7.6 类的静态成员
有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。例如,一个银行账户类可能需要一个数据成员来表示当前的基准利率。在此例中,我们希望利率与类关联,而非与类的每个对象关联。从实现效率的角度来看,没必要每个对象都存储利率信息。而且更加重要的是,一旦利率浮动,我们希望所有的对象都能使用新值。
声明静态成员
我们通过在成员的声明之前加上关键字 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的显式使用,也对调用非静态成员的隐式使用有效。
使用类的静态成员
我们使用作用域运算符直接访问静态成员:

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;
//其他成员与之前的版本一致
}

定义静态成员
和其他的成员函数一样,我们既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句:

void Account::rate(double newRate)
{
	interestRate = newRate;
}

和类的所有成员一样,当我们指向类外部的静态成员时,必须指明成员所属的类名。static关键字则只出现在类内部的声明语句中
Note因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的。而且一般来说,我们不能在类的内部初始化静态成员。相反的,必须在类的外部定义和初始化每个静态成员。和其他对象一样一个静态数据成员只能定义一次。
类似于全局变量,静态数据成员定义在任何函数之外。因此一旦它被定义,就将一直存在于程序的整个生命周期中。
我们定义静态数据成员的方式和在类的外部定义成员函数差不多。我们需要指定对象的类型名,然后是类名、作用域运算符以及成员自己的名字:
//定义并初始化一个静态成员
double Account::interestRate= initRate();
这条语句定义了名为interestRate的对象,该对象是类Account的静态成员,其类型是 double。从类名开始,这条定义语句的剩余部分就都位于类的作用域之内了。因此,我们可以直接使用 initRate 函数。注意,虽然initRate 是私有的,我们也能用它初始化 interestRate。和其他成员的定义一样,interestRate的定义也可以访问类的私有成员。
要想确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一个文件中。
静态成员的类内初始化
通常情况下,类的静态成员不应该在类的内部初始化。然而,我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr(参见7.5.6节,第267页)。初始值必须是常量表达式,因为这些成员本身就是常量表达式,所以它们能用在所有适合于常量表达式的地方。例如,我们可以用一个初始化了的静态数据成员指定数组成员的维度:

class Account
{
public:
	static double rate()(return interestRate;)
	static void rate(double);
private:
	static constexpr int period=30;// period 是常量表达式
	double daily tbl[period];
}

如果某个静态成员的应用场景仅限于编译器可以替换它的值的情况,则一个初始化的const或constexpr static不需要分别定义。相反,如果我们将它用于值不能替换的场景中,则该成员必须有一条定义语句。
例如,如果 period 的唯一用途就是定义 daily_tbl的维度,则不需要在 Account外面专门定义period。此时,如果我们忽略了这条定义,那么对程序非常微小的改动也可能造成编译错误,因为程序找不到该成员的定义语句。举个例子,当需要把Account::period 传递给一个接受const int&的函数时,必须定义period。如果在类的内部提供了一个初始值,则成员的定义不能再指定一个初始值了:

//一个不带初始值的静态成员的定义
constexpr int Account::period;//初始值在类的定义内提供

Best Prantiees 即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。

静态成员能用于某些场景,而普通成员不能
如我们所见,静态成员独立于任何对象。因此,在某些非静态数据成员可能非法的场合,静态成员却可以正常地使用。举个例子,静态数据成员可以是不完全类型。特别的,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用:

class Bar
{
public :
private :
	static Bar mem1;//正确:静态成员可以是不完全类型
	Bar *mem2;//正确:指针成员可以是不完全类型
	Bar mem3;//错误:数据成员必须是完全类型
}

静态成员和普通成员的另外一个区别是我们可以使用静态成员作为默认实参:
class Screen
{
public:
//bkground 表示一个在类中稍后定义的静态成员
Screen& clear(char = bkqround);
private:
static const char bkground;
}
非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。

常量成员函数
定义:常量成员函数是指在函数声明和定义的参数列表后面加上 const 关键字的成员函数。以下是一个简单的类定义示例,其中包含常量成员函数:

class MyClass 
{
private:
    int data;
public:
    MyClass(int value) : data(value) {}
    // 常量成员函数声明
    int getData() const; 
};
// 常量成员函数定义
int MyClass::getData() const 
{
    return data;
}

作用
保证对象状态不被修改:常量成员函数承诺不会修改调用该函数的对象的任何非 mutable 成员变量(普通成员变量)。这在使用常量对象时非常重要,因为常量对象只能调用常量成员函数

const MyClass obj(10);
int value = obj.getData(); // 可以调用常量成员函数

使用规则
常量对象只能调用常量成员函数:如果一个对象被声明为 const,那么它只能调用常量成员函数,不能调用普通成员函数。

const MyClass constObj(20);
// constObj.someNonConstFunction(); // 错误,常量对象不能调用非 const 成员函数
constObj.getData(); // 正确,调用常量成员函数

普通对象可以调用常量成员函数和普通成员函数:普通对象既可以调用普通成员函数,也可以调用常量成员函数。

MyClass normalObj(30);
normalObj.getData(); // 正确,调用常量成员函数

常量成员函数可以重载:可以同时存在一个普通成员函数和一个同名的常量成员函数,它们构成重载关系。编译器会根据调用对象是否为常量来决定调用哪个函数。

class MyClass 
{
private:
    int data;
public:
    MyClass(int value) : data(value) {}
    int getData() { return data; } // 普通成员函数
    int getData() const { return data; } // 常量成员函数
};

int main() 
{
    MyClass normalObj(40);
    const MyClass constObj(50);
    normalObj.getData(); // 调用普通成员函数
    constObj.getData(); // 调用常量成员函数
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值