前言
既然要学习记录的碎片那么多,不如一个碎片一篇文章。
本文全介绍C/C++的static关键字,并记录自己的问题。
C和C++里面static除了可以修饰C++比C多出来的类什么的,还有什么不同?
我们主要针对C++介绍。
什么是C++中的static关键字
static意为静态,static
关键字的作用很多:
比如在类中修饰变量/函数,变为静态成员变量/函数;修饰全局变量/函数,变为静态全局变量/函数;修饰局部变量,变为静态局部变量……
-
在类中修饰变量/函数
类中普通的非static成员,每个实例化的类都有自己的一份拷贝,在程序的栈区或堆区分配内存;
而使用static修饰后,类中的静态成员变量,在程序的全局数据区分配内存(所以不初始化的话默认是全0),由所有实例化的类共享,而不会为每个实例化的类都分配一份,不会被计算在类的
sizeof()
内;而且static成员变量在任何类实例存在之前就会被定义和初始化(需要在类内声明,类和函数之外定义和初始化),也就是在创建任何类实例之前就可以操作它。
那不会出现不同实例化类访问冲突的问题吗?
static修饰成员函数也是同理,静态成员函数与任何实例化的类无关,可以在类实例化之前调用。但需要注意的是静态成员函数没有this 指针,只能访问静态成员。
最典型的例子就是几乎所有static的介绍都会提到的:统计类实例化的数量。
#include <iostream>
using namespace std;
class myclass {
public:
int a;
static int cnt; // 注意 static 数据成员需要在类内声明, 在类外定义和初始化
myclass() : a(0) { cnt++; } // 构造函数中自增
~myclass() { cnt--; } // 析构函数中自减
static void getCnt() {
cout << "myclass instance cnt is: "<<myclass::cnt << endl; // 这里写成cnt也可以正常运行
}
};
// 需要注意的是如果在类外定义静态成员函数getCnt(),不能加static(因为已在类中声明),应该:
// void myclass::getCnt() {...}
int myclass::cnt = 0; // static 数据成员必须在类和函数之外定义和初始化
int main() {
// 静态成员函数与具体的类实例无关,可以直接通过类名::函数名调用
myclass::getCnt(); // 0
myclass m1{};
auto pm2 = new myclass();
myclass::getCnt(); // 2
delete pm2;
myclass::getCnt(); // 1
return 0;
}
程序显示:
myclass instance cnt is: 0
myclass instance cnt is: 2
myclass instance cnt is: 1
这里涉及**声明(Declaration)和定义(Definition)**的区别,简单讲就是:
声明是指出存储类型,并给存储单元指定名称(就是变量名称)。
定义是分配内存空间,还可以包括为变量指定初值。
-
在类中修饰成员函数
与上一部分类中的静态成员变量类似,
-
修饰全局变量
也就指向这个问题:普通的全局变量和加了static的静态全局变量,有什么区别?
这个问题涉及变量的作用域。
作用域:变量的可见代码域,即变量的有效范围,可以在哪部分代码内使用。
全局变量和静态全局变量都在进程的全局数据区分配内存,声明周期都是程序的整个运行期,不同的是他们的作用域。
对于非静态的全局变量来说,默认作用域是整个程序,也就是所有的代码文件,包括源文件和头文件。比如我们定义一个全局变量
glob
,在另一个源文件test.cpp中同样可以使用:/ /// main.cpp #include <iostream> #include "test.cpp" using namespace std; int glob = 555; // 非静态全局变量,作用域在所有源文件 int main() { cout << "glob in main.cpp: " << glob << endl; PrintcntS(); return 0; } / /// test.cpp #include <iostream> using namespace std; extern int glob; // extern用来说明glob变量是在别处定义的,要在此处引用 void PrintcntS() { cout << "glob in test.cpp: " << glob << endl; }
程序会正常运行,输出:
glob in main.cpp: 555 glob in test.cpp: 555
但如果将main.cpp中的全局变量改为:
static int glob = 555;
那么作用域会变为当前源文件,本例也就是只在main.cpp中有效,test.cpp将无法通过extern使用glob变量,编译器会报错。
-
修饰全局函数
也就是变为静态全局函数,与非静态全局函数的区别和全局变量/静态全局变量同理,也是作用域不同,全局函数作用域在所有程序文件,静态全局函数作用域在该文件内。我们也以一个类似的例子说明:
/ /// main.cpp #include <iostream> #include "test.cpp" using namespace std; void globFunc() { cout << "I am globFunc." << endl; } int main() { globFunc(); testFunc(); return 0; } / /// test.cpp #include <iostream> using namespace std; extern void globFunc(); void testFunc() { globFunc(); }
当把main.cpp中的globFunc改为静态全局函数后,编译器报错;
static void globFunc() { cout << "I am globFunc." << endl; }
-
修饰局部变量
这节涉及变量的生命周期:
生命周期:变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。即存在于内存的时间。
正常来讲,局部变量在栈或堆区分配内存,生命周期在当前的局部范围的开始到结束;拿下面例子的
func2()
中i
讲,其生命周期就在函数内,函数执行完了i
就被从内存中删掉了。而静态局部变量,会在全局数据区分配内存,生命周期是整个程序的开始到结束,但是作用域也是仅在当前局部范围内,比如下面例子的
func1()
中的i
,在全局区分配内存,函数运行结束后会以运行完后的值一直存在全局区,下次运行func1()
时会自动跳过初始化赋值,以之前的值继续参与;#include <iostream> using namespace std; void func1() { static int i = 10; // 静态局部变量的初始化赋值只在首次声明时生效,以后会自动跳过 cout << "static local i is: "<< i << endl; i += 10; } void func2() { int i = 10; cout << "non-static local i is: "<< i << endl; i += 10; } int main() { func1(); // 10 func1(); // 20 func2(); // 10 func2(); // 10 return 0; }
程序输出:
static local i is: 10 static local i is: 20 non-static local i is: 10 non-static local i is: 10
最后
仅作为自己的笔记,有错误或补充欢迎指出!