C++ 静态成员

在 C++ 中,使用 static(静态)关键字修饰的类成员称为 “静态成员”。普通成员属于具体的对象,创建 100 个 Student 对象就会有 100 份各自的 name。而静态成员不属于某个具体对象,它属于整个类,由所有对象共享唯一的一份。

在 C++ 的类中,静态成员主要包括以下 2 种:

  • 静态成员变量:属于整个类、由所有对象共享同一份内存的变量,平时也常称为 “静态数据成员”。
  • 静态成员函数:属于整个类、主要用来操作静态成员变量的函数。

C++ 静态成员变量

在 C++ 中,静态成员变量属于整个类。不管我们创建多少个对象,甚至一个对象都不创建,它在内存中都只有唯一的一份。从 C++17 标准开始,我们可以使用 inline static 直接在类的内部定义并初始化静态成员变量。

语法:

class 类名 {
public:
    inline static 数据类型 成员名 = 初始值;
};

// 访问静态成员变量
类名::成员名

说明:

前面我们学过使用 inline 定义内联函数,从 C++17 起,inline 也可以用来修饰静态成员变量。在 static 前面加上 inline 之后,我们就可以直接在类的内部给静态成员变量赋初始值,省去了跑到类的外部再单独写一行定义的麻烦。

定义好之后,访问静态成员变量主要有以下 2 种方式:

  • 通过类名访问(推荐):格式为 类名::成员名(如 Student::year)。因为静态成员变量属于整个类,我们使用这种写法一眼就能看出它是全类共享的静态成员。
  • 通过对象名访问(了解):格式为 对象名.成员名(如 stu1.year)。虽然语法上支持,但容易让人误以为它是某一个对象独有的普通成员变量,在实际开发中我们并不推荐这样写。

下面我们结合具体示例,看看多个对象是如何共享同一个静态成员变量的。

示例 1:共享静态成员变量

#include <iostream>
#include <string>

class Student {
public:
    inline static int year = 2026;    // 静态成员变量
    std::string name;                 // 普通成员变量
};

int main() {
    Student stu1;
    stu1.name = "Jack";

    Student stu2;
    stu2.name = "Lucy";

    std::cout << stu1.name << ":" << Student::year << '\n';
    std::cout << stu2.name << ":" << Student::year << '\n';

    // 修改静态成员变量的值
    Student::year = 2027;

    std::cout << "类名访问:" << Student::year << '\n';
    std::cout << "stu1 访问:" << stu1.year << '\n';
    std::cout << "stu2 访问:" << stu2.year << '\n';
    return 0;
}

运行结果如下。

Jack:2026
Lucy:2026
类名访问:2027
stu1 访问:2027
stu2 访问:2027

分析:

在这个例子中,name 是普通成员变量,所以 stu1 和 stu2 各自拥有一份,分别保存 "Jack" 和 "Lucy"。而入学年份 year 是静态成员变量,整个 Student 类自始至终只有唯一的一份。

当我们执行 Student::year = 2027; 修改这份共享数据后,无论通过 Student::year 还是 stu1.year、stu2.year 访问,读取到的结果都会变成 2027。

在 C++17 之前的早期标准中,普通静态成员变量不能直接在类内赋初值,必须采用 “类内声明、类外定义并初始化” 的两步写法。对于这个例子来说,下面 2 种定义方式是等价的。

// 方式 1(推荐)
class Student {
public:
    inline static int year = 2026;
};

// 方式 2
class Student {
public:
    static int year;         // 类内声明
};
int Student::year = 2026;    // 类外定义

大家阅读早期代码遇到方式 2 时要注意 2 个细节:① 类外写 int Student::year = 2026; 时开头不能再加 static;② 类外定义必须放在全局区域,不能写进 main() 函数内。平时在现代 C++ 开发中,我们优先使用方式 1 即可。

C++ 静态成员函数

在实际开发中,为了保护数据安全,我们经常会把静态成员变量设为 private。比如用来统计对象数量的变量 count,我们并不希望外部代码能随意修改它。那静态成员变量变成 private 之后,我们在类的外部应该怎么读取它呢?

这时我们就可以定义一个使用 static 修饰的 “静态成员函数”。

语法:

class 类名 {
public:
    static 返回值类型 函数名(参数列表) {
        // 函数体
    }
};

// 调用静态成员函数
类名::函数名(实参);

