Static 的意义与实作方式

本文围绕Java中class产生instance后,instance method是否新增一份的问题展开。介绍了class成员的分类,包括class field、class method、instance field和instance method,分析了它们的内存占用情况,指出instance method共用一块内存,还解释了隐匿的this参数的作用。
「将某 class 产生出一个 instance 之后,此 class 所有的 instance field 都会新增一份,那么所有的 instance method 是否也会新增一份?」我常常看到网路上有人在讨论此问题,所以写了这篇文章,以为解释。

Member 的种类
类别(class)的内部组成统称为成员(member),如果依据成员是「资料」还是「程式」来区分,可以分成:

资料,被称为 field
程式,被称为 method
如果再依据有无使用static修饰而细分,则成员可细分成四种:

class field:有用static修饰的field
class method:有用static修饰的method
instance field:没有用static修饰的field
instance method:没有用static修饰的method
顾名思义,class field/method和class本身有密切的关系,而instance field/method则与instance(也就是物件)有密切的关系。请看下面的范例程式。

public class Class1 {

 public static int classField;

 public static void classMethod1() {

  // ...
 }
 public static void classMethod2() {
  // ...
 }
 public int instanceField;
 public void instanceMethod1() {
  // ...
 }
 public void instanceMethod2() {
  // ...
 }
}

public class Class2 {
 public static void classMethod () {
  // ...
 }
 public void instanceMethod() {
  // ...
 }
}

Field
宣告field时,如果前面加上static的修饰字,就会使得此field变成是class field。一个class field永远只占用一块记忆体,而且此记忆体空间是在此class一被载入(load)记忆体之后就立刻配置的(allocate),感觉就如同此field与该class本身相关,而不是与该class的instance相关。class field可以被同一个class的class method内部所直接使用,举例来说,上述的classMethod1()内部可以出现classField。如果Class1的class method或instance method欲使用到Class2的class field,就必须在前面冠上Class2,例如:Class2.classField。

宣告field时,如果前面「不」加上static的修饰字,就会使得此field变成是instance field。对instance field而言,每产生一个instance(物件)就多一块instance field的记忆体,每少一个instance就少一块instance field的记忆体。instance field可以被同一个instance的instance method内部所直接使用,举例来说,上述的instanceMethod1()内部可以出现instanceField。如果某class的class method或instance method欲使用到某instance的instance field,就必须在前面冠上instance名称,例如:obj.classField。

Method
宣告method时,如果前面加上static的修饰字,就会使得此method变成是class method。对class method而言,永远只占用一块记忆体,而且此记忆体空间是在此class一被载入进记忆体之后就立刻配置的,就如同此method是与该class本身相关,而不是与该class的instance相关。class method可以被同一个class的任何class method内部所直接使用,举例来说,上述的classMethod1()内部可以出现classMethod2()。如果Class1的class method或instance method欲使用到Class2的classMethod(),就必须在前面冠上Class2,也就是Class2.classMethod()。

宣告method时,如果前面「不」加上static的修饰字,就会使得此method变成是instance method。对instance method而言,每产生一个instance「并不会」多一块instance method的记忆体。同一个method不管被调用(invoke)几次,也不管被调用时的instance是何者,每次的程式码完全都一样,差别只在每次执行时资料不同,而资料是存放在call stack中,所以不会混淆。在instance method内,资料的来源包括了参数和instance field。参数被传进来变成call stack内的entry,所以不会混淆,这很容易理解,但是instance field是如何区隔开来的(前面刚提过:instance field会随着instance数目增加而增加),这是透过隐匿(implicit)的this参数来达到了,稍后会有说明。instance method可以被同一个instance的instance method内部所直接使用,举例来说,上述的instanceMethod1()内部可以出现instanceMethod2()。如果某class的class method或instance method欲使用到某instance的某instance method,就必须在前面冠上此instance名称,例如:obj.classMethod()。

隐匿的 this 参数
综合上面的叙述来看:

class field:共用一块记忆体
class method:共用一块记忆体
instance field:随着每个instance各有一块记忆体
instance method:共用一块记忆体
instance method为什么不是随着每个instance占有一块记忆体,反倒是共用一块记忆体?其实,让每个instance method如同instance field一样,随着每个instance占有一块记忆体,这么做当然是可以的,只是Java编译器和JVM都不这么做,因为太浪费记忆体空间了。一个field少则占用一个byte,多则占用数百Byte,但是method少则数个byte,多则数百Kilo Byte。Mehtod耗费的记忆体是field的数百倍,甚至数千倍,当然是能共用就尽量共用,比较不会消耗记忆体。既然JVM让一个class的所有instance共用相同的instance method,下面两行程式码在instanceMethod()内部时,如何区分是instance1或instance2?

