关键字static在c++中有多种含义,我觉得非常困惑,我永远无法思考它实际上应该如何工作。

根据我的理解,有一个静态存储持续时间,这意味着它在全局变量的情况下持续整个程序的生命周期,但当你谈论局部变量时,这意味着它默认是零初始化的。

c++标准对关键字为static的类数据成员这样说:

3.7.1静态存储时长[basic.stc.static]

关键字static可以用来声明一个具有静态存储持续时间的局部变量。 在类定义中应用于类数据成员的关键字static给出了数据成员的静态存储持续时间。

局部变量是什么意思?这是一个函数局部变量吗?因为当你将一个函数声明为静态时它只初始化了一次,也就是它第一次进入这个函数时。

它也只讨论了类成员的存储持续时间,它不是特定于实例的,这也是static的一个属性?或者是储存时间?

那么静态作用域和文件作用域的情况呢?默认情况下,是否所有全局变量都被认为具有静态存储持续时间?以下(来自第3.7.1节)似乎表明了这一点:

所有没有动态存储持续时间、没有线程存储持续时间和不是本地的变量都有静态存储持续时间。这些实体的存储应在程序期间持续(3.6.2,3.6.3)。

静态与变量的联系是怎样的?

这整个静态关键字是完全令人困惑的,有人能澄清它的不同用途的英语,并告诉我什么时候初始化一个静态类成员?


当前回答

我不是一个C程序员,所以我不能给你关于静态在C程序中正确使用的信息,但当涉及到面向对象编程时,静态基本上声明一个变量,或一个函数或一个类在程序的整个生命周期中是相同的。举个例子。

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
private:
    void somePrivateMethod();
};

当你在Main中实例化这个类时,你可以这样做。

int main()
{
   A a1;
   //do something on a1
   A a2;
   //do something on a2
}

这两个类实例彼此完全不同,彼此独立操作。但如果你像这样重新创建A类。

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
    static int x;
private:
    void somePrivateMethod();
};

让我们再回到主体部分。

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   A a2;
   a2.x++;
   //do something on a2
}

那么a1和a2将共享同一个int x副本,因此a1中对x的任何操作都将直接影响a2中x的操作。如果我要这么做

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   cout << a1.x << endl; //this would be 1
   A a2;
   a2.x++;
   cout << a2.x << endl; //this would be 2 
   //do something on a2
}

类A的两个实例共享静态变量和函数。希望这能回答你的问题。我有限的C语言知识允许我说,将函数或变量定义为静态意味着它只对函数或变量定义为静态的文件可见。但这个问题最好由一个C男来回答,而不是我。c++允许用C和c++两种方式将变量声明为静态,因为它与C完全向后兼容。

其他回答

为了澄清这个问题,我宁愿把“static”关键字的用法分为三种不同的形式:

(一)变量

(B)功能

(C)类的成员变量/函数

下面是对每个子标题的解释:

(一)变量的'static'关键字

这一点可能有点棘手,但如果解释和理解得当,它是相当简单的。

要解释这一点,首先了解范围和持续时间是非常有用的 还有变量的连杆,没有它就很难看到东西 通过模糊的概念静态关键字

1. 作用域:确定变量在文件中的何处可访问。它可以是两种类型:(i)局部作用域或块作用域。全球范围

2. Duration:决定变量创建和销毁的时间。同样,它有两种类型:(i)自动存储持续时间(对于具有本地或块作用域的变量)。(ii)静态存储持续时间(用于具有全局作用域的变量或具有静态说明符的局部变量(在函数中或代码块中))。

3.链接:确定一个变量是否可以在另一个文件中访问(或链接)。同样(幸运的是)它有两种类型:(i)内部链接 (ii)外部链接(仅适用于全局作用域/文件作用域/全局命名空间作用域的变量)

让我们参考下面的例子,以更好地理解普通全局变量和局部变量(没有静态存储持续时间的局部变量):

//main file
#include <iostream>

int global_var1; //has global scope
const global_var2(1.618); //has global scope

