Notes: Initialization and CleanUp(初始化与清理)

初始化与清理的问题是设计编程“安全”性的两大问题。


Java通过构造器(Constructor)来初始化对象引用,和自动垃圾回收机制(garbage collection)替程序员分忧这两部分的问题。


------------------------------------------------------------------------------C程序的初始化与清理问题------------------------------------------------------------------------------


C程序当中,往往能通过:malloc 来向系统申请内存使用。然而,malloc申请内存后,是需要初始化的(这一点不同于另外一个耳熟能详的函数,calloc)。malloc如果不初始化,里面的数据将会是随机的垃圾数据。


p.s. malloc和calloc的区别


calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。


用 法:
void *calloc(size_t n, size_t size);//意味分配n个size位size字节的连续内存空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

calloc相当于
p = malloc();
memset(p, 0,size);


void *malloc(size_t size);  // 向系统申请分配指定size个字节的内存空间, 如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。


备注:void* 表示未确定类型的指针,void *可以指向任何类型的数据,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者其他数据类型)。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。


上述两者,使用后要使用 free(起始地址的指针) 对内存进行释放,不然内存申请过多会影响计算机的性能,以至于得重启电脑。如果使用过后不清零,还可以使用指针对该块内存进行访问。


所以,初始化变量与清理变量是C程序需要注意的一大问题。


------------------------------------------------------------------------------Java的变量初始化机制------------------------------------------------------------------------------


Java通过提供构造器来确保对象变量(引用)得到初始化。构造器是一种特殊的初始化函数,是创建对象时编译器自动调用的函数(创建对象的过程简述为:分配存储空间+调用相应的构造器初始化对象)。Java规定,构造器的名字与类名同名,所以构造器函数的名称不采用驼峰式写法。Java编译器会在程序员没提供自定义构造器时提供默认的无参构造器(注意:如果程序员提供了自定义构造器,编译器不再提供默认的构造器,即使程序员没有定义无参的构造器)。


创建对象的过程为:假设目前的类是Dog类。当首次创建Dog对象或者调用Dog的静态方法时,搜索Dog.class文件。

1)加载Dog.class文件(得到一个Class对象)。此时,static变量的初始化(显式/ 一般形式)的所有操作都会执行。不过,也只是执行这么一次。

2)当用new Dog()创建对象时,在堆上为Dog分配足够存储空间。

3)清零(初始化)这部分空间。基本类型都设为0。引用类型则设为null。

4)执行字段定义的时候的初始化动作。(指的是Java允许在定义类的时候给类成员赋值的行为)

5)执行构造器。当涉及到继承的时候,这一步牵涉到许多动作。


Java对于函数的局部变量的初始化问题则提供编译不通过的方式来保证初始化。


非静态类成员变量的初始化:

1)在定义类成员变量时直接赋值,但一般不这样做,在C++中直接不允许这么做。因为这种初始化的方法会使得每个对象都具有相同的成员变量值。这里写的类成员指的是,基本类型的类成员以及非基本类型(自定义类的对象)的类成员如:

class Depth();

public class Measurement() {
	Depth d = new Depth();
	// ...
}

如果没有为d指定初始值就使用它,就会出现运行错误,产生一个异常(第12章)。


2)构造器初始化,程序在创建对象时,一是分配内存中的存储空间,在这一步会自动初始化所有的成员变量到默认初始值。二是自动调用构造器,如果存在用户自定义的初始化代码于构造器的函数体内,执行用户自定义的初始化代码。


p.s. 各种基本类型的初始化始值

boolean false (0)

char ''(0)

byte 0

short 0

int 0

long 0

float 0

double 0

对任何对象的符号引用:

reference null


对于类成员变量初始化的时候:成员变量初始化在调用任何的方法之前(包括隐式static的构造器),而初始化的顺序则根据变量定义的先后顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法被调用之前得到初始化。例如:


class Window {
	Window(int marker) {
		System.out.println(“window”+marker);
	}
}

class House {
	Window w1 = new Window(1);
	House() {
		System.out.println("house()");
		w3 = new Window(33);
	}
	Window w2 = new Window(2);
	void f() {
		System.out.println("f()");
	}
	Window w3 = new Window(3);
}

public class test{
	public static void main(String[] args){
		House h = new House();
		h.f();
	}
}


输出结果:

window1

window2

window3

House()

window33

f()


静态成员变量的初始化(显式 / 普通):


静态成员变量的初始化在非静态成员变量之前。总体而言是:静态成员变量 > 非静态成员变量 > 对象引用变量


但需要注意的是,静态成员变量只是在首次创建该类对象时初始化,之后再创建该类对象则不会再初始化。原因是无论创建多少个对象,静态数据成员都只占用一份在静态区的储存区域(独立于堆和栈的内存)。静态成员变量初始化的默认值和非静态成员变量相同。


