[关闭]
@w1024020103 2017-02-18T21:36:40.000000Z 字数 6445 阅读 600

Lab 2 Notes

CS61B UCBerkeley


Lab2 Navigation

Debugging Basics

Application: IntLists

1.JPG-15.4kB

  1. IntList myList = IntList.list(0, 1, 2, 3);
  2. // Creates the IntList 0 -> 1 -> 2 -> 3 -> null
  1. IntList myList = new IntList(0, null);
  2. myList.rest = new IntList(1, null);
  3. myList.rest.rest = new IntList(2, null);
  4. myList.rest.rest.rest = new IntList(3, null);
  5. // One line of using IntList.list() can do the job of four lines!

Destructive vs. Non-Destructive

Let's consider a method dSquareList that will destuctively square every item in a list (similar to what we saw in discussion):

  1. IntList origL = Intlist.list(1, 2, 3)
  2. dSquareList(origL);
  3. // origL is now (1, 4, 9)
  4. Here is one implementation of dSquareList():
  5. public static void dSquareList(IntList L) {
  6. while (L != null) {
  7. L.first = L.first * L.first;
  8. L = L.rest;
  9. }
  10. }

This is a classic example of a destructive method. It iterates through the list and squares each item, causing the values linked by L to change. In other words, after calling this method once on L, every element in L will be squared.

NOTE: The choice to return void rather than the original pointer to L was an arbitrary decision. Different languages and libraries use different conventions (and people get quite grumpy about which is the "right" one).

Examining the code above, we see that the origL variable contains a reference to the created IntList. This origL variable never changes. Although the L variable in dSquareList() gets reassigned all day long, because all parameter passing in method calls are pass-by-copy (remember pass-by-bits from lecture?), the origL variable is still safe and properly points to the first element. In other words, even as we continually call L = L.rest, origL always points to the beginning of the IntList.
2.JPG-18.4kB

