深入理解“静态”和static关键字

「深入理解」系列,本文介绍“静态”的概念、在内存中的分布和应用

关于“静态”的误解:重新认识static关键字

静态(static)这个词,翻译过来就是“静态的、静止的”,至于为什么叫这个名字估计这得问C语言的爸爸,这里就不深究了(虽然在看了我的这篇文章后,你可以从“内存”的角度看但是不知道当时static被提出是不是这个意思)
一旦一个变量或方法被修饰为static,则表明该变量或方法为静态变量或静态方法,其存储方式符合“静态存储方式”

这里关于“static”关键字的最大误解就是“人们往往认为只要某某被修饰为static就表明这个东西和对象是无关的——实际上这句话只对了一半,因为你在一些面向过程的语言(如C)中甚至用不到“对象”的概念——”
要真正的理解“静态”和“static”关键字,必须要结合具体的语言类型才行

(或者在看完这篇文章后也可以说“static关键字的意义就是‘静态存储’,只不过在具体的语言上表现略有不同不过总的来说思想是一致的”)

面向对象的“静态”

>>>以Java为例

面向对象的静态变量和方法

这个估计是人们看到“static”后下意识立马会想到的,也好理解:对于面向对象的语言而言,“静态”等于“和对象无关”(至少90%以上就是这么个意思);如果一个变量被修饰为static,该变量称为静态变量,表明该变量和具体对象无关;如果一个方法被修饰为static,该方法称为静态方法,表明该方法和具体对象无关

具体来说,静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化;除了和对象无关,还可以从“静态存储”这一点来理解“static”(这一点在C语言的局部静态变量中体现的淋漓尽致,但是Java没有所谓的“静态局部变量”,在Java的方法中声明的局部变量其实都是“普通局部变量”即不满足“静态存储”)
对于静态方法,也是“和对象无关的”,实际上“static”表明其是伴随着“类”(现在知道为什么要分别讨论“面向对象”和“面向过程”的“static”了吧)同时产生的,这意味着静态变量和静态方法都是先于对象存在于内存中的,所以我们我们得出“静态方法”的几个重要特点:
1. 静态方法不可以访问非静态变量;
2. 静态方法不可以访问非静态对象;
3. 推荐使用类名调用静态方法
4. 不存在this
关于this关键字,这里作简单说明:每当new一个类的新的实例时,随之产生的还有这个类的实例变量(和该变量绑定),但是实例方法不会与某个方法唯一绑定(否则内存开销太大),为例区分出“每个实例会产生不同的动作”,Java使用this关键字(由编译器自动加上,表示传递的是对象引用)

让我们再深入内存看看:
对于静态变量,它是随着类的加载而加载随着类的消亡而消亡的,如果一个类中有静态变量的话,程序首先会把该静态变量加载进内存中,也就是在堆中开辟一个区域专门存放;以后不管new多少个类的对象,该静态变量永远都是在那里的;也就是说,静态变量在类的初始化一次后,系统就不会为该变量开辟新的内存空间;而每new一个类的对象,系统就会重新在 堆内存中开辟一个新空间来存放该类的实例对象,并且栈中也会有一个新的引用变量去指向它;至于静态方法也是类似的情况;
针对上述现象,Java

面向对象的“静态存储”

以Java为例,所有“静态的”东西都被存储在“方法区”,如图:
深入理解“静态”和static关键字_第1张图片
那个“Method Area”就是所谓的“方法区”,是存储“静态变量”和“静态方法”的内存区域,其中的“运行常量池”用来存放在运行时产生的临时常量,与之对应的“堆”也存放一些new出来的“常量”

“静态存储”和“普通存储(堆、栈)的区别”:
1. 静态存储在类加载时随之加载,这时候对象还没有产生;
2. 静态存储值分配一块存储空间,且之后不管对象怎么变静态的东西都是不变的

面向过程的“静态”

>>>以C语言为例

面向过程的静态变量和方法

因为不存在“”类或“对象”的概念,面向过程语言如C语言的静态变量主要用来划分不同的“作用域”

典型的面向过程编程语言是没有“类”的概念的,其组织形式就是一个个单独的文件(而面向对象可以以类为代码组织的基本单位),同样的也可以在C语言中使用static关键字,表示“静态存储”
这里不再说“和对象有关毕竟人家根本就没有对象,但是作为物理载体的内存中的数据存储方式总是不会变的,在某种意义上可以把“面向对象中static表示‘和对象无关’看成是‘满足静态存储方式’的一种特殊表现形式罢了”
毕竟“静态存储”是物理是在,不管语言载体怎样变化,物理本质是不会变的

在实践开发中,static关键字在C语言的作用就是划分文件作用域:如果一个变量或函数被修饰为static,表明该变量或函数为该文件所有,这意味着它只在定义它的源文件内有效,其他源文件无法访问它,即使想要在头文件中引用头文件也会报错,这样的或函数叫“内部函数”,使用“内部函数”虽然限制了使用范围但是也解决了潜在的“命名空间的冲突”问题,即“允许统一文件夹下有两个同名的静态函数或者说同名内部函数”

通常我们使用include指令引入文件中的函数,如果可以引入的话其默认的修饰符是extern(即时不写编译器也会帮你加上去)表示“在别的文件中”,如:

//mylib.h
#ifndef _MYLIB_H_
#define _MYLIB_H_

static char *str;
int func(); //<==> extern func();

#endif
//mylib.c
#include "mylib.h"
#include

int func() //<==> extern func()..
{
    printf("Hello world");
    return 0;
}

*str = "hello";
//main.c

int main(void)
{
    extern func();
}

简而言之,extern的作用就是“取代include指令”,并且可以更精确的定位到某个具体的函数;extern和static是水火不相容的

值得一提的是,C语言允许“静态局部变量”,静态局部变量和作用域和局部变量一样,不过生存周期不一样,局部变量在定义局部变量的函数调用完之后就从内存中释放其值,而静态局部变量不释放,等整个程序全部执行结束后才会从内存中释放

面向过程的“静态存储”

上图:
深入理解“静态”和static关键字_第2张图片

这是开一个进程时C语言程序的内存区域划分;其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量;在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝

你只要知道,面向过程的static关键字就是用来“限制访问权限”的

总结:还得从内存角度理解“静态”

综上所述,不论是面向对象还是面向过程,只要是“静态”的,它就满足“静态存储方式”,这才是“静态”的本质!语言只是载体,“静态存储”的思想才是实质,其它的不过浮云!

你可能感兴趣的:(基础理论,深入理解系列专栏)