class Bowl {
	public Bowl(int marker) {
		System.out.println("Bowl" + marker);
	}
	void f1(int marker) {
		System.out.println("f1" + marker);
	}
}

Class Table {
	Bowl bowl3 = new Bowl(3);
        static Bowl bowl1 = new Bowl(1);
	Table() {
		System.out.println("Table()");
		bowl2.f1(1);
	}
	void f2(int marker){
		System.out.println("f2"+marker);
	}
	static Bowl bowl2 = new Bowl(2);
}

public class test {
	public static void main(String[] args){
		System.out.println("creating new table in main");
		new Table();
		table.f2(1);
	}
	static Table table = new Table();
} 




输出结果:

Bowl1

Bowl2

Bowl3

Table()

f11

Creating new table in main

Bowl3

Table()

f11


静态数据成员只在创建该类的对象时才会初始化。且只会被初始化一次。


显示静态成员变量的初始化


class Bowl {
	public Bowl(int marker) {
		System.out.println("Bowl" + marker);
	}
	void f1(int marker) {
		System.out.println("f1" + marker);
	}
}

Class Table {
	Bowl bowl3 = new Bowl(3);
	static Bowl bowl1;
	static { // explicit statement
		bowl1 = new Bowl(1);
		bowl2 = new Bowl(2);
	}
	Table() {
		System.out.println("Table()");
		bowl2.f1(1);
	}
	void f2(int marker){
		System.out.println("f2"+marker);
	}
	static Bowl bowl2;
}

public class test {
	public static void main(String[] args){
		System.out.println("creating new table in main");
		new Table();
		table.f2(1);
	}
	static Table table = new Table();
} 


在这里回头讲讲一种特殊的非静态的对象引用变量初始化的语法(针对对象引用),这种语法类似static的显示初始化:


class Mug {
	Mug (int marker){
		System.out.println("Mug"+marker);
	}
	void f (int marker){
		System.out.println("f"+marker);
	}
}

public class Mugs {
	Mug mug1;
	Mug mug2;
	{ // 注意这里少了static
		mug1 = new Mug(1);
		mug2 = new Mug(2);
		System.out.println("mug1 and mug2 initialized");
	}
	public Mugs() {
		System.out.println("Mugs()");
	}
	public Mugs(int marker){
		System.out.println("Mugs(int marker)");
	}
	public static void main(String[] args){
		System.out.println("Inside main()");
		new Mugs();
		new Mugs(1);
	}
}


输出结果:

Inside main()

mug(1)

mug(2)

mug1 and mug2 initialized

Mugs()

mug(1)

mug(2)

mug1 and mug2 initialized

Mugs(int marker)


不同的是,这种不在static块内的初始化代码是初始化非静态变量的代码,会在每次创建该类的对象时运行,用以初始化非静态的成员变量。这种语法对于支持“匿名”内部类的初始化是必须的。(第10章)


------------------------------------------------------------------------------数组的初始化-----------------------------------------------------------------------------


数组是对内存中连续的一片空间的引用(标识符)。通过[]来定义和使用。数组可以存储基本类型,也可以存储对象。如果存储的是非基本类型时,则称这个数组为引用数组。引用数组的内容项的初始值是NULL。如果直接使用引用数组的NULL内容项,会产生Null Pointer的异常。


数组相对于容器类的各个实现而言,最明显的特点(缺点)就是必须在初始化固定长度,不能改变。


数组的声明方式:

int[] array;


注意,声明只是给数组的引用(标识符)分配了存储空间,但并没有为数组的内容分配空间。必须初始化后,数组才获得相应的存储空间。


初始化的方式有以下几种:

1)初始化成默认值:int[] a = new int[int size]; //只指定大小

2)初始化并赋予初始值:int[] a = {1,2,3,4,5,}; 或者等价的:int[] a = new int[]{1,2,3,4,5,};

但注意的是“int[] a = {1,2,3,4,5,}; ”只能用于数组被声明的地方。但后者可以在任何地方使用。如函数的参数传递:

other.main(new String[]{"this", "is" }); 代替穿给main函数的命令行参数。


通过上述的初始化方法,引申到了函数的可变参数(C语言里的varargs),可以通过这样去实现:

由于所有的类都直接或者间接继承Object类,所以可以把函数的形参定义为Object[]。

public static void printArray(Object[] args){

...

}

调用时可以:

printArray(new Object[]{new Integer(18), "Good"});

来实现可变参数。


然而到了后期,Java利用符号 "..." 来实现了可变参数。


static void printArray(Object ... args){

}


调用的时候可以直接:

printArray(new Integer(18),"Good");


但其实两者本质是相同的,都是利用Object数组来实现。所以可以利用foreach语法或者正常遍历数组的for循环去遍历args。 


注意:可变参数接受空输入。


------------------------------------------------------------------------------Java的自动垃圾回收机制------------------------------------------------------------------------------


Java的自动垃圾回收机制请看另一篇文章:http://blog.csdn.net/firehotest/article/details/52066545



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值