instance1.instanceMethod();
instance2.instanceMethod();

因为编译器会帮我们在把instance1和instance2个别传入instanceMethod()中当作第一个参数。也就是说,任何instance method参数的实际个数都会比表面上多一个,这个多出来的参数是由Java编译器帮我们加上去的,用来代表对应的instance。此参数的变数名称为this,也是Java的一个关键字(keyword)。

当调用某个instance method或使用某个instance field时,你必须在前面加上该instance的名称,如果该instance method/field相关的instance和当时程式码所在的instance method的instance指的是同一个instance时,该instance的名称就是this,这种情况下,你也可以选择不在前面加上「this.」。

然而,在某些状况下,非得在前面加上「this.」不可。例如,当method中的参数或区域变数和instance field名称完全相同时,如果不在前面冠上「this.」,那么指的是参数或区域变数;如果在前面冠上「this.」,那么指的才是instance field。
### C语言C++中`static`关键字的区别及其在函数中的用 #### 一、C语言中的`static`关键字 在C语言中,`static`主要用于控制变量或函数的用域生命周期。以下是它的主要用途: 1. **局部静态变量** 当`static`修饰局部变量时,该变量的生存期延长至整个程序运行期间,但仅在其定义的函数范围内可见[^2]。这意味着即使函数执行完毕,变量仍然保留其值直到下次调用。 2. **全局静态变量** 若将`static`应用于全局变量,则此变量的用域被限制为其所在的源文件内,外部文件无法通过`extern`声明访问它[^3]。 3. **静态函数** 使用`static`修饰函数同样限定了其用域为当前源文件内部。其他文件即便知道函数签名也无法直接调用这些函数[^2]。 #### 二、C++中的`static`关键字扩展 除了继承自C的功能外,C++还赋予了`static`更多语义上的意义,尤其是在面向对象编程方面: 1. **类内的静态成员变量** 在C++里,可以定义属于类而不是单个对象的数据成员——即静态数据成员。这种类型的成员共享给所有同类例,并且必须在类体外初始化一次[^1]。 2. **类内的静态成员函数** 类似地,也可以创建只关联到类本身的成员函数。重要的是要注意一点:由于这样的函数不属于任何一个具体对象,所以在现过程中它们不能访问非静态成员(除非借助传入的具体对象),并且也没有隐含参数`this`指针的存在[^1]。 #### 三、关于`this`指针的影响 - 在C语言环境下讨论`this`是没有意义的,因为它并不支持OOP特性。 - 然而,在C++中,当我们将一个普通成员函数设为`static`后,际上就消除了对该特定调用者的绑定关系,从而也就不具备默认传递过来的第一个隐藏参数——也就是所谓的`this`指针[^1]。 --- ### 示例代码对比分析 #### (一)C语言示例展示`static`对函数及变量的用 ```c /* file1.c */ #include <stdio.h> // 静态函数fun只能在此文件中使用 static void fun(void) { printf("This is a static function in file1.\n"); } void publicFun() { fun(); // 可以在这里调用 } int main() { publicFun(); return 0; } ``` 如果尝试从另一个文件(`file2.c`)中调用`fun()`将会失败,因为它是静态的,局限于自己的翻译单元之内。 #### (二)C++示例体现`static`对于类的支持情况 ```cpp #include <iostream> using namespace std; class MyClass { public: static int count; // 声明静态成员变量 static void incrementCount(); // 声明静态成员函数 }; // 初始化静态成员变量 int MyClass::count = 0; // 现静态成员函数 void MyClass::incrementCount() { ++count; // 不涉及this指针 } int main() { MyClass myObj1, myObj2; MyClass::incrementCount(); // 访问静态方法无需例化对象 cout << "Count after first call: " << MyClass::count << endl; MyClass::incrementCount(); cout << "Count after second call: " << MyClass::count << endl; return 0; } ``` 在这个例子中可以看到,无论有多少个MyClass的对象存在,所有的操都是基于同一个共享资源`count`来进行增减计数器的动;而且注意到我们在调用`incrementCount()`的时候并没有指定哪个际存在的myObj来做这件事,这就是因为我们已经把它设定成了静态的方法。 --- ### 结论总结 综上所述,虽然两者都利用到了同样的关键词`static`来达到某种意义上的封装效果或者是改变体的有效期限/可触及区域等功能目标,但是考虑到各自所处的语言环境差异较大(C vs CPP),所以最终呈现出来的行为模式还是有很大区别的。特别值得注意的一点是在C++当中引入了OO概念之后所带来的额外能力表现形式—比如取消掉原本存在于每一个常规成员函数里的那个暗藏版this引用机制等等[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值