简单的利用boost.python 和 boost.numpy 实现python和c++之间数据通信例子

对利用boost库生成python库一点记录

boost.python对c++很友好,甚至能直接将python的一些数据结构 list(列表),dict(字典)和相互嵌套等数据传输到c++,boost.numpy也方便python的数组传输到c++端,有时c++端还能直接调用python的函数(很方便调试一些python有的库而windows编译困难的库,相对来说很多库在linux上编译总是友好些),本人也是因为任务需要处理list、dict和其相互嵌套才尝试弄这个的(boost里面不支持set集合,不过自己试过set强制转换为list好像也是可行的),好东西用起来就很喜欢。

1. 安装boost库需要注意的;

linux上好像编译安装一些库都很容易,自己习惯用vs调试代码,一般尽量windows和linux都安装,只是通过boost.python 编写python库调用,相对来说简单点。

注意:个人是调用boost的静态库,动态库没弄成功过,也没时间、不太想去尝试了,重点是按需先实现功能。

1.1 linux下编译boost时候需注意:

linux下需带上cxxflags=-fPIC cflags=-fPIC编译选项,在解压boost源文件后,本人虚拟机上利用anaconda安装了python3.8环境,用的是 boost_1_79_0的源码(这个好像需要指定的python版本),切换到python3.8环境下,而且该环境下安装了numpy,类似如下:
./bootstrap.sh
sudo ./b2 install cxxflags=-fPIC cflags=-fPIC -j8

1.2 windows下有关boost编译的一点记录

windows下也要配置好python3.8的环境,里面也要安装numpy,自己已经配置了环境变量,最终的python库也是用该环境的,只是编译boost库的话还算顺利,自己是编译了的,先前下载了一个自带动态、静态boost库的版本,解压就有,boost_1_74_0/lib64-msvc-14.2 里面没有libboost_numpy38的库,要用numpy传多维数组则只好自己编译了,就有了libboost_numpy38-vc142-mt-x64-1_79.lib 库;指令好像挺简单,可以两次双击bootstrap.bat,和bootstrap.bat生成的b2.exe(不配置参数的话,甚至32位的库也给编译了):

2. 通过boost编写python接口的例子

个人主要通过cmake,vs2019、anaconda和pycharm等实现方便的c++代码编写生成python库和python环境联调。通过不断查找资料、尝试才实现的,当然能实现大部分也是通过查找资料弄出来的,也谢谢他人的分享,所以自己记录下,提供简化的例子也是方便自己也方便他人,能实现双赢的伙伴或者工作、竞争等关系是最好的,最终实现不了双赢的总会双亏或者失去本该能赢的部分,只是时间问题。

2.1 最外层cmakelist文件:

多层cmakelist文件布局确实好用,以前看过人家开源代码是这样的,自己尝试用,发现真好用,例子用的HelloWorld.cpp文件和对应的CMakeLists.txt文件放到了子目录“01_HelloWorld”下,通过外层cmakelist文件ADD_SUBDIRECTORY(01_HelloWorld)包含子目录“01_HelloWorld”。

注意windows下的cmakelist文件里面:set(BOOST_ROOT "修改为你们自己的boost目录”),linux下安装boost会自动配置环境变量之类的,注册这块就行,下面注释掉的可以不用管,都是自己不断尝试的结果。

project(Boost_Test)
cmake_minimum_required(VERSION 3.17)

# find python
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)

# now search for the boost component
# depending on the boost version it is called either python,
# python2, python27, python3, python36, python37, ...
list(
  APPEND _components
    python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
    python${PYTHON_VERSION_MAJOR}
    python
  )
#set(_Boost_NUMPY_DEPENDENCIES python${component_python_version})

set(BOOST_ROOT "D:/software/app2/Third-party_libraries/boost/boost_1_79_0")
#set(Boost_INCLUDE_DIR "D:/software/app2/Third-party_libraries/boost/1.74.0/boost")
#set(Boost_LIBRARIES "D:/software/app2/Third-party_libraries/boost/1.74.0/stage/lib")

message(BOOST_ROOT " ${BOOST_ROOT}")

