为了澄清这个问题,我宁愿把“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
}
}
然而,如前所述,未命名作用域中的静态变量不能在作用域之外访问