int main()
{
//these variables are local to the block main.
//they have automatic duration, i.e, they are created when the main() is 
//  executed and destroyed, when main goes out of scope
 int local_var1(23);
 const double local_var2(3.14);

 {
/* this is yet another block, all variables declared within this block are 
 have local scope limited within this block. */
// all variables declared within this block too have automatic duration, i.e, 
/*they are created at the point of definition within this block,
 and destroyed as soon as this block ends */
   char block_char1;
   int local_var1(32) //NOTE: this has been re-declared within the block, 
//it shadows the local_var1 declared outside

 std::cout << local_var1 <<"\n"; //prints 32

  }//end of block
  //local_var1 declared inside goes out of scope

 std::cout << local_var1 << "\n"; //prints 23

 global_var1 = 29; //global_var1 has been declared outside main (global scope)
 std::cout << global_var1 << "\n"; //prints 29
 std::cout << global_var2 << "\n"; //prints 1.618

 return 0;
}  //local_var1, local_var2 go out of scope as main ends
//global_var1, global_var2 go out of scope as the program terminates 
//(in this case program ends with end of main, so both local and global
//variable go out of scope together

现在是联动的概念。当在一个文件中定义的全局变量打算在另一个文件中使用时,变量的链接起着重要作用。

全局变量的联动由关键字指定: (i)静态,和(ii)外部

(现在你明白了)

Static关键字可以应用于具有局部和全局作用域的变量,在这两种情况下,它们的含义不同。我将首先解释“static”关键字在具有全局作用域的变量中的用法(在这里我还澄清了关键字“extern”的用法),然后解释那些具有局部作用域的变量。

1. 全局作用域变量的静态关键字

全局变量具有静态持续时间,这意味着当使用它的特定代码块(例如main())结束时,它们不会超出作用域。根据链接的不同,它们要么只能在声明它们的同一个文件中访问(对于静态全局变量),要么只能在声明它们的文件之外访问(extern类型全局变量)

在全局变量具有extern说明符的情况下,如果该变量在已初始化的文件之外被访问,则必须在使用它的文件中前向声明,就像如果函数的定义在与使用它的文件不同的文件中必须前向声明一样。

相反,如果全局变量具有static关键字,则不能在声明全局变量的文件中使用。

(请参见下面的例子进行说明)

eg:

//main2.cpp
 static int global_var3 = 23;  /*static global variable, cannot be                            
                                accessed in anyother file */
 extern double global_var4 = 71; /*can be accessed outside this file                  linked to main2.cpp */
 int main() { return 0; }

main3.cpp

//main3.cpp
#include <iostream>

int main()
{
   extern int gloabl_var4; /*this variable refers to the gloabal_var4
                            defined in the main2.cpp file */
  std::cout << global_var4 << "\n"; //prints 71;

  return 0;
}

现在,c++中的任何变量都可以是const变量或非const变量,对于每个'const-ness',在没有指定的情况下,我们得到两种默认的c++链接:

(i) If a global variable is non-const, its linkage is extern by default, i.e, the non-const global variable can be accessed in another .cpp file by forward declaration using the extern keyword (in other words, non const global variables have external linkage ( with static duration of course)). Also usage of extern keyword in the original file where it has been defined is redundant. In this case to make a non-const global variable inaccessible to external file, use the specifier 'static' before the type of the variable.

(ii)如果一个全局变量是const,它的链接默认是静态的,也就是说,const全局变量不能在定义它的地方以外的文件中访问(换句话说,const全局变量有内部链接(当然是静态持续时间))。此外,使用static关键字来防止在另一个文件中访问const全局变量也是多余的。 在这里,要使const全局变量具有外部链接,请在变量类型之前使用说明符'extern'

下面是具有各种链接的全局作用域变量的摘要

//globalVariables1.cpp 

// defining uninitialized vairbles
int globalVar1; //  uninitialized global variable with external linkage 
static int globalVar2; // uninitialized global variable with internal linkage
const int globalVar3; // error, since const variables must be initialized upon declaration
const int globalVar4 = 23; //correct, but with static linkage (cannot be accessed outside the file where it has been declared*/
extern const double globalVar5 = 1.57; //this const variable ca be accessed outside the file where it has been declared

接下来,我们将研究上述全局变量在不同文件中访问时的行为。

//using_globalVariables1.cpp (eg for the usage of global variables above)

// Forward declaration via extern keyword:
 extern int globalVar1; // correct since globalVar1 is not a const or static
 extern int globalVar2; //incorrect since globalVar2 has internal linkage
 extern const int globalVar4; /* incorrect since globalVar4 has no extern 
                         specifier, limited to internal linkage by
                         default (static specifier for const variables) */
 extern const double globalVar5; /*correct since in the previous file, it 
                           has extern specifier, no need to initialize the
                       const variable here, since it has already been
                       legitimately defined perviously */

局部作用域变量的静态关键字


更新(2019年8月)本地范围内变量的静态关键字

这进一步可以细分为两类:

