一、在面向过程设计中的static关键字
1、静态全局变量
定义:在全局变量前,加上关键字 static 该变量就被定义成为了一个静态全局变量(即全局静态变量)。全局静态变量使得该变量成为定义该变量的
源文件所独享,也即静态变局变量对组成该程序的其他源文件是无效的。具体例子可见C++程序设计教程P103
特点:
A、该变量在全局数据区分配内存。
B、初始化:如果不显式初始化,那么将被隐式初始化为0。
C、访变量只在本源文件可见,严格的讲应该为定义之处开始到本文件结束。
例(摘于C++程序设计教程---钱能主编P103):
//file1.cpp
#include <iostream.h>
void fn();
extern int n;
void main()
{
n=20;
cout < < n < < endl;
fn();
}
//file2.cpp
#include <iostream.h>
static int n; //定义静态全局变量,初始化为0;
void fn()
{
n++;
cout < < n < < endl;
}
文件分别编译能通过,但连接时file1.cpp 中的变量n找不到定义,产生连接错误。
使一个变量只在源文件中全局使用有时是必要的。
第一, 不必担心另外源文件使用它的名字,该名字在源文件中是唯一的。
第二, 源文件的全局变量不能被其他源文件所用,不能被其他的源文件所修改,保证变量的值是可靠的。
D、文件作用域下声明的const的常量默认为static存储类型。
2、静态局部变量
定义:在局部变量前加上static关键字时,就定义了静态局部变量。
特点:
A、该变量在全局数据区分配内存。
B、初始化:如果不显式初始化,那么将被隐式初始化为0。
C、它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
我的简单理解是这样的:
当static用来修饰局部变量时,它就改变了局部变量的存放位置,将其由原来的栈中存放改为全局静态储存区,但是没有改变变量的作用域,还是局限在局
部的{}里。
当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern,限制在了当前文件里,但是没有改变其存放位置,还是在全局
静态储存区。
3、静态函数(注意与类的静态成员函数区别)
函数的定义和声明默认情况下在整个程序中是extern的,有时候可能需要使某个函数只在一个源文件中有效,不能被其他源文件所用,这时在函数前面加上
static.
定义:在函数的返回类型前加上static关键字,函数即被定义成静态函数。
特点:
A、静态函数只能在本源文件中使用(这是与普通函数区别)
例(摘于C++程序设计教程---钱能主编P103):
//file1.cpp
void fn();
void staticFn()
void main()
{
fn();
staticFn();
}
//file2.cpp
#include <iostream.h>
static void staticFn();
void fn();
void fn()
{
staticFn();
cout < < "this is fn() n ";
}
void staticFn()
{
cout < < "this is staticFn() n ";
}
连接时,将产生找不到函数staticFn()定义的错误。
作用:
第一, 它允许其他源文件建立并使用同名的函数,而不相互冲突
第二, 声明为静态的函数不能被其他源文件所调用,因为它的名字不能得到,
B、主意事项
在文件作用域下声明的inline函数默认为static类型。
另其它::
C语言中提供了存储说明符auto,register,extern,static说明的四种存储类别。四存储类别说明符有两种存储期:自动存储期和静态存储期。其中auto
和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块是被建立,它在该程序块活动时存在,退出该程序块时撤销。
关键字extern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量只能被定义该变量的函数所识别,但是不同于自动变量的是,
static变量在其函数被调用退出后,仍保留其值。下次函数被调用时,可以访问最近一次被修改后的值。static变量的声明方法如下:
static int si = 1;
由于static的以上特性,可以实现一些特定的功能。下面说明常见的两种用途。
1. 统计函数被调用的次数
声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因
为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。测试代码如下:
#include <stdio.h>
int fun_1(int);
int main()
{
int i;
for (i = 1; i <= 5; i++)
fun_1(i);
return 0;
}
int fun_1(int x)
{
static count = 0;
count++;
printf( "I have been called %d times.n ", count);
return 2*x;
}
输出结果为:
I have been called 1 times.
I have been called 2 times.
I have been called 3 times.
I have been called 4 times.
I have been called 5 times.
2. 减少局部数组建立和赋值的开销
变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型。在一些含有较多的变量并且被经常调用的函数中,可以将一些数组
声明为static类型,以减少建立或者初始化这些变量的开销。示例代码如下:
#include <stdio.h>
#include <time.h>
#include <string.h>
#define ARRAY_SIZE 10000
#define CALL_TIMES 30000
int fun_1();
int fun_2();
int main()
{
int i;
char string2[10], *string3;
time_t t;
time(&t);
string3 = ctime(&t);
printf( "time 1: %s ", string3);
for (i = 1; i <= CALL_TIMES; i++)
{
fun_1();
}
time(&t);
string3 = ctime(&t);
printf( "time 2: %s ", string3);
for (i = 1; i <= CALL_TIMES; i++)
{
fun_2();
}
time(&t);
string3 = ctime(&t);
printf( "time 3: %s ", string3);
return 0;
}
int fun_1()
{
int a[ARRAY_SIZE], b[ARRAY_SIZE];
int i, t;
for ( i = 0; i < ARRAY_SIZE; i++)
{
a[i] = i;
b[i] = ARRAY_SIZE - i;
}
for ( i = 0; i < ARRAY_SIZE; i++)
{
t = a[i];
a[i] = b[i];
b[i] = t;
}
return 0;
}
int fun_2()
{
static int a[ARRAY_SIZE], b[ARRAY_SIZE];
int i, t;
for ( i = 0; i < ARRAY_SIZE; i++)
{
a[i] = i;
b[i] = ARRAY_SIZE - i;
}
for ( i = 0; i < ARRAY_SIZE; i++)
{
t = a[i];
a[i] = b[i];
b[i] = t;
}
return 0;
}
二、面象对象中的static关键字(主要指类中的static关键字)
1、静态数据成员
特点:
A、内存分配:在程序的全局数据区分配。
B、初始化和定义:
a、静态数据成员定义时要分配空间,所以不能在类声明中定义。
b、为了避免在多个使用该类的源文件中,对其重复定义,所在,不能在类的头文件中定义。
c、静态数据成员因为程序一开始运行就必需存在,所以其初始化的最佳位置在类的内部实现。
C、特点
a、对相于 public,protected,private 关键字的影响它和普通数据成员一样,
b、因为其空间在全局数据区分配,属于所有本类的对象共享,所以,它不属于特定的类对象,在没产生类对象时其作用域就可见,即在没有产生类
的实例时,我们就可以操作它。
D、访问形式
a、 类对象名.静态数据成员名
b、 类类型名:: 静态数据成员名
E、静态数据成员,主要用在类的所有实例都拥有的属性上。比如,对于一个存款类,帐号相对于每个实例都是不同的,但每个实例的利息是相
同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局区的内存,所以
节省存贮空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了,因为它们实际上是共用一个东西。
2、静态成员函数
特点:
A、静态成员函数与类相联系,不与类的对象相联系。
B、静态成员函数不能访问非静态数据成员。原因很简单,非静态数据成员属于特定的类实例。
作用:
主要用于对静态数据成员的操作。
调用形式:
A、类对象名.静态成员函数名()
B、类类型名::静态成员函数名()