set(_boost_python_found "")
#set(Boost_NAMESPACE "libboost")
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_LIBS ON)
#set(Boost_USE_STATIC_RUNTIME OFF)
foreach(_component IN ITEMS ${_components})
  find_package(Boost COMPONENTS ${_component})
  if(Boost_FOUND)
    set(_boost_python_found ${_component})
    break()
  endif()
endforeach()

#if(_boost_python_found STREQUAL "")
#  message(FATAL_ERROR "No matching Boost.Python component found")
#endif()
#

find_package(Boost REQUIRED COMPONENTS
numpy38
)

include_directories("${PYTHON_INCLUDE_DIRS}")
include_directories("${Boost_INCLUDE_DIRS}")
message(PYTHON_INCLUDE_DIRS " ${PYTHON_INCLUDE_DIRS}")
message(PYTHON_LIBRARIES " ${PYTHON_LIBRARIES}")
message(Boost_INCLUDE_DIRS " ${Boost_INCLUDE_DIRS}")
message(Boost_LIBRARIES " ${Boost_LIBRARIES}")

ADD_SUBDIRECTORY(01_HelloWorld)

2.2 内层cmakelist 文件:

这个也是找别人资料基础上修改的。#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY “配置输出python库的路径很有用”) ,这个配置库生成路径挺方便的,个人在linux下有用,cmakelist配置文件内部如果不特意指定的话默认生成的是python动态库,windows上修改后缀为.pyd,linux上后缀.so,个人测试过,将生成的库放到需要调用该库的python文件夹下就可以,个人生成的都是python动态库(虽然调用的boost用的是静态库)。

set(MODULE_NAME  hello_world)

# 设置静态库文件目录
#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 动态库文件目录
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 可执行文件目录
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

include_directories(${CMAKE_SOURCE_DIR})

add_library(${MODULE_NAME} SHARED
	HelloWorld.cpp
	)

if (UNIX)
  set_target_properties(${MODULE_NAME}
    PROPERTIES
    PREFIX ""
  )
elseif (WIN32)
  set_target_properties(${MODULE_NAME}
  PROPERTIES
  SUFFIX ".pyd"
  )
endif()

target_link_libraries(${MODULE_NAME}
  ${Boost_LIBRARIES}
  ${PYTHON_LIBRARIES}
)

2.3 内层HelloWorld.cpp文件:

需要注意的是BOOST_PYTHON_MODULE(hello_world)里面hello_world是模块名,这个需要和里层cmakelist里面的set(MODULE_NAME hello_world)相对应。具体的生成的python类名或者函数名不需要和c++定义时候一模一样,肯定需要做对应的映射,这就是BOOST_PYTHON_MODULE里面做的事情,配置就好,这个比自己先前利用swig做python库方便太多了。


#define BOOST_PYTHON_STATIC_LIB 
#define BOOST_NUMPY_STATIC_LIB 

#include <iostream>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
//#include <boost/python/list.hpp>
//#include <boost/python/return_by_value.hpp>
//#include <boost/python/return_value_policy.hpp>

namespace bp = boost::python;
namespace np = boost::python::numpy;

//using namespace bp;


bp::list list_reverse(bp::list& list1)
{
    std::cout << "Input list length c++ reverse: " << len(list1) << std::endl;

    list1.reverse();
    return list1;
}

bp::dict  dict_test(bp::dict& dt1)
{
    std::cout << "Input dict length : " << len(dt1) << std::endl;
    
    bp::list temKeys = dt1.keys();
    bp::list temValues = dt1.values();
   
    try {
        //Py_Initialize();//VS 嵌入python时调用初始化

        for (int i = 0; i < len(dt1); i++)
        {
            bp::object msg = "%s : %s" % bp::make_tuple(temKeys[i], temValues[i]);
            std::string dt1String = bp::extract<std::string>(msg);
            std::cout << dt1String << std::endl;
         
            //bp::object ignored = bp::exec("print('c++?')");//执行python代码 ?
        }

        //Py_Finalize();VS 嵌入python时调用结束 end?
    }
    catch (bp::error_already_set) {
        PyErr_Print();
    }

    if (dt1.has_key("a"))
    {
        std::string dt1String = bp::extract<std::string>(dt1["a"]);//key "a"需要存在,才能正常调用

        std::cout << "dict has_key a:" << dt1String << std::endl;
        
    }
    else
    {
        dt1.setdefault("a", "default");

        dt1.setdefault("float_pi", 3.1415926);
    }
        return dt1;
    }


