static 静态关键字作用
static关键字在c/c++语言中比较常用,使用恰当能够大大提高程序的模块化特性,有利于扩展和维护,介绍static关键字要从其是否在类中使用,尤其是类中的静态成员变量和静态成员函数。
一. 在C语言及面向过程中的应用
1.修饰局部变量
普通局部变量是再熟悉不过的变量了,在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化,也就是说它的值在初始时是不确定的,除非对其显式赋值。
静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。静态变量存储在静态区,存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。(无论是局部静态还是全局静态)
- 普通局部变量存储于进程栈空间,使用完毕会立即释放。
- 静态局部变量在全局数据区分配内存空间;编译器自动对其初始化,其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束;
#include <stdio.h> void fn(void) { int n = 10; printf("n=%d\n", n); n++; printf("n++=%d\n", n); } void fn_static(void) { static int n = 10; printf("static n=%d\n", n); n++; printf("n++=%d\n", n); } int main(void) { fn(); printf("--------------------\n"); fn_static(); printf("--------------------\n"); fn(); printf("--------------------\n"); fn_static(); return 0; }
-> % ./a.out n=10 n++=11 -------------------- static n=10 n++=11 -------------------- n=10 n++=11 -------------------- static n=11 n++=12
2.修饰全局变量
全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。
- 普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
- 静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性,被static修饰的全局变量只能被该包含该定义的文件访问(即改变了作用域)。
3.修饰函数
如果它不是出现在类中,那么它是一个普通的全局的静态函数。函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数。
其特性如下:
- 静态函数只能在声明它的文件中可见,其他文件不能引用该函数
- 不同的文件可以使用相同名字的静态函数,互不影响
- 非静态函数可以在另一个文件中直接引用,甚至不必使用extern声明;但静态函数仅限在其声明的本文件中应用,其他文件不能引用该函数。
- static修饰函数使得函数只能在包含该函数定义的文件中被调用。对于静态函数,声明和定义需要放在同一个文件夹中。
这样的static函数与普通函数的区别是:用static修饰的函数,限定在本源码文件中,不能被本源码文件以外的代码文件调用。而普通的函数,默认是extern的,也就是说它可以被其它代码文件调用。
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。普通 函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。因此定义静态函数有以下好处:
- 其他文件中可以定义相同名字的函数,不会发生冲突。
- 静态函数不能被其他文件所用。
/* file1.c */ #include <stdio.h> static void fun(void) { printf("hello from fun.\n"); } int main(void) { fun(); fun1(); return 0; } //-------------------------------------------------------------
/* file2.c */ #include <stdio.h> static void fun1(void) { printf("hello from static fun1.\n"); }
//直接编译不通过
static_fun.c:(.text+0x20):对‘fun1’未定义的引用
collect2: error: ld returned 1 exit status
C语言中使用静态函数的好处:
- 静态函数会被自动分配在一个一直使用的存储区,直到程序结束才从内存消失,避免调用函数时压栈出栈,速度快很多
- 其他文件可以定义相同名字的函数,不会发生冲突
- 静态函数不能被其它文件调用,作用于仅限于本文件
因此,在编码规范习惯方面,有的公司编码规范明确规定只用于本文件的函数要全部使用static关键字声明,这是一个良好的编码风格。
二.类中static静态关键字
静态成员的提出是为了解决数据共享的问题。实现共享有许多方法,如:设置全局性的变量或对象是一种方法。但是,全局变量或对象是有局限性的。这里,我们主要讲述类的静态成员来实现数据的共享。
2.1 静态数据成员
在类中数据成员的声明前加上static,该成员是类的静态数据成员。类中声明,使用时要在类外定义并完成初始化。
对于非静态数据成员,每个类对象都有自己的拷贝,而静态数据成员被当做是类的成员,无论这个类被实例化多少个,静态数据成员都只有一份拷贝,为该类型的所有对象所共享(包括其派生类)。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新。
因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以它不属于特定的类对象,在没有产生类对象前就可以使用。
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
- 无论这个类被实例化了多少个,静态数据成员都只有一份拷贝,为该类型的所有对象所共享(包括其派生类)
- 静态数据成员是类的成员,而不是对象的成员
- 该成员定义时要在全局数据区分配空间,所以不能在类声明里面定义,类中声明,使用时要在类外定义并完成初始化。
静态数据成员的使用方法和注意事项如下:
1、静态数据成员在定义或说明时前面加关键字static。//静态变量的定义 2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下: <数据类型><类名>::<静态数据成员名>=<值> //静态变量的初始化
3、引用静态数据成员时,采用如下格式: <类名>::<静态成员名> //静态变量的使用方式
这表明:
- 初始化在类体外进行,而前面不加static,(这点需要注意)以免与一般静态变量或对象相混淆。
- 初始化时不加该成员的访问权限控制符private,public等。
- 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
- 静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
- 如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
2.2 静态成员函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。与普通的成员函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针.从这个意义上来说,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,只能调用其他的静态成员函数。在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:
<类名>::<静态成员函数名>(<参数表>);
- 静态成员函数不需要声明实例对象,直接调用<类名>::<静态成员函数名>(<参数表>);
- 静态成员函数只能访问静态成员变量,不能访问非静态变量;
class StaticTest { public: StaticTest(int a, int b, int c); void GetNumber(); void GetSum(); static void f1(StaticTest &s); private: int A, B, C; static int Sum; }; //.cpp #include "StaticTest.h" #include <iostream> using namespace std; int StaticTest::Sum = 0;//静态成员在此初始化 StaticTest::StaticTest(int a, int b, int c) { A = a; B = b; C = c; Sum += A + B + C; } void StaticTest::GetNumber() { cout << "Number = " << endl; } void StaticTest::GetSum() { cout << "Sum = " << Sum <<endl; } void StaticTest::f1(StaticTest &s) { cout << s.A << endl;//静态方法不能直接调用一般成员,可以通过对象引用实现调用 cout << Sum <<endl; } //-———————————————————————————————————————————————————— #include "StaticTest.h" #include <stdlib.h> int main(void) { StaticTest M(3, 7, 10), N(14, 9, 11); M.GetNumber(); N.GetSum(); M.GetNumber(); N.GetSum(); StaticTest::f1(M); system("pause"); return 0; }
注意,static成员的初始化要在实现中进行,不能在头文件进行。
从输出结果可以看到Sum的值对M对象和对N对象都是相等的。这是因为在初始化M对象时,将M对象的三个int型数据成员的值求和后赋给了Sum,于是Sum保存了该值。在初始化N对象时,对将N对象的三个int型数据成员的值求和后又加到Sum已有的值上,于是Sum将保存另后的值。所以,不论是通过对象M还是通过对象N来引用的值都是一样的,即为54,s.A=3。
三.static/const/extern
3.1 不可以同时用const和static修饰成员函数。
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。