ci 透彻指南

原标题:ConsolePauserPlusPlus 透彻指南

Q: 为什么叫这个名字?

A: ConsolePauserPlus & ConsolePauser

这篇文章可以看作是对 solstice23 Dalao 的补充,所以又加了一个 Plus,简称 CPPP。

在 CPPP 基础上再次增加新功能,命名为 ci,精简版为 ci_mini。

0. 开始之前

  1. 首先你需要一台电脑(雾,还需要安装好 VSCode,然后就可以继续了。

  2. Command Runner 的使用:配置指南 by Ptilopsis_w Dalao。

1. 重定向文件输入输出

你不觉得复制粘贴样例很痛苦么?

emm,你用 freopen 啊!

啊? 我不想手打,,,

然后就有了这篇文章。

其实 cmd 里提供了重定向文件IO的功能,如:

P0000.exe < sample.in > sample.ans

套到上面的编译运行指令里就好,

如果我们需要将 DEBUG 信息输出到控制台而非输出文件,可以用 std::cerr,用法与 std::cin 基本相同。

2. 计时误差

大多数情况下,计时误差主要来源于:

  1. 评测机状态波动,这点在个人电脑尤为明显;

  2. 调用计时函数,终端等误差。

虽然前者已经决定了 99.114514%,但为了严谨,还是引入了系统调用误差。

在原版 ConsolePauserPlus 和 ConsolePauser 中调用 GetClockTick()GetClockFrequency() 实现计时,为减少函数调用时间损耗,将其替换成了精度更高更简单的 chrono,但还是存在终端调用的时间损耗,因此,我们创建了一个空程序,模拟调用并计时,计入 deviation

这个空程序的源代码只有 12 bytes:

int mian(){}

有它我们可以得到当前状态下系统调用误差,经测试,误差正常范围在 $\text{0.005s-0.008s}$ 之间,虽然这个数据并没有什么用,但还是保留在了 DEBUG 中。

保留在 CPPP 的 DEBUG 中,在 ci_mini 中移除,ci 待确定。

3. 调试信息颜色

为了好 van,在输出中加入了字体颜色控制。

4. 加入编译运行功能

为了支持更多的功能,将编译运行纳入程序。

程序传参示例:

CPPP.exe 2 P0000

2 表示功能类型:

在 CPPP 中:0 为仅编译,1 为仅运行,2 为编译运行;

在 ci 和 ci_mini 中:1 为仅编译,2 为仅运行,3 为编译运行;

P0000 表示需要编译/运行的程序名,不含后缀名。

暂不支持自定义复杂性传参,倒是可以加入咕咕列表。

ci 中支持较复杂的传参。

此时你就可以将 setting.json 的内容改为:

    "command-runner.terminal.autoFocus": true,
    "command-runner.commands": {
        "compile-cpp":     "cls && cd ${fileDirname} && ci_mini 1 ${fileBasenameNoExtension}",
        "run-cpp":         "cls && cd ${fileDirname} && ci_mini 2 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
        "compile&run-cpp": "cls && cd ${fileDirname} && ci_mini 3 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
        
        // "compile-cpp":     "cd ${fileDirname} && consolepauserplusplus 0 ${fileBasenameNoExtension}",
        // "run-cpp":         "cd ${fileDirname} && consolepauserplusplus 1 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
        // "compile&run-cpp": "cd ${fileDirname} && consolepauserplusplus 2 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",

        // "compile-cpp":      "cls && cd ${fileDirname} && echo Compiling... && g++ -g -std=c++14 -O2 -lpsapi ${fileBasenameNoExtension}.cpp -o ${fileBasenameNoExtension}.exe && cls && echo Compilation Completed!",
        // "run-cpp":          "cls && cd ${fileDirname} && echo The program is running! && consolepauserplus 1 ${fileBasenameNoExtension} < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out",
        // "compile&run-cpp":  "cls && cd ${fileDirname} && echo Compiling... && g++ -g -std=c++14 -O2 ${fileBasenameNoExtension}.cpp -o ${fileBasenameNoExtension}.exe && cls && echo Compilation Completed! && cls && cd ${fileDirname} && echo The program is running! && consolepauserplus 2 ${fileBasenameNoExtension}.exe < D:\\code\\.vscode\\sample.in > D:\\code\\.vscode\\sample.out"
    },

5. fc 判断结果

调用 system("fc a.out a.ans >> a.diff") 可以实现文件比较,进而判断样例运行是否通过。

在 CPPP 中存在误判情况,ci 中运行正常。

6. 完整代码

ci_mini

#include <bits/stdc++.h>
#include <windows.h>
#include <psapi.h>
using namespace std;
typedef unsigned long uint;

const string compileOptions = " -g -std=c++14 -O2 -lpsapi ";

double usedMemory = 0;
int    usedMemoryUnit = 0;
char   usedMemoryUnitsName[5][5] = {"B", "KB", "MB", "GB", "TB"};

uint ExecuteCommand(int opt, string command) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si, 0, sizeof si);
    si.cb = sizeof(si);
    memset(&pi, 0, sizeof pi);
    
    if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
        fprintf(stderr, "\nFailed to execute \"%s\":",command.c_str());
    }
    WaitForSingleObject(pi.hProcess, INFINITE);
    
    if (opt == 1) {
        PROCESS_MEMORY_COUNTERS pmc;
        GetProcessMemoryInfo(pi.hProcess, &pmc, sizeof(pmc));
        usedMemory = pmc.PeakPagefileUsage;
        while (usedMemory >= 1024){
            usedMemory /= 1024.0;
            usedMemoryUnit++;
        }
    }

    uint result = 0;
    GetExitCodeProcess(pi.hProcess, &result);
    return result;
}