void list_maps(bp::list& list2)
{
   auto a= list2[0];

   for (size_t i = 0; i < len(list2); i++)
   {
       a = list2[i];
       bp::dict dic = bp::extract<bp::dict>(a);

       bp::list temKeys = dic.keys();
       bp::list temValues = dic.values();
       std::cout << "list :"<<i << std::endl;
       for (int j = 0; j < len(dic); j++)
       {
           bp::object msg = "%s : %s" % bp::make_tuple(temKeys[j], temValues[j]);
           std::string dt1String = bp::extract<std::string>(msg);
           std::cout << dt1String << " ";
       }
       std::cout << "" << std::endl;
   }
}


void two_dimensional_array(np::ndarray & ndArray1)
{
    //ndArray1.get_shape();
    int shapeSize = ndArray1.get_nd();//len(ndArray1);
    std::cout << "shapeSize :" << shapeSize << std::endl;
    if (2 != shapeSize)
    {
        std::cout << "ndarray is not two dimension ,but is:" << shapeSize << std::endl;
        return;
    }
    auto shapePtr= ndArray1.get_shape();
    auto strdPtr = ndArray1.get_strides();
    std::cout << "shape is :[" << shapePtr[0]<<","<< shapePtr[1]<<"]" << std::endl;
    std::cout << "get_strides is :[" << strdPtr[0]<<","<< strdPtr[1]<<"]" << std::endl;
   
    int step = strdPtr[0] / strdPtr[1];
    std::cout << "step is :[" << step << "]" << std::endl;

    float* npF=  (float*)ndArray1.get_data();//浮点类型的话,strdPtr[1]=4(4个字节),strdPtr[0]/strdPtr[1]==shapePtr[1](列数)

    for (size_t i = 0; i < shapePtr[0]* shapePtr[1]; i++)
    {
        npF[i] *= npF[i];//square
    }
}


char const* greet()
{
   return "hello, world,are you ready";
}


float cppCallPythonLogaddexp(PyObject* func, int a, int b)
{
    //可不返回,目的是c++端调用python函数获得结果,比如某些函数很难通过编译c++的库进而调用(比如windows编译难,linux编译简单,则windows上临时调试可采用此方法,待移植到linux上用相应的c++库替换则可)

    float result = bp::call<float>(func, a, b);
    std::cout << "cppCallPythonLogaddexp(" << a << "," << b << ")=" << result << std::endl;

   return result;

}

class Cpp2PythonClass
{
public:
    Cpp2PythonClass(PyObject* func1) :cppLogaddexp(func1) {};
    ~Cpp2PythonClass() {};


    void callbackFun1Test() {
        int a1 = 55;
        int a2 = 66;
        float result2 = bp::call<float>(cppLogaddexp, a1, a2);//可保存函数func到本地,根据自己需要调用
        std::cout << "cppCallPythonLogaddexp(" << a1 << "," << a2 << ")=" << result2 << std::endl;
    };

private:
    PyObject* cppLogaddexp;

};



BOOST_PYTHON_MODULE(hello_world)
{
    using namespace boost::python;
    np::initialize();//boost::python::numpy;需要用到
  
    boost::python::def("greet", greet);//python调用名字和c++可不一致,但参数需要保持一致
    def("cppCallPythonLogaddexp", cppCallPythonLogaddexp);//好像不加return_value_policy<return_by_value>()也可以

    def("list_reverse", list_reverse, args("list1"), return_value_policy<return_by_value>());
    def("dict_test", dict_test, args("dt1"), return_value_policy<return_by_value>());

    def("list_maps", list_maps);
        
    def("two_dimensional_array", two_dimensional_array);


    class_<Cpp2PythonClass>("Cpp2PythonClass", boost::python::init<PyObject*>())
        .def("callbackFun1Test", &Cpp2PythonClass::callbackFun1Test);


}

自己是配置好cmakelist文件和c++等文件后,用cmake-gui.exe生产vs工程,然后配置vs工程里面库输出路径(放到需要调用该库的python文件下,如自己的test_boost_python_api.py),cmakelist文件没修改的话其实就不用cmake-gui.exe来配置了,每次修改cpp文件就可以了,vs上生成库就好。

