Static关键字 C/C+/Java

45 篇文章 1 订阅

1. 什么是static?

static 是 C/C++/Java 中常用静态修饰符,它被用来控制变量的存储方式和可见性。C++作为中间过渡语言,兼具了面向对象中static特性也具备了面向过程的static特性。

1.1. static 的引入

我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义为全局的变量,但定义一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅只受此函数控制)。static 关键字则可以很好的解决这个问题。

另外,在面向对象C++、Java中,需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性。static在类被加载的时候即可访问,可以用来修饰类中的方法和成员变量。在C++/Java中可以利用static进行代码优化,提高代码执行效率。当然static方法这种特定属性也存在一些弊端,static定义的方法只能其内部访问数据必须是static成员变量,负责对象在没有初始化的情况下被访问是与面向对象的基本概念相背的。

1.2 静态数据的存储

全局(静态)存储区:分为 DATA 段和 BSS 段。DATA 段(全局初始化区)存放初始化的全局变量和静态变量;BSS 段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。

在 C++ 中 static 的内部实现机制:静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。

这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main() 函数前的全局数据声明和定义处。

静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的"尺寸和规格",并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。

static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

优势:可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

2. 在 C/C++ 面向过程中static的作用

2.1 总的来说

  • (1)在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
  • (2)static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。
  • (3)static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。
  • (4)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用 static 修饰。
  • (5)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)。

2.2 静态变量与普通变量

静态全局变量有以下特点:

  • (1)静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量;
  • (2)未经初始化的静态全局变量会被程序自动初始化为0(在函数体内声明的自动变量的值是随机的,除非它被显式初始化,而在函数体外被声明的自动变量也会被初始化为 0);
  • (3)静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的。
  • (4)static可以作为函数的形参变量,这个变量在传递过程中是跟着函数改变的,虽然编译器不抱错误,但是没有实际的意义。

优点:静态全局变量不能被其它文件所用;其它文件中可以定义相同名字的变量,不会发生冲突。

(1)全局变量和全局静态变量的区别

  • 1)全局变量是不显式用 static 修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过 extern 全局变量名的声明,就可以使用全局变量。
  • 2)全局静态变量是显式用 static 修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用 extern 声明也不能使用。

(2)静态局部变量有以下特点:

  • (1)该变量在全局数据区分配内存;
  • (2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
  • (3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为 0;
  • (4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。

一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。这个特性对应约束是static没有办法和extern联合使用,无论是全局还是局部被static声明的变量,extern static定义命令会被编译器告警不能指定多个存储类。当然这个特性也说明全局static修饰的变量只能在本文件中使用,在其他文件中没有办法使用这个文件。

#include <stdio.h>

static int static_global1 = 0;
int global1 = 0;

int func1(static int var1, int var2)
{
	static int static_func_local1 = 0;
	int func_local1 = 0;
    printf("Func parameter static int %d, int %d\n", var1, var2);
	printf("Func static local1 %d, local1 %d\n", static_func_local1++, func_local1++);
	printf("Func static global1 %d, global1 %d\n", static_global1--, global1--);

	return 0;
}


int main(int argc, int** argv)
{
	static int static_local1 = 0;
	int local1 = 10;
	
	while (local1--)
	{
		static_global1++; global1++;
		func1(static_local1, local1);
		printf("Main func static local1 %d, local1 %d\n", static_local1, local1);
		printf("-------------------------------------------------------------------\n");
	}

	return 0;
}

2.3 静态函数与普通函数

函数分为内部函数和外部函数,当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。静态函数的使用可以减少不必要的文件冲突出现,对文件访问域进行设定,同时也保证了一些关键函数的访问范围。

(1)内部函数(又称静态函数)

如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数,其作用域仅局限与本文件。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:static 函数类型 函数名(函数参数表){……}

使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。

(2)外部函数

外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:[extern] 函数类型 函数名(函数参数表){……}

调用外部函数时,需要对其进行说明:[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];

File1: 

#include <stdio.h>
int func1(int var1, int var2)
{
	printf("Extern local func1 var1 %d, var2 %d\n", var1, var2);
	return 0;
}

extern int func2(int val1, int val2)
{
	func1(val1, val2);
	printf("Func2 parameter val1 %d, val2 %d\n ", val1, val2);
	
	return 0;
}

File2:

#include <iostream>
using namespace std;
static int static_global1 = 0;
int global1 = 0;

static int func1(static int var1, int var2)
{
	static int static_func_local1 = 0;
	int func_local1 = 0;
	printf("Func parameter static int %d, int %d\n", var1, var2);
	printf("Func static local1 %d, local1 %d\n", static_func_local1++, func_local1++);
	printf("Func static global1 %d, global1 %d\n", static_global1--, global1--);

	return 0;
}

int main(int argc, int** argv)
{
	static int static_local1 = 0;
	int local1 = 10;
	
	while (local1--)
	{
		static_global1++; global1++;
		func1(local1, static_local1);
		printf("Main func static local1 %d, local1 %d\n", static_local1++, local1);
		printf("-------------------------------------------------------------------\n");
	}

	extern int func2(int val1, int val2);

	func2(local1, static_local1);

	system("pause");
	return 0;
}

3. C++/Java面向对象中Static作用

在面向对象中static是今天类型说明,指定的是地址存储的位置空间。因此,static约束的变量或者方法都是受面向对象中public、private和protect成员访问限定范围的限制的。

3.1 在 C++/Java 中

static 关键字最基本的用法是:

  • 1、被 static 修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要 new 出一个类来
  • 2、被 static 修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要 new 出一个类来

被 static 修饰的变量、被 static 修饰的方法统一属于类的静态资源,是类实例之间共享的,换言之,一处变、处处变。在 C++ 中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。因此,在很多Math这种算法类中,常用的方法是被定义成静态形式,毕竟算法是没有必要具体对象化的。

静态成员的定义或声明要加个关键 static。静态成员可以通过双冒号来使用即 <类名>::<静态成员名>。

3.2 实例

#include <iostream>
using namespace std;

class CPoint {
public:
	void UsingStaticValue()
	{
		printf("Class function using class static member %d\n", static_point);
	}
	
	static void StaticUsingStaticValue()
	{
		printf("Static class function using class static member %d \n", static_point);
	}

	static void StaticUsingVarValue()
	{
		// printf("Static class function using class variable member %d \n", point);  // 提示错误是非静态成员引用必须与特定对象对应
	}

	static int static_point;
private:
	int point;

};

int CPoint::static_point = 10;	// 静态成员初始化方法

int main(int argc, int** argv)
{
	CPoint::StaticUsingStaticValue();			// 静态方法直接使用
	printf("Using static variable %d\n", CPoint::static_point);	  // static point是静态public静态成员,可以访问,如果是private则没有办法访问

	// 对象实例可以使用静态函数,同事修改静态变量的情况下,对应其他对象的静态变量也被修改
	CPoint point1;
	CPoint point2;
	point1.StaticUsingStaticValue();
	point1.static_point++;
	point2.StaticUsingStaticValue();

	system("pause");
	return 0;
}

静态数据成员

  • (1)静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
  • (2)静态数据成员是在程序开始运行时被分配空间,到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。
  • (3)静态数据成员可以被初始化,但是只能在类体外进行初始化,若未对静态数据成员赋初值,则编译器会自动为其初始化为 0。
  • (4)静态数据成员既可以通过对象名引用,也可以通过类名引用。
  • (5)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”
  • (6)静态成员初始化与一般数据成员初始化不同,初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。初始化时不加该成员的访问权限控制符private,public等;初始化时不加该成员的访问权限控制符private,public等;初始化时使用作用域运算符来标明它所属类;
  • (7) 为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。在各通信公司的笔试面试中经常出现的考题就是static的作用及功能。
  • (8) static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。

静态成员函数

  • (1)静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
  • (2)非静态成员函数有 this 指针,而静态成员函数没有 this 指针。但是并不是说我们不可以使用this指针来访问我们的静态成员函数或者静态变量
  • (3)静态成员函数主要用来方位静态数据成员而不能访问非静态成员。
  • (4)不能将静态成员函数定义为虚函数。

思考总结:静态资源属于类,但是是独立于类存在的。从类的加载机制的角度讲,静态资源是类初始化的时候加载的,而非静态资源是类实例化对象的时候加载的。 类的初始化早于类实例化对象,比如 Class.forName("xxx") 方法,就是初始化了一个类,但是并没有实例化对象,只是加载这个类的静态资源罢 了。所以对于静态资源来说,它是不可能知道一个类中有哪些非静态资源的;但是对于非静态资源来说就不一样了,由于它是实例化对象出来之后产生的,因此属于类的这些东西它都能认识。所以上面的几个问题答案就很明确了:

  • 1)静态方法能不能引用非静态资源?不能,实例化对象的时候才会产生的东西,对于初始化后就存在的静态资源来说,根本不认识它。
  • 2)静态方法里面能不能引用静态资源?可以,因为都是类初始化的时候加载的,大家相互都认识。
  • 3)非静态方法里面能不能引用静态资源?可以,非静态方法就是实例方法,那是实例化对象之后才产生的,那么属于类的内容它都认识。

