第5讲:建立自己的C函数库,js调用自己写的C/C++函数,并包含依赖C/C++第三方静态库。

在javascript中,Array有很多内置的功能,比如Array.map,Array.filter,Array.find等等,能用内置的功能就用内置的功能,最好不要自己实现一套,因为底层调用的可能压根就不是js语言本身,底层的实现可能由C/C++实现的。如果我们要做的一些功能,需要高性能密集计算,但是JavaScript内置函数无法满足我们要求的时候,这时候我们就要自己用C/C++编写一个程序,然后封装成wasm文件给JavaScript调用了,此时wasm还包含了.a文件这样的第三方库。

我们这里有个需求,就是在地球上有两艘船,船A和船B在某个经纬度位置触发,以某个航向、速度行驶,求它们间最小距离是多少,达到最小距离的时候,经过时间是多少秒?
首先这个功能用C/C++来编写,并且还要用到开源第三方库。
下图的红圈注释里面有几个参数,分别表示经度、纬度、速度、航向,当然getCPA最后一个参数6.5表示6.5分钟的时间长度。表示计算6.5分钟以内,两船最小距离是多少,并且到达最小距离时,经过时间是多少。
在这里插入图片描述

打开Visual Studio 2022,新建一个cmake工程,项目名称为GeoCompute。这仅仅是一个测试项目,如果测试通过,没有问题了,就把该代码交给emcc或者em++去编译。
CMakeLists.txt文件内容如下:
在这里,我采用vcpkg来安装GeographicLib库
可以执行如下命令安装。

vcpkg install GeographicLib:x64-windows
# CMakeList.txt: GeoCompute 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
set(VCPKG_ROOT "D:/CppPkg/WinVcpkg/vcpkg" CACHE PATH "")
set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
  cmake_policy(SET CMP0141 NEW)
  set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()

project ("GeoCompute")

# 将源代码添加到此项目的可执行文件。
add_executable (GeoCompute "GeoCompute.cpp"  )


find_package (GeographicLib CONFIG REQUIRED)
target_link_libraries (GeoCompute PRIVATE ${GeographicLib_LIBRARIES})

if (CMAKE_VERSION VERSION_GREATER 3.12)
  set_property(TARGET GeoCompute PROPERTY CXX_STANDARD 20)
endif()

# TODO: 如有需要,请添加测试并安装目标。

然后编写一个GeoCompute.cpp文件

#include <iostream>
#include <GeographicLib/Geodesic.hpp>
#include <GeographicLib/Constants.hpp>
#include <cmath>
#include <vector>

const double EARTH_RADIUS = 6377830.0;  // 地球的平均半径,单位为千米
const double M_PI = 3.14159265359;

struct LatLon {
    double first;
    double second;
};

double deg2rad(double deg) {
    return deg * M_PI / 180.0;
}

double haversine_distance(double lat1, double lon1, double lat2, double lon2) {
    double dlat = deg2rad(lat2 - lat1);
    double dlon = deg2rad(lon2 - lon1);

    double a = std::sin(dlat / 2) * std::sin(dlat / 2) +
        std::cos(deg2rad(lat1)) * std::cos(deg2rad(lat2)) *
        std::sin(dlon / 2) * std::sin(dlon / 2);

    double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));

    return EARTH_RADIUS * c;
}

LatLon new_position_with_geolib(double lat, double lon, double speed, double cog, double T) {
    const GeographicLib::Geodesic& geod = GeographicLib::Geodesic::WGS84();

    double s12 = speed * T;
    double lat2, lon2;

    // Direct method gives the destination point given start point, initial azimuth, and distance
    geod.Direct(lat, lon, cog, s12, lat2, lon2);
    return { lat2, lon2 };
}

double new_distance(double T, double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB) {
    auto resA = new_position_with_geolib(latA, lonA, speedA, cogA, T);
    auto resB = new_position_with_geolib(latB, lonB, speedB, cogB, T);
    return haversine_distance(resA.first, resA.second, resB.first, resB.second);
}

