2020春软件构造Lab2 ADT and OOP
ADT and OOP
- 1 实验目标概述
- 2 实验环境配置
- 3 实验过程
- 3.1 Poetic Walks
- 3.1.1 Get the code and prepare Git repository
- 3.1.2 Problem 1: Test Graph
- 3.1.3 Problem 2: Implement Graph
- 3.1.3.1 Implement ConcreteEdgesGraph
- 3.1.3.2 Implement ConcreteVerticesGraph
- 3.1.4 Problem 3: Implement generic Graph
- 3.1.4.1 Make the implementations generic
- 3.1.4.2 Implement Graph.empty()
- 3.1.5 Problem 4: Poetic walks
- 3.1.5.1 Test GraphPoet
- 3.1.5.2 Implement GraphPoet
- 3.1.5.3 Graph poetry slam
- 3.1.6 Before you’re done
- 3.2 Re-implement the Social Network in Lab1
- 3.2.1 FriendshipGraph类
- 3.2.2 Person类
- 3.2.3 客户端main()
- 3.2.4 测试用例
- 3.2.5 提交至Git仓库
- 3.3 Playing Chess
- 3.3.1 ADT设计/实现方案
- 3.3.2 主程序ChessGame设计/实现方案
- 3.3.3 ADT和主程序的测试方案
- 3.4 Multi-Startup Set (MIT)
- 4 实验进度记录
- 5 实验过程中遇到的困难与解决途径
1 实验目标概述
本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象编程(OOP)技术实现 ADT。具体来说:
⚫ 针对给定的应用问题,从问题描述中识别所需的 ADT;
⚫ 设计 ADT 规约(pre-condition、post-condition)并评估规约的质量;
⚫ 根据 ADT 的规约设计测试用例;
⚫ ADT 的泛型化;
⚫ 根据规约设计 ADT 的多种不同的实现;针对每种实现,设计其表示(representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
⚫ 使用 OOP 实现 ADT,并判定表示不变性是否违反、各实现是否存在表示泄露(rep exposure);
⚫ 测试 ADT 的实现并评估测试的覆盖度;
⚫ 使用 ADT 及其实现,为应用问题开发程序;
⚫ 在测试代码中,能够写出 testing strategy 并据此设计测试用例。
2 实验环境配置
延续Lab1中的实验环境,除此之外本次实验需要在 Eclipse IDE 中安装配置 EclEmma(一个用于统计 JUnit 测试用例的代码覆盖度的 plugin)。
建立自己的 Lab2 仓库并关联至自己的学号:
3 实验过程
3.1 Poetic Walks
3.1主要完成一个图的模块并基于该模块进行使用,只需修改graph中的构造函数。
1.对给定的接口Graph,完成两个实例类ConcretVerticesGraph和ConcreateEgdesGraph:
①分别构造ConcretVerticesGraph和ConcreateEgdesGraph的Vertex和Edge
②用String作为L的特例,分别实现Graph接口中的方法,写test文件测试正确性
③将所有String变量替换为泛型L
2.对给定的文本文件建立有向图,调用Graph作为存储的数据结构,利用该结构对输入的字符串进行扩充。
3.1.1 Get the code and prepare Git repository
如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。
- 获取任务代码
Download ZIP获取任务代码 或 使用
git clone https://github.com/rainywang/Spring2020_HITCS_SC_Lab2.git - 管理本地开发
git init //生成.git目录
git add * //该目录下所有文件纳入git管理【文件->暂存区】
git status //具体信息
git commit -m “v0.1 All code in main()” //创建待提交的数据结构【暂存区->本地仓库】
git log //查看提交信息
git remote add origin “仓库地址”
git push -u -f origin master【本地仓库->线上仓库】
3.1.2 Problem 1: Test Graph
方法 | 实现 |
---|---|
private void checkRep() | 判断点的数目n和边的数目m关系:m<=0.5n(n-1) |
public boolean add(String vertex) | 判断参数vertex是否存在vertices中,存在则返回false;否则将参数vertex加入点集vertices中,返回true |
public int set(String source, String target, int weight) | 增加、修改或删除边(的权值)。先判断weight是否小于0。若是,直接返回-1;若不是,判断是否为0。若是,检查以source和target为两端点的边,若存在则删除边,没有则抛出异常;若不为0,检查该边是否已存在,若已存在则修改边的权值为weight。 |
public boolean remove(String vertex) | 判断vertices中是否有传入的参数vertex,若不存在则返回false;若存在则先后序遍历edges,删除与该点有关的所有边,再删除该点。 |
public Set vertices() | 使用Collections.unmodifiableSet将vertices复制到新的Set中,返回这个Set(注意Safety from rep exposure) |
public Map<String, Integer> sources(String target) | 建立map,遍历edges,若某个edge.getTarget和传入的参数target相等,将该edge的source和weight存入map中。返回map |
public Map<String, Integer> targets(String source) | 建立map,遍历edges,若某个edge.getSource和传入的参数source相等,将该edge的target和weight存入map中。返回map |
3.1.3 Problem 2: Implement Graph
- checkRep要求边数m与点数n满足:m<=0.5n(n-1)
- 实现抽象类,重写函数
- Edge类
域 | 作用 |
---|---|
private final L source | 边的起点 |
private final L target | 边的终点 |
private final int weight | 边的权值 |
方法 | 作用 |
---|---|
public L getSource() | Getter |
public L getTarget() | Getter |
public int getWeight() | Getter |
public void setSource(L source) | Setter |
public void setTarget(L target) | Setter |
public void setWeight(int weight) | Setter |
- ConcreteEdgesGraph
方法 | 实现 |
---|---|
boolean add(String vertex) | 判断参数vertex是否存在vertices中,存在则返回false;否则将参数vertex加入点集vertices中,返回true |
public int set(String source, String target, int weight) | 增加、修改或删除边(的权值)。先判断weight是否小于0。若是,直接返回-1;若不是,判断是否为0。若是,检查以source和target为两端点的边,若存在则删除边,没有则抛出异常;若不为0,检查该边是否已存在,若已存在则修改边的权值为weight。 |
public boolean remove(String vertex) | 判断vertices中是否有传入的参数vertex,若不存在则返回false;若存在则先后序遍历edges,删除与该点有关的所有边,再删除该点。 |
public Set vertices() | 使用Collections.unmodifiableSet将vertices复制到新的Set中,返回这个Set(注意Safety from rep exposure) |
public Map<String, Integer> sources(String target) | 建立map,遍历edges,若某个edge.getTarget和传入的参数target相等,将该edge的source和weight存入map中。返回map |
public Map<String, Integer> targets(String source) | 建立map,遍历edges,若某个edge.getSource和传入的参数source相等,将该edge的target和weight存入map中。返回map |
3.1.3.1 Implement ConcreteEdgesGraph
3.1.3.2 Implement ConcreteVerticesGraph
- Vertex类
fields
private String name;//标志量
private Map<String, Integer> relationMap = new HashMap<>();[ ]
// Abstraction function:
// 使用HashMap存取映射关系
// 存取顶点vertex以及一个记录所有边的map// Representation invariant:
// 每个顶点的source或target不能是自身
// HashMap中的weight必须不小于0// Safety from rep exposure:
// 所有fields是private final
// String是imutable类型constructor
public Vertex(String name) {
this.name = name;
checkRep();
}public String getSource() {
// TODO Auto-generated method stub
return this.name;
}function
方法 | 作用 |
---|---|
checkRep() | 每条边的权值不能为负 |
getWeight(String target) | 得到权重 |
deleteEdge(String target) | 删除边 |
addEdge(String target, int weight) | 加边 |
Map<String, Integer> getRelationMap() | 获取关系图 |
toString() | 转化为字符串形式 |
setRelationMap(Map<String, Integer> relationMap) | setter |
- ConcreteVerticesGraph类
- rep
private final List<Vertex> vertices = new ArrayList<>(); - // Abstraction function:
// TODO 顶点AF为有target和对应映射weight的有向加权图
// Representation invariant:
// TODO weight>0
// Safety from rep exposure:
// TODO private final (field) - function
Column 1 | Column 2 |
---|---|
void checkRep() | 权重不为负 |
boolean add(String vertex) | 遍历,若不存在相同点则加入顶点vertex,返回true;否则返回false |
int set(String source, String target, int weight) | 为该图加入一条带source、target和权重weight的边。Source在原图有无需分类,若无则先把source加入图中;加入/或原图已有边后,权重是否为0也需分类,若为0则删除该边,返回权值 |
boolean remove(String vertex) | 删除与该顶点vertex有关的边的点,最后删除该顶点 |
Set vertices() | 将所有的顶点加入一个新的HashSet |
Map<String, Integer> sources(String target) | 返回sourceMap |
Map<String, Integer> targets(String source) | 返回targetMap |
String toString() | 转化为字符串形式 |
3.1.4 Problem 3: Implement generic Graph
3.1.4.1 Make the implementations generic
将两个实例类中的所有String类的参数替换为泛型的参数(声明、函数参数、返回值、rep)
3.1.4.2 Implement Graph.empty()
3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
方法 |
---|
centered 文本居中 |
语料库文件配置为:“test/P1/poet/one_line.txt” |
文本为:This is a test of the Mugar Omni Theater sound system. |
完整代码如下: |
@Test public void testGraphPoet() throws IOException {
final GraphPoet nimoy = new GraphPoet(new File(“src/P1/poet/mugar-omni-theater.txt”));
final String input = “Test the system.”;
assertEquals(“Test of the system.”,nimoy.poem(input));
}|
3.1.5.2 Implement GraphPoet
fields
private final Graph graph = Graph.empty();
private Map<String, Integer> targetMap;
private Map<String, Integer> sourceMap;
private Map<String, Integer> chosenMap = new HashMap<>();// Abstraction function:
// TODO 边图
// Representation invariant:
// TODO 图的顶点为非空字符串
// Safety from rep exposure:
// TODO private final (field).constructor
public GraphPoet(File corpus) throws IOException | 读入文件;按空格分割单词存入字符串数组words中;由于读入不分大小写,因此调用toLowerCase()将字符全部转化为小写;按边的出现次数设置每个单词的权重;checkRep(); |
---|
- function
方法 | 作用 |
---|---|
String toString() | 转化为字符串 |
void checkRep() | 桥接词转换为小写;顶点非空 |
String poem(String input) | 分割句子;找到所有桥接单词加入chosenMap;找到权值最大的桥接单词;去掉空白字符,加入桥接词;toString输出 |
3.1.5.3 Graph poetry slam
Test自定义文本进行测试
// Testing strategy
// 给定一个input.从文件中读取poet
// 调用Graph.poem后观察输出与预期是否相等
// 一个单词、一行单词
3.1.6 Before you’re done
在这里给出你的项目的目录结构树状示意图。
3.2 Re-implement the Social Network in Lab1
继承P1中ConcreteEdgesGraph或者ConcreteVerticesGraph类,实现FriendshipGraph.java中 addVertex、addEdge、getDistances三个接口
3.2.1 FriendshipGraph类
方法 | 作用 |
---|---|
void addVertex(Person person) | 要点:加入person前检查该person是否已存在 |
void addEdge(Person p1, Person p2) | 设置两人之间的关系 |
int getDistance(Person p1, Person p2) | 主要用队列方法实现import java.util.Queue;首先判断p1,p2是否为同一个person;若不是,用队列寻找与p1有关系的person,队列不空时循环,找到第一个与该person有关系的person,循环直到无人与他有关系——直到找到p2;若没有找到p2,则两人无关系 |
static void main(String[] args) | 主函数无返回 |
3.2.2 Person类
private String name;
private static Set nameSet = new HashSet<>();
方法 | 作用 |
---|---|
Person(String name) | 判断person是否已存在 |
String getName() | 返回该人的名字 |
3.2.3 客户端main()
3.2.4 测试用例
3.2.5 提交至Git仓库
git add * //该目录下所有文件纳入git管理【文件->暂存区】
git status //具体信息
git commit -m “v0.1 All code in main()” //创建待提交的数据结构【暂存区->本地仓库】
git log //查看提交信息
git remote add origin “仓库地址”
git push -u -f origin master【本地仓库->线上仓库】
3.3 Playing Chess
3.3.1 ADT设计/实现方案
- Position
- fields
private int x;
private int y; - function
Column 1 | Column 2 |
---|---|
getter | |
setter | public void setPos(int x, int y) {this.x = x;this.y = y;} 设置坐标时直接设置x,y |
boolean posEqual(Position pos) | 判断x和y是否与pos的x和y相等,若相等返回true |
String toString() | 转化为字符串,格式为(x, y) |
boolean checkPos(int type) | 选择下棋模式,1为国际象棋,2为围棋 |
- Player
- fields
private String playerName; - function
方法 | 作用 |
---|---|
Player(String playerName) | setter |
String getPlayerName() | getter |
- Piece
- fields
private Position piecePos = new Position();//棋子位置
private String pieceName = new String();//棋子名字
private boolean player;//棋子拥有者,true为棋手1,false为棋手2 - function
方法 | 作用 |
---|---|
Piece(int x, int y, String pieceName, boolean player) | 初始化棋子信息(坐标、拥有者、名字) |
void setPiecePos(int x, int y) | setter |
void setPlayer(boolean player) | |
void setPieceName(String pieceName) | |
Position getPiecePos() | getter |
boolean getPlayer() | |
String getPieceName() |
- Board
- fields
//每种下棋模式设置两个bollean二维数组,一个存储占用情况,一个存储棋手
private boolean[][] goboard_taken = new boolean[19][19];//true:占用
private boolean[][] goboard_player = new boolean[19][19];//true:player1.
private boolean[][] chessboard_taken = new boolean[8][8];
private boolean[][] chessboard_player = new boolean[8][8]; - function
方法 | 作用 |
---|---|
void Go_setPiece(Piece piece) | 落子操作【围棋】 |
void Chess_deletePiece(Piece piece) | 删除操纵【象棋/围棋】 |
void Chess_movePiece(Piece piece) | 移子操作【象棋】 |
boolean getOccupationOrNot(Position pos, int type) | 判断棋盘某位置是否被占用,有关goboard_taken和chessboard_taken |
boolean getOccupationPlayer(Position pos, int type) | 判断棋盘某位置被谁占用,有关c goboard_player和hessboard_player |
void checkOccupation(Position pos, int type) | 检查占用情况 |
Board(int type) | 初始化棋盘(象棋初始有棋手1和棋手2对称放置的棋子;围棋初始无棋子——在对应位置设置taken数组和player数组的初始boolean情况) |
- Action
检查移子和吃子坐标是否合理
- 移子
需检查(当前位置和目标位置)是否超出棋盘范围【超出返回false】,当前位置是否有棋子【无棋子返回false】,当前位置是否为己方棋子【否返回false】,目标位置是否有棋子【有返回false】,其余返回true。 - 吃子
需检查(当前位置和目标位置)是否超出棋盘范围【超出返回false】,当前位置是否有棋子【无棋子返回false】,当前位置是否为己方棋子【否返回false】,目标位置是否有棋子【无返回false】,目标位置是否为敌方棋子【否返回false】,其余返回true。
- Game
- fields
private Map<String, String> map = new HashMap<String, String>();//(英,中)
private static Board chessBoard = new Board(1);//象棋棋盘
private static Board goBoard = new Board(2);//围棋棋盘
private Player player1;
private Player player2;
private static List Pieces1 = new ArrayList<>();
private static List Pieces2 = new ArrayList<>(); - function
方法 | 作用 |
---|---|
void ChessMenu() | 象棋模式 |
void GoMenu() | 围棋模式 |
boolean putPiece(int x, int y, boolean player) | 围棋落子。首先检查落子合法性,接着调用Piece类给定落子的玩家 |
boolean checkPutPiece(int x, int y) | 检查落子合法性(当前位置是否有棋子、是否超出棋盘范围) |
boolean extractPiece(int x, int y, boolean player) | 围棋提子。首先检查提子合法性,调用Piece类给定提子的玩家。原则:己方提子,对方少子 |
boolean checkExtracPiece(int x, int y, boolean player) | 检验提子合法性(当前位置是否有棋子、当前位置是否为敌方棋子、是否超出棋盘范围) |
boolean movePiece(int x, int y, int xNew, int yNew, boolean player) | 象棋移子。子类Piece getPiece(Position pos) |
Piece getPiece(Position pos) | 返回要移动的棋子。子类int getPiecesSize(boolean player) |
int getPiecesSize(boolean player) | 返回移动棋子的棋手。 |
boolean eatPiece(int x, int y, int xNew, int yNew, boolean player) | 象棋吃子。首先检验吃子合法性,吃子包括两步(删除目标位置敌方棋子,移动己方棋子到目标位置上) |
public void setPlayer1(String name) | setter |
public String getPlayer1() | getter |
Board getBoard(int type) | 选择棋盘模式。1为象棋,2为围棋 |
void printBoard(int type) | 输出当前棋盘状态 |
Game(int type) |
3.3.2 主程序ChessGame设计/实现方案
public static void main(String[]args) {Scanner input = new Scanner(System.in);int player = 0;System.out.println("选择模式(chess or go):");String type = input.nextLine();int choice;if(type.equals("chess")) {Game game = new Game(1);System.out.print("输入棋手1的名字");String player1 = input.nextLine();game.setPlayer1(player1);System.out.print("输入棋手2的名字");String player2 = input.nextLine();game.setPlayer2(player2);game.ChessMenu();choice = input.nextInt();while(true) {switch(choice) {case 1:System.out.println("请输入移子坐标");if(game.movePiece(input.nextInt(),input.nextInt(),input.nextInt(),input.nextInt(),player % 2 == 0)) {//这一步用于交换主动权player++;}game.printBoard(1);break;case 2:System.out.println("请输入吃子坐标");if(game.eatPiece(input.nextInt(),input.nextInt(),input.nextInt(),input.nextInt(),player % 2 == 0)) {//这一步用于交换主动权player++;}game.printBoard(1);break;case 3:input.close();System.exit(0);case 4:game.printBoard(1);break;case 5:System.out.println("请输入d待查询的坐标");Position pos = new Position();pos.setPos(input.nextInt(), input.nextInt());game.getBoard(1).checkOccupation(pos, 1);break;default:System.out.println("请重新输入!");}//循环game.ChessMenu();choice = input.nextInt();}}else if(type.equals("go")) {Game game = new Game(2);System.out.print("输入棋手1的名字");String player1 = input.nextLine();game.setPlayer1(player1);System.out.print("输入棋手2的名字");String player2 = input.nextLine();game.setPlayer2(player2);game.GoMenu();choice = input.nextInt();while(true) {switch(choice) {case 1:System.out.println("请输入落子坐标");if(game.putPiece(input.nextInt(),input.nextInt(),player % 2 == 0)) {//这一步用于交换主动权player++;}game.printBoard(2);break;case 2:System.out.println("请输入提子坐标");if(game.extractPiece(input.nextInt(),input.nextInt(),player % 2 == 0)) {//这一步用于交换主动权player++;}game.printBoard(1);break;case 3:input.close();System.exit(0);case 4:game.printBoard(1);break;case 5:System.out.println("请输入d待查询的坐标");Position pos = new Position();pos.setPos(input.nextInt(), input.nextInt());game.getBoard(1).checkOccupation(pos, 1);break;default:System.out.println("请重新输入!");}//循环game.ChessMenu();choice = input.nextInt();}}else {System.out.println("MODE ERROR!");input.close();System.exit(0);}input.close();}
3.3.3 ADT和主程序的测试方案
- public class BoardTest
删除棋子后,检验棋子在当前位置的占用情况;再次添加棋子,检验占用情况 - public class ClassTest
1)检验移子testMovePiece()
是否越界、是否移动对方棋子、目标位置是否已被占用
2)检验吃子testEatPiece()
是否越界、目标棋子是否为对方棋子、起始棋子是否为己方棋子
3)检验落子testCheckPlacePiece()
是否越界、目标位置是否被占用
4)检验提子tetsCheckExtracPiece()
是否越界、目标位置是否为对方棋子
3.4 Multi-Startup Set (MIT)
4 实验进度记录
日期 | 时间段 | 计划任务 | 实际完成情况 |
---|---|---|---|
4.6 | 13:00~18:00 | P1 Test Graph | P1中ConcreteEdgesGraph.java及其测试文件ConcreteEdgesGraphTest.java、Graph.java |
4.7 | 16:00~20:00 | P1 ConcreteVerticesGraph | 未完成 |
4.7 | 22:00~24:00 | P1 ConcreteVerticesGraph、P2 | 完成P1、P2.Person |
4.8 | 18:00~22:00 | P2 | 完成 |
4.9 | 13:00~18:00 | 理解P3,设定基本需要完成的类 | 完成v1.0,但无法运行,重新设计 |
4.9 | 24:00~? | 重新设计P3 | 在小组讨论情况下决定了Action、Board、Game、Piece、Player、Position几个类 |
4.10 | 9:00~11:30 | P3 | 完成Player、Position、Piece、Board几个较简单的类的设计 |
4.10 | 15:00~18:00 | P3 | 完成Action、开始设计Game |
4.11 | 8:30~11:30 | P3 | 重新设计Position和Action,完成Game |
4.11 | 18:00~24:00 | P3 | 完成 |
4.12 | / | / | 修改部分代码完善功能 |
5 实验过程中遇到的困难与解决途径
Abstraction function
Representation invariant
rep
寻找同学解释、回顾课上内容
“AF是抽象函数
RI是表示不变量
Rep是你内部的表示
让你在TODO那行把你的ADT相应的内容填上”
Lab1实验时不能太理解this的用法。Lab2用起来吃力
寻找同学解释,CSDN查询
P2有关泛型类独自无法解决
小组讨论、CSDN查询、JDK1.6 帮助文档
在设计P3时,单纯的Set或Map解决问题有些繁琐,工程量较大
通过查询JDK1.6 帮助文档,发现可以使用队列Queue解决问题
2020春软件构造Lab2 ADT and OOP相关推荐
- 哈工大2020春软件构造实验二实验报告
2020年春季学期 计算机学院<软件构造>课程 Lab 2实验报告 姓名 赵俊 学号 1180300508 班号 1836101 电子邮件 手机号码 目录 1 实验目标概述 1 2 实验环 ...
- [HITSC]哈工大2020春软件构造Lab3实验报告
Github地址 1 实验目标概述 本次实验覆盖课程第 3.4.5 章的内容,目标是编写具有可复用性和可维护 性的软件,主要使用以下软件构造技术: 子类型.泛型.多态.重写.重载 继承.代理.组合 常 ...
- 哈工大2020软件构造Lab2 Problem3 Playing Chess 架构设计思路
哈工大2020春软件构造实验2 Problem 3 Playing Chess 架构设计思路 问题简述 整体结构 ADT功能设计 功能实现路径 问题简述: 设计一款棋类游戏,同时支持国际象棋(Ches ...
- 哈工大2021春软件构造实验
2021春软件构造(Software Construction)课程共3个实验,其中lab1和lab2同往年一样,lab3是全新的实验. 3个实验的内容如下: Lab-1: Fundamental J ...
- 软件构造lab2 - 实验报告
软件构造lab2 - 实验报告 1.实验目标概述 2.环境配置 3.实验过程 3.1Poetic Walks 3.1.1Get the code and prepare Git repository ...
- 哈工大2021春软件构造实验总结
哈工大2021春软件构造实验总结 文章目录 一.实验一 1. 实验概述 1.1 Magic Squares 1.2 Turtle Graphics 1.3 Social Network 2. 实验感受 ...
- 哈工大18年春软件构造课程讨论题
这是哈工大18年春软件构造课程(徐汉川老师)的讨论题目,少部分答案摘录自课件PPT和网上的资源(链接在文中给出).如有错误还望指出,谢谢. 一.在软件测试过程中,"测试用例的数目" ...
- 哈工大2020软件构造Lab2实验报告
本项目于3.17日实验课验收,请放心参考 参考时文中有给出一些建议,请查看 基本更新完成 2020春计算机学院<软件构造>课程Lab2实验报告 Software Construction ...
- 软件构造Lab2总结
2020年春季学期 计算机学院<软件构造>课程 Lab2实验报告 ·· 1 3.1 Poetic Walks· 1 3.1.1 Get the code and prepare Git r ...
最新文章
- FZU 1686 神龙的难题(DLX反复覆盖)
- eclipse + android 自动补全
- mysql删除了密码怎样恢复_window 下如何恢复被删除的mysql root账户及密码(mysql 8.0.17)...
- linux的mysql主主_Linux下指定mysql数据库数据配置主主同步的实例
- python编写一个登陆验证程序_python项目实战:实现验证码登录网址实例
- 低成本、高性能创客开发板——PYB Nano
- 关于Unity中坐标系的种类
- 京东方计划为苹果iPhone 13供应6.06英寸OLED面板
- 树莓派安装qq linux,在(Raspberry Pi)树莓派上安装NodeJS
- Java书籍推荐(这些书你看过几本?)
- Could not get a resource since the pool is exhausted
- 自由技艺 (Liberal arts)
- 要么听我的,要么走开(摘自《代码之道》第8章)
- vsftpd cmds_allowed 权限控制
- 云桌面有哪些优势-为什么企业使用云桌面是趋势
- matplotlib+basemap画出标记地图
- 在 Mac 上通过“启动转换助理”安装 Windows 10
- 微信小程token_微信小程序登录换取token
- ABC166E This Message Will Self-Destruct in 5s 题解
- win10 蓝牙忽然消失 华硕主板