CS61B-JAVA

发布于 26 天前  76 次阅读


大概是已完结不会在更,虽然还差最后一个lab没做 qwq

Error Sum

无法从静态上下文中引用非静态:https://www.baeldung.com/java-non-static-method-cannot-be-referenced-from-a-static-context

Adress Pointer

java visualize

https://cscircles.cemc.uwaterloo.ca/java_visualize/#mode=display

public class StringList {
   String head;
   StringList tail;
   public StringList(String head, StringList tail) {
      this.head = head;
      this.tail=tail;
   }
   public static void main(String[] args) {
   StringList L = new StringList("eat", null);
    L = new StringList("shouldn't", L);
    L = new StringList("you", L);
    L = new StringList("sometimes", L);
    StringList M = L.tail;
    StringList R = new StringList("many", null);
    R = new StringList("potatoes", R);
    R.tail.tail = R;
    M.tail.tail.tail = R.tail;
    L.tail.tail = L.tail.tail.tail;
    L = M.tail;
   }
}

Class

Note

  • a class have static and non-static members, and members is an instance variable or a method

  • for static member you access it through the class name

    the static variable is specific to the class

    it means that all the objects of the class share the same static variable

    A.K.A If you have a static variable, Let's use the class name (nice, straight-forward way knowing what we deal with)

    If the class access the instance method (where include instance variable) it will error

    Also we cannot access the instance value in static method

    And we allow to access the variable in constructor

    golden rule for static method

    !!!The golden rule for static methods to know is that static methods can only modify static variables!!!

  • for non-static member you can not invoke them by class name

    A.K.A the instance, the instance is specific to the object that the class create

    you can access instance valuabe in instance method

Debug

referrence

https://sp21.datastructur.es/materials/lab/lab2/lab2

https://sp21.datastructur.es/materials/lab/lab3/lab3#randomized-function-calls

  • the “step into” shows the literal next step of the program,
  • the “step over” button allows us to complete a function call without showing the function executing.
  • green triangle: 将程序重新回到断点处

Helper Method

Sentinel

Project 1 2048

Tilt

正确思路

在实现 Tilt 方法时我们需要进行两次循环

  • 第一次循环:对 tile(c,r) != null

    是去找有没有可以合并的两个方块,如果有就用 board.move 进行合并,同时更新 score

  • 第二次循环:对tile(c,r) == null

    建立在第一次循环基础上,将所有方块往指定方向上移动。此时所有方块已经 merge 完,所以只需要往 null 的处移动即可

总结:the hard part is figuring out which row each tile should end up on.

错误思路

将整一个大步骤放到一次循环写

  • 问题1:会导致 tripple 方块重复merge
  • 问题2:如果在 merge 后就执行 break;会导致合并后的方块不能继续会指定方向移动填充null方块

妙点

  • 在处理 board 状态过程中,会涉及到上下左右的移动,如果我们把这四个方法都写一遍,是非常愚蠢的,也会使代码很冗杂。在这路cs61b采用了旋转参考系的方式来解决。这样我们就只用写一种方法,再加多两行代码就可实现此过程。

    比如要想左移,那么就相当于整个 board 向右旋转90后的视图进行操作。此时执行up操作和在没有旋转执行 left的操作是等价的。

  • 总结:只需要写一个方法,实现转换视角的功能(旋转board)。就可以实现四个方向的移动

细节

  • 在找能合并的方块时,是从上往下找(基本操作是up)。这样才符合游戏规则
  • 成功合并完后要执行break 因为只能合并一次(题目规则)
  • 当不为null的方块移动到null执行 break 因为一个坑只能有一个占位(这是在board merge后才成立)
  • tile(c,r).value() 如果 tile.(c,r)是null的话,会指向空指针,进而引发空指针报错