static 修饰类:这个用得相对比前面的用法少多了,static 一般情况下来说是不可以修饰类的, 如果 static 要修饰一个类,说明这个类是一个静态内部类(注意 static 只能修饰一个内部类),也就是匿名内部类。像线程池 ThreadPoolExecutor 中的四种拒绝机制 CallerRunsPolicy、AbortPolicy、DiscardPolicy、 DiscardOldestPolicy 就是静态内部类。静态内部类相关内容会在写内部类的时候专门讲到。)

3.3. 应用场景

(1)可以实现某些特殊的设计模式:如Singleton;

(2)由于没有this指针,可以把某些系统API的回调函数以静态函数的形式封装到类的内部。因为系统API的回调函数通常都是那种非成员函数(孤立函 数),没有this指针的。比如你可以在类的内部写一个线程函数供CreateThread创建线程用,如果没有静态函数,那么这种回调函数就必须定义成 全局函数(非静态成员函数指针无法转换成全局函数指针),从而影响了OO的“封装性”。

(3)可以封装某些算法,比如数学函数,如ln,sin,tan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类Math,调用Math::sin(3.14);如果非要用非静态函数,那就必须:Math  math;    math.sin(3.14);行是行,只是不爽:就为了一个根本无状态存储可言的数学函数还要引入一次对象的构造和一次对象的析构,当然不爽。而且既然有了对象,说不得你还得小心翼翼的定义拷贝构造函数、拷贝赋值运算符等等,对于一些纯算法的东西显然是不合适的。

        总之,从OOA/OOD的角度考虑,一切不需要实例化就可以有确定行为方式的函数都应该设计成静态的。以上只是一些基本的考虑,并不是绝对的。绝对东西的只有一点:“静态函数不需要实例化就可以被调用,不会也不可以调用或操纵非静态成员”。记住这一点,那么实际编程中何时需要用,何时适合用,自然就更容易作出决定了。

4. JAVA语言独特与C++

Java语言是完全的面向对象编程语言,C++中的兼容C特性功能其都不具备,比方说Java中static不能修饰局部变量。

Java语言中另外一个独特的特点是Java静态关键字可以用来修改代码块,这一点在C++中是不行的。

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }
     
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
     
    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值