【设计模式手册017】备忘录模式 - 对象状态保存与恢复

设计模式手册017:备忘录模式 - 对象状态保存与恢复

本文是「设计模式手册」系列第017篇,我将以深入浅出、追本溯源的风格,带你真正理解备忘录模式的精髓。

1. 我们为何需要备忘录模式?

在软件设计中,我们经常会遇到这样的场景:需要保存对象的内部状态,并在需要时能够恢复到之前的状态。比如:

  • 文本编辑器:撤销/重做功能
  • 游戏系统:存档/读档功能
  • 数据库事务:回滚操作
  • 表单填写:自动保存草稿

初级程序员的写法

java 复制代码
public class TextEditor {
    private String content;
    private String fontSize;
    private String fontColor;
    
    // 直接暴露内部状态用于保存
    public String getContent() { return content; }
    public String getFontSize() { return fontSize; }
    public String getFontColor() { return fontColor; }
    
    public void setContent(String content) { this.content = content; }
    public void setFontSize(String fontSize) { this.fontSize = fontSize; }
    public void setFontColor(String fontColor) { this.fontColor = fontColor; }
    
    // 保存状态 - 直接返回内部状态
    public TextEditorState save() {
        return new TextEditorState(content, fontSize, fontColor);
    }
    
    // 恢复状态 - 直接设置内部状态
    public void restore(TextEditorState state) {
        this.content = state.getContent();
        this.fontSize = state.getFontSize();
        this.fontColor = state.getFontColor();
    }
}

// 状态类直接暴露所有字段
public class TextEditorState {
    public String content;
    public String fontSize;
    public String fontColor;
    
    public TextEditorState(String content, String fontSize, String fontColor) {
        this.content = content;
        this.fontSize = fontSize;
        this.fontColor = fontColor;
    }
    
    // 所有getter方法直接暴露内部数据
    public String getContent() { return content; }
    public String getFontSize() { return fontSize; }
    public String getFontColor() { return fontColor; }
}

这种写法的痛点

  • 违反封装原则:外部可以直接访问和修改对象内部状态
  • 状态保存逻辑分散在各个类中
  • 难以维护:当对象结构变化时,需要修改所有保存/恢复的代码
  • 无法保护历史状态不被修改

2. 备忘录模式:本质与定义

2.1 模式定义

备忘录模式(Memento Pattern):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后可以将该对象恢复到原先保存的状态。

2.2 模式结构

java 复制代码
// 备忘录 - 存储原发器对象的内部状态
public class Memento {
    private final String state;
    
    public Memento(String stateToSave) {
        this.state = stateToSave;
    }
    
    // 只有原发器可以访问备忘录的内部状态
    public String getSavedState() {
        return state;
    }
}

// 原发器 - 创建备忘录并使用备忘录恢复状态
public class Originator {
    private String state;
    
    public void setState(String state) {
        System.out.println("设置状态: " + state);
        this.state = state;
    }
    
    public String getState() {
        return state;
    }
    
    // 创建备忘录
    public Memento saveToMemento() {
        System.out.println("保存状态到备忘录: " + state);
        return new Memento(state);
    }
    
    // 从备忘录恢复
    public void restoreFromMemento(Memento memento) {
        state = memento.getSavedState();
        System.out.println("从备忘录恢复状态: " + state);
    }
}

// 看管人 - 负责保存和管理备忘录
public class Caretaker {
    private List<Memento> savedStates = new ArrayList<>();
    
    public void addMemento(Memento memento) {
        savedStates.add(memento);
    }
    
    public Memento getMemento(int index) {
        return savedStates.get(index);
    }
}

3. 深入理解:备忘录模式的三重境界

3.1 第一重:状态封装与保护

核心思想:备忘录应该保护原发器的内部状态不被其他对象随意访问。

java 复制代码
// 好的设计:窄接口
public interface Memento {
    // 不提供任何公共方法,保护状态
}

// 原发器拥有的宽接口
public interface Originator {
    Memento createMemento();
    void restoreMemento(Memento memento);
}

3.2 第二重:状态管理的职责分离

设计原则的体现:将状态保存和管理的职责从业务对象中分离出来。

  • 原发器:负责业务逻辑和状态变化
  • 备忘录:负责状态存储
  • 看管人:负责状态管理