Code

    /** Tilt the board toward SIDE. Return true iff this changes the board.
     *
     * 1. If two Tile objects are adjacent in the direction of motion and have
     *    the same value, they are merged into one Tile of twice the original
     *    value and that new value is added to the score instance variable
     * 2. A tile that is the result of a merge will not merge again on that
     *    tilt. So each move, every tile will only ever be part of at most one
     *    merge (perhaps zero).
     * 3. When three adjacent tiles in the direction of motion have the same
     *    value, then the leading two tiles in the direction of motion merge,
     *    and the trailing tile does not.
     * */
    public boolean tilt(Side side) {
        boolean changed;
        changed = false;

        // TODO: Modify this.board (and perhaps this.score) to account
        // for the tilt to the Side SIDE. If the board changed, set the
        // changed local variable to true.
        board.setViewingPerspective(side);

        for(int c = 0; c < board.size(); c ++)
            for(int r = board.size()-1; r >= 0; r --) {
                Tile t = board.tile(c,r);
                if(t != null){
                    for(int r2 = r-1; r2>=0; r2--){
                        Tile t2 = board.tile(c,r2);
                        if(t2 == null) continue;
                        if(t.value() == t2.value()){
                            board.move(c,r,t2);
                            score += t.value() * 2;
                            r = r2;
                            changed = true;
                            break;
                        }
                        else break;
                    }
                }
            }
        for(int c = 0; c < board.size(); c ++)
            for(int r = board.size()-1; r >= 0; r --){
                Tile t = board.tile(c,r);
                if(t == null){
                    for(int r2 = r-1; r2 >= 0; r2 --){
                        Tile t2 = board.tile(c,r2);
                        if(t2 != null){
                            board.move(c,r,t2);
                            changed = true;
                            break;
                        }
                    }
                }
            }

        board.setViewingPerspective(Side.NORTH);

        checkGameOver();
        if (changed) {
            setChanged();
        }
        return changed;
    }

Project2 Gitlet

StageArea

KeyPoints

*  Key points:
*      File file = join('DIR',"filename")
*      也就是说我们只要知道文件的路径我们就可以拿到这个文件对象
*      进而通过Utils的接口获得里面的内容
*
*      当有文件更新到tracked时,要将这个文件new一个blob对象存储到/objects
*      文件存储命名来自hash BlobID,这个ID由文件(路径)和内容生成

DataModel

 * tracked表示被追踪的文件,当要执行commit的时候,commit存储的就是这个tracked,通过tracked可以找到对应的所有文件
 * added表示新创建或者被修改的文件(这里有的文件属于tracked有的属于unctracked(可以等效于removed))
 * removed表示从tracked中移除的文件,意味着在commit时将忽略它
 * is_modify_index每当对一个文件执行add操作时,判断该操作是否对操作前的index产生变化。若是,则将stagearea这个对象写入index

func add()

/**
 * 处理add的核心逻辑
 * File copying to stagearea
 *      a. 重写文件(有修改,包括文件名和内容)
 *      b. 新创建的文件
 *      c. 如果你要add的文件已经在暂存区了,但是此时你要add的这个文件和最新commit存的那个文件的内容未发生改变,
 *         那么这时执行add操作会将它移除暂存区,等效于rm --cached
 *
 *      tips: only the file differ from the one in commit can add into stagearea
 */

image-20241110005149291

func commit()

/**
 * 处理commit的核心逻辑
 *      将tracked更新:
 *          added的所有键值对加到tracked里
 *          removed的所有键值对在tracked里面对应移除
 *      将added清空
 *      将removed清空
 *
 */

Repository

key features

/**
 * 每次在Main输入要执行的操作时,都会重新new一个Repository实例
 * 将执行命令的逻辑大部分封装到了这个文件中
 * 要从文件管理和面向对象的角度处理交互的过程
 */

DataModel

/**
 * .gitlet/objects/
 *  用于存储新的blob对象和commit对象
 *  取hashid 前两位作为dir名称
 *  取hashid 除去前两位作为file名称
 *  file里面存储的是序列化后的对象,是一串字符串
 */

/**
 * .gitlet/index
 * 用于存储stagearea对象的序列化信息
 * 在执行add操作时,会先查看index是否存在
 * True:说明之前已经有过提交,已经创建了stagearea的实例,
 *      通过readobject取回这个实例
 *
 * False:说明之前没有过提交,要new一个
 */

func commit()

/**
 * 从index文件中获得stagearea实例
 * 从中获取所有tracked的文件
 */

Serialize


Serializable obj 表示传入的对象必须是实现了 Serializable 接口的对象,只有这种对象才能被序列化为字节数组。

为什么需要 Serializable:这是 Java 的序列化机制的基础要求。只有实现了 Serializable 接口的对象才能被序列化并写入到文件、数据库,或者通过网络传输。

类必须实现 Serializable:否则对象无法被序列化,并会抛出异常

总结

这里运用了java内置I/O流的库实现了一个序列化存储数据的接口

轮询

package deque;

import java.util.*;

public class IteratorOfIterators implements Iterator<Integer> {
    LinkedList<Iterator<Integer>> iterators;

    public IteratorOfIterators(List<Iterator<Integer>> a) {
        iterators = new LinkedList<>();
        for (Iterator<Integer> iterator : a) {
            if (iterator.hasNext()) {
                iterators.add(iterator);
            }
        }
    }

    @Override
    public boolean hasNext() {
        return !iterators.isEmpty();
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        Iterator<Integer> iterator = iterators.removeFirst();
        int ans = iterator.next();
        if (iterator.hasNext()) {
            iterators.addLast(iterator);
        }
        return ans;
    }

}