void CompileFile(string filename) {
    string compile = "g++ " + compileOptions + filename + ".cpp -o " + filename + ".exe";
    uint returnVal = ExecuteCommand(0, compile);
    
    if (returnVal) {
        fprintf(stderr, "\033[1;33;40mci >> Compile Error\033[0m\n"), exit(0);
    } else {
        fprintf(stderr, "\033[0;32;40mci >> Compiled Successfully\033[0m\n");
    }
}

void RunFile(string filename) {
    string command = filename + ".exe";    
    chrono::time_point<chrono::system_clock> start = chrono::system_clock::now();
    uint returnVal = ExecuteCommand(1, command);
    chrono::time_point<chrono::system_clock> end = chrono::system_clock::now();
    chrono::duration<double> elapsed = end - start;
    
    if (returnVal) {
        return fprintf(stderr, "\033[1;35;40mci >> Runtime Error\033[0m\n"), void();
    } else {
        fprintf(stderr, "\033[0;32;40mci >> Run Successfully\033[0m\n");
    }
    fprintf(stderr, "\033[1;34;40mTime \033[0m%0.3lf s \033[1;34;40mMemery \033[0m%0.2lf %s\n",
        elapsed, usedMemory, usedMemoryUnitsName[usedMemoryUnit]);
}

int main(int argc, char* argv[]) {   
    if ((string)argv[1] == "1") {
        CompileFile((string)argv[2]);
    }
    if ((string)argv[1] == "2") {
        RunFile((string)argv[2]);
    }    
    if ((string)argv[1] == "3") {
        CompileFile((string)argv[2]);
        RunFile((string)argv[2]);
    }
    return 0;
}

ci

#include <string>
#include <chrono>
#include <stdio.h>
#include <direct.h>
#include <iostream>
#include <windows.h>
#include <Psapi.h>

#include <SimpleIni.h>
using namespace std;

typedef unsigned long uint;

const char version[32] = "0.1.2 Dev";
const int MAX_COMMAND_LENGTH = 32768;
const int MAX_ERROR_LENGTH   =  2048;

string CiPath = "";
string CiSettingPath = "";

// ci_setting.ini
bool showDebug = 1;
#define DEBUG if(showDebug)
string compileOptions = "-g -std=c++14 -O2 -lpsapi";
string samplePath = "";