3.3 第三重:增量状态与性能优化

对于大对象,可以只保存变化的部分(增量备忘录):

java 复制代码
public class IncrementalMemento {
    private final Map<String, Object> changedFields;
    
    public IncrementalMemento(Map<String, Object> changes) {
        this.changedFields = new HashMap<>(changes);
    }
    
    public Map<String, Object> getChanges() {
        return new HashMap<>(changedFields);
    }
}

4. 实战案例:完整的文本编辑器撤销系统

让我们来看一个完整的例子:

java 复制代码
// 文本状态备忘录
public class TextStateMemento {
    private final String content;
    private final TextFormat format;
    private final int cursorPosition;
    private final LocalDateTime timestamp;
    
    public TextStateMemento(String content, TextFormat format, int cursorPosition) {
        this.content = content;
        this.format = format != null ? format.copy() : null;
        this.cursorPosition = cursorPosition;
        this.timestamp = LocalDateTime.now();
    }
    
    // 包级私有,只有同包的原发器可以访问
    String getContent() {
        return content;
    }
    
    TextFormat getFormat() {
        return format != null ? format.copy() : null;
    }
    
    int getCursorPosition() {
        return cursorPosition;
    }
    
    public LocalDateTime getTimestamp() {
        return timestamp;
    }
}

// 文本格式
public class TextFormat implements Cloneable {
    private String fontFamily;
    private int fontSize;
    private String color;
    private boolean bold;
    private boolean italic;
    
    public TextFormat(String fontFamily, int fontSize, String color) {
        this.fontFamily = fontFamily;
        this.fontSize = fontSize;
        this.color = color;
    }
    
    // 复制方法
    public TextFormat copy() {
        TextFormat copy = new TextFormat(fontFamily, fontSize, color);
        copy.bold = bold;
        copy.italic = italic;
        return copy;
    }
    
    // getters and setters
    public String getFontFamily() { return fontFamily; }
    public void setFontFamily(String fontFamily) { this.fontFamily = fontFamily; }
    
    public int getFontSize() { return fontSize; }
    public void setFontSize(int fontSize) { this.fontSize = fontSize; }
    
    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }
    
    public boolean isBold() { return bold; }
    public void setBold(boolean bold) { this.bold = bold; }
    
    public boolean isItalic() { return italic; }
    public void setItalic(boolean italic) { this.italic = italic; }
    
    @Override
    public String toString() {
        return String.format("%s %dpx %s%s%s", 
            fontFamily, fontSize, color,
            bold ? " Bold" : "",
            italic ? " Italic" : "");
    }
}

// 文本编辑器 - 原发器
@Slf4j
public class TextEditor {
    private String content;
    private TextFormat currentFormat;
    private int cursorPosition;
    
    public TextEditor() {
        this.content = "";
        this.currentFormat = new TextFormat("Arial", 12, "#000000");
        this.cursorPosition = 0;
    }
    
    // 业务操作
    public void type(String text) {
        if (cursorPosition <= content.length()) {
            String before = content.substring(0, cursorPosition);
            String after = content.substring(cursorPosition);
            content = before + text + after;
            cursorPosition += text.length();
            log.info("输入文本: '{}',光标位置: {}", text, cursorPosition);
        }
    }
    
    public void delete(int count) {
        if (cursorPosition > 0 && cursorPosition <= content.length()) {
            int deleteFrom = Math.max(0, cursorPosition - count);
            String before = content.substring(0, deleteFrom);
            String after = content.substring(cursorPosition);
            content = before + after;
            cursorPosition = deleteFrom;
            log.info("删除 {} 个字符,光标位置: {}", count, cursorPosition);
        }
    }
    
    public void setFormat(TextFormat format) {
        this.currentFormat = format.copy();
        log.info("设置格式: {}", format);
    }
    
    public void setCursorPosition(int position) {
        this.cursorPosition = Math.max(0, Math.min(position, content.length()));
        log.info("移动光标到位置: {}", cursorPosition);
    }
    
    // 创建备忘录
    public TextStateMemento save() {
        log.info("保存状态 - 内容长度: {}, 光标位置: {}", content.length(), cursorPosition);
        return new TextStateMemento(content, currentFormat, cursorPosition);
    }
    
