数组(array)是相同类型变量的集合,可以使用共同的名字引用它。数组可被定义为任何类型,可以是一维或多维。数组中的一个特别要素是通过下标来访问它。数组提供了一种将有联系的信息分组的便利方法。
注意:如果你熟悉C/C++,请注意, Java数组的工作原理与它们不同。
1、数组不是集合,它只能保存同种类型的多个原始类型或者对象的引用。数组保存的仅仅是对象的引用或者是基本类型,而不是对象本身。
2、数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
3、数组声明的两种形式:一、int[] arr; 二、int arr[]; 推荐使用前者,这符合Sun的命名规范,而且容易了解到关键点,这是一个int数组对象,而不是一个int原始类型。
4、在数组声明中包含数组长度永远是不合法的!如:int[5] arr; 。因为,声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。
5、在数组构造的时候必须指定长度,因为JVM要知道需要在堆上分配多少空间。反例:int[] arr = new int[];
6、多维数组的声明。int[][][] arr; 是三维int型数组。
7、一维数组的构造。形如:String[] sa = new String[5];
或者分成两句:String[] sa; sa = new String[5];
8、原始类型数组元素的默认值。对于原始类型数组,在用new构造完成而没有初始化时,JVM自动对其进行初始化。默认值:byte、short、 int、long--0 float--0.0f double--0.0 boolean--false char--'"u0000'。(无论该数组是成员变量还是局部变量)
9、对象类型数组中的引用被默认初始化为null。如:Car[] myCar = new Car[10]; 相当于从myCar[0]到myCar[9]都这样被自动初始化为myCar[i] = null;
10、对象类型的数组虽然被默认初始化了,但是并没有调用其构造函数。也就是说:Car[] myCar = new Car[10];只创建了一个myCar数组对象!并没有创建Car对象的任何实例!
11、多维数组的构造。float[][] ratings = new float[9][]、int[][][] aaray = new int[2][][];;第一维的长度必须给出(从左到右初始化),其余的可以不写,因为JVM只需要知道赋给变量ratings的对象的长度。
12、数组索引的范围。数组中各个元素的索引是从0开始的,到length-1。每个数组对象都有一个length属性,它保存了该数组对象的长度。(注意和String对象的length()方法区分开来,这两者没有统一起来是很遗憾的。)
随机访问性原理:数组的索引值由0开始并不是没有原因的。事实上索引值表示的是:所指定的数组元素相对于数组第一个元素内存位置的位移量(Offset)。索引为0表示位移量为0,所以就是指第一个元素,而索引i就是指相对于第一个元素的位移量为i。不过在Java中您不直接处理关于内存地址的操作,以上的观念主要是让您了解一下数组索引的运作原理。
13、Java有数组下标检查,当访问超出索引范围时,将产生ArrayIndexOutOfBoundsException运行时异常。注意,这种下标检查不是在编译时刻进行的,而是在运行时(是jvm内置的安全机制)!也就是说int[] arr = new int[10]; arr[100] = 100; 这么明显的错误可以通过编译,但在运行时抛出!
Java的数组下标检查是需要额外开销的,但是出于安全的权衡还是值得的,因为很多语言在使用数组时是不安全的,可以任意访问自身内存块外的数组,编译运行都不会报错,产生难以预料的后果!
14、可以变相的动态产生数组(这种动态同ArrayList一样并不是真正意义上的动态,实际上他根据你的需要最终还是产生一个固定大小的数组):
由于数组的内存空间是使用new配置而来,这意味着您也可以使用动态的方式来定义数组长度,而不用在程序中事先决定数组大小。范例5.4示范了如何由使用者的输入来决定数组长度,它是一个计算输入分数平均的程序。
AverageInput.java import java.util.Scanner;
public class AverageInput {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入学生人数: ");
int length = scanner.nextInt();
float[] score = new float[length]; // 动态配置长度
for(int i = 0; i < score.length; i++) {
System.out.print("输入分数:");
float input = scanner.nextFloat();
score[i] = input;
}
System.out.print("\n分数:");
float total = 0;
for(int i = 0; i < score.length; i++) {
total = total + score[i];
System.out.print(score[i] + " ");
}
System.out.printf("\n平均:%.2f", total / score.length);
}
或者
public static void main (String args[]){
int iii = Integer.parseInt(args[0]);
int[] wer = new int[iii];//此处iii必须先初始化,利用形参或者是通过形参计算出来的值或者是控制台输入的值来实现动态性;
}
15、java中只有数组类(jdk中没有数组的源码)默认设计了clone方法。(即数组已经实现了Cloneable接口的。可以直接调用。)并且数组的clone方法只是实现浅拷贝(即若数组内为对象引用时,他不会对引用指向的对象进行拷贝,这样拷贝数组与原数组内元素所指向的对象是同一个对象)。
clone方法是Object的protected性质的方法。 在java中,Object是所有类的父类!
但是在对象调用clone()这个方法时,会检查此对象是否有实现Cloneable这个接口(只有实现了Cloneable接口的类才可以被复制,Cloneable 接口没有定义任何成员。它用来指明一个类对象可以被逐位复制。如果你试图对一个没有实现cloneable接口的类调用clone()方法,一个CloneNotSupportedException就会抛出。因为复制可以引起许多问题,clone()在object类中被声明为protected.这意味着,它要么在一个实现了cloneable接口的类中的某一方法里被调用,要么在明确的在那个类中的被重写,且被声明为public的。)Cloneable与Serializable都是标志性接口。
16、数组这样初始化是错误的:int[] wer = new int[1]{1}; 应该:int[] wer = new int[]{1};
17、Arrays类新增的两方法:
deepEquals() 对数组作深层比较,简单地说,可以对二维仍至三维以上的数组进行比较是否相等
deepToString() 将数组值作深层输出,简单地说,可以对二维仍至三维以上的数组输出其字符串值
================================
C/C++动态内存分配:
void* malloc(unsigned size); void* calloc(size_t nelem, size_telsize); 和void* realloc(void* ptr, unsigned newsize);都在stdlib.h函数库内,是C语言的标准内存分配函数。
1. 函数malloc()和calloc()都可以用来动态分配内存空间。malloc()函数有一个参数,即要分配的内存空间的大小,malloc 在分配内存时会保留一定的空间用来记录分配情况,分配的次数越多,这些记录占用的空间就越多。另外,根据 malloc 实现策略的不同,malloc 每次在分配的时候,可能分配的空间比实际要求的多些,多次分配会导致更多的这种浪费。当然,这些都和
malloc 的实现有关;calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。如果调用成功,它们都将返回所分配内存空间的首地址。
2. 函数malloc()和函数calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能。
3. realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变。当然,对于缩小,则被缩小的那一部分的内容会丢失。
4. realloc 并不保证调整后的内存空间和原来的内存空间保持同一内存地址。相反,realloc 返回的指针很可能指向一个新的地址。所以在代码中,我们必须将realloc返回的值,重新赋值给 p :
p = (int *) realloc (p, sizeof(int) *15);
=====================================================
java 深克隆与浅克隆
1.浅复制与深复制概念
⑴浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不
复制它所引用的对象。
⑵深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原
有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
2.浅克隆
Java中Object对象的clone()方法,只能实现浅复制。
①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。 派生类中覆盖Object的clone()方法时,一定要调用super.clone()。在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
④在派生类中实现Cloneable接口。
3.深克隆
(1)编程保证要复制的类以及其中所引用的其他类均实现了Cloneable接口,且重写了clone()方法,并且clone()中,要编程调用所引用对象的clone()方法来实现所引用对象的拷贝。
(2)利用串行化来做深复制
先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
如下为深复制源代码。
public Object deepClone()
{
//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。
class Teacher implements Serializable{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
}
class Student implements Serializable{
String name;//常量对象
int age;
Teacher t;//学生1和学生2的引用值都是一样的。
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.p=p;
}
public Object deepClone() throws IOException,
OptionalDataException,ClassNotFoundException{//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.deepClone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);//学生1的老师不改变
}