C++ 语言类的静态成员 雨点打透心脏的1/2处 2022-09-07 06:24 153阅读 0赞 # C++ 语言类的静态成员 # **类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。** 一个银行账户类可能需要一个数据成员来表示当前的基准利率。我们希望利率与类关联,而非与类的每个对象关联。从实现效率的角度来看,没必要每个对象都存储利率信息。而且更加重要的是,一旦利率浮动,我们希望所有的对象都能使用新值。 成员函数 (member function) 即类的函数成员。普通的成员函数通过隐式的 `this` 指针与类的对象绑定在一起。静态成员函数不与对象绑定在一起也没有 `this` 指针。成员函数可以重载,此时隐式的 `this` 指针参与函数匹配的过程。 ## 1. 声明静态成员 ## 在成员的声明之前加上关键字 `static` 使得其与类关联在一起。和其他成员—样,静态成员可以是 `public` 的或`private` 的。静态数据成员的类型可以是常量、引用、指针、类类型等。 class Account { public: void calculate() { amount += amount * interest_rate; } static double rate() { return interest_rate; } static void rate(double); private: std::string owner; double amount; static double interest_rate; static double init_rate(); }; 类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。每个 `Account` 对象将包含两个数据成员:`owner` 和 `amount`。只存在一个 `interest_rate` 对象而且它被所有 `Account` 对象共享。静态成员函数也不与任何对象绑定在一起,它们不包含 `this` 指针。作为结果,静态成员函数不能声明成 `const` 的,而且我们也不能在 `static` 函数体内使用 `this` 指针。这一限制既适用于 `this` 的显式使用,也对调用非静态成员的隐式使用有效。 ## 2. 使用类的静态成员 ## 我们使用作用域运算符直接访问静态成员: double r; r = Account::rate(); // 使用作用域运算符访问静态成员 虽然静态成员不属于类的某个对象,但是我们仍然可以使用类的对象、引用或者指针来访问静态成员: Account acl; Account *ac2 = &acl; // 调用静态成员函数 rate 的等价形式 r = acl.rate(); // 通过 Account 的对象或引用 r = ac2->rate(); // 通过指向 Account 对象的指针 成员函数不用通过作用域运算符就能直接使用静态成员: class Account { public: void calculate() { amount += amount * interest_rate; } private: static double interest_rate; …… }; ## 3. 定义静态成员 ## 和其他的成员函数一样,我们既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复 `static` 关键字,该关键字只出现在类内部的声明语句: void Account::rate(double new_rate) { interest_rate = new_rate; } 和类的所有成员一样,当我们指向类外部的静态成员时,必须指明成员所属的类名。`static` 关键字则只出现在类内部的声明语句中。 **因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。它们不是由类的构造函数初始化的。一般来说,我们不能在类的内部初始化静态成员,必须在类的外部定义和初始化每个静态成员。和其他对象一样, —个静态数据成员只能定义一次。** 类似于全局变量,静态数据成员定义在任何函数之外。因 此一旦它被定义,就将一直存在于程序的整个生命周期中。我们定义静态数据成员的方式和在类的外部定义成员函数差不多。我们需要指定对象的类型名,然后是类名、作用域运算符以及成员自己的名字: // 定义并初始化一个静态成员 double Account::interest_rate = init_rate(); 这条语句定义了名为 `interest_rate` 的对象,该对象是类 `Account` 的静态成员,其类型是 `double`。从类名开始,这条定义语句的剩余部分就都位于类的作用域之内了,我们可以直接使用 `init_rate()` 函数。虽然`init_rate()` 是私有的,我们也能用它初始化 `interest_rate`。和其他成员的定义一样,`interest_rate` 的定义也可以访问类的私有成员。要想确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一个文件中。 ## 4. 静态成员的类内初始化 ## 通常情况下,类的静态成员不应该在类的内部初始化。**我们可以为静态成员提供 `const` 整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的 `constexpr`。**初始值必须是常量表达式,因为这些成员本身就是常量表达式,所以它们能用在所有适合于常量表达式的地方。我们可以用一个初始化了的静态数据成员指定数组成员的维度: class Account { public: static double rate() { return interest_rate; } 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; // 初始值在类的定义内提供 即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。 ## 5. 静态成员能用于某些场景,而普通成员不能 ## 静态成员独立于任何对象。在某些非静态数据成员可能非法的场合,静态成员却可以正常地使用。静态数据成员可以是不完全类型。静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用: class Bar { public: // ...... private: static Bar meml; // 正确:静态成员可以是不完全类型 Bar *mem2; // 正确:指针成员可以是不完全类型 Bar mem3; // 错误:数据成员必须是完全类型 }; 静态成员和普通成员的另外一个区别是我们可以使用静态成员作为默认实参: class Screen { public: // bkground 表示一个在类中稍后定义的静态成员 Screen &clear(char = bkground); private: static const char bkground; }; 非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。 ## References ## (美) Stanley B. Lippman, (美) Josée Lajoie, (美) Barbara E. Moo 著, 王刚, 杨巨峰 译. C++ Primer 中文版\[M\]. 第 5 版. 电子工业出版社, 2013. [https://www.informit.com/store/c-plus-plus-primer-9780321714114][https_www.informit.com_store_c-plus-plus-primer-9780321714114] [https_www.informit.com_store_c-plus-plus-primer-9780321714114]: https://www.informit.com/store/c-plus-plus-primer-9780321714114
还没有评论,来说两句吧...