4.9 C++ Boost 命令行解析库

命令行解析库是一种用于简化处理命令行参数的工具,它可以帮助开发者更方便地解析命令行参数并提供适当的帮助信息。C++语言中,常用的命令行解析库有许多,通过本文的学习,读者可以了解不同的命令行解析库和它们在C++项目中的应用,从而更加灵活和高效地处理命令行参数。

一般比较常见的解析库:

  • getopt:一个C语言的命令行解析库,也被广泛用于C++程序。它提供了一组函数来解析命令行参数,并支持短选项(如 -a)和长选项(如 --help)。
  • Boost.Program_options:这是Boost库中的一个模块,提供了一个强大的命令行解析库。它支持短选项、长选项、位置参数等,并且具有很好的错误处理和帮助信息生成功能。
  • TCLAP:TCLAP(Templatized C++ Command Line Parser Library)是一个C++的命令行解析库,它提供了简单易用的API来解析命令行参数,并支持短选项和长选项。
  • CLI11:CLI11是一个现代化的C++命令行解析库,它使用C++11标准,并提供了一组简单易用的API。

这些库各有特点,开发者可以根据项目的需求和个人喜好选择合适的命令行解析库。通过使用这些库,开发者可以更轻松地处理命令行参数,提高程序的易用性和用户体验。在命令行程序中,argc和argv是C++程序中用于接收命令行参数的主要机制。其中:

  • argc(Argument Count)表示命令行参数的个数,包括程序本身。至少为1,即程序名。
  • argv(Argument Vector)是一个指向字符指针数组的指针,每个指针指向一个以空字符结尾的C字符串,表示一个命令行参数。

通常,argv[0] 存储的是程序的名称,argv[1] 开始存储的是传递给程序的实际命令行参数。开发者可以通过对 argcargv 的处理,来获取和解析命令行参数,从而完成特定功能的配置和操作。例如,使用 getoptBoost.Program_options 或者其他命令行解析库来解析和处理命令行参数,更方便地获取用户的输入。

9.1 在字符串上解析

该段代码通过简单的字符串切割实现了对命令行参数的解析。它将命令行字符串切割为选项和参数,并输出它们的内容。同时,根据特定的选项和参数组合条件,输出用户登录的相关信息。

代码的主要流程如下:

  1. 定义了一个函数 GetOpt,该函数接收一个字符指针 command(命令行字符串)和一个二维字符数组 command_ptr(用于存储解析后的选项和参数)。
  2. GetOpt 函数中,使用 strtok 函数对 command 字符串进行切割和解析,将选项和参数存储在 command_ptr 数组中,并返回选项和参数的总数。
  3. main() 函数中,构造一个命令行字符串 cmd,然后调用 GetOpt 函数,将解析后的选项和参数存储在 Opts 数组中,并获取选项和参数的总数 count
  4. 使用 for 循环遍历 Opts 数组,根据数组索引的奇偶性分别输出命令行选项和参数。

读者需要注意,此代码使用简单的字符串切割来实现命令行参数的解析,并假设输入的命令行格式是固定的,即选项和参数的顺序和格式是固定的(如 “–address 127.0.0.1 --password 123456 --port 22”)。如果输入的命令行格式有变化或者更复杂的需求,可能需要使用更强大的命令行解析库来完成更灵活的解析工作。

#include <iostream>
#include <Windows.h>using namespace std;// 传入命令行,切割解析
int GetOpt(IN char *command, OUT char command_ptr[][1024])
{char* ptr;ptr = strtok(command, " ");int count = 0;while (ptr != NULL){strcpy(command_ptr[count++], ptr);ptr = strtok(NULL, " ");}return count;
}int main(int argc, char* argv[])
{char cmd[4096] = "--address 127.0.0.1 --password 123456 --port 22";char Opts[30][1024];int count = GetOpt(cmd, Opts);for (int x = 0; x < count; x++){if (x % 2 == 0){std::cout << "命令行: " << Opts[x] << std::endl;}else{std::cout << "参数: " << Opts[x] << std::endl;}}// 参数解析使用if ((strcmp(Opts[0], "--address") == 0) && (strcmp(Opts[2], "--password") == 0) && (strcmp(Opts[4], "--port") == 0)){std::cout << "用户登录: " << Opts[1] << " 密码: " << Opts[3] << " 端口: " << Opts[5] << std::endl;}return 0;
}

9.2 自实现参数解析