The reason that dSquareList is destructive is because we change the values of the original input IntList. As we go along, we square each value, and the action of changing the internal data persists (unlike the reassignment of L, which doesn't effectively last after the method finishes).

By the end of the method, L is null, and origL is still pointing at the beginning of the IntList, but every value in the IntList that origL points to is now squared.

Now, look at squareListIterative() and squareListRecursive(). These methods are both non-destructive. That is, the underlying IntList passed into the methods does not get modified, and instead a fresh new copy is modified and returned.

  1. public static IntList squareListIterative(IntList L) {
  2. if (L == null) {
  3. return null;
  4. }
  5. IntList res = new IntList(L.first * L.first, null);
  6. IntList ptr = res;
  7. L = L.rest;
  8. while (L != null) {
  9. ptr.rest = new IntList(L.first * L.first, null);
  10. L = L.rest;
  11. ptr = ptr.rest;
  12. }
  13. return res;
  14. }

3.JPG-27kB

Pay attention to res, it has changed when ptr changed. This is because an object has been created in heap, so they pointed to the same object before ptr=ptr.rest which changed the pointer of ptr. This method is non-destructive because origL always pointed to the same thing and never has been changed.

Implementing Destructive vs. Non-destructive Methods

  1. /**
  2. * Returns a list consisting of the elements of A followed by *the elements of B. May modify items of A. Don't use 'new'.
  3. */
  4. public static IntList dcatenate(IntList A, IntList B){
  5. if(A == null) {
  6. return B;
  7. }else if(B == null){
  8. return A;
  9. }
  10. IntList L = A;
  11. while(A.rest != null){
  12. A = A.rest;
  13. }
  14. A.rest = B;
  15. return L;
  16. }
  1. /**
  2. * Returns a list consisting of the elements of A followed by * the elements of B. May NOT modify items of A. Use 'new'.
  3. */
  4. public static IntList catenateRecursive(IntList A, IntList B){
  5. if(A == null){
  6. return B;
  7. }
  8. return new IntList(A.first, catenateRecursive(A.rest, B));
  9. }
  1. public static IntList catenateIterative(IntList A, IntList B){
  2. if (A == null) {
  3. return B;
  4. }
  5. IntList res = new IntList(A.first, null);
  6. IntList ptr = res;
  7. A = A.rest;
  8. while (A != null) {
  9. ptr.rest = new IntList(A.first, null);
  10. A = A.rest;
  11. ptr = ptr.rest;
  12. }
  13. ptr.rest = B;
  14. return res;
  15. }

In ctenateIterative(), I was wondering why A=A.rest doesn't modify A.

disscusion 2 Bonus Problems: Squaring a List

Write the following methods to destructively and non-destructively square a linked list. Destructively squaring means modifying the items in the IntList passed as an argument. Nondestructively means all values in the original list are unaffected by the function.

  1. /** Destructively squares each element of the given IntList L.
  2. * Don’t use ’new’; modify the original IntList.
  3. * Should be written iteratively. */
  4. public static IntList SquareDestructiveIterate(IntList L) {
  5. IntList B = L;
  6. while(L != null){
  7. B.first = B.first * B.first;
  8. B = B.rest;
  9. }
  10. return L;
  11. }
  1. /** Non-destructively squares each element of the given IntList L.
  2. * Don’t modify the given IntList.
  3. * Should be written recursively*/
  4. public static IntList SquareNonDestructiveRecursive(IntList L) {
  5. if(L == null){
  6. return null;
  7. }
  8. return new IntList(L.first * L.first, SquareNonDestructive(L.rest));
  9. }

Even more bonus problems: Write SquareDestructive recursively. Write SquareNonDestructive iteratively

  1. public static IntList SquareDestructiveRecursive(IntList L){
  2. if(L == null){
  3. return null;
  4. }
  5. L.first *= L.first;
  6. return SquareDestructiveRecursive(L.rest);
  7. }
  1. public static IntList SquareNonestructiveIterate(IntList L){
  2. if (L == null) {
  3. return null;
  4. }
  5. IntList res = new IntList(L.first*L.first, null);
  6. IntList ptr = res;
  7. L = L.rest;
  8. while (L != null) {
  9. ptr.rest = new IntList(L.first*L.first, null);
  10. L = L.rest;
  11. ptr = ptr.rest;
  12. }
  13. return res;
  14. }

From this practice, I lack a deeper understanding of the passing of parameters in Java and I can't write recursive or iterative method really quickly. Here are some extra information I found helpful:
java参数传递(超经典)
java到底是值传递还是引用传递?
在java方法中改变传递的参数的值
java 参数传递探究
Java中的参数传递方式
Java参数引用传递引发的惨案(又一次Java的String的“非对象”特性的踩坑经历)

刚在写一个用例,需要在方法中改变传递的参数的值,可是java中只有传值调用,没有传址调用。所以在java方法中改变参数的值是行不通的。但是可以改变引用变量的属性值。

可以仔细理解一下下面几句话:

也就是说,对于基本数据类型,实现的是传值,只是个形参,不会改变原有值。对于引用数据类型,对这个引用进行操作,其实也是相当于对形参的操作,不会改变原来的引用。但是,当对这个引用的属性进行操作的时候,可以改变这个引用的属性的值。

三句话总结一下:

Integer 和 String一样。保存value的类变量是Final属性,无法被修改,只能被重新赋值/生成新的对象。 当Integer做为方法参数传递进方法内时,对其的赋值都会导致原Integer 的引用被指向了方法内的栈地址,失去了对原类变量地址的指向.对赋值后的Integer对象做得任何操作,都不会影响原来对象。

Let's see an example from the above links:

  1. public class Argument {
  2. public static class User{
  3. public String name;
  4. }
  5. public static void main(String[] args) {
  6. User user = new User();
  7. user.name="AA";
  8. change(user);
  9. System.out.print(user.name); //输出BB
  10. }
  11. public static void change(User user){
  12. user.name="BB";
  13. }
  14. }

上面就是改变了引用的属性user.name,所以可以改变这个引用的属性的值.

Let's see another example:

  1. public class Argument {
  2. public static void main(String[] args) {
  3. String value = "小明";
  4. change(value);
  5. System.out.print(value); //output: 小明
  6. }
  7. public static void change(String value){
  8. String str="小红";
  9. value=str;
  10. }
  11. }

在未进入change方法前,value 的指向的地址为 453

java参数传递3.jpg

进入方法后 形参 value 的指向的地址依然为 453 说明形参复制了实参的地址

java参数传递4.jpg

其中str变量的指向的地址为 454
java参数传递5.jpg

value=str;这行很关键,出现以上错觉的原因就是String的赋值,java里赋值的做法是将地址赋值给变量,因为value指向的地址453,现在将它赋值后,指向的地址将变成str的地址454,只是指向的地址进行了改变,对于地址453上存的值便没有发生改变,change方法的形参在change生命周期结束后便回收了,而在方法change外面的value的指向地址便没有发生改变还是指向453,所以value的值不会改变。

类似C中的两个指针,一个是change外面的value,一个是change里面的value,一开始他们指向同一个地址453,后来change里面的value指向了454,便没有对453上面存储的值进行改变,所以外面的value的值不会发生改变。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注