“静态”之静——深入解析C++中的static关键字

引言

C++中的static关键字,是一个多功能并且非常强大的工具,不仅可以控制变量的生命周期,还能影响作用域和访问权限。本文将从类内用法和类外用法来讲解static关键字。通过理解static的多种用途,开发者可以更有效的管理内存、更方便的玩转C++编程语言。

什么是static关键字

普通教材或者互联网搜索static的概念的时候一般都会说:声明变量或函数具有静态生命周期。 但是这句话的概念太奇怪了,什么叫静态的生命周期?这个“静”字到底指的是什么意思?接下来我从两方面来解读一下静态的生命周期。第一方面生命周期从程序开始运行一直持续到程序结束。第二方面也是最重要的一方面,静态变量或者静态函数的存储空间只会分配一次。举例来说,普通的局部变量每次进入作用域时创建,作用域结束时销毁,但是声明为静态,静就静在了保持不变,无需反复创建和销毁。

看一下C++程序的内存分布能让我们更清楚静态的概念

局部静态变量

局部静态变量的生命周期贯穿整个程序的运行时间(宛如一个全局变量)。它们在第一次进入其所在的函数或代码块时被初始化,之后即使函数或代码块退出,该变量依然存在,不会被销毁。再次进入函数时,这个变量不会重新初始化,保留上一次函数调用结束时的值。作用域就是普通的作用范围之内,即使生命周期很长,但是作用域在函数外部是不可见的。另外对局部静态变量多说一句,C++11标准及以后,局部静态变量的初始化是线程安全的。

示例代码如下;

#include <iostream>

void counter() {
    static int count = 0; // 局部静态变量,只初始化一次
    count++;
    std::cout << "Count: " << count << std::endl;
}

int main() {
    counter(); // 输出:Count: 1
    counter(); // 输出:Count: 2
    counter(); // 输出:Count: 3
    return 0;
}

那么局部静态变量的应用场景有哪些呢?

1.记录函数被调用的次数

代码如上

2.避免重复初始化,只在第一次调用时初始化

void initHeavyResource() {
    static Resource res = initializeResource(); // 仅在第一次调用时初始化
    res.use();
}

3.缓存计算结果

如果计算特别复杂但是接下来要用到计算所得结果,利用局部静态变量可以做到“一次计算,终身使用”的效果。

int expensiveComputation() {
    static int result = compute(); // 只计算一次
    return result;
}

静态全局变量

静态全局变量的生命周期依然是贯穿整个程序的。但是它和普通全局变量的最大不同就是静态全局变量的作用域仅限于定义它的文件。

// file1.cpp
static int counter = 0; // 静态全局变量,仅在file1.cpp内可见

void incrementCounter() {
    counter++;
}

int getCounter() {
    return counter;
}

静态函数

静态函数的作用与限定在定义它的源文件内部。也就是说只能声明它的文件能够调用它。

静态全局变量和静态函数的使用场景:

静态全局变量通常用于需要在模块内部共享数据,但不希望暴露给模块外部的场景。它可以帮助避免命名冲突,确保变量不会被外部不小心访问或修改。

静态函数通常用于实现一些辅助功能或模块内的实现细节,这些功能不需要在文件之外被访问。

可以用两个字来总结,隐藏

这里多说一句,C++具有三大性质,封装、继承和多态,这里很明显就是C++的封装性,通过合适的封装来使我们的代码,高内聚、低耦合,看上去也赏心悦目。

// file1.cpp
static void helperFunction() {
    // 仅在file1.cpp中可用
    std::cout << "This is a static function." << std::endl;
}

类中的静态成员

先说静态成员变量

和普通的成员变量不同,静态成员变量属于类本身,而不是类的某个对象。接下来将通过这一点来介绍一下静态成员变量的主要性质,首先静态成员变量在内存中只有一份,无论创建多少个类的对象,所有对象共享同一个静态成员变量。其次如何访问静态成员变量?可以通过类名直接访问,也可以通过对象访问,但通常更推荐使用类名访问,以强调它属于类而不是对象。生命周期不用说了,还是那四个字,贯穿始终。最后说一下初始化,静态成员变量必须在类外部初始化一次,且只能初始化一次。

示例代码如下

#include <iostream>

class MyClass {
public:
    static int count; // 声明静态成员变量

    MyClass() {
        count++;
    }
};

// 类外部初始化静态成员变量
int MyClass::count = 0;

int main() {
    MyClass obj1;
    MyClass obj2;

    std::cout << "Count: " << MyClass::count << std::endl; // 输出: Count: 2

    return 0;
}