这段代码是笔者突发奇想之后写出来的一个简易版参数解析器,通过检查参数个数和特定的选项和参数组合,输出对应的类型、地址和端口信息。如果参数个数小于等于2,则输出使用说明;如果参数个数等于7且满足特定格式 “–type tcp/udp --address 127.0.0.1 --port 8888”,则输出用户指定的类型、地址和端口信息。

代码的主要流程如下:

  1. 通过检查 argc 的值,如果小于等于2,则输出使用说明提示用户正确输入命令行参数。
  2. 如果参数个数等于7,按照特定的格式 “–type tcp/udp --address 127.0.0.1 --port 8888” 进行解析和判断。
  3. 使用 strcmp 函数判断命令行选项是否为 “–type”、“–address” 和 “–port”,并检查其后的参数是否符合预期格式。

根据特定的选项和参数组合条件,输出对应的类型、地址和端口信息。

#include <Windows.h>
#include <iostream>int main(int argc, char* argv[])
{// 如果小于两个参数则输出提示if (argc <= 2){fprintf(stderr, "\nUsage:\n\n"" \t --type              指定类型(string) \n"" \t --address           指定地址(string) \n"" \t --port              指定端口(int)    \n\n");exit(0);}// 如果参数个数是7那么总共需要有6个参数传递// 其中 1,3,5 代表的是参数开关// 剩余 2,4,6 则代表每个开关传入参数if (argc == 7){// --type tcp/udp --address 127.0.0.1 --port 8888if (strcmp((char*)argv[1], "--type") == 0 && strcmp((char*)argv[3], "--address") == 0 && strcmp((char*)argv[5], "--port") == 0 ){// 开关内部,也可以嵌套继续判断类型if (strcmp((char*)argv[2], "tcp") == 0){printf("[+] 类型: %s 地址: %s 端口: %d \n", argv[2], argv[4], atoi(argv[6]));}else if (strcmp((char*)argv[2], "udp") == 0){printf("[+] 类型: %s 地址: %s 端口: %d \n", argv[2], argv[4], atoi(argv[6]));}}}return 0;
}

如上代码所示,是笔者最常用的命令行解析方式,这种方式比较死板无法更智能的判断参数类型,如果需要判断的更全面则需要将其改进为以下格式,改进后虽然解析更灵活了,但管理起来也会变得更复杂。

如下所示,代码实现了一个32位端口快速扫描器的简单功能。通过解析命令行参数,用户可以指定待扫描的IP地址、开始端口和结束端口,并根据参数选择相应的扫描方式。如果没有指定合法的参数或缺少必要参数,则输出工具的菜单选项供用户参考。

代码的主要流程如下:

  1. 定义了一个结构体 GetOpt 用于保存参数信息,并定义了全局变量 opt 作为全局参数存储对象。
  2. 编写函数 getOpts 对命令行参数进行解析,并将解析结果存储到结构体 opt 中。
  3. 编写函数 ShowOptions 输出工具的菜单选项,包含待扫描的IP地址、开始端口和结束端口的参数说明。

main() 函数中,根据命令行参数的解析结果,输出对应的信息:如果同时指定了IP地址、开始端口和结束端口,则输出对应的扫描信息;如果只指定了开始端口和结束端口,则输出端口范围信息;否则,显示工具的菜单选项。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>typedef struct GetOpt    // 全局保存每个参数
{char Address[128];   // IP地址int Start;           // 开始端口int End;             // 结束端口
}GetOpt;static struct GetOpt opt; // 定义全局结构体// getOpts 针对参数的解析与赋值
int getOpts(int argc, char **argv)
{strcpy(opt.Address, "null");   // 初始化参数解析opt.Start = 0; opt.End = 0;    // 初始化参数解析for (int each = 1; each < argc; each++){if (!strcmp(argv[each], "--addr") && each + 1 < argc){strcpy(opt.Address, argv[++each]);}else if (!strcmp(argv[each], "--start") && each + 1 < argc){opt.Start = atoi(argv[++each]);}else if (!strcmp(argv[each], "--end") && each + 1 < argc){opt.End = atoi(argv[++each]);}else { return 0; }}return 1;
}// 输出工具菜单选项
void ShowOptions()
{fprintf(stderr, "\n""Usage: 32位端口快速扫描器 Ver:1.0  By:Lyshark \n\n""options: \n""\t --addr [addr]      指定待扫描的Ip地址 \n""\t --start [count]    指定待扫描的开始端口 \n""\t --end [count]      指定待扫描的结束端口 \n");
}// 主函数还是用来判断参数,并执行相应的命令
int main(int argc, char* argv[])
{if (getOpts(argc, argv) != 1){ShowOptions();}else if (strcmp(opt.Address, "null") != 0 && opt.Start != 0 && opt.End != 0){for (int x = 0; x < 100; x++)printf("扫描: %s 开始地址: %d 结束地址: %d \n", opt.Address, x, opt.End);}else if (opt.Start != 0 && opt.End != 0){for (int y = 0; y < 10; y++)printf("端口范围: %d -> %d \n", y, opt.End);}else { ShowOptions(); }return 0;
}

