通过static可以作用的对象,分为:局部静态变量、静态函数、全局静态变量、静态成员变量、静态成员函数、静态类(值得考究)
1、局部静态变量
函数内部使用的格式:
static 类型 对象名;
1 #include <iostream>
2 using namespace std;
3
4 void fun()
5 {
6 static int i = 1;
7 cout << i << endl;
8 i++;
9 }
10 void main()
11 {
12 fun();
13
14 fun();
15 }
输出:1 \n 2
局部静态变量,一种经常要接触到的技术手段。从首次执行静态对象语句起,其对象内存就开辟在全局、静态内存区,一直到程序结束。
所以,首先执行static int i = 1;时,i就一直存在,直到程序结束。当i++时,i会等于2。因此,有输出中的结果。
注意点:
不易把局部静态对象作为函数的返回值。如下列:
1 unsigned int sum_int( unsigned int base )
2 {
3 unsigned int index;
4 static unsigned int sum = 0; // 注意,是static类型的。
5 for (index = 1; index <= base; index++)
6 {
7 sum += index;
8 }
9 return sum;
10 }
虽然程序不会报错(vs2008),但是使得函数没有好的封装,不能保证每次相同参数的函数调用能够得到相同的返回值(可预测性、可再现性)。因此,我们要合理的使用局部静态对象。
2、静态函数、全局静态变量
当一个源程序由多个头文件和多个源文件组成时,c++语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数(static)和外部函数(extern)。
//当一个源程序由多个头文件和多个源文件组成时,c++语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数(static)和外部函数(extern)。
2.1内部函数
仅能为本源文件使用,不能给其他文件调用。
1 // Test1.h
2 #pragma once
3 int fun();
4 static void ChangeI(int &j);
5
6 // Test1.cpp
7 #include "Test1.h"
8
9 int fun()
10 {
11 ChangeI(i);
12 return i;
13 }
14
15 static void ChangeI(int &j)
16 {
17 j = 11;
18 }
19
20 // Main.cpp
21 #include <iostream>
22 #include "Test1.h"
23 using namespace std;
24
25 void main()
26 {
27 cout << fun() << endl;
28 }
输出:11
如果想在其他文件中使用Test1.cpp定义的ChangeI函数则会报错。
注:这里的static不是表示该函数的存储方式,而是对函数的作用域仅局限于本文件的指示。
2.2 全局静态变量【这个的变量都定义在源文件中,头文件定义变量在之后讨论】
源文件中定义变量
都知道全局变量是作用于整个源程序,数据存储在全局、静态存储区。那么,全局变量加上静态构成全局静态变量后,会有什么性质呢?
1 //Test1.h
2 #pragma once
3
4 int fun();
5
6 static void ChangeI(int &i);
7
8 //////////
9 //Test1.cpp
10 #include "Test1.h"
11 extern int ii; // use ii intger object in the Test2.cpp file
12 int fun()
13 {
14 ChangeI(ii);
15 return ii;
16 }
17
18 static void ChangeI(int &j)
19 {
20 j = 11;
21 }
22
23 //////////
24 //Test2.h
25 #pragma once
26
27 void print();
28
29
30 ///////////
31 //Test2.cpp
32 #include <iostream>
33 #include "Test1.h"
34
35 int ii = 1;
36
37 void print()
38 {
39 //std::cout << i << std::endl;
40 }
41
42 /////////
43 //Main.cpp
44 #include <iostream>
45 #include "Test1.h"
46 using namespace std;
47
48 void main()
49 {
50 cout << fun() << endl;
51 }
输出:11
如果在Test2.cpp文件定义ii对象前加上static是的ii变成静态全局对象后,程序还能执行吗?
答案是明显的,不能执行。
因为在静态全局对象不能被其他源文件访问。
总结:
把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期
把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围
头文件中定义变量
1 ////////
2 //Test1.h
3 #pragma once
4
5 int fun();
6
7 static void ChangeI(int &i);
8
9
10
11 //////////
12 //Test1.cpp
13 #include "Test1.h"
14 extern int ii;
15 int fun()
16 {
17 ChangeI(ii);
18 return ii;
19 }
20
21 static void ChangeI(int &j)
22 {
23 j = 11;
24 }
25
26
27 ////////
28 //Test2.h
29 #pragma once
30
31 int ii = 1; // move head file
32 void print();
33
34 ///////
35 //Test2.cpp
36 #include <iostream>
37 #include "Test2.h"
38
39 void print()
40 {
41 //std::cout << i << std::endl;
42 }
43
44 ////////
45 //Main.cpp
46 #include <iostream>
47 #include "Test1.h"
48 using namespace std;
49
50 void main()
51 {
52 cout << fun() << endl;
53 }
同样是上面的代码,如果我们把全局对象的定义移到头文件中呢?
不报错..... 哇塞... 我被编译器骗了...
只需要在Main.cpp源文件,加上Test2.h的包含则会报错....
因为,会出现多重定义问题。把全局对象变成静态全局对象呢?
问题解决呢.... 我再次编译器骗了....
——————————————————————————————————————————————————
下面引用一个网上的简单实例:
用static修饰的全局变量
首先,我要告诉你static与extern是一对“水火不容”的家伙,也就是说extern和static不能同时修饰一个变量;其次,static修饰的全局变量声明与定义同时进行,也就是说当你在头
文件中使用static声明了全局变量后,它也同时被定义了;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到
它,如:
test1.h:
#ifndef TEST1H
#define TEST1H
static char g_str[] = "123456";
void fun1();
#endif
test1.cpp:
#include "test1.h"
void fun1()
{
cout << g_str << endl;
}
test2.cpp
#include "test1.h"
void fun2()
{
cout << g_str << endl;
}
以上两个编译单元可以连接成功, 当你打开test1.obj时,你可以在它里面找到字符串"123456", 同时你也可以在test2.obj中找到它们,它们之所以可以连接成功而没有报重复定义的错
误是因为虽然它们有相同的内容,但是存储的物理地址并不一样,就像是两个不同变量赋了相同的值一样,而这两个变量分别作用于它们各自的编译单元。
也许你比较较真,自己偷偷的跟踪调试上面的代码,结果你发现两个编译单元(test1, test2)的g_str的内存地址相同,于是你下结论static修饰的变量也可以作用于其他模块,但是我
要告诉你,那是你的编译器在欺骗你,大多数编译器都对代码都有优化功能,以达到生成的目标程序更节省内存,执行效率更高,当编译器在连接各个编译单元的时候,它会把相同内容的内
存只拷贝一份,比如上面的"123456", 位于两个编译单元中的变量都是同样的内容,那么在连接的时候它在内存中就只会存在一份了, 如果你把上面的代码改成下面的样子,你马上就可以拆
穿编译器的谎言:
test1.cpp:
#include "test1.h"
void fun1()
{
g_str[0] = ''a'';
cout << g_str << endl;
}
test2.cpp
#include "test1.h"
void fun2()
{
cout << g_str << endl;
}
void main()
{
fun1(); // a23456
fun2(); // 123456
}
这个时候你在跟踪代码时,就会发现两个编译单元中的g_str地址并不相同,因为你在一处修改了它,所以编译器被强行的恢复内存的原貌,在内存中存在了两份拷贝给两个模块中的变量
使用。
正是因为static有以上的特性,所以一般定义static全局变量时,都把它放在原文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染,同样记住这个原则吧!
总结:
在源文件中,把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围
在头文件中,把全局变量改变为静态变量后是没有限制它的使用范围(还是全局作用域),它会为每个源文件创建自己的内存对象。【会造成信息污染,头文件中的全局变量、静态全局变量慎用!】
——————————————————————————————————————————————————————
引用华为的一个面试题:
全局变量可不可以定义在可被多个.C文件包含的头文件中,为什么?
an:可以的,只需要把该全局变量变成静态全局变量。
3、静态成员变量、静态成员函数
3.1 静态成员变量
可以把一些类的共同性质的变量(如:动物类中动物的个数)定义为类的静态成员变量,特点:类所有的对象共享,不属于哪个具体的对象。
1 #include <iostream>
2 using namespace std;
3
4 class animal
5 {
6 private:
7 static int num;
8 public:
9 void Add()
10 {
11 num ++;
12 }
13 void Print()
14 {
15 cout << num << endl;
16 }
17 };
18
19 int animal::num = 0;
20
21 void main()
22 {
23 animal a1;
24 a1.Add();
25 a1.Print();
26 animal a2;
27 a2.Add();
28 a2.Print();
29 }
输出:1 \n 2
静态数据成员有以下特点:
a、对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
b、静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。需要在类外初始化该静态变量,如第e点。
c、静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
d、因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
e、静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
<数据类型><类名>::<静态数据成员名>=<值>
f、类的静态数据成员有两种访问形式:
<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
g、静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
3.2 静态成员函数
1 #include <iostream>
2 using namespace std;
3
4 class animal
5 {
6 public:
7 static int num;
8 public:
9 void Add()
10 {
11 num ++;
12 }
13 void Print()
14 {
15 cout << num << endl;
16 }
17 static void sum() // 静态成员函数
18 {
19 cout << "一共有多少动物:" << num << endl;
20 }
21
22 };
23
24 int animal::num = 0;
25
26 void main()
27 {
28 animal a1;
29 a1.Add();
30 a1.Print();
31 animal a2;
32 a2.Add();
33 a2.Print();
34
35 animal::sum();
36
37 }
输出:
关于静态成员函数,可以总结为以下几点:
a、出现在类体外的函数定义不能指定关键字static;
b、静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
c、非静态成员函数可以任意地访问静态成员函数和静态数据成员;
d、静态成员函数不能访问非静态成员函数和非静态数据成员;
e、由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
f、调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,或者类名加:: 访问函数。
访问性质的记忆:
可以把类中的静态对象看成是一个公家的东西,那么可以是个人的东西去访问公家的东西,公家的东西也可以访问公家的东西,但是公家的东西决不能访问个人的东西。
4、静态类
静态类只用于包含静态成员的类型,它既不能实例化,静态类的特性是防止继承,防止外部来NEW。(因为是共享的资源)
静态类本身是一个半成品(或是终极产品),由于其无法继承,静态类的产品化会因为不便升级而成为一种垃圾。 所以,静态类语法上不存在问题,但是这种申明是没有什么意义的,故c++中没有静态类一说。