ChatGPT解决这个技术问题 Extra ChatGPT

static 关键字及其在 C++ 中的各种用途

关键字 static 在 C++ 中具有多种含义,我发现它非常令人困惑,我永远无法思考它实际上应该如何工作。

据我了解,有 static 存储持续时间,这意味着它在全局情况下会持续到程序的整个生命周期,但是当您谈论本地时,这意味着它默认为零初始化。

C++ 标准对带有关键字 static 的类数据成员这么说:

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

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

局部变量是什么意思?那是一个函数局部变量吗?因为还有当你声明一个函数 local 为 static 时,它只初始化一次,第一次进入这个函数。

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

现在 static 和文件范围的情况如何?默认情况下是否所有全局变量都被认为具有静态存储持续时间?以下(来自第 3.7.1 节)似乎表明了这一点:

所有没有动态存储时长、没有线程存储时长、非本地变量都有静态存储时长。这些实体的存储应持续整个程序(3.6.2、3.6.3)

static 与变量的链接有何关系?

这整个 static 关键字完全令人困惑,有人可以澄清它的不同用法吗英语并告诉我何时初始化一个 static 类成员?


C
Community

变量:

static 变量存在于定义它的翻译单元的“生命周期”,并且:

如果它在命名空间范围内(即在函数和类之外),则不能从任何其他翻译单元访问它。这称为“内部链接”或“静态存储持续时间”。 (除了 constexpr 之外,不要在标题中执行此操作。其他任何内容,您最终会在每个翻译单元中得到一个单独的变量,这非常令人困惑)

如果它是函数中的变量,则不能从函数外部访问它,就像任何其他局部变量一样。 (这是他们提到的当地人)

由于静态,类成员没有限制范围,但可以从类以及实例(如 std::string::npos)中寻址。 [注意:你可以在类中声明静态成员,但它们通常仍应在翻译单元(cpp 文件)中定义,因此,每个类只有一个]

位置作为代码:

static std::string namespaceScope = "Hello";
void foo() {
    static std::string functionScope= "World";
}
struct A {
   static std::string classScope = "!";
};

在执行翻译单元中的任何函数之前(可能在 main 开始执行之后),该翻译单元中具有静态存储持续时间(命名空间范围)的变量将被“常量初始化”(在可能的情况下为 constexpr,否则为零),然后非本地变量被正确地“动态初始化”按照它们在翻译单元中定义的顺序(例如 std::string="HI"; 不是 constexpr)。最后,函数局部静态将在第一次执行“到达”声明它们的行时被初始化。所有 static 变量都以初始化的相反顺序销毁。

获得这一切的最简单方法是将所有未初始化的静态变量 constexpr 初始化为函数静态局部变量,这样可以确保在您尝试使用它们时正确初始化所有静态变量/全局变量,从而防止static initialization order fiasco

T& get_global() {
    static T global = initial_value();
    return global;
}

小心,因为当规范说命名空间范围变量默认具有“静态存储持续时间”时,它们的意思是“翻译单元的生命周期”位,但这并不意味着它不能在文件之外访问。

功能

明显更直接的是,static 通常用作类成员函数,很少用于独立函数。

静态成员函数与常规成员函数的不同之处在于它可以在没有类的实例的情况下调用,并且由于它没有实例,它不能访问类的非静态成员。当您希望为绝对不引用任何实例成员的类或管理 static 成员变量提供函数时,静态变量非常有用。

struct A {
    A() {++A_count;}
    A(const A&) {++A_count;}
    A(A&&) {++A_count;}
    ~A() {--A_count;}

    static int get_count() {return A_count;}
private:
    static int A_count;
}

int main() {
    A var;

    int c0 = var.get_count(); //some compilers give a warning, but it's ok.
    int c1 = A::get_count(); //normal way
}

static 自由函数意味着该函数不会被任何其他翻译单元引用,因此链接器可以完全忽略它。这有几个目的:

可以在 cpp 文件中使用,以保证该函数永远不会从任何其他文件中使用。