2.4 python调用库的例子

test_boost_python_api.py文件内容如下,一些简单测试,比如传递list、dict和嵌套的数据,c++调用python的简单测试。

#!/usr/bin/env python
import sys
import numpy as np
import hello_world


def test1():
    l1 = ["df", "df2", "56", 1]
    l2 = hello_world.list_reverse(l1)
    hello_world.dict_test({"a": "aaa", "b": "222"})
    # print(l2)
    # print(hello_world.greet())
    list_dict = [
        {'g1': '545', 'h2': '什么', 'p1': -1.26, 'p2': -1.65854,'p3': -9.56447},
        {'g1': '545', 'h2': '纳尼', 'p1': -5.65, 'p2': -8.15,'p3': -63.15},
        {'g1': '545', 'h2': 'what?', 'p1': -15.9414, 'p2': -4.3564,'p3': -56.348},
        ]

    hello_world.list_maps(list_dict)


def test2():
    np1 = np.arange(15, dtype="float32").reshape(3, 5)
    print(type(np1), np1.dtype)
    print(np1)
    hello_world.two_dimensional_array(np1)
    print(np1)


def test3():
    dic1 = {"a1": "aaa", "b": "222"}

    hello_world.dict_test(dic1)
    print(dic1)
    print(type(dic1["float_pi"]))

    a2 = hello_world.cppCallPythonLogaddexp(np.logaddexp, 2, 3)
    print("a2==", a2)
    a1 = np.logaddexp(2, 3)
    print("python call np.logaddexp(2, 3)==", a1)

    print("\n----------------------Cpp2PythonClass----------------------------")
    cpc_obj = hello_world.Cpp2PythonClass(np.logaddexp)
    cpc_obj.callbackFun1Test()
    print("----------------------Cpp2PythonClass.callbackFun1Test ----------------------------\n")
    a3 = np.logaddexp(55, 66)
    print("python call np.logaddexp(55, 66)==", a3)


if __name__ == '__main__':
    test1()

    pass

结语:

以上做一些记录,不一定都对,个人很多时候都是尝试并加上自己的一些理解才弄出来的,对需要的人应该总有些启发和帮助的,长时间不用的东西肯定会忘记的,所以需要积累和记录,不过一旦看了记录就大致能明白怎么做了。评论一般不回的,谢谢,愿世界恶意少点,对自己可以严格点,但对别人需要宽容点,坏情绪会传染的。