9.3 交互式参数解析

交互式参数解析器,其实就是类似于Linux系统终端那样的页面,运行代码后进入一个可交互环境,读者可以执行需要的命令。该功能的实现依赖于tokenizer.hpp模块,该模块提供了灵活、高效的字符串分割工具,可以帮助简化字符串处理的任务,特别是在文本处理、配置文件解析、数据解析等方面有着广泛的应用。

boost/tokenizer.hpp 主要功能是将一个字符串拆分成多个子串(tokens),通过指定分隔符或者符合某种条件的位置来实现字符串的分割。这在处理文本文件、解析命令行参数、数据处理等方面非常有用。

使用 boost::tokenizer 需要包含 <boost/tokenizer.hpp> 头文件,并在代码中使用 boost::tokenizer 类的实例对象来进行字符串的分割。该类提供了灵活的选项,允许用户指定分隔符、忽略空白字符、指定分隔符类型等。

例如,下面是一个使用 boost::tokenizer 进行字符串分割的简单示例:

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>int main()
{std::string input = "Boost C++ Libraries";boost::tokenizer<> tokens(input); // 默认使用空格作为分隔符for (const auto& token : tokens) {std::cout << token << std::endl;}return 0;
}

上述示例会将字符串 "Boost C++ Libraries" 按照空格进行分割,并输出拆分得到的子串。运用此功能并配合一个死循环结构我们就可以构建出一个交互式命令行环境,并可以根据用户输入的命令执行相应的操作。

根据上述所示的库函数,我们可以灵活的实现参数的解析功能,并实现一个简单的交互式参数解析功能,如下所示将提供三个交互命令,读者可自行编译并运行测试。