double usedMemory = 0;
int    usedMemoryUnit = 0;
char   usedMemoryUnitsName[5][5] = {"B", "KB", "MB", "GB", "TB"};

void Init();
void FirstUse();
void CheckFile();
void MakeSampleExe();
void RunFile(string);
void CompileFile(string);

string GetCiPath();
string GetErrorMessage();
uint ExecuteCommand(int, string);

int main(int argc, char* argv[]) {

    Init();
    
    if (argv[1][0] == '-') {
        // help
        if (argv[1][1] == 'h') {
            fprintf(stderr,"ci >> help\n");
            fprintf(stderr, "-------------------------------------\n");
            fprintf(stderr, "-h(elp)             | ci help        \n");
            fprintf(stderr, "-a(bout)            | ci about       \n");
            fprintf(stderr, "-d(ebug)            | ci debug       \n");
            fprintf(stderr, "-v(ersion)          | ci version     \n");
            fprintf(stderr, "-s(ettings)         | ci settings    \n");
            fprintf(stderr, "1 <filename>        | compile   file \n");
            fprintf(stderr, "2 <filename>        | run       file \n");
            fprintf(stderr, "3 <filename>        | cp & run  file \n");
            fprintf(stderr, "4                   | check file(fc) \n");
            fprintf(stderr, "compile <filename>  | compile   file \n");
            fprintf(stderr, "run     <filename>  | run       file \n");
            fprintf(stderr, "cprun   <filename>  | cp & run  file \n");
            fprintf(stderr, "check               | check file(fc) \n");
            fprintf(stderr, "make sample.exe     | DEBUG & TEST   \n");
            fprintf(stderr, "-------------------------------------\n");
        }
        
        // version
        if (argv[1][1] == 'v') {
            fprintf(stderr, "\nci version: %s\n", version);
        }
        
        // settings
        if (argv[1][1] == 's') {
            fprintf(stderr,"ci >> settings(current)\n");
            fprintf(stderr, "-------------------------------------------------------------\n");
            fprintf(stderr, "sample_path      | %s\n", samplePath.c_str());
            fprintf(stderr, "compile_options  | %s\n", compileOptions.c_str());
            fprintf(stderr, "-------------------------------------------------------------\n");
            fprintf(stderr,"plause edit \'ci_settings.ini\' to change settings \n");
        }
        
        // about
        if (argv[1][1] == 'a') {
            //
        }
        
        // debug
        if (argv[1][1] == 'd') {
            fprintf(stderr,"ci >> debug(current status)\n");
            fprintf(stderr, "-------------------------------------------------------------\n");
            fprintf(stderr, "ci_path          | %s\n", CiPath.c_str());
            fprintf(stderr, "ci_setting_path  | %s\n", CiSettingPath.c_str());
            fprintf(stderr, "sample_path      | %s\n", samplePath.c_str());
            fprintf(stderr, "compile_options  | %s\n", compileOptions.c_str());
            fprintf(stderr, "-------------------------------------------------------------\n");
        }
        
        return 0;
    }
    
    if ((string)argv[1] == "make") {
        if ((string)argv[2] == "sample.exe") {
            MakeSampleExe();
        }
    }
    
    if ((string)argv[1] == "1" || (string)argv[1] == "compile") {
        CompileFile((string)argv[2]);
    }
    
    if ((string)argv[1] == "2" || (string)argv[1] == "run"    ) {
        RunFile((string)argv[2]);
    }
    
    if ((string)argv[1] == "3" || (string)argv[1] == "cprun"  ) {
        CompileFile((string)argv[2]);
        
        RunFile((string)argv[2]);
    }
    
    if ((string)argv[1] == "4" || (string)argv[1] == "check"  ) {
        CheckFile();
    }
}

string GetErrorMessage() {
    string result(MAX_ERROR_LENGTH,0);
 
    FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),&result[0],result.size(),NULL);
 
    for(int i = result.length()-1;i >= 0;i--) {
        if(isspace(result[i])) {
            result[i] = 0;
        } else {
            break;
        }
    }
    return result;
}

