C++下web框架corw的完全使用手册(实现中文支持)

corw是一个开源、轻量化的c++web库,在使用上与python的flask是类似的。本文档为corw的完整使用文档,含项目配置(基于cmakelist)、路由绑定、返回数据(json、文本、response对象、静态资源、模板文件)、接口请求处理(REST请求,url参数绑定、json请求、GET参数和POST参数)和各种高级操作(Cookie操作、Session操作、文件上传操作、文件下载操作、websocket操作、自定义loghandler)。此外,还对各类参数请求、结果返回过程中对中文的支持(如get参数、post参数、url参数、json结果中中文参数的正确解读)

1 基本设置

crow库官网:https://crowcpp.org/master/guides/app/
crow库源码:https://gitcode.net/mirrors/CrowCpp/Crow

1.1 Camkelist

项目的cmake文件如下,需要添加对asio、boost库的依赖,其中还补充了msysql库依赖(需要安装mysql)。

Asio库----》https://think-async.com/Asio/
下载地址:https://nchc.dl.sourceforge.net/project/asio/asio/1.26.0%20%28Stable%29/asio-1.26.0.zip

boost库----》https://www.boost.org/
下载地址:https://www.boost.org/users/news/

cmake_minimum_required(VERSION 3.5.1)
#生成生成项目的名称
project(CmakeTest)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_SUPPRESS_REGENERATION FALSE)
#设置生成release项目
set(CMAKE_BUILE_TYPE Release)
set(CMAKE_CXX_STANDARD 17)
# It prevents the decay to cpp98 when the compiler does not support cpp14
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# It disables the use of compiler-specific extensions
# e.g. -std=cpp14 rather than -std=gnu++14
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")file(GLOB SOURCE_FILES src/*.cpp)set(msysql_dir "C:/Program Files/MySQL/MySQL Server 8.0")
include_directories(${msysql_dir}/includeE:/Lib/Crow-master/includeE:/Lib/boost_1_77_0E:/Lib/asio-1.26.0/include${PROJECT_SOURCE_DIR}/include
)
link_directories(E:/Lib/boost_1_77_0/stage/lib${msysql_dir}/lib${PROJECT_SOURCE_DIR}/include
)
add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES})
target_link_libraries(${CMAKE_PROJECT_NAME}libmysql.lib${Opencv_lib}
)

1.2 起步设置