可以放在标题中,每个文件都有自己的函数副本。没用,因为 inline 做的事情几乎一样。

通过减少工作来加快链接时间

可以在每个翻译单元中放置一个同名的函数,它们都可以做不同的事情。例如,您可以在每个 cpp 文件中放置一个静态 void log(const char*) {},它们都可以以不同的方式记录。


班级成员呢?这不是第三个单独的案例吗?
@Etienne - 静态类数据成员与静态全局变量相同,只是您可以从其他翻译单元访问它们,并且任何访问(成员函数除外)都必须指定 classname:: 范围。静态类成员函数类似于全局函数但仅限于类,或者类似于普通成员但没有 this(这不是一个选择 - 这两个应该是等价的)。
@LuchianGrigore:虽然我明白你的意思,但我不确定使用什么措辞。
@Steve314:我明白你的意思,但是在处理像 static 这样一个非常可怕的超载术语时,我希望我们都更加小心。特别是所有全局(真正命名空间级别)的变量都有静态持续时间,所以在静态全局变量中加入static可以理解为namespace A { static int x; },表示内部链接,非常不同于静态类数据成员的行为。
“如果它在命名空间范围内,则无法从任何其他翻译单元访问它......”如果它在命名空间范围内,您是什么意思?不是一直都这样吗,能举个例子和反例吗?
V
Vassilis

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

链接与此正交。

我认为这是您可以做出的最重要的区别。理解这一点和其余的,以及记住它,应该很容易(不是直接联系@Tony,而是将来可能会读到这个的人)。

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

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

是的。无论何时初始化变量(第一次调用函数以及执行路径到达声明点时),它都将在程序的生命周期内驻留在内存中的同一位置。在这种情况下,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,达到这个效果 - 混淆。它用于表示不同的事物(我可以说,可能是相反的事物)。


让我直截了当地说 - 你是说当我在命名空间范围内说 static int x 时,它会提供 非静态 存储空间?
@MichaelHagar 根据第 3 项 (en.cppreference.com/w/cpp/language/storage_duration) 下的参考资料,它确实具有静态存储持续时间:“在对象声明中使用时,它指定静态存储持续时间 [...]”。但是,确实每个 TU 都有自己的副本,如果您将静态变量放在头文件中(通常不应该这样做!)。
g
ggulgulia

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

(一个)。变量

(乙)。功能

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

每个子标题的解释如下:

(A) 变量的“静态”关键字

这可能有点棘手,但是如果解释和理解得当,它就很简单了。

为了解释这一点,首先了解变量的范围、持续时间和链接是非常有用的,没有它们总是很难通过 staic 关键字的模糊概念看到

1. 范围:确定在文件中的什么位置,变量是可访问的。它可以有两种类型:(i)本地或块范围。 (ii) 全球范围

2. 持续时间:确定变量的创建和销毁时间。同样它有两种类型:(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) static 和 (ii) extern

(现在你明白了)

static 关键字可以应用于具有局部和全局范围的变量,在这两种情况下,它们的含义不同。我将首先解释“静态”关键字在具有全局范围的变量中的用法(我还将阐明关键字“外部”的用法),然后再解释具有局部范围的变量。

1. 全局范围变量的静态关键字

全局变量具有静态持续时间,这意味着当使用它的特定代码块(例如 main() )结束时它们不会超出范围。根据链接的不同,它们可以仅在声明它们的同一文件内访问(对于静态全局变量),或者在文件外部甚至在声明它们的文件之外访问(外部类型全局变量)

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

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

(请参阅下面的示例以进行说明)

例如:

//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-ness”,我们得到两种默认 c++ 链接的情况,以防未指定:

(i) 如果一个全局变量是非常量的,它的链接默认是外部的,即非常量全局变量可以在另一个.cpp文件中使用extern关键字前向声明来访问(换句话说,非常量全局变量)变量具有外部链接(当然具有静态持续时间))。在已定义的原始文件中使用 extern 关键字也是多余的。在这种情况下,要使外部文件无法访问非常量全局变量,请在变量类型之前使用说明符“静态”。

