关注公众号【高性能架构探索】,第一时间获取最新文章;公众号内回复【pdf】,免费获取经典书籍
static
笔者在前面章节吐槽了const
这个命名,也吐槽了“右值引用”这个命名。那么static
就是笔者下一个要重点吐槽的命名了。static
这个词本身没有什么问题,其主要的槽点就在于“一词多用”,也就是说,这个词在不同场景下表示的是完全不同的含义。(作者可能是出于节省关键词的目的吧,明明是不同的含义,却没有用不同的关键词)。
-
在局部变量前的
static
,限定的是变量的生命周期 -
在全局变量/函数前的
static
,限定的变量/函数的作用域 -
在成员变量前的
static
,限定的是成员变量的生命周期 -
在成员函数前的
static
,限定的是成员函数的调用方(或隐藏参数)
上面是static
关键字的 4 种不同含义,接下来逐一我会解释。
静态局部变量
当用static
修饰局部变量时,static
表示其生命周期:
void f() { static int count = 0; count++; }
上例中,count
是一个局部变量,既然已经是“局部变量”了,那么它的作用域很明显,就是f
函数内部。而这里的static
表示的是其生命周期。普通的全局变量在其所在函数(或代码块)结束时会被释放。而用static
修饰的则不会,我们将其称为“静态局部变量”。 静态局部变量会在首次执行到定义语句时初始化,在主函数执行结束后释放,在程序执行过程中遇到定义(和初始化)语句时会忽略。
void f() { static int count = 0; count++; std::cout << count << std::endl; } int main(int argc, const char *argv[]) { f(); // 第一次执行时count被定义,并且初始化为0,执行后count值为1,并且不会释放 f(); // 第二次执行时由于count已经存在,因此初始化语句会无视,执行后count值为2,并且不会释放 f(); // 同上,执行后count值为3,不会释放 } // 主函数执行结束后会释放f中的count
例如上面例程的输出结果会是:
1 2 3
详细的说明已经在注释中,这里不再赘述。
内部全局变量/函数
当static
修饰全局变量或函数时,用于限定其作用域为“当前文件内”。同理,由于已经是“全局”变量了,生命周期一定是符合全局的,也就是“主函数执行前构造,主函数执行结束后释放”。至于全局函数就不用说了,函数都是全局生命周期的。
因此,这时候的static
不会再对生命周期有影响,而是限定了其作用域。与之对应的是extern
。用extern
修饰的全局变量/函数作用于整个程序内,换句话说,就是可以跨文件:
// a1.cc int g_val = 4; // 定义全局变量 // a2.cc extern int g_val; // 声明全局变量 void Demo() { std::cout << g_val << std::endl; // 使用了在另一个文件中定义的全局变量 }
而用static
修饰的全局变量/函数则只能在当前文件中使用,不同文件间的static
全局变量/函数可以同名,并且互相独立。
// a1.cc static int s_val1 = 1; // 定义内部全局变量 static int s_val2 = 2; // 定义内部全局变量 static void f1() {} // 定义内部函数 // a2.cc static int s_val1 = 6; // 定义内部全局变量,与a1.cc中的互不影响 static int s_val2; // 这里会视为定义了新的内部全局变量,而不会视为“声明” static void f1(); // 声明了一个内部函数 void Demo() { std::cout << s_val1 << std::endl; // 输出6,与a1.cc中的s_val1没有关系 std::cout << s_val2 << std::endl; // 输出0,同样不会访问到a1.cc中的s_val2 f1(); // ERR,这里链接会报错,因为在a2.cc中没有找到f1的定义,并不会链接到a1.cc中的f1 }
所以我们发现,在这种场景下,static
并不表示“静态”的含义,而是表示“内部”的含义,所以,为什么不再引入个类似于inner
的关键字呢?这里很容易让程序员造成迷惑。
静态成员变量
静态成员变量指的是用static
修饰的成员变量。普通的成员变量其生命周期是跟其所属对象绑定的。构造对象时构造成员变量,析构对象时释放成员变量。
struct Test { int a; // 普通成员变量 }; int main(int argc, const char *argv[]) { Test t; // 同时构造t.a auto t2 = new Test; // 同时构造t2->a delete t2; // t2所指对象析构,同时释放t2->a } // t析构,同时释放t.a
而用static
修饰后,其声明周期变为全局,也就是“主函数执行前构造,主函数执行结束后释放”,并且不再跟随对象,而是全局一份。
struct Test { static int a; // 静态成员变量(基本等同于声明全局变量) }; int Test::a = 5; // 初始化静态成员变量(主函数前执行,基本等同于初始化全局变量) int main(int argc, const char *argv[]) { std::cout << Test::a << std::endl; // 直接访问静态成员变量 Test t; std::cout << t.a << std::endl; // 通过任意对象实例访问静态成员变量 } // 主函数结束时释放Test::a
所以静态成员变量基本就相当于一个全局变量,而这时的类更像一个命名空间了。唯一的区别在于,通过类的实例(对象)也可以访问到这个静态成员变量,就像上面的t.a
和Test::a
完全等价。
静态成员函数
static
关键字修饰在成员函数前面,称为“静态成员函数”。我们知道普通的成员函数要以对象为主调方,对象本身其实是函数的一个隐藏参数(this 指针):
struct Test { int a; void f(); // 非静态成员函数 }; void Test::f() { std::cout << this->a << std::endl; } void Demo() { Test t; t.f(); // 用对象主调成员函数 }
上面其实等价于:
struct Test { int a; }; void f(Test *this) { std::cout << this->a << std::endl; } void Demo() { Test t; f(&t); // 其实对象就是函数的隐藏参数 }
也就是说,obj.f(arg)
本质上就是f(&obj, arg)
,并且这个参数强制叫做this
。这个特性在 Go 语言中尤为明显,Go 不支持封装到类内的成员函数,也不会自动添加隐藏参数,这些行为都是显式的:
type Test struct { a int } func(t *Test) f() { fmt.Println(t.a) } func Demo() { t := new(Test) t.f() }
回到 C++的静态成员函数这里来。用static
修饰的成员函数表示“不需要对象作为主调方”,也就是说没有那个隐藏的this
参数。
struct Test { int a; static void f(); // 静态成员函数 }; void Test::f() { // 没有this,没有对象,只能做对象无关操作 // 也可以操作静态成员变量和其他静态成员函数 }
可以看出,这时的静态成员函数,其实就相当于一个普通函数而已。这时的类同样相当于一个命名空间,而区别在于,如果这个函数传入了同类型的参数时,可以访问私有成员,例如:
class Test { public: static void f(const Test &t1, const Test &t2); // 静态成员函数 private: int a; // 私有成员 }; void Test::f(const Test &t1, const Test &t2) { // t1和t2是通过参数传进来的,但因为是Test类型,因此可以访问其私有成员 std::cout << t1.a + t2.a << std::endl; }
或者我们可以把静态成员函数理解为一个友元函数,只不过从设计角度上来说,与这个类型的关联度应该是更高的。但是从语法层面来解释,基本相当于“写在类里的普通函数”。
小结
其实 C++中static
造成的迷惑,同样也是因为 C 中的缺陷被放大导致的。毕竟在 C 中不存在构造、析构和引用链的问题。说到这个引用链,其实 C++中的静态成员变量、静态局部变量和全局变量还存在一个链路顺序问题,可能会导致内存重复释放、访问野指针等情况的发生。这部分的内容详见后面“平凡、标准布局”的章节。
总之,我们需要了解static
关键字有多义性,了解其在不同场景下的不同含义,更有助于我们理解 C++语言,防止踩坑。
≈
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/1262
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!