Kyle Chen's Blog

Action speaks louder than Words

0%

C++中的static关键字

静态全局变量

实例

  • 在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include<iostream>
    using namespace std;

    static int n; //定义静态全局变量

    void fn()
    {
    n++;
    cout<<n<<endl;
    }

    int main(void)
    {
    n = 20;
    cout<<n<<endl;
    fn();
    return 0;
    }

特点

  • 静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。
  • 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);

区别

全局变量本身就是静态存储方式,
静态全局变量当然也是静态存储方式。

这两者在存储方式上并无不同。

和全局变量的区别
这两者的区别在于非静态全局变量的作用域是整个源程序,
当一个源程序由多个原文件组成时,
非静态的全局变量在各个源文件中都是有效的。
而静态全局变量则限制了其作用域,
即只在定义该变量的源文件内有效,
在同一源程序的其它源文件中不能使用它。

如果是全局变量的话
你在别的源文件加一个extern
就可以使用这个源文件中的全局变量
但是如果是静态全局变量就不可以
也就是说定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量就不可以

这样带来了以下好处:

  • 静态全局变量不能被其它文件所用;静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;

  • 其它文件中可以定义相同名字的变量,不会发生冲突;

静态局部变量

实例

在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
我们先举一个静态局部变量的例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;

void fn();

int main(void)
{
fn();
fn();
fn();
return 0;
}

void fn()
{
static int n = 10;
cout<<n<<endl;
n++;
}

作用

通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
  但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
  静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。

特点

静态局部变量有以下特点:

  • (1)该变量在全局数据区分配内存;
  • (2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
  • (3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
  • (4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
  • (5)初始化时机
    静态变量什么时候初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    初始化只有一次,但是可以多次赋值,
    在主程序之前,编译器已经为其分配好了内存。
    静态局部变量和全局变量一样,数据都存放在全局区域,
    所以在主程序之前,编译器已经为其分配好了内存,
    但在C和C++中静态局部变量的初始化节点又有点不太一样。
    在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,
    就会进行初始化,所以我们看到在C语言中
    无法使用变量对静态局部变量进行初始化,
    在程序运行结束,变量所处的全局内存会被全部回收。

    而在C++中,初始化时在执行相关代码时才会进行初始化,
    主要是由于C++引入对象后,
    要进行初始化必须执行相应构造函数和析构函数,
    在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,
    并非简单地分配内存。
    所以C++标准定为全局或静态对象是有首次用到时才会进行构造,
    并通过atexit()来管理。在程序结束,
    按照构造顺序反方向进行逐个析构。
    所以在C++中是可以使用变量
    对静态局部变量进行初始化的。

静态全局函数

  • 这样的static函数与普通函数的区别是:用static修饰的函数,限定在本源码文件中,不能被本源码文件以外的代码文件调用。而普通的函数,默认是extern的,也就是说它可以被其它代码文件调用。

 - 在函数的返回类型前加上关键字static,函数就被定义成为静态函数。普通 函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。因此定义静态函数有以下好处:
  <1> 其他文件中可以定义相同名字的函数,不会发生冲突。
    <2> 静态函数不能被其他文件所用。

静态成员变量

作用

假设你现在要写一个存款类
存款类里面有一个利息的成员变量
那么把利息这个成员变量设定为static 有什么作用呢?

  • 一是不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省了存储空间。

  • 二是一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了。

  • 三是有一些状态是和类本身相关的而不是和对象相关的 这些状态数据可以用静态成员变量去表达

使用

  • 类内声明

  • 类外初始化

  • 使用

静态成员函数

使用

特点

static作用总结

作用1:隐藏

  • 1.先来介绍它的第一条也是最重要的一条:隐藏。(static函数,static变量均可)
    当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
    举例来说明。同时编译两个源文件,一个是a.c,另一个是main.c。

//a.c
char a = ‘A’; // global variable
void msg()
{
printf(“Hello\n”);
}

//main.c

int main()
{
extern char a; // extern variable must be declared before use
printf(“%c “, a);
(void)msg();
return 0;
}

程序的运行结果是:
A Hello

为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏.

2. 内容的持久

  • 2.static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)
    存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见
    PS:如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。

程序举例:

#include <stdio.h>

int fun(){
static int count = 10; //在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,以后每次进入该函数,a
return count–; //就不会被再次初始化了,仅进行自减1的操作;在static发明前,要达到同样的功能,则只能使用全局变量:

}

int count = 1;

int main(void)
{
printf(“global\t\tlocal static\n”);
for(; count <= 10; ++count)
printf(“%d\t\t%d\n”, count, fun());
return 0;
}

程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1

—基于以上两点可以得出一个结论:把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。

3. 默认初始化为0

其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’;太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’;不妨做个小实验验证一下。

#include <stdio.h>

int a;

int main()
{
int i;
static char str[10];
printf(“integer: %d; string: (begin)%s(end)”, a, str);
return 0;
}

程序的运行结果是:
integer: 0; string: (begin) (end)

最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0.

4. 类成员声明static

4.static的第四个作用:C++中的类成员声明static(有些地方与以上作用重叠)
在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。
(2)不能将静态成员函数定义为虚函数。
(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。
(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没遇见过)
(5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。
(6)静态数据成员在<定义或说明>时前面加关键字static。
(7)静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)
(8)静态成员初始化与一般数据成员初始化不同:
初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用作用域运算符来标明它所属类;
所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>
(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。