0. 关于static
static
是C++语言中的一个关键字。它的用法很多,且容易混淆,经常在各大考试、面试中出现,本文对其进行总结归纳。
static
共有如下几种用法:(可以直接看第5节总结)
- 修饰局部变量
- 修饰全局变量
- 修饰普通函数
- 修饰类的成员(变量、函数)
1. static修饰局部变量
1.1 局部变量
首先明白局部变量的定义:
形参和函数体内部定义的变量统称为局部变量(local variable)
局部变量仅在函数的作用域可见,会隐藏外部作用域的同名变量。这里的局部指的是作用域的局部。
void func() {
// 这是一个局部变量,同时也是自动对象
int i = 0;
return;
}
在C++中,变量和对象的概念通常是等价的。
加入了static
之后,为了便于区分,引入自动对象的概念。
将只存在于块执行期间的对象称为自动对象。
如之前定义的函数func()
中的变量i
就是一个自动对象。
函数的控制路径经过变量时创建对象,到达块末尾时销毁它。
举上例来说,func()
执行到语句int i
时候创建对象i
,func()
执行完毕后销毁i
。
自动对象若没有显示初始化,则执行默认初始化(参考【深度C++】之“初始化”)。
1.2 局部静态对象
那有没有办法,不让func()
执行完毕后,销毁i
?
可以将i
定义为局部静态对象,改变他的生存期。
void func() {
// 这是一个局部变量,同时也是局部静态对象
static int i = 0;
return;
}
函数的控制流第一次经过static int i = 0
时创建对象i
,执行完毕后不销毁i
。
局部静态对象若没有显示初始化,则执行值初始化(参考【深度C++】之“初始化”)。
这样就有意思了,考虑如下代码:
int count_calls() {
static int ctr = 0;
return ++ctr;
}
int main() {
for (int i = 0; i < 10; ++i)
cout << count_calls() << endl; // 输出1到10(包括10)
return 0;
}
函数的控制流第一次经过static int ctr = 0
时创建对象ctr
并初始化为0;每次调用count_calls()
时,ctr
存在,且等于上一次退出函数时的值,因此函数每次给ctr
加1。
1.3 小结
2. staic修饰全局变量
2.1 全局变量
有局部就有全局。
在所有函数体之外定义的对象称为全局变量。
全局变量存在于程序的整个执行过程中,在程序启动时被创建,程序结束时被销毁。
全局变量的作用域在整个程序范围内都可以使用,在其他文件中可以添加extern
关键字来声明,使得别的文件也可以使用(见下面的例子)。
2.2 全局静态变量
用static
修饰全局变量后,改变了它的作用域,使得变量只能在本文件中被访问到。
// file1.cpp
extern void print_sz();
int sz = 16;
int main() {
print_sz(); // 程序正常工作,输出16
return 0;
}
// file2.cpp
#include <iostream>
extern int sz;
void print_sz() {
std::cout << sz << std::endl;
}
上例中,在file1.cpp中使用了file2.cpp中的函数print_sz()
,打印了定义在file1.cpp中的sz
。
若我们将sz
声明为static
:
// file1.cpp
extern void print_sz();
static int sz = 16;
int main() {
print_sz(); // 链接错误,找不到sz
return 0;
}
可以看出,static
改变了sz
的作用域,使其只能在file1.cpp中被使用。
3. static修饰普通函数
static
修饰普通函数的作用和全局变量类似,改变函数的作用域,使其只能在本文件中使用。
// file1.cpp
extern void print_sz();
int sz = 16;
int main() {
print_sz(); // 链接错误,找不到print_sz
return 0;
}
// file2.cpp
#include <iostream>
extern int sz;
static void print_sz() {
std::cout << sz << std::endl;
}
上例将原来普通函数print_sz
添加声明为static void print_sz()
,在执行程序的时候会出现链接错误。
4. static修饰类的成员
4.1 静态成员
有的时候需要类的一些成员与类本身直接相关,而不是与类的各个对象实例保持关联,如银行账户类的基准利率。当我调整该利率的时候,所有使用该利率的银行账户实例都得到了调整。
此时可以使用静态成员,包括静态成员变量和静态成员函数。
4.2 定义静态成员
如下示例展示了如何定义静态成员变量和静态成员函数(内部与外部):
// Account.h
class Account {
private:
double amount;
static double interest_rate; // 声明一个静态成员变量
static double init_rate();
public:
void Calcuate() { amount += amount * interest_rate; }
// 演示如何在类内定义静态成员函数
static double GetRate() { return interest_rate; }
static double SetRate(double);
};
// Account.cpp
#include "Account.h"
// 必须在类的外部定义和初始化每个静态成员变量
// 可以写在.h文件中,推荐写在此处,与非内联成员在一起定义
double Account::interest_rate = init_rate();
// 私有的静态成员函数写法和平常一样
double Account::init_rate() {
return 0.025;
}
// 演示如何在类外定义静态成员函数
double Account::SetRate(double new_rate) {
interest_rate = new_rate;
}
4.3 使用静态成员
有两种方式:
- 使用作用域运算符
::
直接访问静态成员:
#include "Account.h"
int main() {
double r = Account::GetRate();
}
- 使用类的对象、引用或指针访问静态成员
#include "Account.h"
int main() {
Account ac1;
double r = ac1.GetRate();
}
静态成员函数只能使用静态变量。
5. 总结
- 修饰局部变量:改变生存期,函数结束后不销毁;
- 修饰全局变量:改变作用域,其他文件不可访问;
- 修饰普通函数:改变作用域,其他文件不可调用;
- 修饰类的成员:使成员与类关联。