接下来重点说下静态成员变量的应用场景。

1.计数器(对象数量的计数)

比如说一个关卡里已经通过小怪类生成了多少个小怪对象了。这里很适合使用静态成员变量来做到,因为它在所有对象之间共享。

class Monster  {
public:
    static int instanceCount;
    Monster() { instanceCount++; }
    ~Monster() { instanceCount--; }
};

int Monster::instanceCount = 0;

2.全局共享参数。当多个对象要共享一个参数的时候,比如服务端最大连接数量。

class Config {
public:
    static int maxConnections;
};

int Config::maxConnections = 100;

3.单例模式。单例模式要求一个类只能有一个实例,静态成员变量通常用于存储这个唯一的实例。

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;

int main()
{
    Singleton& temp1 = Singleton::getInstance();
    temp1.showMessage();

    Singleton& temp2 = Singleton::getInstance();
    temp2.showMessage();                            // temp1和temp2是同一个实例
}

4.缓存共享资源。当某些资源需要在类中的所有实例共享时,静态成员变量可以存储这些共享资源。例如缓存某个计算结果。

class Cache {
public:
    static std::unordered_map<int, std::string> dataCache;
};

std::unordered_map<int, std::string> Cache::dataCache;

静态成员变量就到这里。

再说静态成员函数。

与普通成员函数不同,静态成员函数不依赖于任何对象实例,可以直接通过类名来调用。

静态成员函数与静态成员变量挺像的,都是不需要通过对象,变量就是不需要对象初始化,函数就是不需要对象调用,直接可以使用类名调用。对于每个对象之间也是共享的。静态成员函数有一个很重要的特点就是没有this指针。因为它不针对某个对象的实例,所以它也就没有办法访问对象的成员变量和其它成员函数,也就只能访问静态成员。

下面介绍一下类的静态成员函数的使用场景。

1.类的工具函数

实现与特定对象无关的工具或实用的功能,这些函数不需要访问类的成员变量。比如一个包含数学计算公式的类,只要调用函数而不用构造对象。

#include <cmath>

class MathUtils {
public:
    // 计算两个整数的最大公约数
    static int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    // 计算一个数字的平方根
    static double sqrt(double number) {
        return std::sqrt(number);
    }

    // 计算阶乘
    static int factorial(int n) {
        int result = 1;
        for (int i = 1; i <= n; ++i) {
            result *= i;
        }
        return result;
    }
};

int main() {
    int a = 48, b = 18;
    std::cout << "GCD of " << a << " and " << b << " is " << MathUtils::gcd(a, b) << std::endl;
    std::cout << "Square root of 25 is " << MathUtils::sqrt(25) << std::endl;
    std::cout << "Factorial of 5 is " << MathUtils::factorial(5) << std::endl;
    return 0;
}

2.访问和操作静态成员变量

class Counter {
private:
    static int count;
public:
    static void increment() {
        count++;
    }

    static int getCount() {
        return count;
    }
};

int Counter::count = 0;

3.回调函数

例如,在GUI编程中,当用户点击一个按钮时,你可能希望调用某个函数来处理这个点击事件。假设你正在编写一个类来管理一个窗口,按钮点击事件的处理函数可以是一个静态成员函数。

class Window {
public:
    static void onButtonClick() {
        std::cout << "Button clicked!" << std::endl;
    }
};

// 模拟GUI库中的按钮类
class Button {
public:
    using Callback = void(*)();
    void setClickCallback(Callback cb) {
        callback = cb;
    }

    void click() {
        if (callback) callback();
    }

private:
    Callback callback = nullptr;
};

int main() {
    Button button;
    button.setClickCallback(Window::onButtonClick);
    button.click();  // 输出: Button clicked!
    return 0;
}

为什么用回调函数设置为静态成员变量呢?还是为了封装,上面的例子,如果Window类还有其他的回调如双击,这样就能更好的模块化,更有助于代码的组织和维护。

最后总结一下类的静态成员,两个词,独立(独立于对象,只属于类),共享(被所有对象所共享)

总结

看着C++的内存分布图就能理解静态的静是什么意思了,程序运行期间那个部分的内存会一直存在,而不是像堆和栈一样不停的申请和释放。其中类外的局部静态变量可以总结为“仅有一次初始化,便可终身使用”。全局静态变量和静态函数可以总结为隐藏,只有本文件可以使用。类内的静态成员可以总结为独立(独立于对象,只属于类),共享(被所有对象所共享)

好了,以上就是C++静态的全部内容了,希望能给您带来一下不一样的思考角度,能够让各位更好理解static这个关键字,迈向成功的路上又进了一步,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值