(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) 功能块内变量的静态关键字。

之前,我提到具有局部范围的变量具有自动持续时间,即它们在进入块时存在(无论是普通块,还是功能块)并且在块结束时不再存在,长话短说,变量具有本地范围的自动持续时间和自动持续时间变量(和对象)没有链接,这意味着它们在代码块之外不可见。

如果 static 说明符应用于功能块内的局部变量,它将变量的持续时间从自动更改为静态,其生存时间是程序的整个持续时间这意味着它具有固定的内存位置,并且它的值仅在程序启动之前初始化一次,如 cpp reference 中所述(初始化不应与赋值混淆)

让我们看一个例子。

//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;
}

看看上述静态局部变量和静态全局变量的标准,人们可能会想问,它们之间的区别是什么。虽然全局变量可以在代码中的任何位置访问(在相同以及不同的翻译单元中,取决于常量和外部性),但在功能块中定义的静态变量不能直接访问。变量必须由函数值或引用返回。让我们通过一个例子来证明这一点:

//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;
}

有关选择静态全局变量和静态局部变量的更多说明,请参见 this stackoverflow thread

(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 的方式进行优化。 Here's a small example

我还建议读者查找 this stackoverflow thread 中变量的 constexprstatic const 之间的区别。这结束了我对应用于变量的静态关键字的解释。

B. 用于函数的“静态”关键字

就功能而言,static 关键字具有直截了当的含义。这里指的是函数的链接 通常在一个cpp文件中声明的所有函数默认都有外部链接,即一个文件中定义的函数可以通过前向声明在另一个cpp文件中使用。

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

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 的所有变量共享

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

类中的 const 与非 const 静态成员变量

(i) 非常量类静态成员变量 在前面的示例中,静态成员(公共和私有)是非常量的。 ISO 标准禁止在类中初始化非常量静态成员。因此,与前面的示例一样,它们必须在类定义之后初始化,需要注意的是需要省略 static 关键字

(ii) 类的 const-static 成员变量 这很简单,符合其他 const 成员变量初始化的约定,即类的 const 静态成员变量可以在声明时初始化,也可以在结束时初始化类声明的一个警告,即在类定义之后初始化时,需要将关键字 const 添加到静态成员中。

但是,我建议在声明时初始化 const 静态成员变量。这符合标准 C++ 约定,使代码看起来更干净

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

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

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

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

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

有关类中静态成员函数的更多示例,请从 learncpp.com 查找以下链接

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

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

Lambda 表达式遵循正常的名称查找规则,因此范围(本地与全局)和存储类(静态与自动)会影响 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
        }
    }

但是,如前所述,无法在范围外访问未命名范围内的静态变量


1)在c++17之前,只能在类内初始化整型静态const成员变量,例如struct Foo{static const std::string name = "cpp";};是错误的,name必须在类外定义;使用c ++ 17中引入的内联变量可以编码:struct Foo{static inline const std::string name = "cpp";}; 2)公共静态成员/成员函数可以通过类名和范围解析运算符访问,也可以通过点运算符访问(例如:instance.some_static_method())
“m_anyVariable”不应该变成“m_anyNumber”吗?在你的最后一个代码示例中?
我无法判断答案的完整性和正确性,但它看起来非常全面且易于理解。非常感谢!如果您想改进它,开头的简短摘要可能会有所帮助,因为它是一个相当长的文本,并且对于了解诸如“内部/外部”等术语的人来说,要点可以很容易地可视化为嵌套列表或树形图连锁”
static int globalId = 0;通过 extern 前向声明在其他文件中访问?和你之前说的不冲突吗?
使用 c++20 我们有模块 linage
C
Community

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

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,因为 first.a 和 second.a 本质上是相同的变量。至于初始化,见this question.


这不涉及命名空间范围变量。
k
korish

