静态成员变量和静态成员函数

本文详细讲解了C++中静态成员变量和静态成员函数的概念,如何工作以及它们与普通成员的区别。通过实例展示了如何正确使用静态成员来维护类的全局状态,以及为何在类的设计中引入静态成员。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、静态成员变量和静态成员函数

类的静态成员有两种:静态成员变量和静态成员函数。静态成员变量就是在定义时前面加了static关键字的成员变量;静态成员函数就是在声明时前面加了static关键字的成员函数。下面的CRectangle类就有两个静态成员变量和一个静态成员函数:

class CRectangle{
   private:
    int w,h;
    static int nTotalArea;      // 静态成员变量
    static int nTotalNumber;    // 静态成员变量
   public:
    static void PrintTotal ();  // 静态成员函数
};

普通成员变量每个对象有各自的一份,而静态成员变量只有一份,被所有同类对象共享。普通成员函数一定是作用在某个对象上的,而静态成员函数并不具体作用在某个对象上。访问普通成员时,要通过对象名.成员名等方式,指明要访问的成员变量是属于哪个对象的,或要调用的成员函数作用于哪个对象;访问静态成员时,则可以通过类名::成员名的方式访问,不需要指明被访问的成员属于哪个对象或作用于哪个对象。因此,甚至可以在还没有任何对象生成时就访问一个类的静态成员。当然,非静态成员的访问方式(也即对象名.成员名)其实也适用于静态成员,但效果和类名::成员名这种访问方式没有区别。使用sizeof运算符计算对象所占用的存储空间时,不会将静态成员变量计算在内。对上面的CRectangle类来说,sizeof(CRectangle)的值是 8。静态成员变量本质上是全局变量。一个类,哪怕一个对象都不存在,其静态成员变量也存在。静态成员函数并不需要作用在某个具体的对象上,因此本质上是全局函数。设置静态成员的目的,是为了将和某些类紧密相关的全局变量和全局函数写到类里面,形式上成为一个整体。考虑一个需要随时知道矩形总数和总面积的图形处理程序,当然可以用全局变量来记录这两个值,但是将这两个变量作为静态成员封装进类中,就更容易理解和维护。例如下面的程序:

#include <iostream>
using namespace std;
class CRectangle {
   private:
    int w, h;
    static int totalArea;    // 矩形总面积
    static int totalNumber;  // 矩形总数
   public:
    CRectangle(int w_, int h_);
    ~CRectangle();
    static void PrintTotal();
};
CRectangle::CRectangle(int w_, int h_) {
    w = w_;
    h = h_;
    totalNumber++;       // 有对象生成则增加总数
    totalArea += w * h;  // 有对象生成则增加总面积
}
CRectangle::~CRectangle() {
    totalNumber--;       // 有对象消亡则减少总数
    totalArea -= w * h;  // 有对象消亡则减少总而积
}

void CRectangle::PrintTotal() {
    cout << totalNumber << "," << totalArea << endl;
}
int CRectangle::totalNumber = 0;  // 必须在定义类的文件中对静态成员变量进行一次声明
int CRectangle::totalArea = 0;    // 或初始化,否则编译能通过,链接不能通过
int main() {
    CRectangle r1(3, 3), r2(2, 2);
    // cout << CRectangle::totalNumber;  // 错误,totalNumber 是私有
    CRectangle::PrintTotal();
    r1.PrintTotal();
    return 0;
}

程序的输出是:

2, 13
2, 13

这个程序的基本思想是:CRectangle类只提供一个构造函数,所有CRectangle对象生成时都需要用这个构造函数初始化,因此在这个构造函数中增加矩形的总数和总面积的数值即可;而所有CRectangle对象消亡时都会执行析构函数,所以在析构函数中减少矩形的总数和总面积的数值即可。第 6 行和第 7 行的两个成员变量用来记录程序中所有矩形对象的总数和它们的总面积。这两个值显然不能由每个对象都维护一份,而应该只有一份。虽然也可以用两个全局变量来存放这两个值,但那样就无法从形式上一眼看出这两个全局变量和CRectangle类的紧密联系,也就看不出这两个全局变量会在哪些函数中被访问。把它们写成CRectangle类的静态成员变量,这个问题就迎刃而解了。输出矩形总数和总面积的函数PrintTotal没有写成全局函数,而是写成CRectangle类的静态成员函数,道理也是一样的。静态成员变量必须在类定义的外面专门声明,声明时变量名前面加类名::,如第 27 行和第 28 行。声明的同时可以初始化。如果没有声明,那么程序编译时虽然不会报错,但是在链接(link)阶段会报告“标识符找不到”,不能生成.exe文件。第 31 行如果没有注释掉,编译会出错。因为totalNumber是私有成员,不能在成员函数外面访问。第 32 行和第 33 行的输出结果相同,说明二者是等价的。因为静态成员函数不具体作用于某个对象,所以静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数。假如上面程序中的 PrintTotal函数如下编写:

void CRectangle::PrintTotal() {
    cout << w << "," << totalNumber << "," << totalArea << endl;  // 错误
}

其中访问了非静态成员变量w,这是不允许的,编译无法通过。因为如果用CRetangle::PrintTotal();这种形式调用PrintTotal函数,那就无法解释进入PrintTotal函数后,w到底是属于哪个对象的。为什么在静态成员函数内不能调用非静态成员函数?在上面的程序中,CRectangle类的写法表面看起来没有什么问题,实际上是有漏洞的。原因是,并非所有的CRectangle对象生成时都会用程序中的那个构造函数初始化。如果使用该类的程序稍微复杂一些,包含以CRectangle对象为参数的函数,或以CRectangle对象为返回值的函数,或出现CRectangle rl(r2);这样的语句,那么就有一些CRectangle对象是用默认复制构造函数,而不是CRectangle(int w_, int h)进行初始化的。这些对象生成时没有增加totalNumbertotalArea的值,而消亡时却减少了totalNumbertotalArea的值,这显然是有问题的。解决办法是为CRectangle类编写如下复制构造函数:

CRectangle::CRectangle(CRectangle & r) {
    totalNumber++;
    totalArea += r.w * r.h;
    w = r.w; h = r.h;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值