    // 从备忘录恢复
    public void restore(TextStateMemento memento) {
        this.content = memento.getContent();
        this.currentFormat = memento.getFormat();
        this.cursorPosition = memento.getCursorPosition();
        log.info("恢复状态 - 内容长度: {}, 光标位置: {}, 时间: {}", 
                 content.length(), cursorPosition, memento.getTimestamp());
    }
    
    // 获取当前状态信息
    public void printStatus() {
        System.out.println("=== 当前状态 ===");
        System.out.println("内容: " + (content.isEmpty() ? "(空)" : content));
        System.out.println("格式: " + currentFormat);
        System.out.println("光标: " + cursorPosition);
        System.out.println("长度: " + content.length());
        System.out.println("==============");
    }
    
    // getters
    public String getContent() { return content; }
    public TextFormat getCurrentFormat() { return currentFormat; }
    public int getCursorPosition() { return cursorPosition; }
}

// 历史管理器 - 看管人
@Slf4j
public class HistoryManager {
    private final Deque<TextStateMemento> history = new ArrayDeque<>();
    private final Deque<TextStateMemento> redoStack = new ArrayDeque<>();
    private final int maxHistorySize;
    
    public HistoryManager(int maxHistorySize) {
        this.maxHistorySize = maxHistorySize;
    }
    
    public void saveState(TextStateMemento memento) {
        history.push(memento);
        redoStack.clear(); // 新的操作清空重做栈
        
        // 限制历史记录大小
        if (history.size() > maxHistorySize) {
            history.removeLast();
        }
        
        log.info("保存历史记录,当前历史大小: {}", history.size());
    }
    
    public TextStateMemento undo() {
        if (history.size() > 1) { // 保留当前状态作为重做的起点
            TextStateMemento current = history.pop();
            TextStateMemento previous = history.peek();
            redoStack.push(current);
            log.info("执行撤销,历史大小: {}, 重做大小: {}", history.size(), redoStack.size());
            return previous;
        }
        log.warn("无法撤销,历史记录不足");
        return null;
    }
    
    public TextStateMemento redo() {
        if (!redoStack.isEmpty()) {
            TextStateMemento next = redoStack.pop();
            history.push(next);
            log.info("执行重做,历史大小: {}, 重做大小: {}", history.size(), redoStack.size());
            return next;
        }
        log.warn("无法重做,重做栈为空");
        return null;
    }
    
    public boolean canUndo() {
        return history.size() > 1;
    }
    
    public boolean canRedo() {
        return !redoStack.isEmpty();
    }
    
    public void clear() {
        history.clear();
        redoStack.clear();
        log.info("清空所有历史记录");
    }
    
    public List<String> getHistoryTimestamps() {
        return history.stream()
                     .map(m -> m.getTimestamp().format(DateTimeFormatter.ofPattern("HH:mm:ss")))
                     .collect(Collectors.toList());
    }
}

// 使用示例
@Slf4j
public class TextEditorDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        HistoryManager history = new HistoryManager(10);
        
        // 初始保存
        history.saveState(editor.save());
        
        // 一系列操作
        editor.type("Hello");
        history.saveState(editor.save());
        
        editor.type(" World");
        history.saveState(editor.save());
        
        editor.setFormat(new TextFormat("Times New Roman", 16, "#FF0000"));
        history.saveState(editor.save());
        
        editor.type("!");
        history.saveState(editor.save());
        
        System.out.println("\n=== 当前状态 ===");
        editor.printStatus();
        
        // 执行撤销
        System.out.println("\n=== 执行两次撤销 ===");
        TextStateMemento undoState = history.undo();
        if (undoState != null) {
            editor.restore(undoState);
        }
        editor.printStatus();
        
        undoState = history.undo();
        if (undoState != null) {
            editor.restore(undoState);
        }
        editor.printStatus();
        
        // 执行重做
        System.out.println("\n=== 执行一次重做 ===");
        TextStateMemento redoState = history.redo();
        if (redoState != null) {
            editor.restore(redoState);
        }
        editor.printStatus();
        
        // 显示历史记录
        System.out.println("\n=== 历史记录时间点 ===");
        history.getHistoryTimestamps().forEach(System.out::println);
    }
}

5. Spring Boot中的优雅实现

在Spring Boot中,我们可以利用事务和持久化实现备忘录模式:

java 复制代码
// 实体类
@Entity
@Table(name = "document_states")
@Data
public class DocumentState {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String documentId;
    
    @Lob
    private String content;
    
    @Lob
    private String formatJson;
    
    private int cursorPosition;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    private String operationType; // CREATE, EDIT, DELETE等
}

// 备忘录仓库
@Repository
public interface DocumentStateRepository extends JpaRepository<DocumentState, Long> {
    
    List<DocumentState> findByDocumentIdOrderByCreatedAtDesc(String documentId);
    
    @Modifying
    @Query("DELETE FROM DocumentState ds WHERE ds.documentId = :documentId AND ds.createdAt < :beforeDate")
    void deleteOldStates(@Param("documentId") String documentId, 
                        @Param("beforeDate") LocalDateTime beforeDate);
    
    @Query("SELECT ds FROM DocumentState ds WHERE ds.documentId = :documentId AND ds.createdAt = " +
           "(SELECT MAX(ds2.createdAt) FROM DocumentState ds2 WHERE ds2.documentId = :documentId)")
    Optional<DocumentState> findLatestState(@Param("documentId") String documentId);
}

// 文档服务
@Service
@Slf4j
public class DocumentService {
    private final DocumentStateRepository stateRepository;
    private final ObjectMapper objectMapper;
    
    public DocumentService(DocumentStateRepository stateRepository, ObjectMapper objectMapper) {
        this.stateRepository = stateRepository;
        this.objectMapper = objectMapper;
    }
    
    // 保存文档状态
    @Transactional
    public void saveDocumentState(Document document, String operationType) {
        try {
            DocumentState state = new DocumentState();
            state.setDocumentId(document.getId());
            state.setContent(document.getContent());
            state.setFormatJson(objectMapper.writeValueAsString(document.getFormat()));
            state.setCursorPosition(document.getCursorPosition());
            state.setOperationType(operationType);
            
            stateRepository.save(state);
            log.info("保存文档状态: {} - {}", document.getId(), operationType);
            
            // 清理旧的状态记录(只保留最近50条)
            cleanOldStates(document.getId());
            
        } catch (JsonProcessingException e) {
            log.error("序列化文档格式失败", e);
            throw new RuntimeException("保存文档状态失败", e);
        }
    }
    
    // 恢复文档状态
    @Transactional
    public Document restoreDocumentState(String documentId, Long stateId) {
        DocumentState state = stateRepository.findById(stateId)
                .orElseThrow(() -> new IllegalArgumentException("状态记录不存在: " + stateId));
        
        try {
            Document document = new Document();
            document.setId(state.getDocumentId());
            document.setContent(state.getContent());
            document.setFormat(objectMapper.readValue(state.getFormatJson(), DocumentFormat.class));
            document.setCursorPosition(state.getCursorPosition());
            
            log.info("恢复文档状态: {} - {}", documentId, state.getCreatedAt());
            return document;
            
        } catch (JsonProcessingException e) {
            log.error("反序列化文档格式失败", e);
            throw new RuntimeException("恢复文档状态失败", e);
        }
    }
    
    // 获取文档历史记录
    public List<DocumentHistory> getDocumentHistory(String documentId) {
        return stateRepository.findByDocumentIdOrderByCreatedAtDesc(documentId)
                .stream()
                .map(this::toHistory)
                .collect(Collectors.toList());
    }
    
    // 清理旧的状态记录
    private void cleanOldStates(String documentId) {
        List<DocumentState> allStates = stateRepository.findByDocumentIdOrderByCreatedAtDesc(documentId);
        if (allStates.size() > 50) {
            List<DocumentState> toDelete = allStates.subList(50, allStates.size());
            stateRepository.deleteAll(toDelete);
            log.info("清理了 {} 个旧状态记录", toDelete.size());
        }
    }
    
    private DocumentHistory toHistory(DocumentState state) {
        return DocumentHistory.builder()
                .id(state.getId())
                .documentId(state.getDocumentId())
                .operationType(state.getOperationType())
                .contentPreview(getContentPreview(state.getContent()))
                .createdAt(state.getCreatedAt())
                .build();
    }
    
    private String getContentPreview(String content) {
        if (content == null || content.length() <= 50) {
            return content;
        }
        return content.substring(0, 50) + "...";
    }
}