当您在文件范围内声明 static 变量时,该变量仅在 那个 特定文件中可用(从技术上讲,是 *translation 单元,但我们不要让这太复杂)。例如:

a.cpp

static int x = 7;

void printax()
{
    cout << "from a.cpp: x=" << x << endl;
}

b.cpp

static int x = 9;

void printbx()
{
    cout << "from b.cpp: x=" << x << endl;
}

主.cpp:

int main(int, char **)
{
    printax(); // Will print 7
    printbx(); // Will print 9

    return 0;
}

对于 local 变量,static 表示该变量将被零初始化并且在调用之间保持其值:

unsigned int powersoftwo()
{
    static unsigned lastpow;

    if(lastpow == 0)
        lastpow = 1;
    else
        lastpow *= 2;

    return lastpow;
}

int main(int, char **)
{
    for(int i = 0; i != 10; i++)
        cout << "2^" << i << " = " << powersoftwo() << endl;
}

对于类变量,这意味着该变量只有一个实例在该类的所有成员之间共享。根据权限,可以使用其完全限定名称从类外部访问该变量。

class Test
{
private:
    static char *xxx;

public:
    static int yyy;

public:
    Test()
    {        
        cout << this << "The static class variable xxx is at address "
             << static_cast<void *>(xxx) << endl;
        cout << this << "The static class variable yyy is at address "
             << static_cast<void *>(&y) << endl;
    }
};

// Necessary for static class variables.
char *Test::xxx = "I'm Triple X!";
int Test::yyy = 0;

int main(int, char **)
{
    Test t1;
    Test t2;

    Test::yyy = 666;

    Test t3;
};

将非类函数标记为 static 使该函数只能从该文件访问,而不能从其他文件访问。

a.cpp

static void printfilename()
{ // this is the printfilename from a.cpp - 
  // it can't be accessed from any other file
    cout << "this is a.cpp" << endl;
}

b.cpp

static void printfilename()
{ // this is the printfilename from b.cpp - 
  // it can't be accessed from any other file
    cout << "this is b.cpp" << endl;
}

对于类成员函数,将它们标记为 static 意味着不需要在对象的特定实例上调用该函数(即它没有 this 指针)。

class Test
{
private:
    static int count;

public:
    static int GetTestCount()
    {
        return count;
    };

    Test()
    {
        cout << this << "Created an instance of Test" << endl;
        count++;
    }

    ~Test()
    {
        cout << this << "Destroyed an instance of Test" << endl;
        count--;
    }
};

int Test::count = 0;

int main(int, char **)
{
    Test *arr[10] = { NULL };

    for(int i = 0; i != 10; i++)
        arr[i] = new Test();

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    // now, delete them all except the first and last!
    for(int i = 1; i != 9; i++)
        delete arr[i];        

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    delete arr[0];

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    delete arr[9];

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    return 0;
}

J
Jamin Grey

静态变量在类的每个实例之间共享,而不是每个类都有自己的变量。

class MyClass
{
    public:
    int myVar; 
    static int myStaticVar;
};

//Static member variables must be initialized. Unless you're using C++11, or it's an integer type,
//they have to be defined and initialized outside of the class like this:
MyClass::myStaticVar = 0;

MyClass classA;
MyClass classB;

“MyClass”的每个实例都有自己的“myVar”,但共享相同的“myStaticVar”。事实上,您甚至不需要 MyClass 的实例来访问“myStaticVar”,您可以像这样在类之外访问它:

MyClass::myStaticVar //Assuming it's publicly accessible.

当在函数内部用作局部变量(而不是作为类成员变量)时,静态关键字会做一些不同的事情。它允许您创建一个持久变量,而无需提供全局范围。