结构解释

迭代器的 LinkedList:

该类维护一个名为 iterators 的 LinkedList<Iterator>,它按照需要访问的顺序跟踪所有非空迭代器。
通过使用 LinkedList,您可以有效地旋转迭代器,方法是将它们从前面移除,如果它们仍有元素,则将它们添加到后面。
构造函数:

构造函数接受 Iterator 对象列表。
它遍历此列表,并仅将带有元素的迭代器添加到迭代器中。这可确保空迭代器不会使列表混乱或在迭代期间创建不必要的检查。
hasNext() 方法:

hasNext() : 如果 iterators 不为空,则 hasNext() 返回 true,这意味着列表中至少有一个未耗尽的迭代器。
如果 iterators 为空,则意味着所有迭代器都已被完全使用,并且 hasNext() 返回 false。

next() 方法执行主要功能:
它首先检查 hasNext(),如果没有更多元素,则抛出 NoSuchElementException。
否则,它会检索 iterators 中的第一个迭代器(从 LinkedList 的前面),获取其下一个整数,然后检查迭代器是否具有更多元素。
如果迭代器具有更多元素,则将其添加到列表的后面,允许下一个循环访问继续。

tips:在 IteratorOfIterators 类的 next() 方法实现中,虽然 iterator.next() 被调用了,但它并不是递归调用。这只是每个子迭代器的 next() 方法调用,而不是 IteratorOfIterators 自身的 next() 调用自身。因此,这个方法仍然不是递归的。

代码解释

IteratorOfIteratorsnext() 方法中:

  1. 检查是否有下一个元素

    hasNext()

    方法用于检查是否还有可用的元素。

    • 如果没有剩余元素,则抛出 NoSuchElementException
  2. 获取第一个子迭代器的下一个元素

    • Iterator<Integer> iterator = iterators.removeFirst(); 这行代码从 LinkedList 中移除第一个子迭代器(实现了轮询机制),准备从该迭代器中获取下一个元素。
  3. 调用子迭代器的 next()

    • 代码 int ans = iterator.next(); 调用了当前子迭代器的 next() 方法,获取并返回它的下一个元素。
  4. 将有剩余元素的子迭代器放回 LinkedList

    • 如果该子迭代器还有更多元素(iterator.hasNext() 返回 true),则将它放回到 LinkedList 的末尾 iterators.addLast(iterator);,使其轮询至下次使用。

例子

Given three iterators A = [1,3,4,5], B = [ ], C = [2,6]

最后的结果是返回1,2,3,6,4,5

总结

(这个例子的)轮询就是每次执行next()取出每个子迭代器的第一个元素,然后把剩余元素的子迭代器放回LinkedList的末尾。直至所有子迭代器都被遍历完

基本/引用类型变量

传递基本类型参数

int值传递

int传入到方法里,不会改变原变量的值

每次执行函数时,函数都会重新开一个内存区域来执行操作

引用类型参数

地址传递,包括类的对象,实例变量,string

每次执行函数时,会通过取址得到对应内存里的数据,并做出改变

Hyponyms & Overload & Override

Overload

总结

一般是改变方法定义传入的参数(数据类型,对象类型)

Hyponyms

Override

总结

一般是重写方法的implement(一般这个方法是继承自父类)

Interface Inheritance

Implement

Default

tip:

下面的somelist.print()执行的是SLList.print()

这里涉及到staticdynamic type的概念(这是在继承,多态里面很重要的概念)

总结:接口&继承

Static Type vs. Dynamic Type

分析

此处的Animal astatic typeAnimal, dynamic typeDog

当编译时,会锁定要执行方法的Signature,这时候锁定到的方法是static type中的

当运行时,会先触发dynamatic type,先锁定这里面有没有Signature对应的方法

Tips1

方法的“签名”(signature)由以下部分组成:

  1. 方法名:这是方法的名称,用于标识和调用该方法。
  2. 参数的数量:指方法接受的参数的个数。方法可以接受零个或多个参数。
  3. 参数的类型:每个参数的数据类型,比如 intStringdouble 等。参数的类型确定了可以传递给方法的数据类型

Tips 2

有个小技巧来更好理解implement,把父类定义的default也写出来。

就是像下面展示的一样,灰色default void表示DogAnimal继承的方法

Tips 3

Dog这个类中,sniff实现的是override;而praise 实现的是overload

个人理解这里的差异就在于对于传入参数的类的type不一样:一个是Animal, 另一个是Dog


一沙一世界,一花一天堂。君掌盛无边,刹那成永恒。