代码的主要流程如下:

  1. 使用 std::getline(std::cin, command) 从标准输入读取用户输入的命令,并将命令存储在字符串 command 中。
  2. 通过字符串的比较判断用户输入的命令,如果是 “help” 则输出功能菜单,展示可用的命令选项。
  3. 使用 boost::tokenizer 将用户输入的命令进行分割,提取出命令关键词和参数。
  4. 根据分割后的命令关键词和参数,执行相应的功能:
    • 如果是 “AddRule” 命令,则解析地址和DNS参数,并输出解析结果。
    • 如果是 “DeleteRule” 命令,则解析地址参数,并输出删除地址信息。
    • 如果是 “ShowList” 命令,则输出一个简单的数字列表。
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>using namespace std;
using namespace boost;int main(int argc, char const *argv[])
{std::string command;while (1){std::cout << "[LyShark] # ";std::getline(std::cin,command);if (command.length() == 0){continue;}else if (command == "help"){std::cout << "[功能菜单] \n" << std::endl;std::cout << "增加规则: AddRule --address 192.168.1.1 --dns 8.8.8.8" << std::endl;std::cout << "删除规则: DeleteRule --address 192.168.1.1" << std::endl;std::cout << "输出列表: ShowList" << std::endl;}else{// 定义分词器: 定义分割符号为[逗号,空格]boost::char_separator<char> sep(", --");typedef boost::tokenizer<boost::char_separator<char>> CustonTokenizer;CustonTokenizer tok(command, sep);// 将分词结果放入vector链表std::vector<std::string> vecSegTag;for (CustonTokenizer::iterator beg = tok.begin(); beg != tok.end(); ++beg){vecSegTag.push_back(*beg);}// 解析 [shell] # AddRule --address 192.168.1.1 --dns 8.8.8.8if (vecSegTag.size() == 5 && vecSegTag[0] == "AddRule"){if (vecSegTag[1] == "address" && vecSegTag[3] == "dns"){std::string set_address = vecSegTag[2];std::string set_dns = vecSegTag[4];std::cout << "解析地址: " << vecSegTag[2] << "解析DNS: " << vecSegTag[4] << std::endl;}}// 解析 [shell] # DeleteRule --address 192.168.1.1else if (vecSegTag.size() == 3 && vecSegTag[0] == "DeleteRule"){if (vecSegTag[1] == "address"){std::string del_address = vecSegTag[2];std::cout << "删除地址: " << del_address << std::endl;}}// 解析 [shell] # ShowListelse if (vecSegTag.size() == 1 && vecSegTag[0] == "ShowList"){for (int x = 0; x < 10; x++){std::cout << x << std::endl;}}}}return 0;
}

9.4 非交互参数解析

虽然分词器可以用于参数解析,但是其本身并不是用于做参数解析用的,在Boost中提供了Boost.Program_options库,该框架提供了强大而灵活的命令行选项解析功能,可以帮助简化处理命令行参数的过程,并提供良好的帮助信息和错误处理机制,是处理命令行参数的优秀工具库之一。

使用 Boost.Program_options 需要包含 <boost/program_options.hpp> 头文件,并通过创建 boost::program_options::options_description 对象来定义选项描述,然后使用 boost::program_options::parse_command_line 函数解析命令行参数,最后通过 boost::program_options::variables_map 对象获取解析后的选项和参数的值。

例如,下面是一个使用 Boost.Program_options 解析命令行参数的简单示例:

代码的主要流程如下:

  1. 使用 boost::program_options::options_description 定义命令行选项描述,包含三个选项:addressstart_portend_port,以及一个 help 选项用于输出帮助菜单。
  2. 使用 boost::program_options::parse_command_line 函数解析命令行参数,并将解析结果存储在 boost::program_options::variables_map 对象 virtual_map 中。
  3. 使用 boost::program_options::notify 函数检查命令行参数是否符合预期,并存储解析后的值到 virtual_map
  4. 根据 virtual_map中存储的命令行参数值,判断用户输入的选项并执行相应的操作:
    • 如果用户输入了 --help-h 选项,则输出帮助菜单。
    • 如果用户输入了 --address--start_port--end_port 选项,则输出扫描地址、开始端口和结束端口的信息。
    • 如果用户输入了未定义的选项或缺少必需的选项,则输出参数错误信息。

通过使用 Boost.Program_options 库,可以更方便地定义和解析命令行选项,从而使程序的命令行使用更加友好和灵活。

#include <iostream>
#include <boost/program_options.hpp>namespace opt = boost::program_options;int main(int argc, char const *argv[])
{opt::options_description des_cmd("\n Usage: 32位端口快速扫描器 Ver:1.0 \n\n Options");des_cmd.add_options()("address,a", opt::value<std::string>()->default_value("127.0.0.1"), "指定扫描地址")("start_port,s", opt::value<int>()->default_value(0), "扫描开始端口")("end_port,e", opt::value<int>()->default_value(65535), "扫描结束端口")("help,h", "帮助菜单");opt::variables_map virtual_map;try{opt::store(opt::parse_command_line(argc, argv, des_cmd), virtual_map);}catch (...){ return 0; }// 定义消息opt::notify(virtual_map);// 无参数直接返回if (virtual_map.empty()){return 0;}else if (virtual_map.count("help") || virtual_map.count("h")){std::cout << des_cmd << std::endl;return 0;}else if (virtual_map.count("address") && virtual_map.count("start_port") && virtual_map.count("end_port")){std::string address = virtual_map["address"].as<std::string>();int start_port = virtual_map["start_port"].as<int>();int end_port = virtual_map["end_port"].as<int>();// 判断是不是默认参数if ( address == "127.0.0.1" || start_port == 0 || end_port == 65535){std::cout << des_cmd << std::endl;}else{std::cout << "开始扫描: " << address << " 开始地址: " << start_port << "  结束地址: " << end_port << std::endl;}}else{std::cout << "参数错误" << std::endl;}return 0;
}

当然了,上述代码中我们也可以单独增加一个Banner()函数,并将其放入到virtual_map.empty()无参数模式,这样一来当参数输入不当或无参数是则会打印输出我们自己的点阵标志,能使程序变得更友好。

#include <iostream>
#include <boost/program_options.hpp>namespace opt = boost::program_options;void Banner()
{printf(" _           _                _     \n");printf("| |_   _ ___| |__   __ _ _ __| | __ \n");printf("| | | | / __| '_ \\ / _` | '__| |/ / \n");printf("| | |_| \\__ \\ | | | (_| | |  |   <  \n");printf("|_|\\__, |___/_| |_|\\__,_|_|  |_|\\_\\ \n");printf("   |___/                            \n\n");
}int main(int argc, char const *argv[])
{opt::options_description des_cmd("\n Usage: 输出Logo Ver:1.0 \n\n Options");des_cmd.add_options()("address,a", opt::value<std::string>(), "指定扫描地址")("start_port,s", opt::value<int>(), "扫描开始端口")("end_port,e", opt::value<int>(), "扫描结束端口")("help,h", "帮助菜单");opt::variables_map virtual_map;try{opt::store(opt::parse_command_line(argc, argv, des_cmd), virtual_map);}catch (...){ return 0; }// 定义消息opt::notify(virtual_map);// 无参数直接返回if (virtual_map.empty()){Banner();std::cout << des_cmd << std::endl;return 0;}// 帮助菜单else if (virtual_map.count("help") || virtual_map.count("h")){Banner();std::cout << des_cmd << std::endl;return 0;}// 分支结构1else if (virtual_map.count("address") && virtual_map.count("start_port") && virtual_map.count("end_port")){std::string address = virtual_map["address"].as<std::string>();int start_port = virtual_map["start_port"].as<int>();int end_port = virtual_map["end_port"].as<int>();std::cout << "开始扫描: " << address << " 开始地址: " << start_port << "  结束地址: " << end_port << std::endl;}// 分支结构2else if (virtual_map.count("address")){std::string address = virtual_map["address"].as<std::string>();std::cout << "地址: " << address << std::endl;}else{std::cout << "参数错误" << std::endl;}return 0;
}

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/8b095ec4.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

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

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

相关文章

antd5源码调试环境启动(MacOS)

将源码下载至本地 这里antd5 版本是5.8.3 $ git clone gitgithub.com:ant-design/ant-design.git $ cd ant-design $ npm install $ npm start前提&#xff1a;安装python3、node版本18.14.0(这是本人当前下载的版本&#xff09; python3安装教程可参考&#xff1a;https://…

Stable Diffusion入门修炼手册

简介 作为新入门的新手&#xff0c;通常安装完Stable Diffusion之后&#xff0c;一打开界面&#xff0c;在文生图输入girl或者dog&#xff0c;结果出来的画面比较糟糕&#xff0c;看起来像素很低&#xff0c;画面不清晰&#xff0c;人物也不怎么美&#xff0c;等等其他问题&am…

电脑运行缓慢?4个方法,加速电脑运行!

“我电脑才用了没多久哎&#xff01;怎么突然就变得运行很缓慢了呢&#xff1f;有什么方法可以加速电脑运行速度吗&#xff1f;真的很需要&#xff0c;看看我吧&#xff01;” 电脑的运行速度快会让用户在使用电脑时感觉愉悦&#xff0c;而电脑运行缓慢可能会影响我们的工作效率…

【vue】更改角色权限后,实现页面不刷新更改其可展示的导航菜单

登入的角色本身属于领导级别&#xff08;集团权限&#xff09;&#xff0c;没有下级的不同权限&#xff1a; 切换不同身份&#xff08;公司&#xff09;&#xff0c;以获得相应部门的不同导航菜单及权限 这里实现&#xff1a;更改角色权限后&#xff0c;实现页面 不刷新 更改…

攻防世界-supersqli

原题 解题思路 直接查找看不到明显的回显变化 先找回显变化数量 -1 order by 2 #如果是3列就报错&#xff0c;说明只有两列。接下来找数据库名称&#xff1a; -1 union select 1,databases # 结果是后端做了一些简单的过滤&#xff0c;需要更换查找语句。 -1; show …

LeetCode669. 修剪二叉搜索树

669. 修剪二叉搜索树 文章目录 [669. 修剪二叉搜索树](https://leetcode.cn/problems/trim-a-binary-search-tree/)一、题目二、题解方法一&#xff1a;递归法方法二&#xff1a;迭代法 一、题目 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 hig…

ReenTrantLock可重入锁(和synchronized的区别)总结

可重入性&#xff1a; 从名字上理解&#xff0c;ReenTrantLock的字面意思就是再进入的锁&#xff0c;其实synchronized关键字所使用的锁也是可重入的&#xff0c;两者关于这个的区别不大。两者都是同一个线程没进入一次&#xff0c;锁的计数器都自增1&#xff0c;所以要等到锁…

七夕节日表白:七大网页风格与其适用人群

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

如何使用 ChatGPT 将文本转换为 PowerPoint 演示文稿

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 步骤 1&#xff1a;将文本转换为幻灯片演示文稿 第一步涉及指示 ChatGPT 根据给定的文本生成具有特定数量幻灯片的演示文稿。首先&#xff0c;您必须向 ChatGPT 提供要转换的文本。 使用以下提示指示…

SpringMVC-2-Spring MVC拦截器详解:从入门到精通

SpringMVC-2-Spring MVC拦截器详解&#xff1a;从入门到精通 今日目标 能够编写拦截器并配置拦截器 1.拦截器【理解】 1 拦截器介绍 1.1 拦截器概念和作用 拦截器&#xff08;Interceptor&#xff09;是一种动态拦截方法调用的机制&#xff0c;在SpringMVC中动态拦截控制器方…

C的进阶C++学习方向

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;软件配置等领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff01;送给自己和读者的…

L1-028 判断素数 测试点全过

题目 本题的目标很简单&#xff0c;就是判断一个给定的正整数是否素数。 输入格式 输入在第一行给出一个正整数N &#xff08; ≤ 10 &#xff09; &#xff08;≤ 10&#xff09; &#xff08;≤10&#xff09;&#xff0c;随后N行&#xff0c;每行给出一个小于 2 3 1 2^ 31…

Python开发环境(Visual Studio Code、Anaconda、PyInstaller、Enigma Virtual Box)

Python开发环境 [Anaconda、PyInstaller、Enigma Virtual Box] AnacondaAnaconda安装搭建Python环境Anaconda命令 Visual Studio CodeVisual Studio Code中Python设置Visual Studio Code中安装PyQt5Visual Studio Code中使用Qt DesignerVisual Studio Code中Anaconda切换虚拟环…

uniapp 自定义手机顶部状态栏(适配状态栏高度)

开启页面自定义导航栏功能 uniapp 在 pages.json 页面设置了全局的 globalStyle 的 "navigationStyle": "custom" 或单页面的 style 的 "navigationStyle": "custom" 之后页面顶部就没有自带的导航栏了&#xff0c;这时用户可自定义该…

介绍一些编程语言— Perl 语言

介绍一些编程语言— Perl 语言 Perl 语言 简介 Perl 是一种动态解释型的脚本语言。 最初的设计者为拉里・沃尔&#xff0c;它于 1987 1987 1987 年 12 12 12 月 18 18 18 日发表。Perl 借取了 C、sed、awk、shell scripting 以及很多其他编程语言的特性。其中最重要的特性…

仓库管理的重点在哪?仓库管理能有哪些软件?

对于做实体生意的中小商户来说&#xff0c;仓库管理工作是重中之重的&#xff0c;仓库管理的好坏&#xff0c;直接影响着门店销售和财务状况。 但对于很多中小商户来说&#xff0c;没有足够的人力和精力去高效地做好仓库管理工作&#xff0c;而借助仓库管理软件或进销存软件来…

SNAT和DNAT

SNAT和DNAT 一、SNAT策略及应用1.1SNAT策略概述1.2开启SNAT的命令1.2.1 临时打开1.2.2永久打开 1.3SNAT转换1&#xff1a;固定的公网IP地址1.4SNAT转换2&#xff1a;非固定的公网IP地址&#xff08;共享动态IP地址&#xff09;1.5SNAT案例1.5.1实验准备1.5.2配置网关服务器&…

Redis之zset(sorted set)类型解读

目录 基本介绍 常用命令 ZADD key score1 member1 [score2 member2] ZRANGE key start stop [WITHSCORES] ZCARD key ZCOUNT key min max ZREM key member [member ...] ZSCORE key member 基本介绍 Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字…

IDEA启动报错【java.sql.SQLSyntaxErrorException: ORA-00904: “P“.“PRJ_NO“: 标识符无效】

IDEA报错如下&#xff1a; 2023-08-17 11:26:15.535 ERROR [egrant-biz,b48324d82fe23753,b48324d82fe23753,true] 24108 --- [ XNIO-1 task-1] c.i.c.l.c.RestExceptionController : 服务器异常org.springframework.jdbc.BadSqlGrammarException: ### Error queryin…

PDF怎么转成PPT文件免费?一个软件解决

随着科技的不断发展和进步&#xff0c;电子文档已经成为我们日常工作和学习中不可或缺的一部分。PDF作为一种跨平台的文件格式&#xff0c;以其可靠性和易读性而备受推崇。然而&#xff0c;在某些情况下&#xff0c;我们可能需要PDF怎么转成PPT文件免费&#xff0c;以便更好地展…