LatLon getCPA(double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB, double tcpa) {
    double RES_TCPA = INFINITY;
    double RES_DCPA = INFINITY;
    double prev_dist = INFINITY;
    double cur_dist = INFINITY;
    std::vector<int> status;
    int t_lim = tcpa * 60;
    int step = 1;
    if (t_lim > 600) {
        step = int(double(t_lim) / 300.0);
    }
    for (int t = 0;t < t_lim; t += step) {
        prev_dist = new_distance(t, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
        cur_dist = new_distance(t + step, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
        if (prev_dist < RES_DCPA) {
            RES_DCPA = prev_dist;
        }
        if (cur_dist - prev_dist <= 0) {
            if (status.size() == 0) {
                status.emplace_back(-1);
            }
        }
        else {
            if (status.size() == 0) {
                status.emplace_back(1);
                break;
            }
            else {
                if (status[0] == -1) {
                    status.emplace_back(1);
                }
            }
        }
        if (status.size() == 2 && status[0] == -1 && status[1] == 1) {
            RES_TCPA = t;
            break;
        }
    }
    return { RES_TCPA, RES_DCPA };
}

//1.  一开始距离就变大
// 2. 从0时刻到指定tcpa一直减小
// 3. 从0时刻到指定tcpa,先减小后增大
int main() {
    double lat = 40, lon = 100, speed = 10, cog = 45, T = 3600;
    auto result = new_position_with_geolib(lat, lon, speed, cog, T);
    std::cout << "New Latitude: " << result.first << ", New Longitude: " << result.second << std::endl;
    /*
    latA, lonA, speedA, cogA = 21.3058, 109.1014, 15.12, 187.13
    latB, lonB, speedB, cogB = 21.288205, 109.118725, 3.909777, 254.42
    */
    int i = 0;
    while (true) {
        // 先减小后增大
        auto res_ = getCPA(21.3058, 109.1014, 15.12, 187.13, 21.288205, 109.118725, 3.909777, 254.42, 6.5);
        //auto res_ = getCPA(22.3058, 108.1014, 15.12, 187.13, 21.288205, 109.118725, 3.909777, 254.42, 6.0);
        //auto res_ = getCPA(0.0, 0.0, 15.12, 225.0, 0.0000001, 0.0000001, 3.909777, 45.0, 6.0);
        std::cout << res_.first << " --- " << res_.second << std::endl;
        i++;
        printf("%d\n", i);
    }

    return 0;
}

好了,如果代码测试完成了。现在我们创建一个cmake工程,项目名为EmscriptenTest,这是用来生成wasm文件和js文件来给JavaScript调用的。

由于JavaScript运行在浏览器,不能直接支持windows的lib静态库,所以想办法得到.a库。
首先从github拉取geographiclib库的源码

 git clone https://github.com/geographiclib/geographiclib.git

然后进入到根目录:

cd geographiclib

最后用emcmake和emmake命令编译代码(前提是要安装emsdk:官网有教程说明:https://emscripten.org/docs/getting_started/downloads.html)

emcmake cmake .
emmake make

编译完成之后:
![(https://img-blog.csdnimg.cn/direct/3e76d9aba3834e01b2894e163ee5fcc8.png)

转到目录geographiclib/src
找到libGeographicLib.a文件,然后把该文件拷贝到EmscriptenTest项目的lib目录下(如果没有lib目录则自己新建)

然后是CMakeLists.txt文件:

# CMakeList.txt: EmscriptenTest 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
set(CMAKE_TOOLCHAIN_FILE "D:/CppPkg/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
# 手动设置GeographicLib的路径
set(GEOGRAPHICLIB_INCLUDE_DIR "/path/to/vcpkg/installed/x64-windows/include")
#set(GEOGRAPHICLIB_LIB_DIR "/path/to/vcpkg/installed/x64-windows/lib")
set(GEOGRAPHICLIB_LIB_DIR "填写你的EmscriptenTest/lib目录的绝对路径")

# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
  cmake_policy(SET CMP0141 NEW)
  set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()

project ("EmscriptenTest")

# 将源代码添加到此项目的可执行文件。
add_executable (EmscriptenTest "GeoCompute.cpp")

include_directories(D:/CppPkg/WinVcpkg/vcpkg/installed/x64-windows/include )
target_link_libraries(EmscriptenTest ${GEOGRAPHICLIB_LIB_DIR}/libGeographicLib.a)

add_library(GeoCompute STATIC GeoCompute.cpp)
set_target_properties(GeoCompute PROPERTIES SUFFIX ".wasm")
set_target_properties(GeoCompute PROPERTIES LINK_FLAGS "--bind -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='GeoComputeModule' -s EXPORTED_FUNCTIONS='[\"getCPA\"]'")


if (CMAKE_VERSION VERSION_GREATER 3.12)
  set_property(TARGET EmscriptenTest PROPERTY CXX_STANDARD 20)
endif()

# TODO: 如有需要,请添加测试并安装目标。


这个EmscriptenTest.cpp没什么用,里面写个main函数,直接return 0;就完事儿了。
在项目根目录下新建GeoCompute.cpp文件
GeoCompute.cpp文件的内容:
可能会提示报错,但是如果点击重新生成,生成成功的话,是没事儿的。
在这个文件中,我修改了getCPA函数的返回类型为double* , 因为直接返回TDCPA结构体,JavaScript是无法识别的,一定要返回一个指针。

#include <iostream>
#include <GeographicLib/Geodesic.hpp>
#include <GeographicLib/Constants.hpp>
#include <cmath>
#include <vector>
#include <emscripten/emscripten.h>
const double EARTH_RADIUS = 6377830.0;  // 地球的平均半径,单位为千米
const double _M_PI = 3.14159265359;

struct LatLon {
    double lat;
    double lon;
};

struct TDCPA {
    double res_tcpa;
    double res_dcpa;
};

extern "C" {
    EMSCRIPTEN_KEEPALIVE
    double deg2rad(double deg) {
        return deg * _M_PI / 180.0;
    }
    EMSCRIPTEN_KEEPALIVE
    double haversine_distance(double lat1, double lon1, double lat2, double lon2) {
        double dlat = deg2rad(lat2 - lat1);
        double dlon = deg2rad(lon2 - lon1);

        double a = std::sin(dlat / 2) * std::sin(dlat / 2) +
            std::cos(deg2rad(lat1)) * std::cos(deg2rad(lat2)) *
            std::sin(dlon / 2) * std::sin(dlon / 2);

        double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));

        return EARTH_RADIUS * c;
    }
    EMSCRIPTEN_KEEPALIVE
    LatLon new_position_with_geolib(double lat, double lon, double speed, double cog, double T) {
        const GeographicLib::Geodesic& geod = GeographicLib::Geodesic::WGS84();

        double s12 = speed * T;
        double lat2, lon2;

        // Direct method gives the destination point given start point, initial azimuth, and distance
        geod.Direct(lat, lon, cog, s12, lat2, lon2);
        return { lat2, lon2 };
    }
    EMSCRIPTEN_KEEPALIVE
    double new_distance(double T, double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB) {
        auto resA = new_position_with_geolib(latA, lonA, speedA, cogA, T);
        auto resB = new_position_with_geolib(latB, lonB, speedB, cogB, T);
        return haversine_distance(resA.lat, resA.lon, resB.lat, resB.lon);
    }

    EMSCRIPTEN_KEEPALIVE
    double* getCPA(double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB, double tcpa) {
        double RES_TCPA = INFINITY;
        double RES_DCPA = INFINITY;
        double prev_dist = INFINITY;
        double cur_dist = INFINITY;
        double* tdcpaPtr = new double[2];
        std::vector<int> status;
        int t_lim = tcpa * 60;
        int step = 1;
        if (t_lim > 600) {
            step = int(double(t_lim) / 300.0);
        }
        for (int t = 0; t < t_lim; t += step) {
            prev_dist = new_distance(t, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
            cur_dist = new_distance(t + step, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
            if (prev_dist < RES_DCPA) {
                RES_DCPA = prev_dist;
            }
            if (cur_dist - prev_dist <= 0) {
                if (status.size() == 0) {
                    status.emplace_back(-1);
                }
            }
            else {
                if (status.size() == 0) {
                    status.emplace_back(1);
                    break;
                }
                else {
                    if (status[0] == -1) {
                        status.emplace_back(1);
                    }
                }
            }
            if (status.size() == 2 && status[0] == -1 && status[1] == 1) {
                RES_TCPA = t;
                break;
            }
        }
        tdcpaPtr[0] = RES_TCPA;
        tdcpaPtr[1] = RES_DCPA;
        return tdcpaPtr;
    }

}

好了,现在就测试全部重新生成。如果没有报错,则通过测试。通过测试之后。证明可以大胆使用em++命令直接编译GeoCompute.cpp为wasm文件了。

打开cmd进入到EmscriptenTest工程根目录。

em++ GeoCompute.cpp -O1 -o libGeoCompute.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME="GeoComputeModule" -s "EXPORTED_FUNCTIONS=['_deg2rad', '_haversine_distance', '_new_position_with_geolib', '_new_distance', '_getCPA']" -s "EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" -I D:/CppPkg/WinVcpkg/vcpkg/installed/x64-windows/include -L D:/HighPerformanceProjects/CppProjects/EmscriptenTest/lib -lGeographicLib

我这里导出了所有的函数,函数前面要加上下划线。但是在cmakelists.txt中不需要加下划线。
执行完成命令之后,会生成libGeoCompute.js和libGeoCompute.wasm文件。

在这里插入图片描述

现在可以使用npm创建一个原生的JavaScript项目。
这是工程目录结构:
在这里插入图片描述
最重要是main.js的写法:

const fs = require('fs');
const path = require('path');

// 加载 Emscripten 生成的模块
const Module = require('./libGeoCompute.js');

async function main() {
  try {
    const instance = await Module({
      locateFile: (filename) => path.join(__dirname, filename),
    });

    console.log('Module instance:', instance);
    console.log(typeof instance.cwrap)
    // await new Promise((resolve) => {
    //   console.log('Waiting for runtime initialization...');
    //   instance.onRuntimeInitialized = () => {
    //     console.log('Runtime initialized.');
    //     resolve();
    //   };
    // });

    // 使用 cwrap 包装 getCPA 函数
    const getCPA = instance.cwrap('getCPA', 'number', [
      'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'
    ]);
    console.log(typeof getCPA)
    // 调用导出的 getCPA 函数
    const resultPtr = getCPA(21.3058, 109.1014, 15.12, 187.13, 21.288205, 109.118725, 3.909777, 254.42, 6.5);
    console.log(typeof resultPtr)
    // 解析返回值(假设返回指向结构体的指针)
    const res_tcpa = instance.HEAPF64[resultPtr >> 3]; // 读取double指针的第0个元素
    const res_dcpa = instance.HEAPF64[(resultPtr >> 3) + 1]; // 读取地址偏移量+1

    console.log('TCPA:', res_tcpa);
    console.log('DCPA:', res_dcpa);

    console.log('Continuing after runtime initialization.');

    // 继续下面的逻辑...
  } catch (error) {
    console.error('Error:', error);
  }
}

main();

index.html的内容:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Blank Window</title>
</head>
<body>
  <!-- 这里可以添加窗体的内容 -->
</body>
</html>

package.json的内容:

{
  "name": "nodedevtest",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "main.js",
  "scripts": {
    "start": "node main.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.6.8",
    "electron": "^30.0.1",
    "pixi.js": "^8.1.0",
    "request": "^2.88.2"
  }
}

得出的结果:
在这里插入图片描述

得出的结果是154和1519.5687501879786
表示经过154秒以后,两船达到最小距离,并且最小距离为1519米多。

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

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

相关文章

从零开始了解GPT-4o模型:它是如何工作的?

人工智能&#xff08;AI&#xff09;技术正以惊人的速度发展&#xff0c;其中最引人注目的是OpenAI发布的GPT-4o模型。作为GPT系列的新成员&#xff0c;GPT-4o在多模态输入处理和响应速度上取得了重大进展。本文将深入探讨GPT-4o的工作原理&#xff0c;帮助您全面了解这一尖端A…

【教程】DPW 325T FPGA板卡程序下载与固化全攻略

到底什么是固化&#xff1f;&#xff1f;&#xff1f; 在开发板领域&#xff0c;"固化"通常指的是将软件或操作系统的镜像文件烧录&#xff08;Flash&#xff09;到开发板的存储介质上&#xff0c;使其成为开发板启动时加载的系统。这个过程可以确保开发板在启动时能…

Java日志 - JUL

一、JUL学习总结 &#xff08;1&#xff09;总结 JDK自带的日志系统中已经为我们创建了一个顶层的RootLogger&#xff0c;可以针对这个顶层的RootLogger设置多个Handler&#xff08;如ConsoleHandler, FileHandler等&#xff09;&#xff0c;如果想在控制台输出debug级别以上的…

生命在于学习——Python人工智能原理(2.6.1)

六 Python的文件系统 6.1 打开文件 在Python中&#xff0c;可以使用内置的open函数来打开文件&#xff0c;open函数的基本语法如下&#xff1a; file open(file_name, moder, buffering-1, encodingNone, errorsNone, newlineNone, closefdTrue, openerNone)参数说明&#…

IIS在Windows上的搭建

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 目录 一 概念&#xff1a; 二网络…

Mozilla Firefox正在尝试集成ChatGPT等帮助用户总结或改写网页内容

Mozilla基金会开启了一项新计划&#xff1a;在接下来几个月里尝试在Firefox浏览器里集成 ChatGPT 等 AI 服务&#xff0c;帮助用户在网页上总结内容或者改写内容等。Firefox浏览器集成的 AI 服务包括但不限于 ChatGPT、Google Gemini、HuggingChat 等&#xff0c;当然这并不是把…

vue3import的插件全局引入

webpack 的引入 npm install -D unplugin-auto-import const AutoImport require(unplugin-auto-import/webpack).default;configureWebpack: {devtool: source-map,module: {rules: [{test: /\.mjs$/,include: /node_modules/,type: javascript/auto}],}, plugins: [Aut…

超详细的Pycharm使用虚拟环境搭建Django项目并创建新的虚拟环境教程

一、什么是虚拟环境&#xff1f; 通过软件虚拟出来的开发环境&#xff0c;不是真实存在的&#xff0c;一般在多套环境开发时会用到。 二、为什么要使用虚拟环境&#xff1f; 虚拟环境为不同的项目创建不同的开发环境&#xff0c;开发环境内所有使用的工具包互不影响。比如项…

安全工具 | BurpSuite安装使用(保姆级教程!)

Burp Suite下载,破解,代理web,代理模拟器 (一)为Burp Sutie下载运行执行脚本环境(Java) 1.Java官网下载地址&#xff1a;https://www.oracle.com/java/technologies/ 下载Java SE 17.0.8(LTS) 备注&#xff1a;1.2023版Burp Suite 完美的运行脚本的环境是Java17 2.Java8不支持…

matlab中函数meshgrid

(1) 二维网格 [X,Y] meshgrid(x,y) 基于向量 x 和 y 中包含的坐标返回二维网格坐标。X 是一个矩阵&#xff0c;每一行是 x 的一个副本&#xff1b;Y 也是一个矩阵&#xff0c;每一列是 y 的一个副本。坐标 X 和 Y 表示的网格有 length(y) 个行和 length(x) 个列。 x 1:3; y…

昇思25天学习打卡营第8天 | 保存与加载 使用静态图加速

保存与加载 在训练网络模型的过程中&#xff0c;实际上我们希望保存中间和最后的结果&#xff0c;用于微调&#xff08;fine-tune&#xff09;和后续的模型推理与部署&#xff0c;下面是介绍如何保存与加载模型。 先定义一个模型用&#xff1a; import numpy as np import m…

grpc学习golang版( 五、多proto文件示例)

系列文章目录 第一章 grpc基本概念与安装 第二章 grpc入门示例 第三章 proto文件数据类型 第四章 多服务示例 第五章 多proto文件示例 第六章 服务器流式传输 文章目录 一、前言二、定义proto文件2.1 公共proto文件2.2 语音唤醒proto文件2.3 人脸唤醒proto文件2.4 生成go代码2.…

最佳Google Chrome扩展和Mozilla Firefox扩展自动解决验证码

在这个信息爆炸的时代&#xff0c;我们每天都要处理大量的在线内容&#xff0c;验证码已成为不可避免的挑战。尽管它们旨在保护网站安全&#xff0c;但也常常成为我们获取信息的障碍。那么&#xff0c;有没有更简单的方法绕过这些验证码呢&#xff1f;答案是肯定的。通过使用一…

恭喜朱雀桥的越南薇妮她牌NFC山竹汁饮料,成为霸王茶姬奶茶主材

朱雀桥NFC山竹汁饮料&#xff1a;荣登霸王茶姬奶茶主材&#xff0c;非遗传承的天然之选 近日&#xff0c;据小编了解到&#xff1a;霸王茶姬欣喜地宣布&#xff0c;成功与朱雀桥达成合作越南薇妮她VINUT牌NFC山竹汁饮料。这款商超产品凭借其卓越的品质与独特的口感&#xff0c…

小项目——MySQL集训(学生成绩录入)

ddl语句 -- 创建学生信息表 CREATE TABLE students (student_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 学生ID,name VARCHAR(50) NOT NULL COMMENT 学生姓名,gender ENUM(男, 女) NOT NULL COMMENT 性别,class VARCHAR(50) NOT NULL COMMENT 班级,registration_date DATE CO…

【Termius】详细说明MacOS中的SSH的客户端利器Termius

希望文章能给到你启发和灵感~ 如果觉得有帮助的话,点赞+关注+收藏支持一下博主哦~ 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境二、软件的安装2.1 Termius界面介绍2.1.1 Hosts 主机列表2.1.2 SFTP 文件传输2.1.3 Port ForWarding 端口转发2.1.4 Snippets 片…

想要打造高效活跃的私域社群,这些技巧要知道

对一些企业来说“做社群等于做私域”。 在腾讯提到的私域转化场景中&#xff0c;社群与小程序、官方导购三者并列。 社群连接着品牌和群内用户。品牌通过圈住更多用户&#xff0c;来持续免费触达用户实现变现&#xff0c;用户则是从品牌方手中直接获取更多服务和优惠。那么&a…

LabVIEW中卡尔曼滤波的作用与意义

卡尔曼滤波&#xff08;Kalman Filter&#xff09;是一种在控制系统和信号处理领域广泛应用的递推滤波算法&#xff0c;能够在噪声环境下对动态系统的状态进行最优估计。其广泛应用于导航、目标跟踪、图像处理、经济预测等多个领域。本文将详细介绍卡尔曼滤波在LabVIEW中的作用…

手机越用越慢?试试这4个秘籍,让手机流畅如新

智能手机作为日常生活的得力助手&#xff0c;最初总是以惊人的速度和流畅性给我们留下深刻印象。 但你有没有发现&#xff0c;随着时间的推移&#xff0c;手机似乎开始变得不那么敏捷&#xff0c;甚至出现了反应迟缓和卡顿的情况&#xff1f; 别让这个问题困扰你,下面是四个关…

基于springboot、vue影院管理系统

设计技术&#xff1a; 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatisvue 工具&#xff1a;IDEA、Maven、Navicat 主要功能&#xff1a; 影城管理系统的主要使用者分为管理员和用户&#xff0c; 实现功能包括管理员&#xff1a; 首页…