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的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。

3.2 其他区别 

posted on 2021-08-11 19:23  斗战胜佛美猴王  阅读(132)  评论(0编辑  收藏  举报