C++中的static关键字

static 关键字的作用是什么

用法 示例 说明
在局部变量前使用static int main(){ static int a;} 该变量具有静态持续性, 但是无链接性
静态成员数据 class A{static int a}; int A::a = 1 只能在类外部定义并初始化静态成员变量, 不能在类内部声明时定义或初始化静态变量, 因为声明只是描述了如何分配内存, 但实际上并不真正分配内存
静态成员函数 与成员数据类似 this指针
const static成员数据 class Myclass{ public: const static int a = 3; 声明并在内部初始化(也可在在外部初始化, 注意不带static关键字)

首先:在类中声明的静态变量,一定要进行初始化,并且,如果不是const static类型的静态变量,则需要在类外初始化,同时初始化的时候一定要带上类名和作用域解析符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class sum{
public:
static int i ;
static int s;
sum(){i++; s+=i;};
~sum(){};
static void set(){ //静态函数只能使用静态变量
i = 0;
s = 0;
};
};
int sum::i =0; // 非const static类型,要在类外初始化
int sum::s = 0; // 且必须带上类名sum和作用域解析符
class Solution {
public:
int Sum_Solution(int n) {
sum::set(); // 直接用类名调用函数,则该函数必须是静态的
sum a[n];
return sum::s;
}
};

都有哪些存储持续性类型和链接性类型

存储持续性
C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于 数据保留在内存中的时间 :(Primer p304)

  • 自动存储持续性: 在程序开始执行其所在的函数或者代码块时被创建, 在执行完函数或代码块时, 其内存会被自动释放.
  • 静态存储持续性: 在整个程序运行过程当中都存在
  • 线程存储持续性(C++11)
  • 动态存储持续性: 用new运算符分配的内存将一直存在(堆中), 直到使用delete运算符将其释放或者 程序结束 为止.

作用域和链接性

  • 和C语言一样,C++也为 静态 存储持续性变量提供了3种链接性(其他类型的持续性变量均是无链接性的),这三种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。(Primer p309)
    • 外部链接性(可在其他文件中访问)
    • 内部链接性(只能在当前文件中访问)
    • 无链接性(只能在当前函数或代码块中访问,与自动变量不同的是,就算不在函数中,变量也存在,只是不能访问
  • 由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理它们, 而是将它们放在 全局数据区。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。另外,如果没有显式地初始化静态变量,编译器将把它设置为0。在默认情况下,静态 数组和结构将每个元素或成员的所有位都设置为0。p309
  • 创建三种链接性的静态持续变量:p309

    • 外部链接性:必须在代码块的外面声明
    • 内部链接性:必须在代码块的外面声明,并使用static限定符
    • 无链接性:必须在代码块内部声明,并使用static限定符
      1
      2
      3
      4
      5
      6
      7
      8
      9
      int global = 1000; //静态持续变量,外部链接性,作用域为整个文件
      static int one_file = 50; //静态持续变量,内部链接性,作用域为整个文件
      int main(){
      ...
      }
      void funct1(int n){
      static int count = 0; //静态持续变量,无链接性,作用域为局部
      int llama = 0;
      }
  • 五种变量存储方式:p310

    • 自动
    • 寄存器
    • 静态,无链接
    • 静态,外部链接
    • 静态,内部链接
  • 关键字重载: 关键字的含义取决于上下文,static用于局部声明,以指出变量是无链接性的静态变量时,表示的是存储持续性。而用于代码块外的声明时,static表示内部链接性,因为位于代码块外的变量已经是静态持续性了。p310
  • 静态变量的初始化: 静态变量有三种初始化方式:零初始化(变量设为零)、常量表达式初始化和动态初始化。 零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译器处理文件时初始化变量,动态初始化意味着变量将在编译后初始化。p310
  • 静态变量的初始化过程: 首先,所有静态变量都被零初始化,而不管程序员是否显式地初始化了它。接下来,如果使用常量表达式初始化了变量,且编译器仅根据文件内容(包括被包含的头文件)就可计算表达式,编译器将执行常量表达式初始化。必要时,编译器将执行简单计算。最后,剩下的变量将被动态初始化。 常量表达式并非只能是使用字面常量的算术表达式。(sizeof运算符也可以)p310
  • 链接性为外部的变量通常简称为外部变量,也称全局变量,它们的存储持续性为静态,作用域为整个文件。p310

静态持续性、外部链接性

  • 单定义规则(One Definition Rule,ODR): 变量只能定义一次。为满足这种需求,C++提供了两种变量声明:p311

    • 定义声明(简称定义):为变量分配存储空间。
    • 引用声明(简称声明):不给变量分配存储空间,引用已有的变量。使用关键字extern
      1
      2
      double up; //定义声明
      extern int blem; //blem在别处定义
  • 如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他所有文件中,都必须使用关键字extern声明它。p311

    1
    2
    3
    4
    5
    6
    7
    //file01.cpp
    extern int cats = 20; // 由于初始化,所以这里是定义而非声明
    int dogs = 22; //定义
    //即使去掉file01.cpp文件中的extern也无妨,效果相同。
    //file02.cpp
    extern int cats; //使用extern且无初始化,说明使用的是其他文件的cats
    extern int dogs; //同上

静态持续性、内部链接性

  • 将作用域为整个文件的变量声明为静态外部变量(内部链接性),就不必担心其名称与其他文件中的外部变量发生冲突.

通常, 当在函数体内定义一个变量时, 该变量是一个自动存储变量, 每当运行到该语句时都会给该局部变量分配 栈内存, 而随着程序退出函数体, 系统就会自动收回这一部分内存. 但有时候我们需要在两次调用之间对变量的值进行保存. 通常的想法是定义一个全局变量来实现, 但是这样一来, 变量就不再属于该函数本身了. 因此, 可以使用静态局部变量来解决, 静态局部变量保存在全局数据区, 而不是保存在栈中, 每次的值保持到下一次调用, 直到下次赋新值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//file1
int errors = 20;

//file2
int errors = 5;
int main(){
cout<<errors; //报错,errors与file1中的外部变量重定义
...
}

//解决方法:file2
static int errors = 5;
int main(){
cout<<errors; // 输出5
}

静态存储持续性、无链接性

  • 局部静态变量:虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将 保持不变

另外,如果初始化了静态局部变量,则程序 只在启动时进行一次初始化 。以后再调用函数时,将不会被再次初始化。p315

全局变量的链接性是怎么样的? 全局常量呢?

  • 存储说明符(storage class specifier):p317
    • auto(在C++11中不再是说明符)
    • register
    • static
    • extern
    • thread_local(C++11新增的)
    • mutable:即使结构(或类)变量为const,其某个成员也可以被修改
  • cv-限定符(cv-qualifer):p317
    • const:内存被初始化后,程序便不能再对它进行修改
    • volatile:即使程序代码没有对内存单元进行修改,其值也可能发生变化
  • 在默认情况下 全局变量的链接性为外部,但 const全局常量的链接性为内部 (因为是全局的, 所以默认已经具有静态持续性) 。因此,将一组常量放在头文件中,其他引用该头文件的文件都相当于自己定义了私有的常量,这就是能够将常量定义放在头文件中而不会重定义的原因。p318
  • 如果处于某种原因,程序员希望某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性,extern const int states = 50;,在这种情况下,必须在所有使用该常量的文件中使用extern关键字来声明它。同时这种情况下就不能将该常量放在头文件中了, 因为链接性已经变成外部, 这样会引起重复定义. p318
  • const全局常量的链接性为内部, 因此可以放在头文件中而不会重定义

函数存储持续性和是否受 static 关键字影响? static 关键字修饰函数时, 其什么作用?

当static关键字作用于函数时, 它改变的只是函数的链接性, 函数的持续性永远为静态

  • C++不允许在一个函数中定义另一个函数,因此 所有函数的存储持续性都自动为静态,即在整个程序执行期间都一直存在 。p318
  • 在默认情况下,函数的链接性为外部。即可以在文件间共享,使用extern来指出函数实在另一个文件中定义的(可选)。p318
  • 可以使用关键字static将函数的链接性设置为内部 ,使之只能在一个文件中使用,必须同时在原型和函数定义中使用该关键字。p318
  • 内联函数不受单定义规则的约束,这允许程序员能够将内联函数的定义放在头文件中。但是C++要求同一个函数的所有内联定义都必须相同。 p319
  • C++查找函数顺序:静态(在本文件中找)——外部(在所有的程序文件中找)——在库函数中找。因此如果定义了一个与库函数同名的函数,编译器优先使用程序员定义的版本(C++不推荐这样做)。p319

语言链接性

  • 不同的语言采用了不同的链接性,为了解决这种问题,需要特别指定函数采用的链接性(默认为C++链接性)。p319

静态成员数据和静态成员函数有哪些特点?

类的静态成员在类实例化之前就存在了, 并分配了内存, 而函数的静态局部变量会在执行函数时才分配内存.

静态成员数据
在类内的数据成员的声明前加上关键字static, 该数据成员就是类内的静态数据成员, 具有以下特点:

  • 静态数据成员被当作是 类的成员 (注意与具体对象无关), 无论这个类的对象被定义了多少个, 静态数据成员在程序中也只有一份拷贝(只分配一次内存), 由该类型的所有对象共同持有.
  • 静态数据成员存储在全局数据区. 由于静态数据成员在定义时必须分配空间, 所以只能在类中对静态数据成员声明, 而不能在类中对静态数据成员进行定义, 需要放到类外定义, 且定义时不要加static关键字, 但是必须指明命名空间. 如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    class Myclass{
    public:
    int get
    private:
    int a = 1;
    static float Sum; // 不能再类内部声明时定义或初始化静态变量, 因为声明只是描述了如何分配内存, 但实际上并不真正分配内存
    }
    float Myclass::Sum = 0; // 只能在类外部定义并初始化静态成员变量
  • 静态数据成员和普通数据成员一样要遵从public, protected, private等访问规则.

  • 因为静态数据成员是独立于具体对象的, 因此, 即使没有产生任何类的实例, 我们仍然可以对其进行访问.
  • 如果静态数据成员的访问权限允许的话(public), 可以通过下面两种方式来直接访问:
    • <类对象名>.<静态数据成员名>
    • <类名>::<静态数据成员名>
  • 同全局变量相比(静态持续性, 外部链接性), 使用静态数据成员有两个优势:
    • 静态数据成员没有进入程序的全局命名空间, 因此不存在与程序中其他全局变量名冲突的可能性
    • 可以实现信息隐藏, 静态数据成员可以是private成员, 而全局变量不能.

静态成员函数
与静态数据成员一样, 也可以创建一个静态成员函数, 它为类的全部对象服务, 而不是为某一个具体的对象服务 ,具有以下特点:

  • 在定义(实现)静态成员函数时, 同样不能带有关键字static
  • 静态成员之间可以互相访问, 包括静态成员函数访问静态数据成员和静态成员函数
  • 非静态成员函数可以任意的访问静态成员和非静态成员.
  • 静态成员函数不能访问任何非静态成员
  • 由于没有this指针上的开销, 因此静态成员函数在速度上会小优于类的全局函数
  • 类对象或对象指针可以像调用普通函数一样使用.->来调用静态成员函数, 也可以使用<类名>::<静态成员函数名>()的方式来调用

静态成员函数与 this 指针
普通的成员函数一般都隐含了一个this指针, this指针指向类的对象本身, 因为普通成员函数总是属于类的某个具体对象, 通常情况下, this指针是缺省的, 如函数 func() 实际上是this->func(), 但是与普通函数相比, 静态成员函数由于不与任何对象相联系, 因此它不具有this指针, 从这个意义上讲, 它就 无法访问属于类对象的非静态数据成员, 也无法访问非静态成员函数, 只能访问或调用静态的数据或函数.

用 const static 来修饰成员数据需要注意什么?

需要注意必须在声明时初始化或者类外部初始化, 不能再构造函数初始化列表中初始化

const 只是对于单个类对象来说是常量, 而对于整个类来说实际上是变量, 如果要维护一个对于整个类来说的常量, 应该使用const static或者static cosnt(二者等价)来声明, 与普通static成员数据不太一样的是, const static成员数据需要在类中声明的同时就进行初始化(因为const数据在定义是必须初始化, 而static又使得该变量独立于具体对象, 所以必须在声明时初始化或者在类外部初始化, 也不能在构造函数初始化列表中初始化) :

1
2
3
4
5
6
7
8
9
class Myclass
{
public:
const static int a = 3; // 声明并初始化
cosnt int b = 4; //这样是可以的, 但是这样所有变量的b都为4,且不能改变, 还不如声明为const static
//所以最好还是在构造函数的初始化列表中对非静态const进行初始化
static const int c;
}
const int Myclass::c = 5; //在类外部初始化. 不要带static关键字

静态函数不能被const修饰,但是返回值可以

1
2
3
4
static void fun() const {} // 函数不能被const修饰,但是返回值可以

const static int() {} // 该静态函数返回值的类型是 const int
static const int() {} // 该静态函数返回值的类型是 const int

注意: C++ 中不能同时用staticconst修饰成员函数, 因为 C++ 编译器在实现const的成员函数的时候为了确保该函数不会修改类中参数的值, 会在函数中添加一个隐式的参数const this*, 但当一个成员为static的时候, 该函数是没有this指针的, 也就是说此时的conststatic是冲突的, 不能一起使用.