写在前面:
如果您只是想回顾或了解一下static和全局变量的异同点,那么下面的总结将满足您的需求。
如果您是一位初学者或对二者的使用模棱两可,建议您读完该篇文章,相信这一篇将解答完您对于static关键字以及全局变量的所有疑惑。
如有问题或建议欢迎评论 or 私信 or Contact me:jerome.zhou@nio.com~~~
Static关键字和全局变量异同点:
存储位置:全局变量和静态变量都存储在内存的静态存储区。
生命周期:全局变量和静态变量的生命周期都是整个程序的执行期间。
作用域:
- 全局变量:全局变量在整个程序中都是可见的,可以在任何函数中访问。
- 静态变量:静态变量的作用域仅限于声明它的函数或文件。如果在函数内部声明静态变量,那么该静态变量只在该函数内部可见;如果在文件内部声明静态变量,那么该静态变量只在该文件内部可见。
初始化:
- 全局变量:如果没有初始化,编译器会自动初始化为0。
- 静态变量:如果没有初始化,编译器也会自动初始化为0。类内成员变量,需要类内声明,类外定义(定义时,可以使用默认初始化)
声明方式:
- 全局变量:全局变量在所有函数外部声明。
- 静态变量:在变量类型前加上关键字
static
来声明静态变量(可在任意位置)。使用场景:
- 全局变量:当需要在多个函数或文件中共享数据时,可以使用全局变量。
- 静态变量:当需要保持变量的值在函数调用之间不变时,可以使用静态变量。
总的来说,全局变量和静态变量都有全局的生存期,但是静态变量的作用域可以是局部的,因此静态变量相对于全局变量来说,可以更好地保护数据,减少了数据被误操作的风险。
生命周期:变量从定义到销毁的时间范围。堆内存和栈内存章节,介绍了一下程序的内存管理,有一部分是Global Segment(全局段)负责存储静态变量和全局变量,存储在全局段的变量生命周期等于整个程序的运行时间。
作用域: 变量的可见代码区域(块作用域、函数作用域、类作用域和全局作用域)。不管是在全局还是局部声明的static变量都存放在程序的全局段,但是它的作用域却不等同于生命周期,它的作用域决定于它所被定义的位置。
从上述两点可以发现,静态变量和全局变量具有相同的全生命周期,存储在内存的静态存储区,但是二者的作用域却不尽相同。
初始化时机:全局变量、文件域中的静态变量、类中的成员静态变量在main函数执行前初始化(饿汉式);局部变量中的静态变量在第一次调用时初始化(懒汉式)。
在类中声明的静态成员变量,还必须要在类外定义下才可以使用,否则会编译报错。
- 静态成员变量不属于任何一个对象,对象的数据中不应该包含静态成员的数据。所以在定义类的时候不会给静态变量分配内存只是声明,因此就要在其他地方分配即定义。
定义与声明的区别:
声明:向编译器声明(或介绍)了变量的名称,类型,或者函数及其参数类型等信息。声明并不分配存储空间。你可以多次声明同一个变量。
定义:它除了向编译器声明(或介绍)了变量的名称,类型等信息外,还分配了存储空间。换句话说,定义是声明的扩展。你只能一次定义一个变量。,也可以顺便初始化。程序中变量有且只有一个定义(更能说明为什么要在类外再定义下类的静态成员变量了)。
在C++中,如果你试图多次定义同一个变量,编译器会报错。但是,你可以多次声明同一个变量,只要它在某个地方被定义过一次。
所以,所有的定义(包括变量和函数)都是声明,但并非所有的声明都是定义。
全局变量是在所有函数外部定义的变量,其作用域是从定义点开始到文件结尾。全局变量可以被任何函数访问和修改,即全局变量的生命周期是整个程序的执行期间。
在C++中,全局变量的声明和使用主要有以下几点需要注意:
声明全局变量:全局变量在所有函数之外定义,通常是在程序的开头。例如:int x;
使用全局变量:在任何函数中,直接使用变量名即可访问全局变量。例如:x = 10;
如果全局变量和局部变量同名,那么在局部作用域中,局部变量会覆盖全局变量。如果想在局部作用域内访问全局变量,需要使用"::"全局作用域解析运算符。例如:::x = 20;
在一个文件中定义的全局变量,如果想在另一个文件中使用,需要在另一个文件中用关键字 extern
来声明该全局变量。例如:extern int x;
其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
尽量避免使用全局变量,因为全局变量会破坏数据的局部性,使得程序的理解和调试变得困难,同时也增加了数据的不安全性。
示例:
假设我们有两个文件,一个是main.cpp,一个是other.cpp。
在main.cpp文件中,我们定义了一个全局变量,并在main函数中使用该全局变量。
// main.cpp
#include <iostream>
#include "other.cpp"
int x = 10; // 全局变量的定义
int main() {
std::cout << x << std::endl; // 使用全局变量
printX(); // 其他文件使用全局变量
return 0;
}
在other.cpp文件中,我们想要使用main.cpp中定义的全局变量x,因此其他文件中需要用extern关键字声明该全局变量。
// other.cpp
#include <iostream>
extern int x; // 全局变量的声明
void printX() {
std::cout << x << std::endl; // 使用全局变量
}
如果我们有一个局部变量和全局变量同名,那么在这个局部作用域中,局部变量会覆盖全局变量。
// main.cpp
#include <iostream>
int x = 10; // 全局变量的定义
int main() {
int x = 20; // 局部变量的定义
std::cout << x << std::endl; // 输出20,因为这里的x指的是局部变量
std::cout << ::x << std::endl; // 输出10,使用::访问全局变量
return 0;
}
接下来是static关键字,笔者按照面向过程(不引入类的概念)和面向对象(类内的static)两个角度展开static关键字的使用。
在全局变量前,加上关键字static
,该变量就被定义成为一个静态全局变量(相当于限制作用域的全局变量:作用域仅为当前文件内)。
#include <iostream>
using namespace std;
void fn();
static int n; // 定义静态全局变量,默认初始化为0
int main()
{
n = 20; // 全局静态变量赋值
fn();
fn();
return 0;
}
void fn()
{
n++;
cout << n << endl;
}
>>> 21
>>> 22
静态全局变量有以下特点:
static int n; //定义静态全局变量
改为
int n; //定义全局变量
程序照样正常运行。
定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
将上述示例代码改为如下:
#include <iostream.h>
void fn();
static int n; //定义静态全局变量
int main()
{
n=20;
cout << n <<endl;
fn();
return 0;
}
//File2
#include <iostream.h>
extern int n;
void fn()
{
n++;
cout << n << endl;
}
编译并运行,会发现上述代码可以分别通过编译,但运行时出现错误。 这就是因为静态全局变量不能被其它文件所用,即使在其它文件中使用extern 进行声明也不行。
将
static int n; //定义静态全局变量
改为
int n; //定义全局变量
再次编译运行程序,程序可正常运行。
因此,在一个文件中,静态全局变量和全局变量功能相同;而在两个文件中,要使用同一个变量,则只能使用全局变量而不能使用静态全局变量。
感兴趣的小伙伴可以看一下单例。
在局部变量前,加上关键字static
,该变量就被定义成为一个静态局部变量(作用域更狭窄的全局变量:全生命周期,局部变量的作用域)。
//Example 3
#include <iostream>
void fn();
void fn_local();
int main()
{
fn(); //10
fn(); //11
fn(); //12
fn_local(); //10
fn_local(); //10
return 0;
}
void fn()
{
static int n = 10;
std::cout << n << std::endl;
n++;
}
void fn_local()
{
int n = 10;
std::cout << n << std::endl;
n++;
}
在函数fn_local()
定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来**,变量已经不再属于函数本身了,不再仅受函数的控制,**这给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
在函数的返回类型前加上static
关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
#include <iostream>
static void fn();//声明静态函数
int main()
{
fn();
return 0;
}
void fn()//定义静态函数
{
int n = 10;
std::cout << n << std::endl;
}
定义静态函数的好处:(类似于静态全局变量)
在类内成员变量的声明前加上关键字static
,该数据成员就是类内的静态数据成员。
#include <iostream>
class Myclass
{
public:
Myclass(int a, int b, int c);
void GetSum();
private:
int a, b, c;
static int Sum; // 声明静态数据成员 important!!! 此处仅进行了声明
};
// 没有类对象时,作用域就可见。
int Myclass::Sum = 0; // 定义并初始化静态数据成员,类外初始化(const static 可以类内初始化)
Myclass::Myclass(int a, int b, int c)
{
this->a = a; // 同名变量赋值需要用this指针
this->b = b;
this->c = c;
Sum += a + b + c;
}
void Myclass::GetSum()
{
std::cout << "Sum=" << Sum << std::endl;
}
int main()
{
Myclass M(1, 2, 3);
M.GetSum();
Myclass N(4, 5, 6);
N.GetSum();
M.GetSum();
return 0;
}
静态成员变量有以下特点:
int Myclass::Sum=0;
是定义并初始化静态成员变量。初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化,一般是 0 (int Myclass::Sum;
)。静态数据区的变量都有默认的初始值,而动态数据区(堆区、栈区)的变量默认是垃圾值。<数据类型><类名>::<静态数据成员名>=<值>
<类对象名>.<静态数据成员名>
<类类型名>::<静态数据成员名
>,也即,静态成员不需要通过对象就能访问。class CMyclass{
int n;
static int s;
}; //则sizeof(CMyclass)等于4
何时采用静态数据成员?
设置静态成员(变量和函数)这种机制的目的是将某些和类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于理解和维护。如果 想在同类的多个对象之间实现数据共享,又不要用全局变量,那么就可以使用静态成员变量。也即,静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处:
- 不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,节省存储空间。
- 一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了。
你也许会问,用全局变量不是也可以达到这个效果吗?
同全局变量相比,使用静态数据成员有两个优势:
- 静态成员变量没有进入程序的全局命名空间,因此不存在与程序中其它全局命名冲突的可能,不会污染命名空间。
- 可以实现信息隐藏。静态成员变量可以是private成员,而全局变量不能。
静态成员函数为类服务而不是为某一个类的具体对象服务。静态成员函数与静态成员变量一样,都是类的内部实现,属于类定义的一部分。普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
普通的成员函数一般都隐含了一个 this指针(可以参见文章C++中的this指针和Python中的Self),this指针指向类的对象本身(指向实例化后的对象的地址),因为普通成员函数总是具体地属于类的某个具体对象的。当函数被调用时,系统会把当前对象的起始地址赋给 this 指针。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。
与普通函数相比,静态成员函数属于类本身,而不作用于对象,因此它不具有this指针。正因为它没有指向某一个对象,所以它无法访问属于类对象的非静态成员变量和非静态成员函数,它只能调用其余的静态成员函数和静态成员变量。从另一个角度来看,由于静态成员函数和静态成员变量在类实例化之前就已经存在可以访问,而此时非静态成员还是不存在的,因此静态成员不能访问非静态成员。
#include <iostream>
using namespace std;
class Student{
private:
char *name;
int age;
float score;
static int num; //学生人数
static float total; //总分
public:
Student(char *, int, float);
void say();
static float getAverage(); //静态成员函数,用来获得平均成绩
};
int Student::num = 0;
float Student::total = 0;
Student::Student(char *name, int age, float score)
{
this->name = name;
this->age = age;
this->score = score;
num++;
total += score;
}
void Student::say()
{
cout<<name<<"的年龄是 "<<age<<",成绩是 "<<score<<"(当前共"<<num<<"名学生)"<<endl;
}
// 类内静态成员函数,智能访问类内静态成员变量
float Student::getAverage()
{
return total / num;
}
int main()
{
(new Student("小明", 15, 90))->say();
(new Student("李磊", 16, 80))->say();
(new Student("张华", 16, 99))->say();
(new Student("王康", 14, 60))->say();
cout<<"平均成绩为 "<<Student::getAverage()<<endl;
return 0;
}
>>> 小明的年龄是 15,成绩是 90(当前共1名学生)
李磊的年龄是 16,成绩是 80(当前共2名学生)
张华的年龄是 16,成绩是 99(当前共3名学生)
王康的年龄是 14,成绩是 60(当前共4名学生)
平均成绩为 82.25
静态成员函数的特点:
https://zhuanlan.zhihu.com/p/37439983