亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++ Boost命令行解析庫的應用詳解

 更新時間:2023年11月27日 11:27:58   作者:微軟技術分享  
命令行解析庫是一種用于簡化處理命令行參數的工具,它可以幫助開發(fā)者更方便地解析命令行參數并提供適當的幫助信息,本文主要介紹了不同的命令行解析庫和它們在C++項目中的應用,希望對大家有所幫助

命令行解析庫是一種用于簡化處理命令行參數的工具,它可以幫助開發(fā)者更方便地解析命令行參數并提供適當的幫助信息。C++語言中,常用的命令行解析庫有許多,通過本文的學習,讀者可以了解不同的命令行解析庫和它們在C++項目中的應用,從而更加靈活和高效地處理命令行參數。

一般比較常見的解析庫:

getopt:一個C語言的命令行解析庫,也被廣泛用于C++程序。它提供了一組函數來解析命令行參數,并支持短選項(如 -a)和長選項(如 --help)。

Boost.Program_options:這是Boost庫中的一個模塊,提供了一個強大的命令行解析庫。它支持短選項、長選項、位置參數等,并且具有很好的錯誤處理和幫助信息生成功能。

TCLAP:TCLAP(Templatized C++ Command Line Parser Library)是一個C++的命令行解析庫,它提供了簡單易用的API來解析命令行參數,并支持短選項和長選項。

CLI11:CLI11是一個現(xiàn)代化的C++命令行解析庫,它使用C++11標準,并提供了一組簡單易用的API。

這些庫各有特點,開發(fā)者可以根據項目的需求和個人喜好選擇合適的命令行解析庫。通過使用這些庫,開發(fā)者可以更輕松地處理命令行參數,提高程序的易用性和用戶體驗。在命令行程序中,argc和argv是C++程序中用于接收命令行參數的主要機制。其中:

argc(Argument Count)表示命令行參數的個數,包括程序本身。至少為1,即程序名。

argv(Argument Vector)是一個指向字符指針數組的指針,每個指針指向一個以空字符結尾的C字符串,表示一個命令行參數。

通常,argv[0] 存儲的是程序的名稱,argv[1] 開始存儲的是傳遞給程序的實際命令行參數。開發(fā)者可以通過對 argc 和 argv 的處理,來獲取和解析命令行參數,從而完成特定功能的配置和操作。例如,使用 getopt、Boost.Program_options 或者其他命令行解析庫來解析和處理命令行參數,更方便地獲取用戶的輸入。

在字符串上解析

該段代碼通過簡單的字符串切割實現(xiàn)了對命令行參數的解析。它將命令行字符串切割為選項和參數,并輸出它們的內容。同時,根據特定的選項和參數組合條件,輸出用戶登錄的相關信息。

代碼的主要流程如下:

  • 定義了一個函數 GetOpt,該函數接收一個字符指針 command(命令行字符串)和一個二維字符數組 command_ptr(用于存儲解析后的選項和參數)。
  • 在 GetOpt 函數中,使用 strtok 函數對 command 字符串進行切割和解析,將選項和參數存儲在 command_ptr 數組中,并返回選項和參數的總數。
  • 在 main() 函數中,構造一個命令行字符串 cmd,然后調用 GetOpt 函數,將解析后的選項和參數存儲在 Opts 數組中,并獲取選項和參數的總數 count。
  • 使用 for 循環(huán)遍歷 Opts 數組,根據數組索引的奇偶性分別輸出命令行選項和參數。

讀者需要注意,此代碼使用簡單的字符串切割來實現(xiàn)命令行參數的解析,并假設輸入的命令行格式是固定的,即選項和參數的順序和格式是固定的(如 “–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;
}

自實現(xiàn)參數解析

這段代碼是筆者突發(fā)奇想之后寫出來的一個簡易版參數解析器,通過檢查參數個數和特定的選項和參數組合,輸出對應的類型、地址和端口信息。如果參數個數小于等于2,則輸出使用說明;如果參數個數等于7且滿足特定格式 “–type tcp/udp --address 127.0.0.1 --port 8888”,則輸出用戶指定的類型、地址和端口信息。

