C++存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。(auto和register省略)
- static
- extern
- mutable
- thread_local(c++11版本开始有)
1)static存储类
第一篇里面有,觉得重要又搬过来。
1.全局静态变量:
定义:在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.
内存位置:静态存储区
初始化:未经初始化的全局静态变量会被自动初始化为0(而自动对象的值是任意的)
作用域:从定义开始到文件结尾,别的文件不可见。
2.局部静态变量
定义:在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存位置:静态存储区
初始化:未经初始化的局部静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;(static定义的静态局部变量分配在数据段上,普通的局部变量分配在栈上,会因为函数栈帧的释放而被释放掉)
3.静态函数
在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;
4.类的静态成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用
5.类的静态函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);
总结:
1.对于函数定义和代码块之外的变量声明,static修改标识符的链接属性,由默认的external变为internal,作用域和存储类型不改变,这些符号只能在声明它们的源文件中访问
2.对于代码块内部的变量声明,static修改标识符的存储类型,由自动变量改为静态变量,作用域和链接属性不变。这种变量在程序执行之前就创建,在程序执行的整个周期都存在。(static定义的静态局部变量分配在数据段上,普通的局部变量分配在栈上,会因为函数栈帧的释放而被释放掉)
3.对于被static修饰的类成员变量和成员函数,它们是属于类的,而不是某个对象,所有对象共享一个静态成员。静态成员通过<类名>::<静态成员>来使用。加了static关键字,则此变量/函数就没有了this指针了,必须通过类名才能访问。
2)extern存储类
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
即可以在1.cpp中声明一个全局函数/全局变量,在2.cpp中即可不声明直接定义。
如:
1.cpp
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
2.cpp
#include <iostream>
extern int count;
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
现在 ,编译这两个文件,执行得到:
Count is 5
3)mutable存储类
mutable 说明符仅适用于类的对象,更到类时回来补。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
4)thread_local 存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。这将影响变量的链接属性。
那么,哪些变量可以被声明为thread_local?以下3类都是ok的
1.命名空间下的全局变量
2.类的static成员变量
3.本地变量
即可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
既然每个线程都拥有一份独立的thread_local变量,那么就有2个问题需要考虑:
1.各线程的thread_local变量是如何初始化的
2.各线程的thread_local变量在初始化之后拥有怎样的生命周期,特别是被声明为thread_local的本地变量(local variables)
看下例:
#include <thread>
thread_local int g_n = 1; //全局变量
void f()//使g_n自加,打印线程id和g_n
{
g_n++;
printf("id=%d, n=%d\n", std::this_thread::get_id(),g_n);
}
void foo()//本地变量,定义i为0,打印id和i后i自加
{
thread_local int i=0;
printf("id=%d, n=%d\n", std::this_thread::get_id(), i);
i++;
}
void f2()//运行两次foo函数
{
foo();
foo();
}
int main()
{
g_n++; //主线程g_n=2
f(); //主线程g_n=3
std::thread t1(f);//线程t1进入f之前g_n为1,进入之后输出2
std::thread t2(f);//线程t2进入f之前g_n为1,进入之后输出2
t1.join();
t2.join();
f2();//运行两次foo函数,第一次为主线程的id8004,n为i=0,随后i自加为1.注意第二次foo不需要初始化i
std::thread t4(f2);
std::thread t5(f2);
t4.join();
t5.join();
return 0;
}
输出:
id=8004, n=3//线程的id8004,主线程g_n=3
id=8008, n=2//线程t1 id8008 进入f之前g_n为1,进入之后输出2
id=8012, n=2//线程t2 id8012 进入f之前g_n为1,进入之后输出2
id=8004, n=0//主线程开始运行两次foo函数,第一次为主线程的id8004,n第一次初始化为i=0,随后i自加为1
id=8004, n=1//主线程第二次进入foo不需要初始化i,直接输出上次i=1
id=8016, n=0
id=8016, n=1
id=8020, n=0
id=8020, n=1
1.输出的前3行打印能帮助解答thread_local变量是如何初始化的,可以看到每个线程都会进行一次初始化。例子中的g_n在主线程中最早被初始化为1,随后被修改为2和3。
虽然t1和t2线程启动的时候主线程中的变量值已经被更新为3,但主线程修改操作并不影响g_n在线程t1和t2中的初始值(值为1),,所以主线程、t1、t2打印结果分别为3,2,2。
2.后6行打印说明了一个事实,声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期,它具有static变量一样的初始化特征和生命周期,虽然它并没有被声明为static。例子中foo函数中的thread_local变量 i 在每个线程第一次执行到的时候初始化,在每个线程各自累加,在线程结束时释放。
总结要注意的就是:
某线程生命周期内被thread_local修饰的本地变量只初始化一次