C++学习:静态(static)
首先
在学习过程中多次见到静态这个概念,它出现在不同的地方时,含义和用法也不尽相同。想要有一个比较完整的了解,你需要掌握的是:编译与链接、作用域、类与对象的基本知识。static
关键字显式地规定了数据的储存方式,有助于栈区内存管理。
在类与结构体外使用static关键字
静态变量
存储在静态存储区的变量即为静态变量,一般我们说的“全局变量”指的是“具有外部链接性的静态变量”
在全局变量声明时加入static修饰符,使得这个变量:1,具有内部链接性;2,生命周期(life time)扩展到整个程序 与天同寿
项目中的一个 t1.cpp 源文件中如下定义:
1 |
|
静态全局变量:其他源文件别想用
身为全局变量,已定义的b
生命周期是整个程序,可以作用于外部所有源文件。
在t2.cpp中,如果ß想要使用t1.cpp中的b
变量,需要使用extern
关键字声明
1 | extern int b; |
如果想要修改这个变量的可见性(visibility),请像t1.cpp中的全局变量a一样,使用static
关键字
1 | static int a = 114; |
此时我们就告诉了编译器:变量a仅在当下这个编译单元(即t1.cpp)中可见
也就是说其他源文件不能通过从外部链接到此源文件的方法来调用这个全局变量。
静态局部变量:更加“自私”
在函数或代码块内声明的变量叫局部变量
1 | { |
静态局部变量的特点:
- 作用域仍然是局部作用域 i.e.只对定义其的函数内部可见,可视为无链接性
- 生命周期更长(直到程序结束)这是因为静态局部变量被存储在静态存储区而不是在栈区(和全局变量一样)
- 程序当且仅当执行到变量声明处时进行一次初始化,此后若函数调用则不再,也不必(想想为什么)重复初始化;
初始化
使用静态变量的好处之一就是:如果定义时不显式地初始化,编译器提供默认初始化如0
, "\0"
, nullptr
等等
静态函数
用static
关键字修饰一个函数,则该函数具有内部链接性, 也就是说,其他源文件不能通过从外部链接到此源文件的方法来调用这个函数。
在类与结构体内使用static关键字
对于一个类来说,有时需要多个实例/对象共用这个类的一部分属性(成员变量、成员函数)。
为了不破坏类的封装性,能否吩咐编译器,让一个类在实例化之前就分配一点内存空间来存储这种类的“共用”成员呢?这正是static
的用处
eg. 在一个water.cpp文件中定义且实现的一个water类如下:
1 | class water { |
静态成员变量
在类初始化之后,实例化对象之前产生并储存(需要在类的外部初始化)。一个静态成员变量是独一无二的,所有对象共享的一个变量。
使用例:
1 | int water::m_TotalTons = 1000;//初始化静态成员变量 |
最后编译调试water.cpp
Output:
1 | 1000 |
可以看到,静态成员变量的值被增加了三次。三个不同对象的方法会作用于同一个变量上。
静态成员函数
又称静态方法,是在类的对象实例化之前就能够被调用的函数
1 | static void water::Evaporate() {--m_TotalTons;} |
使用例:
1 | int water::m_TotalTons = 1000;//初始化静态成员变量 |
Output:
1 | 1000 |
可以看到,即使不为water类创建一个对象,也能调用静态方法。
正因如此,静态方法不能对类中的非静态成员进行操作。
关系 | 静态成员函数 | 非静态成员函数 |
---|---|---|
调用静态成员函数/变量 | 能 | 能 |
调用非静态成员函数/变量 | 不能 | 能 |
事实上,一个类内部的非静态方法,在编译时表现为:首先传入一个类型为object*的指针参数this
,指向调用的对象自己
在water类的内部如下定义:
1 | void foo(){std::cout<<"hello"<<std::endl;} |
并执行
1 | water a; |
和在类的外部如下定义:
1 | void water::foo(water *this){std::cout<<"hello"<<std::endl;} |
并执行
1 | water a; |
二者编译上并无区别。
C++在为类定义方法时默认隐藏了这个参数this
(C++ 23之前),但在一些语言如python中仍然需要显式地写出:
1 | class water: |
2023.1.17