代碼的主要流程如下:

  • 通過檢查 argc 的值,如果小于等于2,則輸出使用說明提示用戶正確輸入命令行參數。
  • 如果參數個數等于7,按照特定的格式 “–type tcp/udp --address 127.0.0.1 --port 8888” 進行解析和判斷。
  • 使用 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 8888
        if (strcmp((char*)argv[1], "--type") == 0 && strcmp((char*)argv[3], "--address") == 0 && strcmp((char*)argv[5], "--port") == 0 )
        {
            // 開關內部,也可以嵌套繼續(xù)判斷類型
            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;
}

如上代碼所示,是筆者最常用的命令行解析方式,這種方式比較死板無法更智能的判斷參數類型,如果需要判斷的更全面則需要將其改進為以下格式,改進后雖然解析更靈活了,但管理起來也會變得更復雜。

如下所示,代碼實現(xiàn)了一個32位端口快速掃描器的簡單功能。通過解析命令行參數,用戶可以指定待掃描的IP地址、開始端口和結束端口,并根據參數選擇相應的掃描方式。如果沒有指定合法的參數或缺少必要參數,則輸出工具的菜單選項供用戶參考。

代碼的主要流程如下:

  • 定義了一個結構體 GetOpt 用于保存參數信息,并定義了全局變量 opt 作為全局參數存儲對象。
  • 編寫函數 getOpts 對命令行參數進行解析,并將解析結果存儲到結構體 opt 中。
  • 編寫函數 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"
    );
}

// 主函數還是用來判斷參數,并執(zhí)行相應的命令
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;
}

交互式參數解析

交互式參數解析器,其實就是類似于Linux系統(tǒng)終端那樣的頁面,運行代碼后進入一個可交互環(huán)境,讀者可以執(zhí)行需要的命令。該功能的實現(xiàn)依賴于tokenizer.hpp模塊,該模塊提供了靈活、高效的字符串分割工具,可以幫助簡化字符串處理的任務,特別是在文本處理、配置文件解析、數據解析等方面有著廣泛的應用。

boost/tokenizer.hpp 主要功能是將一個字符串拆分成多個子串(tokens),通過指定分隔符或者符合某種條件的位置來實現(xiàn)字符串的分割。這在處理文本文件、解析命令行參數、數據處理等方面非常有用。

使用 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" 按照空格進行分割,并輸出拆分得到的子串。運用此功能并配合一個死循環(huán)結構我們就可以構建出一個交互式命令行環(huán)境,并可以根據用戶輸入的命令執(zhí)行相應的操作。

根據上述所示的庫函數,我們可以靈活的實現(xiàn)參數的解析功能,并實現(xiàn)一個簡單的交互式參數解析功能,如下所示將提供三個交互命令,讀者可自行編譯并運行測試。

代碼的主要流程如下:

1.使用 std::getline(std::cin, command) 從標準輸入讀取用戶輸入的命令,并將命令存儲在字符串 command 中。

2.通過字符串的比較判斷用戶輸入的命令,如果是 “help” 則輸出功能菜單,展示可用的命令選項。

3.使用 boost::tokenizer 將用戶輸入的命令進行分割,提取出命令關鍵詞和參數。

4.根據分割后的命令關鍵詞和參數,執(zhí)行相應的功能:

  • 如果是 “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 << "增加規(guī)則: AddRule --address 192.168.1.1 --dns 8.8.8.8" << std::endl;
      std::cout << "刪除規(guī)則: 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.8
      if (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.1
      else if (vecSegTag.size() == 3 && vecSegTag[0] == "DeleteRule")
      {
        if (vecSegTag[1] == "address")
        {
          std::string del_address = vecSegTag[2];
          std::cout << "刪除地址: " << del_address << std::endl;
        }
      }
      // 解析 [shell] # ShowList
      else if (vecSegTag.size() == 1 && vecSegTag[0] == "ShowList")
      {
        for (int x = 0; x < 10; x++)
        {
          std::cout << x << std::endl;
        }
      }
    }
  }
  return 0;
}