(i)静态关键字用于函数块中的变量,以及(ii)静态关键字用于未命名局部块中的变量。

(i)静态关键字用于函数块中的变量。

前面,我提到局部作用域的变量具有自动持续时间,即它们在进入块时存在(无论是普通块还是函数块),并在块结束时停止存在,长话短说,局部作用域的变量具有自动持续时间,而自动持续时间变量(和对象)没有链接,这意味着它们在代码块之外是不可见的。

如果静态说明符应用于函数块中的局部变量,它会将变量的持续时间从自动更改为静态,它的生命时间是程序的整个持续时间,这意味着它有一个固定的内存位置,它的值只在cpp参考中提到的程序启动之前初始化一次(初始化不应该与赋值混淆)

让我们看一个例子。

//localVarDemo1.cpp    
 int localNextID()
{
  int tempID = 1;  //tempID created here
  return tempID++; //copy of tempID returned and tempID incremented to 2
} //tempID destroyed here, hence value of tempID lost

int newNextID()
{
  static int newID = 0;//newID has static duration, with internal linkage
  return newID++; //copy of newID returned and newID incremented by 1
}  //newID doesn't get destroyed here :-)


int main()
{
  int employeeID1 = localNextID();  //employeeID1 = 1
  int employeeID2 = localNextID();  // employeeID2 = 1 again (not desired)
  int employeeID3 = newNextID(); //employeeID3 = 0;
  int employeeID4 = newNextID(); //employeeID4 = 1;
  int employeeID5 = newNextID(); //employeeID5 = 2;
  return 0;
}

Looking at the above criterion for static local variables and static global variables, one might be tempted to ask, what the difference between them could be. While global variables are accessible at any point in within the code (in same as well as different translation unit depending upon the const-ness and extern-ness), a static variable defined within a function block is not directly accessible. The variable has to be returned by the function value or reference. Lets demonstrate this by an example:

//localVarDemo2.cpp 

//static storage duration with global scope 
//note this variable can be accessed from outside the file
//in a different compilation unit by using `extern` specifier
//which might not be desirable for certain use case.
static int globalId = 0;

int newNextID()
{
  static int newID = 0;//newID has static duration, with internal linkage
  return newID++; //copy of newID returned and newID incremented by 1
}  //newID doesn't get destroyed here


int main()
{
    //since globalId is accessible we use it directly
  const int globalEmployee1Id = globalId++; //globalEmployeeId1 = 0;
  const int globalEmployee2Id = globalId++; //globalEmployeeId1 = 1;
  
  //const int employeeID1 = newID++; //this will lead to compilation error since newID++ is not accessible direcly. 
  int employeeID2 = newNextID(); //employeeID3 = 0;
  int employeeID2 = newNextID(); //employeeID3 = 1;

  return 0;
}

关于静态全局变量和静态局部变量选择的更多解释可以在这个stackoverflow线程中找到

(ii)静态关键字用于未命名局部块中的变量。

一旦局部块超出作用域,局部块(不是函数块)内的静态变量就不能在块外访问。对这条规则没有任何警告。

    //localVarDemo3.cpp 
    int main()
    {
    
      {
          const static int static_local_scoped_variable {99};
      }//static_local_scoped_variable goes out of scope
        
      //the line below causes compilation error
      //do_something is an arbitrary function
      do_something(static_local_scoped_variable);
      return 0;
    }

c++ 11引入了关键字constexpr,它保证在编译时对表达式求值,并允许编译器优化代码。现在,如果在编译时已知作用域中的静态const变量的值,则代码将以类似于使用constexpr的方式进行优化。这里有一个小例子

我还建议读者在这个stackoverflow线程中查找变量的constexprand static const之间的区别。 这就是我对应用于变量的static关键字的解释。

B。'static'关键字用于函数

就函数而言,static关键字具有简单的含义。这里,它指的是函数的连杆 通常在cpp文件中声明的所有函数默认情况下都具有外部链接,即一个文件中定义的函数可以通过前向声明在另一个cpp文件中使用。

在函数声明之前使用static关键字将其链接限制在内部,即静态函数不能在其定义之外的文件中使用。

C. Staitc类的成员变量和函数的关键字

1. 'static'关键字用于类的成员变量

我直接从一个例子开始

#include <iostream>

class DesignNumber
{
  private:
     
      static int m_designNum;  //design number
      int m_iteration;     // number of iterations performed for the design

  public:
    DesignNumber() {     }  //default constructor

