从C学C++(7)——static成员

发布于:2025-06-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

从C学C++(7)——static成员

若无特殊说明,本博客所执行的C++标准均为C++11.

static成员和成员函数

对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量。比如说统计某种类型对象已创建的数量。

通常在C中使用全局变量来实现,如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量,这时我们可以用类的静态成员来解决这个问题。

static数据成员存在于类类型的每个对象中static数据成员独立该类的任意对象存在,它是与类关联的对象,不与类对象关联。

个人理解:把它等同于python中类成员就可以了,不过C++中使用static关键字其实说明它和一般静态变量一样,存在于.bss段或者.data段,生命周期是整个程序(不随着对象实例创建或者销毁),所以其是独立于类对象实例存在的,但只是C++编译器在语法上把它归类于某个类(本质在运行时和其他静态变量无异),需要我们使用类域或者对象实例去访问(这个功能就和python中的类成员基本一致)。

类的static成员

特点:
  • static成员的名字是在类的作用域中,因此可以避免与其它类成员或全局对象名字冲突
  • 可以实施封装,static成员可以是私有的,而全局对象不可以
  • 阅读程序容易看出static成员与某个类相关联,这种可见性可以清晰地反映程序员的意图。
定义和注意事项
  • static成员需要在类定义体外进行初始化与定义。

    这个还好理解,因为类定义其实和C中结构类型声明是类似的,它只是声明了一种类似,因此,它的static 成员也只是声明里面有一个static 而已,真正这个独立于类对象实例的static 成员总得找个地方定义它(毕竟它和一般的成员不一样,一般的成员跟随着对象的定义而创建(分配内存空间))。

  • 特殊的整型static const成员:整型static const成员可以在类定义体中初始化和定义。(个人感觉更像是个语法糖,平时还是不用为好,而且只有整形可以,浮点和其它类型不可以,所以还是不用为好)。

枚举常量类型

在类中定义的枚举常量类型类似于static const 成员,是常量,而且被所有对象共享,属于类。

类的static成员函数

  • static 成员函数没有隐含的this指针。

    因为是类成员函数,不是任何一个特定对象的成员函数,所以自然没有隐含的this指针,也就意味着,在静态成员函数中,没有办法直接访问其他非静态成员(函数)。

  • 静态成员函数不可以访问非静态成员。

  • 非静态成员函数可以访问静态成员。

    具体对象实例可以访问类变量,也可以访问类函数一个道理。因为static 声明的成员(函数)是所有类共享的。

类/对象实例的大小计算

  • 类大小计算遵循前面学过的结构体对齐原则。
  • 类的大小与数据成员有关与成员函数无关。
  • 类的大小与静态数据成员(函数)无关。
  • 虚函数对类的大小的影响:会使类对象多4个字节的大小,用于存放虚表指针。
  • 虚继承对类的大小也会有影响。

static用法总结

函数内部修饰变量使其生存期为整个程序(C也一样)

用于函数内部修饰变量,即函数内的静态变量这种变量的生存期长于该函数,使得函数具有一定的"状态”。使用静态变量的函数一般是不可重入的,也不是线程安全的,比如strtok(3)

函数外部修饰变量使其限制于该文件(C也一样)

用在文件级别(函数体之外),修饰变量或函数,表示该变量或函数只在本文件可见,其他文件看不到也访问不到该变量或函数。专业的说法叫“具有internal linkage”(简言之:不暴露给别的translation unit,C/C++中最小编译单元为文件)

修饰类的数据成员使其成为类成员(C没有)

用于修饰类的数据成员,即所谓"静态成员”。这种数据成员的生存期大于class的对象(实例/instance)。静态数据成员是每个class有一份,普通数据成员是每个instance 有一份。

修饰类的成员函数使其成为类方法(C没有)

用于修饰class的成员函数,即所谓“静态成员函数”。这种成员函数只能访问静态成员和其他静态成员函数,不能访问非静态成员和非静态成员函数。

四种对象的作用域和生存期

栈对象

  • 隐含调用构造/析构函数(程序中没有显示调用)
  • 作用域为所属的{} 的块作用域内。
  • 生存期跟随所属函数的执行(分配空间)和退出(释放空间)。

