【C++项目】从零实现一个在线编译器

在这里插入图片描述

前言

身为一名程序员,想必大家都有接触过像leetcode这样的刷题网站,不知你们在刷题的过程中是否思考过一个问题:它们是如何实现在线编译运行的功能。如果你对此感到好奇,那么本文将一步步带你来实现一个简易在线编译器。

项目概述

项目的基本逻辑:前端用户在网页上输入代码与参数,后端通过多进程的方式来编译运行代码,然后将标准输出、标准错误信息返回给前端页面。

前后端交互数据格式

// 前端发送
{
  "code": "代码",
  "cpu_limit": "CPU限制",
  "mem_limit": "内存限制"
}

// 后端发送
{
  "reason": "错误原因",
  "status": "状态码",
  "stderr": "错误输出",
  "stdout": "标准输出"
}

使用的第三方库

后端:

  • cpp-httplib:用于处理HTTP请求和响应。

  • jsoncpp:用于解析和生成JSON数据。

  • spdlog:用于日志记录。

前端:

  • jquery:简化JavaScript操作,方便进行DOM操作和Ajax请求。

  • ace:提供代码编辑器功能,支持语法高亮和代码自动完成。

运行效果

具体实现

后端逻辑

后端分为编译模块和运行模块,均使用多进程的方式来运行,并根据用户所选语言的语言来选择不同的编译器和运行方式。后端代码分为四部分:公共模块(工具类)、编译模块、运行模块、编译运行模块(整合编译与运行)。

公共模块

日记类

日记系统对spdlog进行了最低程度的封装,实现了单例日记系统,并定义宏来简化其使用。

//
// Created by lang liu on 24-4-23.
//

#pragma once

#ifdef DEBUG
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#endif

#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>

namespace ns_log {
    //TODO 初始化日记 完善
    class Log {
    public:
        static Log &getInstance() {
            std::call_once(_flag, []() {
                _instance = new Log();
            });
            return *_instance;
        }

        auto getLogger()
        ->std::shared_ptr<spdlog::logger>
        {
            return _logger;
        }

    private:
        Log() {
            _logger = spdlog::stdout_color_mt("nil");
            _logger->set_level(spdlog::level::debug);
            _logger->set_pattern("[%^%l%$] [%Y-%m-%d %H:%M:%S] [%t] [%s:%#] %v");
        }

        ~Log() {
            spdlog::drop_all();
        }

    private:
        static std::once_flag _flag;
        static Log *_instance;

        std::shared_ptr<spdlog::logger> _logger;
    };

    std::once_flag Log::_flag;
    Log *Log::_instance = nullptr;


#define LOG_DEBUG(...)    SPDLOG_LOGGER_DEBUG(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_INFO(...)     SPDLOG_LOGGER_INFO(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_WARN(...)     SPDLOG_LOGGER_WARN(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_ERROR(...)    SPDLOG_LOGGER_ERROR(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(Log::getInstance().getLogger(), __VA_ARGS__)
}


// #else
//[x] 无spdlog
// #include <iostream>
// #include <format>
// #include "util.hpp"

// namespace ns_log {

//     using namespace ns_util;

//     enum {
//         INFO,
//         DEBUG,
//         WARN,
//         ERROR,
//         CRITICAL
//     };

//     inline std::ostream &Log(const std::string &level, const std::string &str) {
//         std::string msg = std::format("[{}] [{}] [{}:{}] {}", level, TimeUtil::GetTimeStamp(), __FILE__, __LINE__,
//                                       str);
//         // auto ret = __FILE_NAME__;
//         return std::cout << msg;
//     }

// #define LOG_INFO(...)     Log("INFO", __VA_ARGS__)
// #define LOG_DEBUG(...)    Log("DEBUG", __VA_ARGS__)
// #define LOG_WARN(...)     Log("WARN", __VA_ARGS__)
// #define LOG_ERROR(...)    Log("ERROR", __VA_ARGS__)
// #define LOG_CRITICAL(...) Log("CRITICAL", __VA_ARGS__)

// }

// #endif

工具类

工具类分为时间工具、文件工具、路径工具。

时间工具:时间工具类用于生成时间戳,辅助文件工具生成唯一的文件名(UUID)。