   int  getItrNum() //get the iteration number of design
   {
      m_iteration = m_designNum++;
      return m_iteration;
   }
     static int m_anyNumber;  //public static variable
};
int DesignNumber::m_designNum = 0; // starting with design id = 0
                     // note : no need of static keyword here
                     //causes compiler error if static keyword used
int DesignNumber::m_anyNumber = 99; /* initialization of inclass public 
                                    static member  */
enter code here

int main()
{
   DesignNumber firstDesign, secondDesign, thirdDesign;
   std::cout << firstDesign.getItrNum() << "\n";  //prints 0
   std::cout << secondDesign.getItrNum() << "\n"; //prints 1
   std::cout << thirdDesign.getItrNum() << "\n";  //prints 2

   std::cout << DesignNumber::m_anyNumber++ << "\n";  /* no object
                                        associated with m_anyNumber */
   std::cout << DesignNumber::m_anyNumber++ << "\n"; //prints 100
   std::cout << DesignNumber::m_anyNumber++ << "\n"; //prints 101

   return 0;
}

在这个例子中,静态变量m_designNum保留了它的值,这个单独的私有成员变量(因为它是静态的)与所有对象类型为DesignNumber的变量共享

同样像其他成员变量一样,类的静态成员变量不与任何类对象相关联,这可以通过在main函数中打印anyNumber来证明

类中的Const和非Const静态成员变量

(i)非const类静态成员变量 在前面的例子中,静态成员(包括public和private)都是非常量。ISO标准禁止在类中初始化非const静态成员。因此,就像前面的例子一样,它们必须在类定义之后初始化,但需要注意的是,需要省略static关键字

(ii)类的常量static成员变量 这很简单,并且符合其他const成员变量初始化的惯例,即类的const静态成员变量可以在声明时初始化,也可以在类声明的末尾初始化,但需要注意的是,在类定义之后初始化时,需要将关键字const添加到静态成员中。

但是,我建议在声明时初始化const static成员变量。这符合标准的c++约定,使代码看起来更简洁

有关类中静态成员变量的更多示例,请参阅下面的链接 http://www.learncpp.com/cpp-tutorial/811-static-member-variables/

2. 类的成员函数的'static'关键字

就像类的成员变量可以是静态的,类的成员函数也可以是静态的。类的普通成员函数总是与类类型的对象相关联。相比之下,类的静态成员函数与类的任何对象都没有关联,即它们没有*this指针。

其次,由于类的静态成员函数没有*this指针,因此可以使用主函数中的类名和作用域解析操作符来调用它们(ClassName::functionName();)

第三,类的静态成员函数只能访问类的静态成员变量,因为类的非静态成员变量必须属于类对象。

有关类中静态成员函数的更多示例,请参阅下面的链接

http://www.learncpp.com/cpp-tutorial/812-static-member-functions/


2021年4月更新:静态关键字和lambda表达式

Lambda表达式遵循正常的名称查找规则,因此作用域(本地vs全局)和存储类(静态vs自动)会影响变量对Lambda表达式的可用性

非静态全局变量可用于局部作用域中的lambda表达式。

    //global member   
    int i=10;
    
    int main(){
        []{std::cout << i;}();
        //prints 10
    }

非静态局部变量对于出现在相同或不同作用域内的lambda表达式不可用。在这种情况下,就像我们最习惯的那样,必须通过值或引用来捕获变量

    int main(){
        int i{11};
        []{std::cout << i;}(); //compiler error
        [i]{std::cout << i;}(); //capture by value; correct
        //or
        [&i]{std::cout << i;}(); //capture by reference; correct
    }

局部作用域的静态变量可以由相同或更低/子作用域内的lambda表达式捕获

    int main(){
        static int i{12};
        []{std::cout << i;}(); //prints 12
        {
             []{std::cout << i;}();//also prints 12
        }
    }

然而,如前所述,未命名作用域中的静态变量不能在作用域之外访问

静态对象:我们可以使用关键字Static定义类成员Static。当我们将类的成员声明为静态时,这意味着无论创建了多少个类的对象,都只有一个静态成员的副本。

静态成员由类的所有对象共享。在创建第一个对象时,如果没有其他初始化,则所有静态数据都初始化为零。我们不能把它放在类定义中,但它可以在类外初始化,就像下面的例子中那样,通过重新声明静态变量,使用范围解析操作符::来确定它属于哪个类。

让我们尝试下面的例子来理解静态数据成员的概念:

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects.
   cout << "Total objects: " << Box::objectCount << endl;

   return 0;
}

