什么是static
static 是 C/C++ 中很常用的修饰符,它被用来控制变量的存储方式和可见性。
1.1 static 的引入
我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义为全局的变量,但定义一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅只受此函数控制)。static 关键字则可以很好的解决这个问题。
另外,在 C++ 中,需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见时,可将其定义为静态数据。
1.2 静态数据的存储
全局(静态)存储区:分为 DATA 段和 BSS 段。
- DATA 段(全局初始化区)存放初始化的全局变量和静态变量;
- BSS 段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。
- 其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
在 C++ 中 static 的内部实现机制:静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main() 函数前的全局数据声明和定义处。
静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的"尺寸和规格",并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
优势:可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
2. 在 C/C++ 中static的作用
2.1 总的来说
- (1)在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
- (2)static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。
- (3)static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。
- (4)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用 static 修饰。
- (5)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)。
2.2 静态变量与普通变量
静态全局变量有以下特点:
- (1)静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量;
- (2)未经初始化的静态全局变量会被程序自动初始化为0(在函数体内声明的自动变量的值是随机的,除非它被显式初始化,而在函数体外被声明的自动变量也会被初始化为 0);
- (3)静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的。
优点:静态全局变量不能被其它文件所用;其它文件中可以定义相同名字的变量,不会发生冲突。
(1)全局变量和全局静态变量的区别
- 1)全局变量是不显式用 static 修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过 extern 全局变量名的声明,就可以使用全局变量。
- 2)全局静态变量是显式用 static 修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用 extern 声明也不能使用。
2.3 静态局部变量有以下特点:
- (1)该变量在全局数据区分配内存;
- (2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
- (3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为 0;
- (4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
3. 用法
3.1 在 C++ 中
static关键字最基本的用法是:
- 被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来
- 被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个类来
被static修饰的变量,被static修饰的方法是类的静态资源,是类实例间共享的,也就是说,一处变,处处变。
在C++中,静态成员属于整个类而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量可以实现多个变量之间共享数据不会破坏隐藏的原则,保证了安全性还可以节省内存
静态成员的定义或声明要加个关键 static。静态成员可以通过双冒号来使用即 <类名>::<静态成员名>。
3.2 静态类相关
不能通过类名来调用类的非静态成员函数
class Point
{
public:
void init()
{
}
static void output()
{
}
};
void main()
{
Point::init(); // 'Point::init' : illegal call of non-static member function
Point::output(); // OK
}
报错:
类的对象可以使用静态成员函数和非静态成员函数
class Point
{
public:
void init()
{
}
static void output()
{
}
};
void main()
{
Point pt;
pt.init();
pt.output();
}
编译通过
静态成员函数中不能引用非静态成员
由于static修饰的类成员属于类不属于对象,因此static类成员函数是没有this指针的,this指针是 指向本对象的指针。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员
#include <stdio.h>
class Point
{
public:
void init()
{
}
static void output()
{
printf("%d\n", m_x);
}
private:
int m_x;
// static int m_x;
};
void main()
{
Point pt;
pt.output();
}
编译出错:
原因:因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象之后才有内存空间,所以这个调用就出错了
类的非静态成员函数可以调用用静态成员函数
class Point
{
public:
void init()
{
output();
}
static void output()
{
}
};
void main()
{
Point pt;
Pt.init();
pt.output();
}
编译通过
类的静态成员变量在使用前必须先初始化
#include <stdio.h>
class Point
{
public:
Point()
{
m_nPointCount++;
}
~Point()
{
m_nPointCount--;
}
static void output()
{
printf("%d\n", m_nPointCount);
}
private:
static int m_nPointCount;
};
int main()
{
Point pt;
pt.output();
return 0;
}
- 编译没有错误,但是运行报错:undefined reference to `Point::m_nPointCount'
collect2.exe: error: ld returned 1 exit status - 这是因为类的静态成员变量在使用前必须初始化:static类对象必须在类外进行初始化:static修饰的变量先于对象存在,所以static修饰的变量要在类外进行初始化
- 对于非常量的静态成员变量,必须到头文件之外去定义它,这会保证编译时,类静态成员的定义只存在于最后一个目标文件中
代码纠正之后:
#include <stdio.h>
class Point
{
public:
Point()
{
m_nPointCount++;
}
~Point()
{
m_nPointCount--;
}
static void output()
{
printf("%d\n", m_nPointCount);
}
private:
static int m_nPointCount;
};
int Point::m_nPointCount = 0;
int main()
{
Point pt;
pt.output();
return 0;
}
static修饰的类变量是所有对象共享的
static修饰的类变量是所有对象共享的,只要有一个对象改变了static静态变量,整个对象的都会变
#include <iostream>
using namespace std;
class Test
{
public:
static int count;
};
int Test::count(0); /*static类对象必须在类外进行初始化:static修饰的变量先于对象存在,所以static修饰的变量要在类外进行初始化*/
/*static修饰的成员变量在对象中是不占用内存的,因为它不是跟对象一起在堆或者栈中生成的,它是在静态存储区生成的*/
int main()
{
Test t1;
cout << t1.count << endl;
Test t2;
t1.count = 100; /*static修饰的类变量是所有对象共享的,只要有一个对象改变了static静态变量,整个对象的都会变*/
cout << t2.count << endl;
return 0;
}
static修饰的成员变量在对象中是不占用内存的
static修饰的成员变量在对象中是不占用内存的,因为它不是跟对象一起在堆或者栈中生成的,它是在静态存储区生成的
#include <iostream>
using namespace std;
class Test
{
public:
static int count;
};
class Test2
{
};
/*static修饰的成员变量在对象中是不占用内存的,因为它不是跟对象一起在堆或者栈中生成的,它是在静态存储区生成的*/
int main()
{
Test t1; Test2 t2;
cout << sizeof(Test) << endl;
cout << sizeof(Test2) << endl;
cout << sizeof(t1) << endl;
cout << sizeof(t2) << endl;
return 0;
}
思考
- 静态资源属于类,但是是独立于类存在的
- 从类的加载机制的角度来讲,静态资源就是类初始化的时候加载的,而非静态资源是类实例化对象的时候加载的
- 类的初始化早于类实例化对象,比如 Class.forName("xxx") 方法,就是初始化了一个类,但是并没有实例化对象,只是加载这个类的静态资源罢 了
- 所以对于静态资源来说,它是不可能知道一个类中有哪些非静态资源的;但是对于非静态资源来说就不一样了,由于它是实例化对象出来之后产生的,因此属于类的这些东西它都能认识。所以上面的几个问题答案就很明确了
1)静态方法能不能引用非静态资源?不能,实例化对象的时候才会产生的东西,对于初始化后就存在的静态资源来说,根本不认识它。
2)静态方法里面能不能引用静态资源?可以,因为都是类初始化的时候加载的,大家相互都认识。
3)非静态方法里面能不能引用静态资源?可以,非静态方法就是实例方法,那是实例化对象之后才产生的,那么属于类的内容它都认识。
总结
在类中,static可以用来修饰静态数据成员和静态成员方法
静态数据成员
(1)静态数据成员可以实现多个数据之间的数据共享,它是类的所有对象的共享成员,它在内存中只占用一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变
(2)静态数据成员是在程序开始运行时被分配空间,到程序结束之后才能被释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间
(3)静态数据成员可以被初始化,但是只能在类外进行初始化,如果没有对静态数据成员赋值,则编译器会自动初始化为0
(4)静态数据成员既可以通过对象名引用,也可以通过类名引用。
静态成员函数
(1)静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
(2)非静态成员函数有 this 指针,而静态成员函数没有 this 指针。
(3)静态成员函数可以访问静态数据成员而不能访问非静态成员。
4. C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C中static的用来修饰局部静态变量和全局静态变量和函数;而C++除了上述功能外,还用来定义类的成员变量和函数。即静态成员变量和静态成员函数。
注:编译时static的记忆性和全局性的特点可以让不同时期的函数进行通信,传递信息;而C++静态成员可以在多个对象实例间进行通信,传递信息
5. C++ 中 static 关键字(定义,⽤途)
static作用:控制变量的存储方式以及可见性
(1)修饰局部变量:一般情况下,局部变量在程序中是存放在栈区的,并且局部变量的生命周期在包含语句块执行结束时就结束了。但是如果用static关键字修饰的话,该变量就会存放在静态数据区,其生命周期会一直延续到整个程序执行结束。但是要注意的是,虽然static在局部变量进行修饰之后,其声明周期以及存储方式发生了变化,但是其作用域没有改变,还是限制在其语句块
(2)修饰全局变量:一般情况下,全局变量既可以在本文件中被访问到,也可以在同一个工程中的其他源文件被访问到(添加extern进行声明即可)。但用static对全局变量进行修饰后改变了其作用域范围,有原来的整个工程可见变成了本文件可见
(3)修饰函数:用static修饰函数,情况和修饰全局变量类似,也是改变了函数的作用域
(4)修饰类:如果用static修饰类中的某个函数,则表示该函数属于一个类而不是属于此类的任何特定对象;如果用static修饰类中的某个变量,则表示该变量属于一个类而不是属于此类的任何特定对象,整个程序的存储空间中只存在一个副本,可以通过类和对象去调用这个变量(注意,静态非常量数据成员,其只能在类外定义和初始化,在类内只是声明而已)
(5)函数体内/类中/模块内的static
-
函数体内static变量的作用范围是该函数体,不同于auto变量,该变量的内存只分配一次,因此其值在下次调用时仍维持上次的值
-
在模块内的static全局变量可以被模块内的所有函数访问,但是不能被其他函数访问
-
在模块内的static函数只可被这⼀模块内的其它函数调⽤,这个函数的使⽤范围被限制在声明它的模块内;
-
在类中的static成员变量属于整个类所共享,对类的所有对象只有一份拷贝
-
在类中的static成员函数属于整个类所共有,这个函数不接受this指针,因而只能访问类内的static成员变量
-
static类对象必须在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化
-
由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针,正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问static修饰的类成员
-
static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义。静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr指针是通过this指针调用的,所以不能为virtual。虚函数的调⽤关系,this->vptr->ctable->virtual function。
场景:使用static修饰局部变量确保当前函数只能被调用一次[单线程]
void acl_vstream_init(void){
static int __called = 0;
if (__called)
return;
__called = 1;
...
}
----