文件工具:用于实现读写、创建、删除文件。

路径工具:用于更改文件的后缀。

//
// Created by lang liu on 24-4-23.
//

#ifndef OJ_UTIL_HPP
#define OJ_UTIL_HPP

#include "log.hpp"
#include <sys/time.h>
#include <sys/stat.h>
#include <fstream>
#include <atomic>
#include <unordered_map>
#include <filesystem>
#include <vector>

namespace ns_util
{
    using namespace ns_log;

    // 定义文件后缀名的映射表
    static inline std::unordered_map<std::string, std::string> suffixTable {
        {"c_cpp", ".cc"},
        {"csharp", ".cs"},
        {"python", ".py"},
        {"javascript", ".js"}
    };

    // 定义可执行文件后缀名的映射表
    static inline std::unordered_map<std::string, std::string> excuteTable {
        {"c_cpp", ".exe"},
        {"csharp", ".cs"},
        {"javascript", ".js"},
        {"python", ".py"}
    };

    // 时间工具类
    class TimeUtil
    {
    public:
        // 获取时间戳(秒)
        static std::string GetTimeStamp()
        {
            struct timeval _tv{};
            gettimeofday(&_tv, nullptr);    // 获取时间戳
            return std::to_string(_tv.tv_sec);
        }

        // 获取时间戳(毫秒),用于生成随机文件名
        static std::string GetTimeNs()
        {
            struct timeval _tv{};
            gettimeofday(&_tv, nullptr);
            return std::to_string(_tv.tv_sec * 1000 + _tv.tv_usec / 1000);
        }
    };

    // 文件路径和后缀名变量
    static std::string path;
    static std::string srcSuffix;
    static std::string excuteSuffix;
    static const std::string temp_path = "./template/";

    // 路径工具类
    class PathUtil
    {
    public:
        // 初始化模板路径和后缀名
        static void InitTemplate(const std::string& lang) {
            path = temp_path + lang + "/";
            srcSuffix = suffixTable.at(lang);
            excuteSuffix = excuteTable.at(lang);
        }

        // 添加后缀名到文件名
        static std::string AddSuffix(const std::string &file_name, const std::string& suffix)
        {
            std::string path_name = path;
            path_name += file_name;
            path_name += suffix;
            return path_name;
        }

        // 获取源文件路径
        static std::string Src(const std::string& file_name)
        {
            return AddSuffix(file_name, srcSuffix);
        }

        // 获取可执行文件路径
        static std::string Exe(const std::string& file_name)
        {
            return AddSuffix(file_name, excuteSuffix);
        }

        // 获取编译错误文件路径
        static std::string CompilerError(const std::string& file_name)
        {
            return AddSuffix(file_name, ".compile_error");
        }

        // 获取标准输入文件路径
        static std::string Stdin(const std::string& file_name)
        {
            return AddSuffix(file_name, ".stdin");
        }

        // 获取标准输出文件路径
        static std::string Stdout(const std::string& file_name)
        {
            return AddSuffix(file_name, ".stdout");
        }

        // 获取标准错误文件路径
        static std::string Stderr(const std::string& file_name)
        {
            return AddSuffix(file_name, ".stderr");
        }
    };

    // 文件工具类
    class FileUtil
    {
    public:
        // 移除文件夹中的所有文件(递归)
        static void RemoveAllFile(const std::string& dir) {
            std::vector<std::filesystem::path> removeArray;

            try {
                for(auto& it : std::filesystem::directory_iterator(dir)) {
                    if(std::filesystem::is_regular_file(it.path()))
                        removeArray.push_back(it.path());
                    else if (std::filesystem::is_directory(it.path()))
                        RemoveAllFile(it.path().string());
                }

                for(auto& it : removeArray) {
                    std::filesystem::remove(it);
                }
            }
            catch (const std::filesystem::filesystem_error& e) {
                LOG_ERROR("移除全部文件失败: {}", e.what());
            }
        }

        // 移除指定文件
        static void RemoveFile(const std::string& filename) {
            if(std::filesystem::exists(filename)) {
                std::filesystem::remove(filename);
            }
        }

        // 检查文件是否存在
        static bool IsFileExists(const std::string & path_name)
        {
            struct stat st = {};
            if(stat(path_name.c_str(), &st) == 0)
                return true;

            return false;
        }

