从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
成员函数中都可以被修改。