// 历史记录DTO
@Data
@Builder
public class DocumentHistory {
    private Long id;
    private String documentId;
    private String operationType;
    private String contentPreview;
    private LocalDateTime createdAt;
}

// REST控制器
@RestController
@RequestMapping("/api/documents")
@Slf4j
public class DocumentController {
    private final DocumentService documentService;
    
    public DocumentController(DocumentService documentService) {
        this.documentService = documentService;
    }
    
    @PostMapping("/{documentId}/save-state")
    public ResponseEntity<Void> saveState(@PathVariable String documentId,
                                         @RequestBody SaveStateRequest request) {
        documentService.saveDocumentState(request.getDocument(), request.getOperationType());
        return ResponseEntity.ok().build();
    }
    
    @GetMapping("/{documentId}/history")
    public ResponseEntity<List<DocumentHistory>> getHistory(@PathVariable String documentId) {
        List<DocumentHistory> history = documentService.getDocumentHistory(documentId);
        return ResponseEntity.ok(history);
    }
    
    @PostMapping("/{documentId}/restore/{stateId}")
    public ResponseEntity<Document> restoreState(@PathVariable String documentId,
                                                @PathVariable Long stateId) {
        Document document = documentService.restoreDocumentState(documentId, stateId);
        return ResponseEntity.ok(document);
    }
}

6. 备忘录模式的变体与进阶用法

6.1 命令模式 + 备忘录模式

结合命令模式实现更强大的撤销/重做系统:

java 复制代码
// 命令接口
public interface Command {
    void execute();
    void undo();
}

// 具体命令
@Slf4j
public class TextEditCommand implements Command {
    private final TextEditor editor;
    private final String newText;
    private final int position;
    private TextStateMemento backup;
    
    public TextEditCommand(TextEditor editor, String newText, int position) {
        this.editor = editor;
        this.newText = newText;
        this.position = position;
    }
    
    @Override
    public void execute() {
        // 保存当前状态
        backup = editor.save();
        
        // 执行编辑操作
        editor.setCursorPosition(position);
        editor.type(newText);
        
        log.info("执行编辑命令: '{}' 在位置 {}", newText, position);
    }
    
    @Override
    public void undo() {
        if (backup != null) {
            editor.restore(backup);
            log.info("撤销编辑命令");
        }
    }
}

// 命令管理器
@Slf4j
public class CommandManager {
    private final Deque<Command> undoStack = new ArrayDeque<>();
    private final Deque<Command> redoStack = new ArrayDeque<>();
    
    public void executeCommand(Command command) {
        command.execute();
        undoStack.push(command);
        redoStack.clear();
        log.info("执行命令,撤销栈大小: {}", undoStack.size());
    }
    
    public void undo() {
        if (!undoStack.isEmpty()) {
            Command command = undoStack.pop();
            command.undo();
            redoStack.push(command);
            log.info("撤销命令,撤销栈大小: {}, 重做栈大小: {}", undoStack.size(), redoStack.size());
        }
    }
    
    public void redo() {
        if (!redoStack.isEmpty()) {
            Command command = redoStack.pop();
            command.execute();
            undoStack.push(command);
            log.info("重做命令,撤销栈大小: {}, 重做栈大小: {}", undoStack.size(), redoStack.size());
        }
    }
    
    public boolean canUndo() {
        return !undoStack.isEmpty();
    }
    
    public boolean canRedo() {
        return !redoStack.isEmpty();
    }
}

6.2 增量备忘录模式

对于大对象,保存完整状态开销大,可以只保存变化的部分:

java 复制代码
// 增量备忘录
public class IncrementalMemento {
    private final Map<String, Object> stateChanges;
    private final LocalDateTime timestamp;
    
    public IncrementalMemento(Map<String, Object> changes) {
        this.stateChanges = new HashMap<>(changes);
        this.timestamp = LocalDateTime.now();
    }
    
    public Map<String, Object> getChanges() {
        return new HashMap<>(stateChanges);
    }
    
    public LocalDateTime getTimestamp() {
        return timestamp;
    }
}

// 支持增量保存的原发器
@Slf4j
public class LargeObject {
    private Map<String, Object> state = new HashMap<>();
    private Map<String, Object> changesSinceLastSave = new HashMap<>();
    