        // 生成全局唯一的文件名
        static std::string UniqFileName()
        {
            static std::atomic_uint id(0);
            ++id;
            std::string ms = TimeUtil::GetTimeNs();
            std::string uniq_id = std::to_string(id);
            return ms + uniq_id;
        }

        // 将内容写入文件
        static bool WriteFile(const std::string & path_name, const std::string & content)
        {
            std::ofstream out(path_name);
            if(!out.is_open())
            {
                LOG_ERROR("write file failed!");
                return false;
            }
            out.write(content.c_str(), (int)content.size());
            out.close();
            return true;
        }

        // 读取文件内容
        static bool ReadFile(const std::string& path_name, std::string* content)
        {
            content->clear();

            std::ifstream in(path_name);
            if(!in.is_open())
            {
                LOG_ERROR("read file({}) failed!", path_name.c_str());
                return false;
            }
            in.seekg(0, std::ios::end);
            auto size = in.tellg(); // 获取文件大小
            in.seekg(0, std::ios::beg);

            content->resize(size);
            in.read(content->data(), size);

            return true;
        }
    };

};

#endif //OJ_UTIL_HPP

编译模块

编译模块因为其大部分代码都是相似的,所以这里将其具体执行逻辑分离开来,并使用工厂模式决定实例化不同的类。

编译模块

#pragma once

#include <unistd.h>
#include <sys/fcntl.h>

#include "log.hpp"
#include "util.hpp"

namespace ns_compiler {
    using namespace ns_log;
    using namespace ns_util;

    // 编译器基类
    class Compiler {
    public:
        virtual ~Compiler() = default;

        // 编译函数,接受文件名作为参数
        int Compile(const std::string& file_name) {
            // 打开标准错误和标准输出文件
            int _stderr = open(PathUtil::Stderr(file_name).c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
            int _stdout = open(PathUtil::Stdout(file_name).c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);

            // 检查文件是否成功打开
            if(_stderr < 0 || _stdout < 0) {
                LOG_ERROR("打开文件失败");
                return -1;
            }

            // 创建子进程进行编译
            pid_t pid = fork();
            if(pid < 0 ) {
                LOG_ERROR("线程创建失败: {}", strerror(errno));
                return -1;
            }
            else if(pid == 0) {
                // 子进程
                umask(0); // 重置文件掩码

                // 重定向标准错误和标准输出到文件
                dup2(_stderr, STDERR_FILENO);
                dup2(_stdout, STDOUT_FILENO);

                // 执行编译命令
                Execute(file_name);
                LOG_INFO("exec错误:{}", strerror(errno));
                return -1; // exec 出错
            }

            // 父进程等待子进程结束
            waitpid(pid, nullptr, 0);

            // 检查编译后的可执行文件是否存在
            if(!FileUtil::IsFileExists(PathUtil::Exe(file_name))) {
                LOG_INFO("文件编译失败");
                return -2;
            }
            return 0; // 编译成功
        }

    protected:
        // 执行具体编译命令的纯虚函数,需由子类实现
        virtual void Execute(const std::string& filename) = 0;
    };

    // C++ 编译器类,继承自 Compiler
    class CppCompiler : public Compiler {
    protected:
        // 执行 g++ 编译命令
        void Execute(const std::string& filename) override {
            execlp("g++", "g++", "-o", PathUtil::Exe(filename).c_str(),
                PathUtil::Src(filename).c_str(), nullptr);
        }
    };

    // 编译器工厂类,用于创建编译器对象
    class CompilerFactory {
    public:
        // 根据语言创建相应的编译器对象
        static std::unique_ptr<Compiler> CreateCompiler(const std::string& lang) {
            if(lang == "c_cpp")
                return std::make_unique<CppCompiler>();
            // 如果需要支持其他语言,可以在这里添加相应的编译器创建逻辑
            else
                return {};
        }
    };
}

运行模块

运行模块与编译模块的逻辑其实相差不大。

#pragma once

#include "compiler.hpp"
#include <iostream>
#include <sys/resource.h>
#include <sys/wait.h>

using namespace ns_log;
using namespace ns_util;

// Runner 基类,负责运行已编译的程序
class Runner {
public:
    // 运行指定的程序文件,并限制其 CPU 和内存使用
    int Run(const std::string& filename, const int cpu_limit, const int mem_limit) {
        // 获取输入、输出和错误文件路径
        std::string _execute = PathUtil::Exe(filename);
        std::string _stdin = PathUtil::Stdin(filename);
        std::string _stdout = PathUtil::Stdout(filename);
        std::string _stderr = PathUtil::Stderr(filename);

        umask(0); // 重置文件掩码
        // 打开标准输入、输出和错误文件
        int stdin_fd = open(_stdin.c_str(), O_CREAT | O_WRONLY, 0644);
        int stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
        int stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);