人生需要多点尝试,也需时刻反省。交际、勇气、认知、格局等等限制了个人的能力,自己也是很菜的,搞工程、软件这么多年,没做太深、交际太差、弄得处境很难看,虽然还在挣扎,最后怕是难免转行,也不想抱怨什么,也不想评价别人,且行且珍惜,能通过分享节约别人的时间也好,什么都从头开始很不现实、也太难了,孤军奋战基本注定悲剧的…

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
# 一、项目介绍 **项目名称:天气预测和天气可视化** 天气预测和天气可视化是一个基于python机器学习(ml)的长春地区的天气预报项目,它实现了天气数据的爬取,预测和可视化。 项目结构如下: ![img](image/wps26.jpg) * 天气数据的来源 GetData文件使用python爬虫技术,爬取长春和全国的天气信息数据 爬取网站:http://tianqi.2345.com/wea_history/54161.htm ProcessDate文件对爬取的天气数据进行了预处理 几个CSV文件保存的是爬取后并经过处理的数据 * 天气数据的预测 GetModel文件通过训练预测模型来预测长春近一周的天气,该文件利用Joblib将模型保存到本地 Main文件是项目主文件,通过运行该文件即可运行整个项目,该文件前部分获取保存到本地的预测模型来进行预测,并将预测结果打印到控制台 * 天气数据的可视化 Main文件后部分实现了天气数据的可视化 # 二、详细介绍 本项目分为三个部分,即爬取和处理数据,数据预测(包含评价方法)和数据可视化 ## 1. 爬取和处理数据 数据爬取代码: ````py resq = requests.get(url, headers=headers, params=params) data = resq.json()["data"] # data frame df = pd.read_html(data)[0] ```` 即使用python爬取网站的json数据 ### **数据预处理:** 获取到的天气信息包括最高温,最低温都不是int格式的数字,通过对数据截取,将部分指标的数据变换为int类型 并对缺失值进行了处理 ````py my_imputer = SimpleImputer() imputed_X_train = pd.DataFrame(my_imputer.fit_transform(X_train)) imputed_X_valid = pd.DataFrame(my_imputer.transform(X_valid)) ```` 通过SimpleImputer ,可以将现实数据中缺失的值通过同一列的均值、中值、或者众数补充起来,本项目使用了SimpleImputer的fit_transform对缺失值进行填充 ## 2. 数据预测和模型评价方法 预测数据采用了机器学习算法——线性回归 模型使用过程: ### A. 提取数据 ````py 获取测试集、训练集、验证集 [X_train, X_valid, y_train, y_valid, X_test] = ProcessData.ProcessData() ```` 其中ProcessData()函数里使用了如下语句: ````py X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2, random_state=0) ```` train_test_split()是sklearn包的model_selection模块中提供的随机划分训练集和验证集的函数;使用train_test_split函数将完整的数据集和验证集以同等的比例分成2组不同的数据集和验证集 ### B. 训练模型 选择了随机树森林模型(randomforest),然后用fit来训练模型 ````py # 随机树森林模型 model = RandomForestRegressor(random_state=0, n_estimators=1001) # 训练模型 model.fit(X_train, y_train) ```` ### C. 根据数据预测 ````py # 最终预测结果 preds = model.predict(r[1]) -------- 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! <项目介绍> 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 --------
1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用!在使用过程中,如有问题或建议,请及时私信沟通。 2.主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 记录之前爬下来的中华传统文化内容,比如成语,诗词,谜语之类的 成语 30470 经史子集(带详情) 366本 7888章 91,226,112余字 诗词 38353 红色家书 红色景点 华夏山水(景点) 名句 11820 谜语 59997 谜语故事 160 中医偏方 5437 幼儿科普 歇后语 14033 谚语 5304 医书(带详情) 559本 49045章 86,507,520余字 中国神话 242 中药 5056 粥谱 400 酒方 924 周公解梦 人物 1045 国学经典(经,史,子,集,诗词,名句) 民族历史(成语,歇后语,神话传说,谚语,谜语,谜语故事) 中医养生(医书,粥谱,中药,酒方) 投稿栏目(传统文化,养生健康,民间工艺) 基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip基于Python实现中华传统文化内容爬取源码+项目说明.zip
【资源说明】 1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载食用体验! 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【项目介绍】 基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明. 基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip 基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip 基于Hadoop和Python实现对豆瓣电电影数据的爬取及可视化分析源码+项目说明.zip
【资源介绍】 课程大作业基于Python爬取各省降水量数据及可视化分析源码+项目说明+超详细注释. 因为相关课程的缘故,需要在1个月内速成python,故选取了python最常用的爬虫作为实操训练 同时,还添加了可视化和GUI入门的内容使爬取的内容应用更丰富 在具体数据的选取上,我爬取的是各省份降水量实时数据 ###### *话不多说,开始实操* ### 正文 1. 爬取数据 - 使用python爬虫,爬取中国天气网各省份24时整点气象数据 - 由于降水量为动态数据,以js形式进行存储,故采用selenium方法经xpath爬取数据 ps:在进行数据爬取时,最初使用的方法是漂亮汤法(beautifulsoup)法,但当输出爬取的内容(<class = split>时,却空空如也。在源代码界面Ctrl+Shift+F搜索后也无法找到降水量,后查询得知此为动态数据,无法用该方法进行爬取 - 使用循环和分类的方式爬取省份不同、网址相似的降水量数据,顺带记录数据对应的城市 - f—string: ```python url_a= f'http://www.weather.com.cn/weather1dn/101{a}0101.shtml' ``` *f-string 用大括号 {} 表示被替换字段,其中直接填入替换内容* - 将城市和降水量相对应后存入字典再打印 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,也适用于小白学习入门进阶。当然也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或者热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载,沟通交流,互相学习,共同进步!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值