非交互參數解析

雖然分詞器可以用于參數解析,但是其本身并不是用于做參數解析用的,在Boost中提供了Boost.Program_options庫,該框架提供了強大而靈活的命令行選項解析功能,可以幫助簡化處理命令行參數的過程,并提供良好的幫助信息和錯誤處理機制,是處理命令行參數的優(yōu)秀工具庫之一。

使用 Boost.Program_options 需要包含 <boost/program_options.hpp> 頭文件,并通過創(chuàng)建 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 定義命令行選項描述,包含三個選項:address、start_port 和 end_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中存儲的命令行參數值,判斷用戶輸入的選項并執(zhí)行相應的操作:

  • 如果用戶輸入了 --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;
  }
  // 分支結構1
  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>();

    std::cout << "開始掃描: " << address << " 開始地址: " << start_port << "  結束地址: " << end_port << std::endl;
  }

  // 分支結構2
  else 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;
}

以上就是C++ Boost命令行解析庫的應用詳解的詳細內容,更多關于C++ Boost命令行解析的資料請關注腳本之家其它相關文章!

相關文章

  • QT5編譯使用QFtp的方法步驟

    QT5編譯使用QFtp的方法步驟

    這篇文章主要介紹了QT5編譯使用QFtp的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • C語言中volatile關鍵字的作用及說明

    C語言中volatile關鍵字的作用及說明

    文中主要介紹了C語言中volatile關鍵字的含義和使用場景,volatile是一個類型修飾符,主要用來修飾被不同線程訪問和修改的變量,它的作用是防止編譯器對代碼進行優(yōu)化,確保每次直接讀取原始內存地址的值
    2024-10-10
  • C語言進階可變參數列表

    C語言進階可變參數列表

    這篇文章主要為大家介紹了C語言進階可變參數列表的示例詳解有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2022-02-02
  • 探討C++中數組名與指針的用法比較分析

    探討C++中數組名與指針的用法比較分析

    本篇文章是對C++中數組名與指針用法的比較進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • C++面試八股文之智能指針詳解

    C++面試八股文之智能指針詳解

    智能指針是C++11引入的類模板,用于管理資源,行為類似于指針,但不需要手動申請、釋放資源,本文主要為大家介紹了它的相關知識,需要的可以參考一下
    2023-06-06
  • c語言實現(xiàn)的hashtable分享

    c語言實現(xiàn)的hashtable分享

    哈希表效率高,眾所周知。應用廣泛,php中大部分存儲使用的都是hashtable,包括變量,數組…如何使用c語言實現(xiàn)hashtable呢,現(xiàn)提供自己的思路,如有不妥之處,敬請賜教
    2014-01-01
  • 深入理解goto語句的替代實現(xiàn)方式分析

    深入理解goto語句的替代實現(xiàn)方式分析

    本篇文章是對goto語句的替代實現(xiàn)方式進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • OpenGL通過中點法繪制直線和圓

    OpenGL通過中點法繪制直線和圓

    這篇文章主要為大家詳細介紹了OpenGL通過中點法繪制直線和圓,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • C語言 數據類型詳細介紹

    C語言 數據類型詳細介紹

    本文主要講解C語言 數據類型,這里整理了詳細的數據類型的資料,希望能幫助剛剛開始學習C語言的同學
    2016-08-08
  • visual?studio?2022?編譯出來的文件被刪除并監(jiān)視目錄中的文件變更(示例詳解)

    visual?studio?2022?編譯出來的文件被刪除并監(jiān)視目錄中的文件變更(示例詳解)

    這篇文章主要介紹了visual?studio?2022?編譯出來的文件被刪除?并監(jiān)視目錄中的文件變更,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08

最新評論