int myFunc()
{
   int myVar = 0; //Each time the code reaches here, a new variable called 'myVar' is initialized.
   myVar++;

   //Given the above code, this will *always* print '1'.
   std::cout << myVar << std::endl;

   //The first time the code reaches here, 'myStaticVar' is initialized. But ONLY the first time.
   static int myStaticVar = 0;

   //Each time the code reaches here, myStaticVar is incremented.
   myStaticVar++;

   //This will print a continuously incrementing number,
   //each time the function is called. '1', '2', '3', etc...
   std::cout << myStaticVar << std::endl;
}

就持久性而言,它是一个全局变量……但在范围/可访问性方面不是全局的。

您还可以拥有静态成员函数。静态函数基本上是非成员函数,但在类名的命名空间内,并且可以私有访问类的成员。

class MyClass
{
    public:
    int Func()
    {
        //...do something...
    }

    static int StaticFunc()
    {
        //...do something...
    }
};

int main()
{
   MyClass myClassA;
   myClassA.Func(); //Calls 'Func'.
   myClassA.StaticFunc(); //Calls 'StaticFunc'.

   MyClass::StaticFunc(); //Calls 'StaticFunc'.
   MyClass::Func(); //Error: You can't call a non-static member-function without a class instance!

   return 0;
}

当你调用一个成员函数时,有一个名为“this”的隐藏参数,它是一个指向调用该函数的类的实例的指针。静态成员函数没有那个隐藏参数......它们可以在没有类实例的情况下调用,但也不能访问类的非静态成员变量,因为它们没有“this”指针可以使用。它们不会在任何特定的类实例上被调用。


“假设它可以公开访问。” - 它不是。
myStaticVar 也需要定义。值得一提的是,在回答有关 static 关键字语义的问题时,您不这么认为吗?
@Praetorian:谢谢,已修复。
@JaminGrey“静态独立”是指静态非成员函数,每当我只在当前CPP文件中需要一些新功能并且不希望链接器必须处理额外的符号时,我都会这样写。
@VR 奇怪!我从来不知道that functionality的存在。谢谢你拓宽了我的知识!
D
David Tr

我不是 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
}

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


j
justin

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

是 - 非全局的,例如函数局部变量。

因为还有当你声明一个本地函数为静态时,它只初始化一次,第一次进入这个函数。

正确的。

它也只谈论关于类成员的存储持续时间,它不是特定于实例的,这也是静态的属性吗?或者是存储时间?

class R { static int a; }; // << static lives for the duration of the program

也就是说,R 共享 int R::a -- int R::a 的所有实例永远不会被复制。

现在静态和文件范围的情况如何?

实际上是一个在适当的地方具有构造函数/析构函数的全局变量——初始化不会推迟到访问。

静态与变量的链接有何关系?

对于本地功能,它是外部的。访问:函数可以访问它(当然,除非你返回它)。

对于一个类,它是外部的。访问:标准访问说明符适用(公共、受保护、私有)。

static 还可以指定内部链接,具体取决于它的声明位置(文件/命名空间)。

这整个静态关键字是彻头彻尾的混乱

它在 C++ 中有太多用途。

有人可以澄清它的不同用途英语并告诉我何时初始化静态类成员吗?

如果它已加载并具有构造函数,它会在 main 之前自动初始化。这听起来可能是件好事,但初始化顺序在很大程度上超出了您的控制范围,因此复杂的初始化变得非常难以维护,并且您希望将其最小化——如果您必须有一个静态的,那么函数本地规模会更好地跨库和项目。至于具有静态存储持续时间的数据,您应该尽量减少这种设计,尤其是在可变(全局变量)的情况下。初始化“时间”也因多种原因而变化——加载程序和内核有一些技巧来最小化内存占用和延迟初始化,具体取决于相关数据。


N
Nima Soroush

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

静态成员由类的所有对象共享。如果不存在其他初始化,则在创建第一个对象时将所有静态数据初始化为零。我们不能将它放在类定义中,但可以在类外部初始化它,如下例所示,通过重新声明静态变量,使用范围解析运算符 :: 来标识它属于哪个类。

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

#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

值得一提的是,此范例取自 tutorialspoint.com/cplusplus/cpp_static_members.htm