    public void setProperty(String key, Object value) {
        Object oldValue = state.get(key);
        if (!Objects.equals(oldValue, value)) {
            state.put(key, value);
            changesSinceLastSave.put(key, value);
            log.info("设置属性 {}: {} -> {}", key, oldValue, value);
        }
    }
    
    public Object getProperty(String key) {
        return state.get(key);
    }
    
    public IncrementalMemento saveIncremental() {
        if (changesSinceLastSave.isEmpty()) {
            log.info("没有变化,不保存增量");
            return null;
        }
        
        IncrementalMemento memento = new IncrementalMemento(changesSinceLastSave);
        changesSinceLastSave.clear();
        log.info("保存增量备忘录,包含 {} 个变化", memento.getChanges().size());
        return memento;
    }
    
    public void restoreIncremental(IncrementalMemento memento) {
        if (memento != null) {
            state.putAll(memento.getChanges());
            log.info("从增量备忘录恢复 {} 个属性", memento.getChanges().size());
        }
    }
    
    public IncrementalMemento saveFull() {
        log.info("保存完整状态,包含 {} 个属性", state.size());
        return new IncrementalMemento(new HashMap<>(state));
    }
    
    public void restoreFull(IncrementalMemento memento) {
        if (memento != null) {
            this.state = new HashMap<>(memento.getChanges());
            this.changesSinceLastSave.clear();
            log.info("从完整备忘录恢复状态");
        }
    }
}

7. 备忘录模式 vs 其他模式

7.1 备忘录模式 vs 原型模式

  • 原型模式:通过复制现有对象来创建新对象,关注对象创建
  • 备忘录模式:保存和恢复对象状态,关注状态管理

7.2 备忘录模式 vs 命令模式

  • 命令模式:将请求封装为对象,支持撤销/重做
  • 备忘录模式:保存对象状态,用于状态恢复

7.3 备忘录模式 vs 序列化

  • 序列化:将对象转换为字节流,用于持久化或传输
  • 备忘录模式:设计模式,强调封装性和状态管理

8. 总结与思考

8.1 备忘录模式的优点

  1. 封装性保护:不暴露对象内部实现细节
  2. 简化原发器:状态保存逻辑分离到备忘录中
  3. 易于实现撤销:提供状态恢复机制
  4. 状态持久化:支持状态保存到外部存储

8.2 备忘录模式的缺点

  1. 内存开销:保存大量状态可能占用较多内存
  2. 看管人职责:看管人不知道备忘录内容,但需要管理生命周期
  3. 性能考虑:大对象的状态保存和恢复可能影响性能

8.3 深入思考

备忘录模式的本质是**"状态封装的时空旅行"**。它让我们可以在不破坏对象封装的前提下,穿越时间回到过去的状态。

设计之美的思考

"备忘录模式体现了'时间管理'的智慧。在软件的世界里,我们不仅需要关注对象当前的状态,更需要有能力回到过去、展望未来。这种时空穿梭的能力,让我们的程序更加健壮和用户友好。"

从源码的角度看,备忘录模式在现实世界中无处不在:

  • 数据库事务的回滚机制
  • 版本控制系统(Git的commit和reset)
  • 浏览器的前进后退
  • 游戏存档系统
  • 编辑器的撤销重做

何时使用备忘录模式

  • 需要保存和恢复对象状态
  • 需要实现撤销/重做功能
  • 需要保存对象状态而不暴露其内部细节
  • 需要实现快照功能

下一篇预告:设计模式手册018 - 访问者模式:如何在不修改类的前提下扩展功能?


版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

相关推荐
ZouZou老师2 小时前
C++设计模式之适配器模式:以家具生产为例
java·设计模式·适配器模式
小小晓.2 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS2 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
steins_甲乙3 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
煤球王子3 小时前
学而时习之:C++中的异常处理2
c++
仰泳的熊猫3 小时前
1084 Broken Keyboard
数据结构·c++·算法·pat考试
我不会插花弄玉3 小时前
C++的内存管理【由浅入深-C++】
c++
CSDN_RTKLIB4 小时前
代码指令与属性配置
开发语言·c++
上不如老下不如小4 小时前
2025年第七届全国高校计算机能力挑战赛 决赛 C++组 编程题汇总
开发语言·c++
雍凉明月夜4 小时前
c++ 精学笔记记录Ⅱ
开发语言·c++·笔记·vscode