uint ExecuteCommand(int opt, string command) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si, 0, sizeof si);
    si.cb = sizeof(si);
    memset(&pi, 0, sizeof pi);
    
    if (samplePath != "" && opt == 1) {
        string Input = samplePath + "sample.in";
        string Output= samplePath + "sample.out";
        
        si.hStdInput = freopen(Input.c_str(), "r", stdin);
        si.hStdOutput=freopen(Output.c_str(), "w", stdout);
    }    
    
    if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
        fprintf(stderr, "\nFailed to execute \"%s\":",command.c_str());
        fprintf(stderr, "\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str());
    }
    WaitForSingleObject(pi.hProcess, INFINITE);
    
    if (opt == 1) {
        PROCESS_MEMORY_COUNTERS pmc;
        GetProcessMemoryInfo(pi.hProcess, &pmc, sizeof(pmc));
        usedMemory = pmc.PeakPagefileUsage;
        while (usedMemory >= 1024){
            usedMemory /= 1024.0;
            usedMemoryUnit++;
        }
    }

    uint result = 0;
    GetExitCodeProcess(pi.hProcess, &result);
    return result;
}

void MakeSampleExe() {
    freopen("ci_sample.cpp", "w", stdout);
        printf("int main(){}");
    fclose(stdout);
    DEBUG fprintf(stderr, "ci >> Write Successfully\n");    
    
    string compile = "g++ -g ci_sample.cpp -o ci_sample.exe";
    uint returnVal = ExecuteCommand(0, compile);
    
    if (returnVal) {
        fprintf(stderr, "ci >> Compile Error\n");
    } else {
        fprintf(stderr, "ci >> Compiled Successfully\n");
    }
}

void CompileFile(string filename) {
    string compile = "g++ " + compileOptions + " " + filename + ".cpp -o " + filename + ".exe";
    DEBUG fprintf(stderr, "ci >> Compile %s\n", compile.c_str());
    uint returnVal = ExecuteCommand(0, compile);
    
    if (returnVal) {
        fprintf(stderr, "\033[1;33;40mci >> Compile Error\033[0m\n");
    } else {
        fprintf(stderr, "\033[0;32;40mci >> Compiled Successfully\033[0m\n");
    }    
}

void RunFile(string filename) {
    string command = filename + ".exe";
    if (samplePath != "") {
        cerr << samplePath << endl;
    }
    DEBUG fprintf(stderr, "ci >> Command %s\n", command.c_str());
    
    chrono::time_point<chrono::system_clock> start = chrono::system_clock::now();
    uint returnVal = ExecuteCommand(1, command);
    chrono::time_point<chrono::system_clock> end   = chrono::system_clock::now();
    chrono::duration<double> elapsed = end - start;   
    
    if (returnVal) {
        return fprintf(stderr, "\033[1;35;40mci >> Runtime Error\033[0m\n"), void();
    } else {
        fprintf(stderr, "\033[0;32;40mci >> Run Successfully\033[0m\n");
    }
    fprintf(stderr, "\033[1;34;40mTime \033[0m%0.3lf s \033[1;34;40mMemery \033[0m%0.2lf %s\n",
        elapsed, usedMemory, usedMemoryUnitsName[usedMemoryUnit]);
}

void CheckFile() {
    if (samplePath == "") {
        return fprintf(stderr, "\033[1;31;40mci >> Please set the sample_path first\033[0m\n"), void();
    }
    
    string command = "fc " + samplePath + "sample.out " + samplePath + "sample.ans > " + samplePath + "sample.diff";
    cerr << command << endl;
    if (system(command.c_str())) {
        fprintf(stderr, "\033[1;31;40mci >> Difference are saved in \'sample.diff\'\033[0m\n");
    } else {
        fprintf(stderr, "\033[0;32;40mci >> No Difference\033[0m\n");
    }
}