起步设置,设置对session的支持,

    using Session = crow::SessionMiddleware<crow::InMemoryStore>;crow::App<crow::CookieParser, Session> app{ Session{// customize cookiescrow::CookieParser::Cookie("session").max_age(/*one day*/ 24 * 60 * 60).path("/"),// set session id length (small value only for demonstration purposes)4,// init the storecrow::InMemoryStore{}} };app.loglevel(crow::LogLevel::Warning);

官网完整起步代码,gitcode地址 https://gitcode.net/mirrors/CrowCpp/Crow

#include "crow.h"int main()
{crow::SimpleApp app;CROW_ROUTE(app, "/")([](){return "Hello world";});app.port(18080).multithreaded().run();
}

1.3 首页绑定

可以用于绑定静态文件

//首页绑定CROW_ROUTE(app, "/")([](const crow::request&, crow::response& res) {res.set_static_file_info("templates/index.html");res.end();//return u8"你好 世界!";});

1.4 静态文件支持

在做路由函数外部,设置静态文件目录,可以实现全局下的静态资源访问

    //设置对静态文件的支持crow::response response;response.set_static_file_info("./");

2、返回各种数据

2.1 返回字符串

    //返回字符串CROW_ROUTE(app, "/api1")([]() {return "Hello world";});

2.2 返回json

{{“name”, “lisi”},{“age”,“32”}}对应json字符串{“name”:“lisi”,“age”:“32”},在crow中以以{name,value}构成json中的键值对

    //返回jsonCROW_ROUTE(app, "/json")([] {crow::json::wvalue x({ {"message", "Hello, World!"} });//添加新字段x["message2"] = "Hello, World.. Again!";return x.dump();//return x;});//返回json数组CROW_ROUTE(app, "/jsonarr")([] {crow::json::wvalue x({ {{"name", "lisi"},{"age","32"}},{{"name", "zhangsan"},{"age","28"}}});x[0]["message2"] = "new ";return x;});

2.3 返回response对象

前面的各种返回其实也是返回response对象,只是没有显性表示。这里可以实例化response对象,然后设置返回的具体对象

    //response细节CROW_ROUTE(app, "/response/<int>")([](int count) {//返回404crow::response resp;resp.code = 404;resp.body = "response body";//return resp;//返回重定向crow::response res;res.redirect("/");//return res;if (count > 100) {//// response(int code, std::string contentType, std::string body)return crow::response(500);//return crow::response(400);}std::ostringstream os;os << count << " bottles of beer!";return crow::response(os.str());});

2.4 返回静态页面

在路由函数中返回静态页面

CROW_ROUTE(app, "/")([](){auto page = crow::mustache::load_text("fancypage.html");return page;});

2.5 返回静态资源

在路由函数中返回静态资源文件

CROW_ROUTE(app, "/imgs/<string>")([](string fname){string final_path =  "static/" + fname + ".jpg";//cout << final_path << endl;crow::response res;res.set_static_file_info(final_path);return res;});

3、参数请求

3.1 url参数绑定及模板参数注入

模板文件需存储在templates目录下,html文件中的模板参数使用{{}}进行包裹

        <p>Hello {{user}}</p><p>体重 {{count}} kg</p>

url参数在请求时以,等形式描述,用/进行分割,在路由lambda函数的参数中又对相应类型顺序的参数进行命名,以便于在代码中使用

    //模板参数绑定CROW_ROUTE(app, "/set_name/<string>/<int>")([](std::string name,int count111) {auto page = crow::mustache::load("index.html");//ctx用于模板参数注入,以{name,value}构成参数对,再以{参数对,参数对,...}的形式构成参数集crow::mustache::context ctx({ {"user", "{set_name}:"+name},{"count",  count111} });return page.render(ctx);});

3.2 处理 JSON Requests

    CROW_ROUTE(app, "/add_json").methods("POST"_method)([](const crow::request& req) {auto x = crow::json::load(req.body);if (!x)return crow::response(crow::status::BAD_REQUEST); // same as crow::response(400)int sum = x["a"].i() + x["b"].i();std::ostringstream os;os << sum;return crow::response{ os.str() };});

3.3 GET参数及POST参数

GET参数的格式为url?key=value,是附加在url路径中的,同时可能还存在一个key对应多个value的情况,具体可查询官网https://crowcpp.org/master/guides/query-string/
GET参数获取示意,可以看到url_params.get用于获取一个值,url_params.get_list用于或者一组值

//get接口测试  127.0.0.1:18000/getfunc?key1=123&key2=342fsaj&keys=123&keys=sdfsCROW_ROUTE(app, "/getfunc").methods("GET"_method)([](const crow::request& req) {//req.url_params.get("key1") != nullptrstring key1=req.url_params.get("key1");string key2=req.url_params.get("key2");string keys0=req.url_params.get_list("keys")[0];string keys1=req.url_params.get_list("keys")[1];});

POST参数通常是通过表单传递的,表单格式如下:

    <form action="/postfunc" method="POST" enctype="multipart/form-data">文件:<input type="file" name="myfile00"  /><br/>name:<input type="input" name="name"  /><br/>size:<input type="input" name="size"  /><br/><input type="submit" value="提交" /></form>

POST接收数据的后端代码如下所示:

    CROW_ROUTE(app, "/postfunc").methods("POST"_method)([](const crow::request& req) {crow::multipart::message x(req);//req.url_params.get("key1") != nullptrstring name = x.get_part_by_name("name").body;string size = x.get_part_by_name("size").body;string file = x.get_part_by_name("myfile00").body;//post表单中的文件只需要将其以二进制格式写入文件即可实现文件上传std::string saveFilePath = "upname";// fileName + "." + fileExt;std::ofstream saveFile;//一定要以二进制格式写入文件saveFile.open(saveFilePath, std::ofstream::binary);saveFile << file;saveFile.close();});

3.4 REST接口区分测试

在绑定url时同时指定methods参数,然后在函数体内部依据req.method区分具体请求类型

//rest接口测试CROW_ROUTE(app, "/pathToApi/<int>").methods("GET"_method, "POST"_method, "DELETE"_method)([](const crow::request& req, const int& id) {if (req.method == "GET"_method){if ((req.url_params.get("v") != nullptr) & (req.url_params.get("q") != nullptr)){// ...}return crow::response(200, "You used GET");}else if (req.method == "POST"_method){return crow::response(200, "You used POST");}else if (req.method == "DELETE"_method){return crow::response(200, "You used DELETE");}else{return crow::response(404);}});

4、高级操作

4.1 Cookie操作

    CROW_ROUTE(app, "/read")([&](const crow::request& req) {auto& ctx = app.get_context<crow::CookieParser>(req);// Read cookies with get_cookieauto value = ctx.get_cookie("key");return "value: " + value;});CROW_ROUTE(app, "/write")([&](const crow::request& req) {auto& ctx = app.get_context<crow::CookieParser>(req);// Store cookies with set_cookiectx.set_cookie("key", "word")// configure additional parameters.path("/").max_age(120).httponly();return "ok!";});

4.2 Session操作

在实现session操作时,切记要使用crow::App<crow::CookieParser, Session>方法初始app对象

    //session测试//http://127.0.0.1:18080/session_set?key=a2&value=li%20si//http://127.0.0.1:18080/session_set?key=a2&value=li%20siCROW_ROUTE(app, "/session_set")([&](const crow::request& req) {auto& session = app.get_context<Session>(req);//get只能获取url|get参数中的一个auto key = req.url_params.get("key");//get_list可以获取url|get参数中多个相同的key的valueauto value = req.url_params.get_list("value", false)[0];session.set(key, value);//在设置session后不能立即使用seesion.get(),要在下一次请求中生效。return "set "+ std::string(key)+"="+ std::string(value);});CROW_ROUTE(app, "/session_get")([&](const crow::request& req) {auto& session = app.get_context<Session>(req);session.get("key", "not-found"); // get string by key and return "not-found" if not foundsession.get("int", -1);session.get<bool>("flag"); // returns default value(false) if not found//删除特定session值session.remove("key");auto keys = session.keys();// return list of keysstd::string out;for (std::string key : keys)// session.string(key) converts a value of any type to a stringout += "<p> " + key + " = "  + session.get(key, "_NOT_FOUND_"); +"</p>";return out;});

4.3 上传文件操作

在上传文件时,获取附件名称时存在bug,无法准确的获取中文名称

    //upload测试CROW_ROUTE(app, "/uploader").methods("POST"_method)([](const crow::request& req) {crow::multipart::message x(req);std::string upname;std::string fileName;std::string fileExt;auto ss = x.get_part_by_name("myfile00");//获取前端from中input标签中name为myfile00的输入auto shead = ss.get_header_object("content-disposition");if (shead.params.size() != 0){upname = getFileNameFull(shead.params);//std::string fnf());int found(upname.find('.'));fileName = std::string(upname.substr(0, found));fileExt = std::string(upname.substr(found + 1));}std::string saveFilePath = upname;// fileName + "." + fileExt;std::ofstream saveFile;//一定要以二进制格式写入文件saveFile.open(saveFilePath, std::ofstream::binary);saveFile << ss.body;saveFile.close();return upname;});

上述代码中getFileNameFull的实现方式如下

inline std::string getFileNameFull(std::unordered_map<std::string, std::string>& map)
{for (auto pair : map){if (pair.first == "filename"){return pair.second;}}return "";
}

上传文件页面中的表单的写法

    <form action="/uploader" method="POST" enctype="multipart/form-data"><input type="file" name="myfile00"  /><input type="file" name="myfile11"  /><input type="submit" value="提交" /></form>

4.4 文件下载操作

在crow中文件下载与返回静态资源基本上是一样的,只是在返回文件下载流时要设置Content-Type与Content-Disposition,若不设置在输出图片或文本文件时会被浏览器强制解析,而无法弹出下载框。这里实现的下载并不支持多线程分段下载。

    //下载图片CROW_ROUTE(app, "/download/<string>/<string>")([&](string dir,string name) {string final_path= dir + "/" + name;//cout << final_path << endl;crow::response res;res.set_header("Content-Type","application/octet-stream");res.set_header("Content-Disposition", "attachment; filename="+ name);res.set_static_file_info(final_path);return res;});

若要实现文件的多线程分段下载,首先要从http请求头中获取Range信息,然后解析出该http请求所对应的文件起始位置和结束位置。在crow中返回的Range是一个字符串,需要自行进行字符串分割,然后将字符串转换为int。

req.get_header_value("Range");//Range: bytes=0-1199  其中位置0,结束位置1199

然后加载二进制文件,按照strat位置和end位置,读取二进制数据到bufer中(一般为char或byte数组)
最后将二进制转string,具体可参考https://www.jb51.net/article/55960.htm,将string赋值给resp.body,并设置好相应的http状态

res.body = bin2str(bin_data);
res.set_header("Content-Length","1200");//响应http请求头中Range的长度
res.set_header("Content-Range","bytes 0-1199/5000");//响应http请求头中Range的位置
res.set_header("Content-Type","application/octet-stream");
res.set_header("Content-Disposition", "attachment; filename="+ name);

前端通过多线程优化图像加载可以参考https://blog.csdn.net/frontend_frank/article/details/109506324

4.5 websocket操作

#include "crow.h"
#include <unordered_set>
#include <mutex>int main()
{crow::SimpleApp app;std::mutex mtx;std::unordered_set<crow::websocket::connection*> users;CROW_WEBSOCKET_ROUTE(app, "/ws").onopen([&](crow::websocket::connection& conn) {CROW_LOG_INFO << "new websocket connection from " << conn.get_remote_ip();std::lock_guard<std::mutex> _(mtx);users.insert(&conn);}).onclose([&](crow::websocket::connection& conn, const std::string& reason) {CROW_LOG_INFO << "websocket connection closed: " << reason;std::lock_guard<std::mutex> _(mtx);users.erase(&conn);}).onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary) {std::lock_guard<std::mutex> _(mtx);//给当前用户发信息conn.send_text(data);//给所有用户发信息for (auto u : users){if (is_binary){u->send_binary(data);}else{u->send_text(data);}}});CROW_ROUTE(app, "/")([] {char name[256];gethostname(name, 256);crow::mustache::context x;x["servername"] = name;auto page = crow::mustache::load("ws.html");return page.render(x);});app.port(18080).multithreaded().run();
}

模板代码如下,在templates目录下创建ws.html

<!doctype html>
<html>
<head><script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
</head>
<body><input id="msg" type="text"></input><button id="send">Send</button><BR><textarea id="log" cols=100 rows=50></textarea><script>
var sock = new WebSocket("ws://{{servername}}:18080/ws");sock.onopen = ()=>{console.log('open')
}
sock.onerror = (e)=>{console.log('error',e)
}
sock.onclose = (e)=>{console.log('close', e)
}
sock.onmessage = (e)=>{$("#log").val(e.data +"\n" + $("#log").val());
}
$("#msg").keypress(function(e){if (e.which == 13){sock.send($("#msg").val());$("#msg").val("");}
});
$("#send").click(()=>{sock.send($("#msg").val());$("#msg").val("");
});</script>
</body>
</html>

4.6 自定义loghandler

使用crow输出log信息时,时区与中国东八区差了8个小时,为修正log信息时间的准确性,可以自定义loghandler;也可也查看logging.h,修改关键代码(如添加#define CROW_USE_LOCALTIMEZONE,使用localtime_s函数生成时间)

//获取指定格式的时间字符串
inline string get_now_time(string format= "%Y-%m-%d %H:%M:%S") {time_t rawtime;struct tm* info;char buffer[80];time(&rawtime);info = localtime(&rawtime);strftime(buffer, 80, format.c_str(), info);string stime(buffer);return stime;
}
//设置log记录器
class CustomLogger : public crow::ILogHandler {
public:int loglevel;CustomLogger(int loglevel) {this->loglevel = loglevel;}void log(std::string message, crow::LogLevel level) {// "message" doesn't contain the timestamp and loglevel// prefix the default logger does and it doesn't end// in a newline.int intlevel = (int)level;if (loglevel >= intlevel) {string time = get_now_time();std::cerr << time <<" " << message << std::endl;}}
};//---------------//绑定log记录器CustomLogger logger(0);crow::logger::setHandler(&logger);//app.loglevel(crow::LogLevel::Warning);//使用默认loghandler
//--------------

5、中文支持

post参数中文支持、 url|get参数中文支持、json结果中文支持请参考https://download.csdn.net/download/a486259/87471152的最后一部分

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL的基础知识

目录 关系型数据库 SQL通用语法 数据类型 数值类型 字符串类型 日期类型 SQL分类 DDL 数据库操作 表操作 DML 添加数据 修改数据 删除数据 DQL 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 执行顺序 DCL 管理用户 权限控制 函数 字符串…

iar如何全擦芯片内存

Project ->Download -> Erase memory

js逆向-某敏感网站登录参数分析

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 如果觉得文章对你有所帮助&#xff0c;可以给博主点击关注和收藏哦&#xff01; 前言 目标网站&#xff1a;aHR0cHM6Ly9tZGZnaGcuNXhwb2lqaHRm…

五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度(MATLAB)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标鳟海鞘算法MSSA 多目标优化算法&#xff1a;多目标鳟海鞘算法&#xff08;Multi-objective Salp Swarm Algorithm &#xff0c;MSSA&#xff09;-CSDN博客 参考文献&#xff1a; S. Mirjalili, A.H. Gandomi, S.Z. M…

【Kotlin】高阶函数和Lambda

文章目录 高阶函数抽象和高阶函数方法引用表达式使用场景 Lambda表达式lambda表达式类型Lambda开销 闭包Java实现闭包Kotlin中的闭包 高阶函数 Kotlin天然支持了部分函数式特性。我们可以在一个函数内部定义一个局部函数。 fun foo(x: Int) {fun double(y: Int): Int {return…

flex布局实战之自动填充剩余

案例目标 文字部分自适应并且居中 图中是一个弹窗&#xff0c;我现在使用flex的布局来实现&#xff0c;标题和关闭按钮。因为是uni-app,所以标签是view 。你可以自行替换为 代码 <view class"popup-box"><view class"title"><view class&…

线程的状态以及状态转移

一. 线程的状态 NEW: 线程刚被创建, 但是并未启动. 还没调用start方法.RUNNABLE: 这里没有区分就绪和运行状态. 因为对于Java对象来说, 只能标记为可运行, 至于什么时候运行, 不是JVM来控制的了, 是OS来进行调度的, 而且时间非常短暂, 因此对于Java对象的状态来说, 无法区分.T…

机器学习探索计划——KNN实现Iris鸢尾花分类

文章目录 1. 加载数据集2.拆分数据集3.预测4.评价 1. 加载数据集 import numpy as np from sklearn import datasetsiris datasets.load_iris()iris.keys()dict_keys([data, target, frame, target_names, DESCR, feature_names, filename, data_module])X iris.data X.shap…

97、Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields

简介 论文地址 使用扩散模型来推断文本相关图像作为内容先验&#xff0c;并使用单目深度估计方法来提供几何先验&#xff0c;并引入了一种渐进的场景绘制和更新策略&#xff0c;保证不同视图之间纹理和几何的一致性 实现流程 简单而言&#xff1a; 文本-图片扩散模型生成一…

STM32入门学习(一):STM32 简介与软件安装

参考引用 STM32 入门教程-江科协 1. STM32 简介 1.1 STM32 套件介绍 1.2 STM32 简介 STM32 是 ST 公司基于 ARM Cortex-M 内核开发的 32 位微控制器 应用&#xff1a;嵌入式领域&#xff0c;如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 1.3 ARM …

go对rabbitmq基本操作

一、安装rabbitmq 1、直接使用docker拉取镜像 docker pull rabbitmq:3.82、启动容器 docker run \-e RABBITMQ_DEFAULT_USERadmin \-e RABBITMQ_DEFAULT_PASS123456 \-v mq-plugins:/plugins \--name rabbit01 \--hostname rabbit01 --restartalways \-p 15672:15672 \-p 5672:…

《C++PrimePlus》第9章 内存模型和名称空间

9.1 单独编译 Visual Studio中新建头文件和源代码 通过解决方案资源管理器&#xff0c;如图所示&#xff1a; 分成三部分的程序&#xff08;直角坐标转换为极坐标&#xff09; 头文件coordin.h #ifndef __COORDIN_H__ // 如果没有被定义过 #define __COORDIN_H__struct pola…

【开源】基于Vue.js的城市桥梁道路管理系统的设计和实现

项目编号&#xff1a; S 025 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S025&#xff0c;文末获取源码。} 项目编号&#xff1a;S025&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…

学生信息管理系统程序Python

系统主界面 在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;有两种方法&#xff0c; 一种是输入1&#xff0c;另一种是按下键盘上的↑或↓方向键进行选择。这两种方法的结果是一样的&#xff0c;所以使用哪种方法都可以。 &#xff08;…

时间序列预测 — Informer实现多变量负荷预测(PyTorch)

目录 1 实验数据集 2 如何运行自己的数据集 3 报错分析 1 实验数据集 实验数据集采用数据集4&#xff1a;2016年电工数学建模竞赛负荷预测数据集&#xff08;下载链接&#xff09;&#xff0c;数据集包含日期、最高温度℃ 、最低温度℃、平均温度℃ 、相对湿度(平均) 、降雨…

什么是零拷贝 、零拷贝优化方案 - 真正的零拷贝,哪些地方会用到零拷贝技术

文章目录 什么是零拷贝3、零拷贝优化方案 - 真正的零拷贝哪些地方会用到零拷贝技术 现在来谈谈零拷贝&#xff0c;以及在开发中哪些地方使用到零拷贝。 开干… 什么是零拷贝 零拷贝指的是&#xff0c;从一个存储区域到另一个存储区域的copy任务无需CPU参与就可完成。零拷贝的底…

setInterval 和 setTimeOut 区别

在 JavaScript 中&#xff0c;setInterval 和 setTimeout 都是用于执行函数或代码片段的定时器函数。它们之间的区别在于触发执行的时间和执行的次数。 setInterval&#xff1a; setInterval 函数会按照指定的时间间隔重复执行函数或代码片段。语法&#xff1a;setInterval(ca…

徕芬不是满分:自称超越戴森,用户称多次故障,品控仍是老大难?

撰稿|行星 来源|贝多财经 “双十一”购物节落下帷幕后&#xff0c;各大品牌纷纷公布“战报”。其中&#xff0c;高速吹风机品牌徕芬&#xff08;也称“徕芬科技”&#xff09;销售额超4.4亿元&#xff0c;全系产品销量超过80万台&#xff0c;高速吹风机系列单品(LF03、SE)销售…

来自Microsoft Teams的摄像头背景图片

原文件在&#x1f446;&#xff0c;下面是预览图 如果你安装了Microsoft Teams也可以搜索MSTeams&#xff0c;就在MSTeams/Backgrounds

【anaconda】numpy.dot 向量点乘小技巧

假设向量A[1,1], 向量B[2,3]。如果想知道他们的内积就可以输入如下代码: 当然&#xff0c;如果是两个列向量相乘&#xff0c;肯定是不对的 但是如果没有维度也一样可以求得内积&#xff0c;而且结果不会套在列表里