        // 创建子进程以执行程序
        pid_t pid = fork();
        if(pid < 0) {
            LOG_ERROR("fork error");
            close(stdin_fd);
            close(stdout_fd);
            close(stderr_fd);
            return -1;
        }
        else if(pid == 0) {
            // 子进程
            dup2(stdin_fd, STDIN_FILENO); // 重定向标准输入到文件
            dup2(stdout_fd, STDOUT_FILENO); // 重定向标准输出到文件
            dup2(stderr_fd, STDERR_FILENO); // 重定向标准错误到文件

            signal(SIGXCPU, [](int sig) { // 处理超时信号
                std::cerr << "timeout" << std::endl;
            });

            SetProcLimit(mem_limit, cpu_limit); // 设置进程资源限制
            Excute(_execute); // 执行具体的程序
            LOG_ERROR("execute error");
            exit(-1); // exec 出错
        }

        // 父进程等待子进程完成
        int status;
        waitpid(pid, &status, 0);
        close(stderr_fd);
        close(stdout_fd);
        close(stdin_fd);

        return WEXITSTATUS(status); // 返回子进程的退出状态
    }

    virtual ~Runner() = default;

protected:
    // 纯虚函数,由子类实现具体的执行逻辑
    virtual void Excute(const std::string& filename) = 0;

    // 设置进程的内存和 CPU 使用限制
    static void SetProcLimit(const int m_limit, const int c_limit) {
        rlimit memLimit{};
        memLimit.rlim_cur = m_limit * 1024; // 内存限制(KB 转换为字节)
        memLimit.rlim_max = RLIM_INFINITY; // 最大内存限制
        setrlimit(RLIMIT_AS, &memLimit);

        rlimit cpuLimit{};
        cpuLimit.rlim_cur = c_limit; // CPU 时间限制(秒)
        cpuLimit.rlim_max = RLIM_INFINITY; // 最大 CPU 时间限制
        setrlimit(RLIMIT_CPU, &cpuLimit);
    }
};

// C++ 运行器类,继承自 Runner
class CppRunner : public Runner {
protected:
    // 使用 execlp 执行编译后的 C++ 程序
    void Excute(const std::string& exec) override {
        execlp(exec.c_str(), exec.c_str(), nullptr);
    }
};

// C# 运行器类,继承自 Runner
class CsharpRunner : public Runner {
protected:
    // 使用 execlp 执行 C# 程序
    void Excute(const std::string &filename) override {
        execlp("dotnet", "dotnet", "run",  filename.c_str(), "--project", path.c_str(), nullptr);
    }
};

// JavaScript 运行器类,继承自 Runner
class JsRunner : public Runner {
protected:
    // 使用 execlp 执行 JavaScript 程序
    void Excute(const std::string &filename) override {
        execlp("node", "node", filename.c_str(), nullptr);
    }
};

// Python 运行器类,继承自 Runner
class PyRunner : public Runner {
protected:
    // 使用 execlp 执行 Python 程序
    void Excute(const std::string &filename) override {
        execlp("python3", "python3", filename.c_str(), nullptr);
    }
};

// 运行器工厂类,用于创建运行器对象
class RunnerFactory {
public:
    // 根据语言创建相应的运行器对象
    static std::unique_ptr<Runner> CreateRunner(const std::string& language) {
        if(language == "c_cpp")
            return std::make_unique<CppRunner>();
        else if (language == "csharp")
            return std::make_unique<CsharpRunner>();
        else if(language == "python")
            return std::make_unique<PyRunner>();
        else
            return std::make_unique<JsRunner>();
    }
};

编译运行模块