void Init() {
    CiPath = GetCiPath();
    CiSettingPath = CiPath + "ci_setting.ini";
    
    CSimpleIniA ini;
    ini.SetUnicode();
    SI_Error rc = ini.LoadFile(CiSettingPath.c_str());
    if (rc < 0) {
        return FirstUse(), void();
    }
    
    showDebug = ini.GetBoolValue("ci_setting", "show_debug");
    samplePath = (string)ini.GetValue("ci_setting", "sample_path");
    compileOptions = (string)ini.GetValue("ci_setting", "compile_options");
}

void FirstUse() {
    CSimpleIniA ini;
    DEBUG fprintf(stderr, "ci >> init ci_setting.ini\n");
    
    samplePath = CiPath;
    
    freopen(CiSettingPath.c_str(), "w", stdout);
        printf("; use \';\' as a note\n");
        printf("; sample_path example: D:\\code\\.vscode\\\n");
    fclose(stdout);
    
    SI_Error rc = ini.LoadFile(CiSettingPath.c_str());
    if (rc < 0) {
        return fprintf(stderr, "ci >> Error !"), void();
    }
    
    ini.SetBoolValue("ci_setting", "show_debug", showDebug);
    ini.SetValue("ci_setting", "sample_path", samplePath.c_str());
    ini.SetValue("ci_setting", "compile_options", compileOptions.c_str());
    ini.SaveFile(CiSettingPath.c_str());
}

string GetCiPath() {
    char _szPath[255 + 1]={0};
    GetModuleFileName(NULL, _szPath, 255);
    (strrchr(_szPath, '\\'))[1] = 0;
    return _szPath;
}

CPPP

已经咕了,不再维护。

#include <string>
#include <chrono>
#include <stdio.h>
#include <iomanip>
#include <iostream>
#include <windows.h>
#include <Psapi.h>
using std::string;
 
#define MAX_COMMAND_LENGTH 32768
#define MAX_ERROR_LENGTH 2048

// LONGLONG GetClockTick() {
// 	LARGE_INTEGER dummy;
// 	QueryPerformanceCounter(&dummy);
// 	return dummy.QuadPart;
// }
 
// LONGLONG GetClockFrequency() {
// 	LARGE_INTEGER dummy;
// 	QueryPerformanceFrequency(&dummy);
// 	return dummy.QuadPart;
// }
 
// void PauseExit(int exitcode) {
// 	system("pause");
// 	exit(exitcode);
// }

string GetErrorMessage() {
    string result(MAX_ERROR_LENGTH,0);
 
    FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),&result[0],result.size(),NULL);
 
    // Clear newlines at end of string
    for(int i = result.length()-1;i >= 0;i--) {
        if(isspace(result[i])) {
            result[i] = 0;
        } else {
            break;
        }
    }
    return result;
}
 
string GetCommand(int argc,char** argv) {
    string result;
    for(int i = 1;i < argc;i++) {
        // Quote the arguments in case they contain spaces
        // Could use additional quoting code around the argument
        if(string(argv[i]).find(" ")!=string::npos) {
            result += string("\"") + string(argv[i]) + string("\"");
        } else {
            result += string(argv[i]);
        }
 
        // Add a space except for the last argument
        if(i != (argc-1)) {
            result += string(" ");
        }
        // std::cerr << "DEBUG::Result " << result << "\n";
    }
 
    if(result.length() > MAX_COMMAND_LENGTH) {
        printf("\nError: Length of command line string is over %d characters\n",MAX_COMMAND_LENGTH);
    }
    return result;
}
 
double usedMemory;
int usedMemoryUnit = 0;
char usedMemoryUnitsName[5][5] = {"B", "KB", "MB", "GB", "TB"};
 
DWORD ExecuteCommand(string& command) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si,0,sizeof(si));
    si.cb = sizeof(si);
    memset(&pi,0,sizeof(pi));
 
    if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
        printf("\nFailed to execute \"%s\":",command.c_str());
        printf("\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str());
    }
    WaitForSingleObject(pi.hProcess, INFINITE); // Wait for it to finish
 
    PROCESS_MEMORY_COUNTERS pmc;
    GetProcessMemoryInfo(pi.hProcess, &pmc, sizeof(pmc));
    usedMemory = pmc.PeakPagefileUsage;
    while (usedMemory >= 1024){
        usedMemory /= 1024.0;
        usedMemoryUnit++;
    }
 
    DWORD result = 0;
    GetExitCodeProcess(pi.hProcess, &result);
    return result;
}