说明:

和静态成员变量一样,静态成员函数也属于整个类,而不绑定到某一个具体对象。调用静态成员函数最规范的方式同样是使用 类名::函数名()。即使当前还没有创建任何一个对象,我们也可以直接通过类名调用静态成员函数。

示例 2:统计对象的创建数量

#include <iostream>
#include <string>

class Student {
private:
    std::string name;
    inline static int count = 0;    // 私有静态成员变量

public:
    Student(std::string n) : name(n) {
        count++;                    // 每创建一个对象加 1
    }

    // 静态成员函数
    static int getCount() {
        return count;
    }
};

int main() {
    // 未创建对象时直接通过类名调用
    std::cout << "初始数量:" << Student::getCount() << '\n';

    Student stu1("Jack");
    std::cout << "当前数量:" << Student::getCount() << '\n';

    Student stu2("Lucy");
    std::cout << "当前数量:" << Student::getCount() << '\n';
    return 0;
}

运行结果如下。

初始数量:0
当前数量:1
当前数量:2

分析:

刚进入 main() 函数时,虽然还没有创建任何 Student 对象,但静态成员 count 和 getCount() 都已经存在,因此我们直接调用 Student::getCount() 能正常输出初始值 0。

随后每创建一个对象,它的构造函数都会自动执行一次 count++,统计结果依次变成 1 和 2。如果我们想统计当前内存中实时存活的对象数量,还可以在类的析构函数里加上 count--,让对象销毁时自动减 1。

注意 如果把静态成员函数拆分为 “类内声明、类外定义”,static 关键字只需要写在类内的函数声明前面。比如在类的外部定义 int Student::getCount() 时,开头不能再重复写 static,否则会编译报错。

静态成员函数的访问限制

在使用静态成员函数时,有一条新手极易踩坑的规则:静态成员函数内部只能直接访问类的 “静态成员”,不能直接访问普通的 “非静态成员”。

为什么会有这个限制呢?结合前面学过的 this 指针就很容易理解了:

  • 普通成员函数:必须由某个具体的对象(如 stu1)来调用,函数内部隐含了指向该对象的 this 指针,因此我们既可以访问当前对象的普通成员,也可以访问全类共享的静态成员。
  • 静态成员函数:属于整个类,调用时甚至可以没有任何对象存在,因此静态成员函数内部根本没有 this 指针。

假设程序里有 stu1 和 stu2 两个对象,它们各自拥有不同的 name。如果我们在静态成员函数里直接写 name,由于没有 this 指针指明当前是哪个对象,编译器根本无法确定到底应该读取谁的 name。下面我们通过一个例子来看看。

示例 3:访问非静态成员报错

#include <iostream>
#include <string>

class Student {
private:
    std::string name = "Jack";        // 普通成员变量
    inline static int count = 0;      // 静态成员变量

public:
    static void showCount() {
        std::cout << count << '\n';
        std::cout << name << '\n';    // 错误:访问非静态成员
    }
};

int main() {
    Student::showCount();
    return 0;
}

运行结果如下。

(报错)error: invalid use of member 'Student::name' in static member function

分析:

在静态成员函数 showCount() 中,读取静态成员变量 count 完全正常。但执行到 std::cout << name 时,因为函数内部没有 this 指针,编译器无法确定 name 属于哪个对象。因此编译时直接报错,提示非法使用了普通成员(invalid use of member)。

对于这两类函数的访问权限,小伙伴们只需要记住一句话:普通成员函数既能访问普通成员,也能访问静态成员,而静态成员函数只能直接访问静态成员。如果确实要在静态成员函数里读取某个对象的普通成员,我们可以把该对象作为参数传进来,通过 对象名.成员名 来访问。

常见问题

1. 静态成员函数末尾可以加上 const 吗?

不可以。const 成员函数本质上是限制函数内部不能修改 this 指针指向的当前对象。而静态成员函数不属于任何具体对象,内部根本没有 this 指针,因此不能写成 static int getCount() const。

2. 在实际开发中,什么时候应该把成员设计为 static 静态成员?

判断标准很简单,主要看这份数据或操作是 “每个对象各自独立” 还是 “全类共享同一份”。比如学生的姓名、成绩是每个对象各自独立的,就应该定义为普通成员。而统计对象总数或全类共用的配置项需要所有对象共享同一份,就适合设计为静态成员。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号