当上面的代码被编译和执行时,它会产生以下结果:

Constructor called.
Constructor called.
Total objects: 2

静态函数成员: 通过将函数成员声明为静态,可以使它独立于类的任何特定对象。即使类不存在对象,并且只使用类名和作用域解析操作符::访问静态函数,也可以调用静态成员函数。

静态成员函数只能从类外部访问静态数据成员、其他静态成员函数和任何其他函数。

静态成员函数有一个类作用域,它们不能访问类的this指针。您可以使用静态成员函数来确定类的某些对象是否已经创建。

让我们尝试下面的例子来理解静态函数成员的概念:

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{

   // Print total number of objects before creating object.
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects after creating object.
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
}

当上面的代码被编译和执行时,它会产生以下结果:

Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

静态存储持续时间意味着变量在程序的整个生命周期内都驻留在内存中的相同位置。

连杆与这个正交。

我认为这是你能做的最重要的区分。理解这一点和其他的,以及记住它,应该很容易(不是直接针对@Tony,而是将来可能会看到这篇文章的人)。

关键字static可以用来表示内部链接和静态存储,但本质上它们是不同的。

局部变量是什么意思?这是一个函数局部变量吗?

是的。不管变量是什么时候初始化的(在第一次调用函数时以及执行路径到达声明点时),它在程序生命周期内都将驻留在内存中的相同位置。在这种情况下,静态为它提供静态存储。

那么静态作用域和文件作用域的情况呢?默认情况下,是否所有全局变量都被认为具有静态存储持续时间?

是的,根据定义,所有全局变量都有静态存储持续时间(现在我们已经澄清了这意味着什么)。但是命名空间作用域变量没有使用static声明,因为那样会给它们内部链接,所以每个翻译单元都有一个变量。

静态与变量的联系是怎样的?

它提供了名称空间作用域变量的内部链接。它为成员和局部变量提供静态存储持续时间。

让我们扩展一下:

//

static int x; //internal linkage
              //non-static storage - each translation unit will have its own copy of x
              //NOT A TRUE GLOBAL!

int y;        //static storage duration (can be used with extern)
              //actual global
              //external linkage
struct X
{
   static int x;     //static storage duration - shared between class instances 
};

void foo()
{
   static int x;     //static storage duration - shared between calls
}

整个静态关键字完全令人困惑

当然,除非你对它很熟悉。:)为了避免在语言中添加新的关键词,委员会重新使用了这个词,IMO,达到了这样的效果-混淆。它用来表示不同的事情(我可以说,可能是相反的事情)。

其实很简单。如果在函数的作用域中将变量声明为静态变量,则它的值将在对该函数的连续调用之间保留。所以:

int myFun()
{
static int i=5;
i++;
return i;
}
int main()
{
printf("%d", myFun());
printf("%d", myFun());
printf("%d", myFun());
}

将显示678而不是666,因为它记住了增加的值。

至于静态成员,它们在类的实例之间保存它们的值。下面的代码:

struct A
{
static int a;
};
int main()
{
A first;
A second;
first.a = 3;
second.a = 4;
printf("%d", first.a);
}

将打印4,因为首先。一个和第二个。A本质上是相同的变量。至于初始化,请参见这个问题。

我不是一个C程序员,所以我不能给你关于静态在C程序中正确使用的信息,但当涉及到面向对象编程时,静态基本上声明一个变量,或一个函数或一个类在程序的整个生命周期中是相同的。举个例子。

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
private:
    void somePrivateMethod();
};

当你在Main中实例化这个类时,你可以这样做。

int main()
{
   A a1;
   //do something on a1
   A a2;
   //do something on a2
}

这两个类实例彼此完全不同,彼此独立操作。但如果你像这样重新创建A类。

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
    static int x;
private:
    void somePrivateMethod();
};

让我们再回到主体部分。

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   A a2;
   a2.x++;
   //do something on a2
}

那么a1和a2将共享同一个int x副本,因此a1中对x的任何操作都将直接影响a2中x的操作。如果我要这么做

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   cout << a1.x << endl; //this would be 1
   A a2;
   a2.x++;
   cout << a2.x << endl; //this would be 2 
   //do something on a2
}

类A的两个实例共享静态变量和函数。希望这能回答你的问题。我有限的C语言知识允许我说,将函数或变量定义为静态意味着它只对函数或变量定义为静态的文件可见。但这个问题最好由一个C男来回答,而不是我。c++允许用C和c++两种方式将变量声明为静态,因为它与C完全向后兼容。