1.Java的String和StringBuffer和StringBuilder详解
2.Java的引用类型、参数传值与传引用区别
3.Java的包装类
2.java参数传递:java语言中,将实参的值传递给形参时,到底传递的是什么?
我们知道,实参和形参在栈上分别有一个内存地址,内存里面存放的内容即是他们的值,比如:
int number = 10;//在栈的内存上开辟一块空间来存放number,具体的内存地址值由jvm来分配和管理,比如在0x12345678的内存地址上存放number的值10
Person person = new Person(“zhangsan”);//在栈上开辟一块内存存放person,在堆上开辟一块内存存放zhangsan对象,其中栈上的person内存地址里面存放zhangsan对象在堆上的地址值,即栈上的person引用着堆上的zhangsan对象。
在参数传递过程中,实参将其自己的存储内容传给了形参的内存存储中。接下来我们看三个例子来验证下。
... ... //定义了一个改变参数值的函数 public static void changeValue(int x) { x = x *2; } ... ... //调用该函数 int num = 5; System.out.println(num); changeValue(num); System.out.println(num); ... ...答案显而易见,调用函数changeValue()前后num的值都没有改变。在该例子中changeValue函数的形参x的值是由实参num的内存存储值传过去的,即将5赋值给了形参x的内存存储中。
在看一个例子
... ... class person { public static String name = "Jack"; ... ... } ... ... //定义一个改变对象属性的方法 public static void changeName(Person p) { p.name = "Rose"; } ... ... public static void main(String[] args) { //定义一个Person对象,person是这个对象的引用 Person person = new Person(); //先显示这个对象的name属性 System.out.println(person.name); //调用changeName(Person p)方法 changeName(person); //再显示这个对象的name属性,看是否发生了变化 System.out.println(person.name); }
输出的答案:
第一次显示:“Jack”
第二次显示:“Rose”
在该例子中,位于栈上的形成p的值是由同样位于栈上的实参person给赋值的,由于实参person栈空间里面存放的是堆空间Person的对象引用,即实参person里面存放的是堆空间Person的地址,在发生实参向形参传递赋值的过程时,实参中将该地址传给了形参,即形参的内存中也存放的是堆空间里面的Person对象的地址。因此,在changeName中对形参的Person对象的name属性修改时会反应到实参中。在看一个例子
public class Example { String str = new String("good"); char[] ch = { 'a', 'b', 'c' }; public static void main(String args[]) { Example ex = new Example(); ex.change(ex.str, ex.ch); System.out.print(ex.str + " and "); System.out.print(ex.ch); } public void change(String str, char ch[]) { str = "test ok"; ch[0] = 'g'; } }分析下这个Demo中为什么 str没被改变,但是ch[0]却改变了。
在main中new了一个Example对象,ex.str指向了该对象中的String成员对象(其值为"good"),然后调用change函数,将ex.str作为实参传递给形参str,此时ex.str和str都是引用,传递的过程是将ex.str引用的值传递给str,那么str也将指向同一字符串对象("good"),
随后进入change函数,str = "test ok"; 该语句将在JVM的字符串常量池中创建一个新的字符串常量("test ok")对象,赋值给str的其实是新字符串常量对象的地址值。好了,str的值改变了,而str本身是String类型的一个对象引用,那么该引用现在指向了新的字符串对象。(注意,此处跟上面例子的区别,上面例子中形参始终指向实参,两者的地址是一样的,形参从未发生指向的改变,改变的只是地址里面的属性,而此处是将指向/地址发生了改变) 而我们外层的Example对象仍然由ex这个引用指向,并且在ex内部还有一个ex.str的引用,它仍然指向"good"字符串,一直没有改变,当你使用ex.str打印时,就显示了"good"没有变化。
以上就是str没有改变的原因,至于ch[0]是数组,跟上面例子中的Person一样,修改的是地址里面的值。
通过上面的三个例子,我们可以得出结论,实参向形参传递的是实参内存存储单元里面的值(该值即可能是基本数据类型也可能是引用类型,如果是引用类型的话则是一个地址)。如果实参里面直接存放的是基本数据类型的话就直接拷贝一份赋值给形参,如果实参里面存放的是堆空间引用类型对象的话,自己存储单元里面存放的就是地址,因此拷贝一份地址传给形参。从这个角度来说的话,无论实参是基本类型还是引用类型,都是将自己存储单元的值直接拷贝了一份给形参,这也解释了java中参数传递都是值传递而没有引用传递的原因。
3.C语言参数传递
接来下我们在来看看C语言中,实参向形参传递时是否跟java一样?
typedef struct mystruct mystruct; struct mystruct { int i; char c; int arrary[2]; char *pChar; }; void pointTest(mystruct *pmystruct) { int a = 100; int *ap = &a; printf("%p\n",&a); //输出:002AF744 printf("%p\n",ap); //输出:002AF744 printf("%d\n",*ap); //输出:100 printf("%p\n",&ap); //输出:002AF738 printf("%p\n",&*ap);//输出:002AF744 printf("&pmystruct address = %p\n", &pmystruct); printf(" pmystruct address = %p\n", pmystruct); scanf("%d"); } int _tmain(int argc, _TCHAR* argv[]) { mystruct temp; printf("temp address = %p\n", &temp); pointTest(&temp);//测试指针本身地址、指针指向地址及指向的内容 return 0; }输出结果:
temp address = 0041F770 0041F688 0041F688 100 0041F67C 0041F688 &pmystruct address = 0041F69C pmystruct address = 0041F770分析:
1. printf("%d\n",&a);//输出:002AF744
这一句输出的是变量a的地址,毋庸置疑。
2. printf("%d\n",ap);//输出:002AF744
这一句是输出的是指针的值,也就是说指针的值是指针所指向的变量的地址
3. printf("%d\n",*ap);//输出:100
在指针变量的前面加了一个*号,不加星号的ap指针是指向变量a的地址,而加了*真变成了指针ap所指向的变量a的内容,所以,我们可以理解为*号是获取指针变量所指向的地址所存放的内容的操作。
4. printf("%d\n",&ap);//输出:002AF738
这一句(同1)是取得指针变量ap的地址
5. printf("%d\n",&*ap);//输出:002AF744
这一句根据第3点的分析,*ap指向的是变量a的内容,而&*ap即是获取变量a的内容的地址,即是变量a的地址所以输出内容同(1)。
C语言中在发生有参函数调用时,实参变量与形参变量之间的数据都是单向的“值传递”方式。包括指针变量和数组名作参数的情况。C语言要求函数的实参要有确定的值,在函数调用时给形参分配相应的内存单元,同时将实参的“值”赋(复制)给形参,实现数据从实参到形参的传递(‘值传递’方式)。因为是复制,所以在操作副本(形参)过程中不会影响到原本(实参)内容。
作为函数实参的量包括常量、变量和表达式,其中变量又包括简单变量、数组元素、数组名、指针变量等。不同类型变量作参数实现的数据传递方式相同,效果不同。所谓方式相同即都是参数间数据单向的“值传递”,效果不同是指被调函数能否改变主调函数中变量的值。