编译运行模块是编译模块与运行模块的整合,用于解析前端发送的信息,并编译运行,最后将结果返回给前端网页。

#pragma once

#include "runner.hpp"
#include "compiler.hpp"
#include "util.hpp"
#include <json/json.h>

using namespace ns_log;
using namespace ns_util;
using namespace ns_compiler;

// json 输入格式
// code: 代码
// language: 编译器
// cpu_limit: CPU 限制
// mem_limit: 内存限制

// json 输出格式
// reason: 错误原因
// status: 状态码
// stderr: 错误输出
// stdout: 标准输出

class CompileAndRun
{
private:
    // 将状态码转换为描述信息
    static std::string codeToDesc(int status) {
        static std::unordered_map<int, std::string> errTable = {
            {-1, "未知错误"},
            {-2, "编译错误"},
            {0, "运行成功"},
            // 更多状态码描述
        };
        return errTable[status];
    }

public:
    // 编译并运行
    static void Start(std::string& in_json, std::string* out_json)
    {
        // 转换 JSON 格式
        Json::Value in_value;
        Json::Value out_value;
        Json::Reader reader;

        if (!reader.parse(in_json, in_value))
        {
            LOG_ERROR("CompileAndRun::Start parse json error");
            return;
        }

        // 记录输入的 JSON 数据
        Json::StyledWriter styledWriter;
        LOG_DEBUG("in_value: {}", styledWriter.write(in_value));

        // 从 JSON 中提取参数
        std::string code = in_value["code"].asString();
        int cpu_limit = in_value["cpu_limit"].asInt();
        int mem_limit = in_value["mem_limit"].asInt();
        std::string lang = in_value["language"].asString();

        int status_code = 0;

        // 初始化模板路径和后缀名
        PathUtil::InitTemplate(lang);
        std::string file_name = FileUtil::UniqFileName(); // 生成唯一文件名

        // 创建编译器和运行器对象
        auto Cp = CompilerFactory::CreateCompiler(lang);
        auto Rn = RunnerFactory::CreateRunner(lang);

        if(code.empty())
        {
            // 如果代码为空,记录错误并设置状态码
            LOG_ERROR("CompileAndRun::Start code is empty");
            status_code = -1;
            goto END;
        }

        // 将代码写入文件
        if(!FileUtil::WriteFile(PathUtil::Src(file_name), code))
        {
            status_code = -1; // 其他错误
            goto END;
        }

        // 编译代码
        if(Cp)
        {
            if(Cp->Compile(file_name) < 0) {
                status_code = -2; // 编译错误
                goto END;
            }
        }

        // 运行编译后的程序
        status_code = Rn->Run(file_name, cpu_limit, mem_limit);
        if(status_code < 0)
        {
            status_code = -1; // 未知错误
            goto END;
        }
        else if(status_code > 0)
        {
            goto END;
        }

    END:
        // 构建输出 JSON 数据
        out_value["status"] = status_code;
        out_value["reason"] = codeToDesc(status_code);
        if(status_code == 0)
        {
            std::string _stdout;
            FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout); // 读取标准输出
            out_value["stdout"] = _stdout;
        }
        else
        {
            std::string _stderr;
            FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr); // 读取错误输出
            out_value["stderr"] = _stderr;
        }

        Json::StyledWriter writer;
        *out_json = writer.write(out_value); // 将结果写入输出 JSON
        LOG_DEBUG("out_json: {}", *out_json);

        // 清理生成的文件
        RemoveFile(file_name);
    }

private:
    // 移除生成的文件
    static void RemoveFile(const std::string& filename) {
        auto filepath = path + filename;
        auto filesrc = filepath + srcSuffix;
        auto fileexe = filepath + excuteSuffix;

        auto fileout = PathUtil::Stderr(filename);
        auto filein = PathUtil::Stdin(filename);
        auto fileerr = PathUtil::Stdout(filename);

        FileUtil::RemoveFile(filesrc);
        FileUtil::RemoveFile(fileexe);
        FileUtil::RemoveFile(fileout);
        FileUtil::RemoveFile(filein);
        FileUtil::RemoveFile(fileerr);
    }
};

程序入口

#include "compile_run.hpp"
#include <httplib.h>