堆对象

  • 隐含调用构造/析构函数(程序中没有显示调用)。
  • 作用域为所属的{} 的块作用域内。
  • 生存期取决于用户何时delete

全局对象、静态全局对象

  • 全局对象的构造先于main函数(这个对于支持嵌入式的C++编译器,会在进入main函数前的启动汇编代码中插入执行如__libc_init_array() 等的全局对象初始化函数链表执行)。
  • 已初始化的全局变量或静态全局对象存储于.data段中。
  • 未初始化的全局变量或静态全局对象存储于.bss(Block Started by Symbol)段中。
  • 全局对象和静态对象的生存期都是整个程序执行期间。

静态局部对象

  • 已初始化的静态局部变量存储于.data段中。
  • 未初始化的静态局部变量存储于.bss段中。
  • 对于静态局部变量,如果是内置类型(整形、浮点等),对象在编译时就初始化好在.data段中了,但对于静态局部的(我们自己定义的)类对象实例,其初始化是在代码运行时完成的。(这个好理解,虽然其存在于.data段而非栈上,但由于类对象实例初始化时需要执行构造函数,而编译器是没有办法执行函数的,只有运行到这段代码的时候才能执行构造函数,所以其初始化必须推迟到运行时刻)。

static 和单例模式

单例模式

设计模式的一种:保证一个类只有一个实例,并提供一个全局访问点。(目前暂时没有想到其的用处,按照之前的经验,感觉可能在对硬件封装时可能会有用,毕竟底层真实的硬件可能只有一个)。

在C++中设计单例模式需要注意:禁止构造函数、拷贝函数和=运算符,并提供共一个全局的访问点

使用static 局部对象和引用返回实现单例模式

class Singleton{
public:
    static Singleton& getInstance() {
        static Singleton instance; // 局部静态变量,保证只创建一次
        return instance;
    }
    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }
private:
    Singleton(const Singleton& other); // 禁止拷贝构造
    Singleton& operator=(const Singleton& other); // 禁止赋值操作
    Singleton() {
        std::cout << "Singleton created." << std::endl;
    }
};

static 局部对象(利用其生存期为整个程序),且static 局部对象只会初始化一次的特性。通过返回引用,保证每次getInstance() 函数返回的都是同一个静态局部对象。需要注意,这里在调用函数的时候,必须使用引用来接收返回值(如果使用对象的话,这里会发生从引用到对象的赋值,而我们已经将拷贝构造函数声明为private,因此会报错,达到单例模式的目的)。

同时,因为返回的是引用,所以不存在多个对象释放的时候出现空指针的问题,在程序结束的时候,只有静态局部变量被释放一次,其他都是引用,不存在释放多次的问题。

Singleton& s1 = Singleton::getInstance(); // 获取单例实例
Singleton& s2 = Singleton::getInstance(); // 再次获取同一实例

const 成员函数/对象和mutable

const 成员函数

  • const成员函数不会修改对象的状态。

  • const 成员函数只能访问数据成员的值,不能修改它的值。

  • 需要注意const 成员函数是在函数声明和函数体之间加上const 关键字

    class Test{
    public:
        int get_x() const{ //这才是const成员函数
            return x; //且const成员函数内部不能修改数据成员的值
        }
        const int get_x1(){
            return x; // 这个函数只是一个返回const int 类型的成员函数而已,不是const成员函数
        }
    private:    
        int x
    }
    

const 对象的使用

  • 如果把一个对象指定为const,就是告诉编译器不要修改它。

  • const对象的定义:const 类名 对象名(参数表);

  • const 对象定义的时候默认会把不会修改内部变量的成员函数定义为const 成员函数,而那些修改到内部变量的成员函数则是非const 的,因此,如果const 对象调用那些非const 成员函数则会报错。

    这个好理解,因为成员函数默认是会传入一个this指针,那些要修改内部对象的成员函数传入的指针是非const的,而const对象传入自身指针是const类型,对C++来说,没有显式使用const_cast<>() 去除const 属性,是不可能完成const到非const的类型转换的。

mutable 关键字

如果我们需要定义个const对象,但这个对象中可能只需要一两个成员是需要被外界配置修改的,这个时候可以使用 mutable 关键字,因为,mutable 修饰的数据成员即使在const对象或在const成员函数中都可以被修改。


网站公告

今日签到

点亮在社区的每一天
去签到