zhaohaoyi_branch
heureux 1 year ago
parent 5f06f531b3
commit 494b46daee

@ -114,313 +114,318 @@ std::ostream& operator<<(std::ostream& out, Generator& generator) {// 是一个
// class ParseItem
ParseItem::ParseItem(const std::string& value) : value_(value) {}
ParseItem::ParseItem(const std::string& value) : value_(value) {}//ParseItem 类的构造函数
//它接受一个 std::string 类型的参数 value。在构造函数体中将传入的 value 直接赋值给类的成员变量 value_。
//这个构造函数用于创建一个 ParseItem 对象,并初始化其 value_ 成员变量。
// class Parser
ParseItem* Parser::get(const std::string& key) {
if (pr_->find(key) != pr_->end()) {
return (*pr_)[key];
ParseItem* Parser::get(const std::string& key) {//Parser 类的 get 方法,它接受一个 std::string 类型的参数 key。
if (pr_->find(key) != pr_->end()) {//如果 key 在 pr_ 中存在
return (*pr_)[key];//那么返回对应的 ParseItem 指针
}
return nullptr;
return nullptr;//返回 nullptr
}
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}//
//Parser 类的构造函数,它使用初始化列表将 subroutines_ 和 pr_ 成员变量初始化为 nullptr。
Parser::~Parser() { this->cleanup(); }
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {
if (!this->init(argc, argv)) {
return nullptr;
//这是 Parser 类的析构函数,它调用 cleanup 方法来清理资源。
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {//Parser 类的 parse 方法,它接受命令行参数的数量 argc 和参数值 argv
if (!this->init(argc, argv)) {//它调用 init 方法来初始化解析过程
return nullptr;//如果初始化失败,那么返回 nullptr
}
auto ibegin = args_.begin() + 1; // ignore the first cmd name
auto ibegin = args_.begin() + 1; // 忽略第一个命令名
auto iend = args_.end();
auto it = ibegin;
auto it = ibegin;//定义开始变量名
if (argc >= 2 && args_[1][0] != '-') {
// the second block may be a subroutine name
// 第二个块可能是一个子程序名
// e.g., ./exec pull --option
if (subroutines_ && (subroutines_->find(args_[1]) != subroutines_->end())) {
subroutine_name_ = args_[1];
it++; // ignore the subroutine name
it++; // 忽略子程序名
} else {
subroutine_name_ = args_[1];
}
} else {
// there is no options as well as subroutine name
// e.g., ./exec
// 没有选项以及子程序名
// 例如,./exec
subroutine_name_ = Subroutine::get_default_name();
}
std::string block;
std::string previous(*ibegin);
std::string block;//声明变量
std::string previous(*ibegin);//声明变量
for (; it != iend; ++it) {
block.assign(*it);
for (; it != iend; ++it) {// 遍历所有的命令行参数
block.assign(*it);// 将当前参数赋值给 block
switch (block.size()) {
case 1:
if (block == "-") {
throw ParseError("single '-' is not allowed");
switch (block.size()) {//// 根据 block 的大小进行不同的处理
case 1://// 如果 block 的大小为 1
if (block == "-") {//// 如果 block 是一个单独的 "-"
throw ParseError("single '-' is not allowed");//// 抛出异常,因为单独的 "-" 是不允许的
}
break;
case 2:
if (block[0] == '-') {
if (block[1] == '-') {
throw ParseError("option '--' is incomplete");
} else if (block[1] == '=') {
throw ParseError("option '-=' is invalid");
case 2:// // 如果 block 的大小为 2
if (block[0] == '-') {//// 如果 block 的第一个字符是 "-"
if (block[1] == '-') {//// 如果 block 的第二个字符也是 "-"
throw ParseError("option '--' is incomplete");//// 抛出异常,因为 "--" 是不完整的选项
} else if (block[1] == '=') {//// 如果 block 的第二个字符是 "="
throw ParseError("option '-=' is invalid");//// 抛出异常,因为 "-=" 是无效的选项
} else {
// single option
// 单个选项
// e.g., ./exec -s
(*pr_)[block.substr(1)] = nullptr;
}
}
break;
default: // >=3
if (block[0] == '-') {
if (block[1] == '-') {
size_t pos_equal = block.find('=');
if (pos_equal == std::string::npos) {
// a long format option
if (block[0] == '-') {//// 如果 block 的第一个字符是 "-"
if (block[1] == '-') {//// 如果 block 的第二个字符也是 "-"
size_t pos_equal = block.find('=');//// 查找 "=" 在 block 中的位置
if (pos_equal == std::string::npos) {//// 如果没有找到 "="
// 长格式选项
// e.g., ./exec --option
(*pr_)[block.substr(2)] = nullptr;
(*pr_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
} else {
if (pos_equal > 3) {
if (pos_equal > 3) {// 如果 "=" 的位置大于 3
// e.g, ./exec --op[..=]value
std::string key(block.substr(2, pos_equal - 2));
if (block.size() > 5)
std::string key(block.substr(2, pos_equal - 2));// 获取选项名
if (block.size() > 5)//// 如果 block 的大小大于 5
// e.g, ./exec --op=v
(*pr_)[key] = new ParseItem(block.substr(pos_equal + 1));
(*pr_)[key] = new ParseItem(block.substr(pos_equal + 1));// 将选项和值添加到 pr_ 中
else
(*pr_)[key] = nullptr;
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
} else {
// a long format option but = is illegal
// 长格式选项但 = 是非法的
// e.g., ./exec --o=[...]
(*pr_)[block.substr(2)] = nullptr;
(*pr_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
}
}
} else if (block[2] == '=') {
// a single option with =
} else if (block[2] == '=') {// // 如果 block 的第三个字符是 "="
// 单个选项带有 =
// e.g., ./exec -o=[...]
std::string key;
key.push_back(block[1]);
if (block.size() > 3)
(*pr_)[key] = new ParseItem(block.substr(3));
key.push_back(block[1]);// 获取选项名
if (block.size() > 3)// 如果 block 的大小大于 3
(*pr_)[key] = new ParseItem(block.substr(3));//// 将选项和值添加到 pr_ 中
else
(*pr_)[key] = nullptr;
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
} else {
// a combination options
// 组合选项
// e.g., ./exec -ab[...]
auto tbegin = block.begin() + 1; // ignore the first '-'
auto tbegin = block.begin() + 1; // 忽略第一个 '-'
auto tend = block.end();
auto t = tbegin;
for (; t != tend; ++t) {
for (; t != tend; ++t) { // 遍历 block 中的每个字符
std::string key;
key.push_back(*t);
(*pr_)[key] = nullptr;
key.push_back(*t);// // 获取选项名
(*pr_)[key] = nullptr; // 将选项添加到 pr_ 中,值为 nullptr
}
}
}
break;
} // switch
if (block[0] != '-' && previous != block // not the first option
if (block[0] != '-' && previous != block // 如果 block 不是选项(不以 "-" 开头)并且不是第一个选项
) {
if (previous[0] != '-') {
// previous is not an option, error occur
if (previous[0] != '-') {//// 如果 previous 不是选项
// previous 不是一个选项,发生错误
// e.g., ./exec abc def
throw ParseError("'" + block + "' is not allowed here");
throw ParseError("'" + block + "' is not allowed here");//抛出异常,因为在这里不允许非选项
}
std::string key;
if (previous[0] == '-' && previous[1] == '-') {
if (previous[0] == '-' && previous[1] == '-') {//// 如果 previous 是一个长格式选项
// previous is a long format option.
// e.g., ./exec --option value
key = previous.substr(2);
key = previous.substr(2);//// 获取选项名
} else {
// it's the value of previous option.
// 它是前一个选项的值。
// e.g., ./exec -o [...]
// e.g., ./exec -opq [...]
key.push_back(*(previous.end() - 1));
key.push_back(*(previous.end() - 1));// // 获取选项名
}
if (pr_->find(key) != pr_->end()) {
(*pr_)[key] = new ParseItem(block);
if (pr_->find(key) != pr_->end()) {//// 如果选项在 pr_ 中存在
(*pr_)[key] = new ParseItem(block); // 将选项和值添加到 pr_ 中
}
}
previous = block;
previous = block;//// 更新 previous 为当前的 block
} // for
if (subroutines_) {
this->set_addition();
this->set_addition();// 如果存在子程序,调用 set_addition 方法处理额外的选项
}
return pr_;
return pr_;//返回解析结果 pr_
}
Parser::ParseResult* Parser::parse(const char* command_line) {
int i = 0;
std::string block;
std::vector<std::string> blocks;
char c;
while ((c = command_line[i++]) != '\0') {
if (c != ' ') {
block.push_back(c);
Parser::ParseResult* Parser::parse(const char* command_line) {//Parser 类的 parse 方法
int i = 0;//初始化计数器
std::string block;//用于存储单个命令行参数
std::vector<std::string> blocks;//声明用于存储所有命令行参数
char c;//声明用于存储当前字符
while ((c = command_line[i++]) != '\0') {// 遍历命令行字符串
if (c != ' ') {// 如果当前字符不是空格
block.push_back(c);//// 将当前字符添加到 block
} else {
if (!block.empty()) {
blocks.push_back(block);
if (!block.empty()) {// 如果 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
}
block.clear();
block.clear();//清空 block
}
}
if (!block.empty()) {
blocks.push_back(block);
if (!block.empty()) {// 如果最后一个 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
}
size_t size = blocks.size(); // argc
char** argv = new char*[size];
char** argv = new char*[size];// 创建一个新的 char* 数组
i = 0;
std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {
argv[i++] = const_cast<char*>(b.c_str());
std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {// 遍历 blocks
argv[i++] = const_cast<char*>(b.c_str());// 将每个 block 转换为 char* 并存储在 argv 中
});
auto pr =
this->parse(static_cast<const int>(size), const_cast<const char**>(argv));
this->parse(static_cast<const int>(size), const_cast<const char**>(argv)); // 调用 parse 方法解析命令行参数
delete[] argv;
delete[] argv;// 删除 argv
argv = nullptr;
return pr;
return pr;// 返回解析结果
}
bool Parser::has(const char* key) {
std::string skey(key);
bool Parser::has(const char* key) {//Parser 类的 has 方法,它接受一个 char 指针 key并检查 key 是否在 pr_ 中存在。
std::string skey(key);// 将 key 转换为 std::string
if (pr_ && !pr_->empty() && !skey.empty()) {
if (pr_ && !pr_->empty() && !skey.empty()) {//判断是否存在
if (skey[0] == '-') {
// check combination options, e.g., Parser::has("-xyz")
for (size_t i = 1; i < skey.size(); ++i) {
// 如果 skey 是一个组合选项,例如 "-xyz"
for (size_t i = 1; i < skey.size(); ++i) {// 遍历 skey 的每个字符
std::string tkey;
tkey.push_back(skey[i]);
if (pr_->find(tkey) == pr_->end()) {
tkey.push_back(skey[i]);// 获取选项名
if (pr_->find(tkey) == pr_->end()) { // 如果选项名在 pr_ 中不存在
return false;
}
}
return true;
} else {
// check single option, e.g., Parser::has("x")
return pr_->find(skey) != pr_->end();
// 如果 skey 是一个单个选项,例如 "x"
return pr_->find(skey) != pr_->end();// 检查选项是否在 pr_ 中存在
}
}
return false;
return false;// 如果 pr_ 为空或 skey 为空,返回 false
}
//parser 类的 has_or 方法,它接受一个初始化列表 options并检查 options 中的任何一个 key 是否在 pr_ 中存在。
bool Parser::has_or(std::initializer_list<const char*> options) {
if (options.size() == 0) {
if (options.size() == 0) {// 如果 options 为空
return false;
}4
for (auto key : options) {// 遍历 options 中的每个选项
if (this->has(key)) return true;// 如果选项在 pr_ 中存在,返回 true
}
for (auto key : options) {
if (this->has(key)) return true;
}
return false;
return false;// 如果 options 中的所有选项都不存在,返回 false
}
bool Parser::has_and(std::initializer_list<const char*> options) {
if (options.size() == 0) {
bool Parser::has_and(std::initializer_list<const char*> options) { // Parser 类的 has_and 方法,接受一个初始化列表 options
if (options.size() == 0) {// 如果 options 为空
return false;
}
for (auto key : options) {
if (!this->has(key)) return false;
for (auto key : options) {// 遍历 options 中的每个选项
if (!this->has(key)) return false;// 如果选项在 pr_ 中不存在,返回 false
}
return true;
return true;// 如果 options 中的所有选项都存在,返回 true
}
bool Parser::init(const int argc, const char** argv) {
argc_ = argc;
argc_ = argc;// 保存参数数量
// argv_ = argv;
// don't save it, point to a local var in parse(const char* command_line).
// use member var args_ instead.
if (argc > 0) {
this->cleanup();
if (argc > 0) {// 如果参数数量大于 0
this->cleanup(); // 清理之前的解析结果
args_.reserve(static_cast<size_t>(argc_));
args_.reserve(static_cast<size_t>(argc_));// 为 args_ 预留空间
for (int i = 0; i < argc_; ++i) {
args_.push_back(argv[i]);
for (int i = 0; i < argc_; ++i) {// 遍历所有的命令行参数
args_.push_back(argv[i]);// 将参数添加到 args_
}
pr_ = new Parser::ParseResult;
pr_ = new Parser::ParseResult;// 创建新的解析结果
return true;
}
return false;
return false;// 如果参数数量为 0返回 false
}
void Parser::cleanup() {
args_.clear();
if (pr_) {
void Parser::cleanup() {// Parser 类的 cleanup 方法,用于清理解析结果
args_.clear();// 清空 args_
if (pr_) {// 如果 pr_ 不为空
auto ibegin = pr_->begin();
auto iend = pr_->end();
auto it = ibegin;
for (; it != iend; ++it) {
for (; it != iend; ++it) {// 遍历 pr_ 中的每个元素
ParseItem* item = it->second;
if (item) delete item;
if (item) delete item;// 删除元素
}
delete pr_;
delete pr_;// 删除 pr_
pr_ = nullptr;
}
}
void Parser::set_addition() {
if (subroutines_->find(subroutine_name_) != subroutines_->end()) {
for (const Row& row : *(subroutines_->at(subroutine_name_))) {
void Parser::set_addition() {// Parser 类的 set_addition 方法,用于处理额外的选项
if (subroutines_->find(subroutine_name_) != subroutines_->end()) {// 如果子程序名在 subroutines_ 中存在
for (const Row& row : *(subroutines_->at(subroutine_name_))) {// 遍历子程序中的每一行
// assume both -o and --option are allowed,
// but only provide -o,
// then set the another --option.
// vice versa.
const std::string& def = row.value();
const std::string& ops = row.oshort();
const std::string& opl = row.olong();
ParseResult& pr = *pr_;
const std::string& def = row.value();// 获取默认值
const std::string& ops = row.oshort();// 获取短选项
const std::string& opl = row.olong();// 获取长选项
ParseResult& pr = *pr_; // 获取解析结果
bool has_short = this->has(ops.c_str());
bool has_long = this->has(opl.c_str());
bool has_short = this->has(ops.c_str());// 检查短选项是否存在
bool has_long = this->has(opl.c_str());// 检查长选项是否存在
// assume -o [ --option ] arg = 1
// but not provide option value,
// then set to default 1.
// otherwise, both set to user defined value
if (!ops.empty()) {
if (has_short) {
if (pr[ops] != nullptr && !opl.empty()) {
pr[opl] = new ParseItem(std::move(pr[ops]->val()));
} else if (pr[ops] == nullptr && !def.empty()) {
pr[ops] = new ParseItem(std::move(def));
if (!opl.empty()) pr[opl] = new ParseItem(std::move(def));
if (!ops.empty()) {// 如果短选项不为空
if (has_short) {// 如果短选项存在
if (pr[ops] != nullptr && !opl.empty()) {// 如果短选项有值且长选项不为空
pr[opl] = new ParseItem(std::move(pr[ops]->val()));// 将短选项的值赋给长选项
} else if (pr[ops] == nullptr && !def.empty()) {// 如果短选项没有值且默认值不为空
pr[ops] = new ParseItem(std::move(def));// 将默认值赋给短选项
if (!opl.empty()) pr[opl] = new ParseItem(std::move(def));// 如果长选项不为空,也将默认值赋给长选项
} else {
pr[opl] = nullptr;
pr[opl] = nullptr;// 将长选项的值设为 nullptr
}
}
}
if (!opl.empty()) {
if (has_long) {
if (pr[opl] != nullptr && !ops.empty()) {
pr[ops] = new ParseItem(std::move(pr[opl]->val()));
} else if (pr[opl] == nullptr && !def.empty()) {
if (!ops.empty()) pr[ops] = new ParseItem(std::move(def));
pr[opl] = new ParseItem(std::move(def));
if (!opl.empty()) {// 如果长选项不为空
if (has_long) { // 如果长选项存在
if (pr[opl] != nullptr && !ops.empty()) { // 如果长选项有值且短选项不为空
pr[ops] = new ParseItem(std::move(pr[opl]->val()));// 将长选项的值赋给短选项
} else if (pr[opl] == nullptr && !def.empty()) {// 如果长选项没有值且默认值不为空
if (!ops.empty()) pr[ops] = new ParseItem(std::move(def));// 如果短选项不为空,将默认值赋给短选项
pr[opl] = new ParseItem(std::move(def));// 将默认值赋给长选项
} else {
pr[ops] = nullptr;
pr[ops] = nullptr;// 将短选项的值设为 nullptr
}
}
}
if (!has_long && !has_short && !def.empty()) {
if (!opl.empty()) pr[opl] = new ParseItem(std::move(def));
if (!ops.empty()) pr[ops] = new ParseItem(std::move(def));
if (!has_long && !has_short && !def.empty()) {// 如果长选项和短选项都不存在且默认值不为
if (!opl.empty()) pr[opl] = new ParseItem(std::move(def));// 如果长选项不为空,将默认值赋给长选项
if (!ops.empty()) pr[ops] = new ParseItem(std::move(def));// 如果短选项不为空,将默认值赋给短选项
}
} // for
} // if
@ -428,114 +433,115 @@ void Parser::set_addition() {
// class Row
Row::Row() : require_value(true) {}
Row::Row() : require_value(true) {} // Row 类的构造函数,初始化 require_value 为 true
// class Subroutine
Subroutine::Subroutine() : first_line_("") {}
Subroutine::Subroutine() : first_line_("") {}// Subroutine 类的默认构造函数,初始化 first_line_ 为空字符串
Subroutine::Subroutine(const char* name, const char* description)
: first_line_(""), description_(description), name_(name) {
usages_.reserve(5);
: first_line_(""), description_(description), name_(name) { // Subroutine 类的构造函数,接受子程序名和描述作为参数
usages_.reserve(5);// 为 usages_ 预留空间
}
void Subroutine::print_with_row(std::ostream& out) {
void Subroutine::print_with_row(std::ostream& out) {// Subroutine 类的 print_with_row 方法,接受一个输出流作为参数
// print the subroutine name and its description
if (strcmp(get_first_line(), "") != 0) {
// 打印子程序名和描述
if (strcmp(get_first_line(), "") != 0) {// 如果 first_line_ 不为空
// print the first line
out << get_first_line();
if (!usages_.empty()) {
out << std::endl;
if (!usages_.empty()) {// 如果 usages_ 不为空
out << std::endl;// 打印换行符
}
}
auto begin = usages_.begin();
auto end = usages_.end();
auto begin = usages_.begin(); // 获取 usages_ 的开始迭代器
auto end = usages_.end();// 获取 usages_ 的结束迭代器
std::vector<std::string> row_list;
row_list.reserve(usages_.size());
std::vector<std::string> row_list;// 创建一个字符串向量用于存储行
row_list.reserve(usages_.size());// 为 row_list 预留空间
// build usage rows without description field,
// find the max-len row at the same time.
size_t max_len = 0;
std::for_each(begin, end, [&max_len, &row_list](const Row& row) {
std::stringstream ss;
ss << " ";
if (!row.oshort().empty()) {
ss << "-" << row.oshort() << " ";
std::for_each(begin, end, [&max_len, &row_list](const Row& row) {// 遍历 usages_
std::stringstream ss;// 创建一个字符串流
ss << " ";// 向字符串流中添加两个空格
if (!row.oshort().empty()) {// 如果短选项不为空
ss << "-" << row.oshort() << " "; // 添加短选项
}
if (!row.olong().empty()) {
if (!row.olong().empty()) {// 如果长选项不为空
if (!row.oshort().empty())
ss << "[ --" << row.olong() << " ] ";
ss << "[ --" << row.olong() << " ] ";// 添加长选项
else
ss << "--" << row.olong() << " ";
ss << "--" << row.olong() << " "; // 添加长选项
}
if (row.required()) {
ss << "arg ";
if (!row.value().empty()) {
ss << "= " << row.value() << " ";
if (row.required()) {// 如果选项是必需的
ss << "arg "; // 添加 "arg "
if (!row.value().empty()) {// 如果选项值不为空
ss << "= " << row.value() << " ";// 添加选项值
}
}
max_len = std::max(max_len, ss.str().size());
row_list.push_back(std::move(ss.str()));
max_len = std::max(max_len, ss.str().size());// 更新最大长度
row_list.push_back(std::move(ss.str()));// 将字符串流的内容添加到 row_list
});
// show all rows and align description field
size_t row_count = usages_.size();
for (size_t i = 0; i < row_count; ++i) {
std::string str_row(std::move(row_list[i]));
size_t row_count = usages_.size();// 获取 usages_ 的大小
for (size_t i = 0; i < row_count; ++i) {// 遍历 usages_
std::string str_row(std::move(row_list[i]));// 获取当前行
// print row without description
out << str_row;
out << str_row;// 打印当前行
// print spaces
size_t spaces = 0;
size_t len = str_row.size();
if (max_len > len) spaces = max_len - len;
size_t spaces = 0;// 打印空格
size_t len = str_row.size();// 获取当前行的长度
if (max_len > len) spaces = max_len - len;// 计算需要打印的空格数量
while (spaces--) {
while (spaces--) {// 打印空格
out << " ";
}
// print description
out << usages_.at(i).desc() << std::endl;
out << usages_.at(i).desc() << std::endl;// 打印描述
}
}
void Subroutine::print_with_template(std::ostream& out) {
for (auto usage : usages_) {
void Subroutine::print_with_template(std::ostream& out) {// Subroutine 类的 print_with_template 方法,接受一个输出流作为参数
for (auto usage : usages_) {// 遍历 usages_
size_t i = 0;
for (auto t = template_str_.begin(); t != template_str_.end(); ++t) {
if (*t == '%') {
switch (*(order_.begin() + i)) {
for (auto t = template_str_.begin(); t != template_str_.end(); ++t) {// 遍历模板字符串
if (*t == '%') {// 如果当前字符是 '%'
switch (*(order_.begin() + i)) { // 根据 order_ 中的值决定打印哪个字段
case Row::kShort:
out << usage.oshort();
out << usage.oshort();// 打印短选项
break;
case Row::kLong:
out << usage.olong();
out << usage.olong();// 打印长选项
break;
case Row::kDefault:
out << usage.value();
out << usage.value();// 打印默认值
break;
case Row::kDescription:
out << usage.desc();
out << usage.desc();// 打印描述
break;
default:
break;
}
++i;
} else {
out << *t;
out << *t;// 如果当前字符不是 '%',直接打印
} // if %
} // for template_str_
out << std::endl;
out << std::endl;// 打印换行符
} // for usages_
}
std::ostream& operator<<(std::ostream& out, Subroutine& subroutine) {
if (subroutine.template_str_.empty()) {
subroutine.print_with_row(out);
std::ostream& operator<<(std::ostream& out, Subroutine& subroutine) {// 重载 << 运算符,接受一个输出流和一个 Subroutine 对象作为参数
if (subroutine.template_str_.empty()) {// 如果模板字符串为空
subroutine.print_with_row(out);// 使用 print_with_row 方法打印
} else {
subroutine.print_with_template(out);
subroutine.print_with_template(out);// 使用 print_with_template 方法打印
}
return out;
return out;// 返回输出流
}
}
Loading…
Cancel
Save