int main()
{
    using namespace httplib;
    using namespace ns_util;



    // system("pwd");

    Server svr;

    svr.set_base_dir("./wwwroot");
    
    svr.Post("/run", [](const Request & req, Response &res) {
        std::string in_json = req.body;
        std::string out_json;
        CompileAndRun::Start(in_json, &out_json);
        res.set_content(out_json, "application/json");
    });

    // 启动服务器
    
    svr.listen("0.0.0.0", 8080);

    return 0;
}

总结

这个简易在线编译器只是我用于学习多进程应用与前后端交互而写的超微小项目,不算上前端网页只有500行代码左右。虽然还有很多设计不完善的地方,但凭我现在的水平也暂时想不到更好的解决方案,有什么不足的地方,欢迎讨论。

github:完整代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/786614.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【kafka】可视化工具cmak(原kafka-manager)安装问题解决

众所周知&#xff08;反正不管你知不知道&#xff09;&#xff0c;kafka-maneger更名了&#xff0c;现在叫cmak&#xff01;原因是什么呢&#xff1f;据不可靠小道信息说&#xff0c;原kafka-manager这个名字涉及到kafka商标使用问题&#xff0c;应该是被律师函警告了&#xff…

游戏AI的创造思路-技术基础-蒙特卡洛树搜索(1)

本篇介绍蒙特卡洛树搜索算法&#xff0c;AlphaGo用于围棋计算的应用就是基于蒙特卡洛树搜索研发的~~~ 目录 1. 定义 2. 发展历史 3. 公式和函数 3.1.算法的公式和函数 3.2. Python实现公式和函数 4. 运行原理 4.1. 运行原理 4.2. 各步骤用Python代码 5. 优缺点和缺陷的…

C语言-预处理详解

文章目录 &#x1f3af;引言&#x1f453;预处理详解1.预定义符号1.1 __FILE__1.2 __LINE__1.3 __DATE__1.4 __TIME__1.5 __STDC__ 2.#define定义常量2.1 定义数值常量2.2 定义字符串常量 3.#define中使用参数3.1**使用示例**3.2注意事项 4.宏替换的规则5.宏函数和函数的对比5.…

使用Redis实现消息队列:List、Pub/Sub和Stream的实践

摘要 Redis是一个高性能的键值存储系统&#xff0c;它的多种数据结构使其成为实现消息队列的理想选择。本文将探讨如何使用Redis的List、Pub/Sub和Stream数据结构来实现一个高效的消息队列系统。 1. 消息队列的基本概念 消息队列是一种应用程序之间进行通信的机制&#xff0…

解锁算力新极限,Xilinx UltraScale+赋能的高性能低延时FPGA加速卡

01、产品概述 AiHPC-V9P 是一款基于 AMD Virtex UltraScale FPGA VU9P 的 PCIe Gen3.0 x16 接口智能网卡&#xff0c;具有最大2*200GbE /或者16*10GbE(典型应用&#xff09;接入容量的高性能低延时智能网卡。 对外接口支持两组QSFP-DD 最高25Gb/s x8Lane 光口接入&#xf…

Java基础-组件及事件处理(中)

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 BorderLayout布局管理器 说明&#xff1a; 示例&#xff1a; FlowLayout布局管理器 说明&#xff1a; …

我跟ai学web知识点:“短链接”

我跟ai学web知识点&#xff0c;短链接不是“免费午餐”。 (笔记模板由python脚本于2024年07月08日 12:44:47创建&#xff0c;本篇笔记适合喜欢Web知识的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经…

Windows下编译OpenSSL静态库

目录 1. 版本与下载地址 2. 下载与安装VS2015 3. 下载与安装Perl 4. 测试ActivePerl是否安装正确 5. 下载OpenSSL 6. 编译32位OpenSSL静态库 6.1 解压openssl-1.0.2l.tar.gz 6.2 打开VS2015 x86本机工具命令提示符 6.3 输入命令进入到openssl的目录中 6.4 执行配置命…

一文洞悉巴基斯坦电子游戏出海引流获客广告风口不容忽视