DWORD ExecuteCommandNoCountMemory(string& command) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si,0,sizeof(si));
    si.cb = sizeof(si);
    memset(&pi,0,sizeof(pi));
 
    if(!CreateProcess(NULL, (LPSTR)command.c_str(), NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
        printf("\nFailed to execute \"%s\":",command.c_str());
        printf("\nError %lu: %s\n",GetLastError(),GetErrorMessage().c_str());
    }
    WaitForSingleObject(pi.hProcess, INFINITE); // Wait for it to finish
 
    DWORD result = 0;
    GetExitCodeProcess(pi.hProcess, &result);
    return result;
}
 
int main(int argc, char* argv[]) {
    // std::cerr << "DEBUG::Argc " << argc << "\n";
    // std::cerr << "DEBUG::Argv " << argv[1] << "\n";
 
    // First make sure we aren't going to read nonexistent arrays
    // if(argc < 2) {
    // 	std::cerr << "Usage: CPPP.exe <options> <filename>\n";
    // }
    
    // Make us look like the paused program
    SetConsoleTitle(argv[2]);
 
    // Then build the to-run application command
    string command = GetCommand(argc-1,argv+1);
    
    // Compile
    if (argv[1][0] == '0' || argv[1][0] == '2') {
        string compile = "g++ -g -std=c++14 -O2 -lpsapi " + command + ".cpp -o " + command + ".exe";
        DWORD returnvalue = ExecuteCommandNoCountMemory(compile);
        // std::cerr << "DEBUG::Compile " << compile << "\n";
    }
    if (argv[1][0] == '0') {
        return 0;
    }
     
    // Save starting timestamp
    std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
 
    // Then execute said command
    DWORD returnvalue = ExecuteCommand(command);
 
    // Get ending timestamp
    std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
    string tmp = "sample.exe"; ExecuteCommandNoCountMemory(tmp);
    std::chrono::time_point<std::chrono::system_clock> end2 = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed = end - start;
    std::chrono::duration<double> deviation = end2 - end;
    
    // Deviation
    std::cerr << "DEBUG::System Deviation " << deviation.count() << " s\n";
        
    if (returnvalue) {
        return std::cerr << "\033[0;35m"<< "Runtime Error" << "\033[0m" << "  ", 0;
    }
    if (system("fc D:\\code\\.vscode\\sample.out D:\\code\\.vscode\\sample.ans >> sample.diff")) {
        std::cerr << "\033[0;31m" << "Wrong Answer" << "\033[0m" << "  ";
    } else {
        std::cerr << "\033[0;32m" << "Accepted"<< "\033[0m" << "  ";
    }
    
     // Done? Print return value of executed program
     std::cerr << "\033[0;34m" << "Return: " << returnvalue << " ";
    std::cerr <<   "Time: " << std::setprecision(4) << elapsed.count() << " s ";
    std::cerr << "Memory: " << std::setprecision(4) << usedMemory  << " " << usedMemoryUnitsName[usedMemoryUnit];
    std::cerr << "\033[0m"  << std::endl;
}

7. 效果展示

2024.02.06 考古,图床寄了。

8. 更新日志

2022 09 03 Add ci code

2022 08 27 修复已知 Bug;

2022 08 27 精简 ci 代码,命名为 ci_mini;

2022 08 26 开发功能更完备的 ci;

2022 08 25 更新,加入新功能;

2022 08 ?? 基于 ConsolePauserPlus 二次开发;

9. 后记

其实做到这一步就不能再叫 ConsolePauser 了,现在的她更像是一个工具箱,开箱即用,方便自在,但为了感谢前人的贡献,仍沿用其命名。

所以有了 ci 和 ci_mini。

上一篇
下一篇