static全局变量
我们知道,一个进程在内存中的布局如图1所示:
其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。
当一个进程的全局变量被声明为static之后,它的中文名叫静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。
未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化)。
static局部变量
普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。
(一般来说,普通类型的变量是在栈空间上分配,而指针类型由new产生的变量在堆上动态分配。)
static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:
1)位置:静态局部变量被编译器放在全局存储区.data(注意:不在.bss段内,原因见3)),所以它虽然是局部的,但是在程序的整个生命周期中存在。
2)访问权限:静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。
3)值:
静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。
而普通局部变量每次在调用它所在的函数时,都是重新分配。
需要注意的是由于static局部变量的这种特性,使得含静态局部变量的函数变得不可重入,即每次调用可能会产生不同的结果。这在多线程编程时可能会成为一种隐患。需要多加注意。
static函数
相信大家还记得C++面向对象编程中的private函数,私有函数只有该类的成员变量或成员函数可以访问。在C语言中,也有“private函数”,它就是接下来要说的static函数,完成面向对象编程中private函数的功能。
当你的程序中有很多个源文件的时候,你肯定会让某个源文件只提供一些外界需要的接口,其他的函数可能是为了实现这些接口而编写,这些其他的函数你可能并不希望被外界(非本源文件)所看到,这时候就可以用static修饰这些“其他的函数”。
所以static函数的作用域是本源文件,把它想象为面向对象中的private函数就可以了。
static函数可以很好地解决不同原文件中函数同名的问题,因为一个源文件对于其他源文件中的static函数是不可见的。
类的静态成员
他们都属于类的内部实现,是类定义的一部分。
1、静态数据成员
- 声明:
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。 - 类的静态成员存在于任何对象之外,不管程序中存在多少个类的对象,静态成员在内存中只有一份拷贝,所有对象共同访问,对象中不包含任何与静态数据成员有关的数据。即使程序中还没有产生任何类的实例,我们仍然可以使用它。
- 定义:
不能在类内定义,必须在类的外部定义和初始化每个静态成员。
class Account
{
public:
static rate() { return rate; }
private:
static double rate;//静态数据成员的声明
}
// 定义语句
double Account::rate = 10.0;
需要注意的是从类名Account开始,这条定义语句的剩余部分就在类的作用域之内了,因此可以用类的私有函数成员来初始化 rate。
- 静态成员的类内初始化
可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr,初始值必须是常量表达式。
class Accout
{
private:
static constexpr int rate = 30;
}
constexpr int Account::rate;
需要注意的是,即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。
- 使用
1、作用域运算符直接访问。
Account::rate
2、虽然静态成员不属于类的某个对象,但仍然可以用类的对象、引用或指针来访问。
Account ac1;
Account* ac2=&ac1;
ac1.rate();
ac2.rate();
3、成员函数直接访问静态成员,不需要作用与运算符。 好处
静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了。同全局变量相比,使用静态数据成员有两个优势:
1、静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
2、可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能。
普通数据成员相比:
1、可以使用静态成员作为默认实参。
2、静态数据成员可以是不完全类型。
Class Bar
{
private:
static Bar mem1;// 正确(静态成员)
Bar* mem2; // 正确(指针成员)
Bar mem3; // 错误(数据成员)
}
2、静态函数成员
普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。
但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,只能调用其余的静态成员函数。
关于静态成员函数,可以总结为以下几点:
• 出现在类体外的函数定义不能指定关键字static;
• 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
• 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
• 静态成员函数不能访问非静态成员函数和非静态数据成员;
• 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
• 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
<类名>::<静态成员函数名>(<参数表>)
调用类的静态成员函数。