一文洞悉巴基斯坦电子游戏出海引流获客广告风口不容忽视 随着全球数字经济的蓬勃发展&#xff0c;电子游戏行业也迎来了前所未有的机遇。巴基斯坦&#xff0c;这个拥有庞大人口基数和日益增长的消费能力的国家&#xff0c;其电子游戏市场潜力巨大。本文旨在探讨巴基斯坦电子游戏…

tomcat安装

tomcat tomcat和php一样&#xff0c;都是用来处理动态页面的。 tomcat也可以作为web应用服务器&#xff0c;开源的。 php .php tomcat .jsp nginx .html tomcat是用java代码写的程序&#xff0c;运行的是java的web应用程序 tomcat的特点和功能&#xff1a; 1、servlet容…

首席数据官CDO证书报考指南:方式、流程、适考人群与考试难度

在信息泛滥的今天&#xff0c;数据已转变为企业不可或缺的宝贵资源。 面对海量的信息&#xff0c;如何提炼出价值&#xff0c;为企业带来实质性的收益&#xff1f;首席数据官&#xff08;CDO&#xff09;认证的出现正是为了满足这一需求&#xff0c;它不仅是个人专业能力的体现…

AI Earth——1990-2022年全国月度气象数据检索应用app

应用结果 代码 #导入安装包 import os import json import datetime import streamlit as st import streamlit.components.v1 as components import traceback from PIL import Imageimport aie#读取当前目录的内容 current_work_dir = os.path.dirname(__file__) #添加地图…

简单的Java面向对象小游戏并使用三层架构(表示层、业务逻辑层、数据访问层)

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

AE-时间轴的基础操作

目录 预览&#xff08;快捷键空格&#xff09; 调整时间线显示比例&#xff08;Alt鼠标滚轮&#xff09; 控制预览长度&#xff08;B/N&#xff09; 逐帧移动&#xff08;笔记本&#xff1a;按住fn上下方向键&#xff09; 视频剪切&#xff08;ctrlshiftD&#xff09; 剪…

数据结构:顺序表+链表

数据结构&#xff1a;顺序表链表 一。顺序表&#xff1a; 首先在了解顺序表和链表之前&#xff0c;先了解一下线性表&#xff0c;**线性表&#xff08;linear list&#xff09;**是n个具有相同特征元素的有限序列 &#xff0c;在逻辑上是线性结构&#xff0c;也就是一条连续的…

深入剖析预处理

目录 1.预定义符号 2.#define 定义常量 3.#define定义宏 4.带有副作用的宏参数 5.宏替换的规则 6.宏函数的对比 7.#和## 7.1 #运算符 7.2 ## 运算符 8.命名约定 9.#undef 10.命令行定义 11.条件编译 12.头文件的包含 12.1 头文件被包含的方式&#xff1a; 12.1…

React setState

老生常谈之setState 是同步的还是异步的&#xff1f; 设想setState是同步的&#xff0c;那也就是每次调用setState都要进行新旧虚拟DOM的对比&#xff0c;然后将差异化的dom更新到页面上&#xff0c;性能损耗很大 所以react把setState设置为了异步&#xff0c;当状态更新时不…

基于springboot+vue实现的厨艺交流平台(文末源码+Lw)093

93基于SpringBootVue的实现的厨艺交流平台&#xff08;源码数据库万字Lun文流程图ER图结构图演示视频软件包&#xff09; 系统功能&#xff1a; 这次开发的厨艺交流平台功能有个人中心&#xff0c;食材分类管理&#xff0c;用户管理&#xff0c;菜品分类管理&#xff0c;菜谱信…

【Axure】产品原型如何在谷歌浏览器中打开

作为一名前端开发来说&#xff0c;在拿到产品的原型图后&#xff0c;如何打开&#xff1f;直接用谷歌浏览器打开&#xff0c;是打不开的&#xff0c;需要安装对应的插件。但是谷歌插件市场在不翻墙的情况下&#xff0c;是没有办法直接打开的&#xff0c;分享一种超级简单的方法…

Softmax回归中的损失函数

目录 一、损失函数介绍&#xff1a; 因为Softmax回归时逻辑回归的推广&#xff0c;所以Softmax回归的损失函数是逻辑回归损失函数的推广。 原文链接&#xff1a;逻辑回归中的损失函数 一、损失函数介绍&#xff1a; 与回归问题成本函数不同的是&#xff0c;Softmax回归模型&a…