在编写可运行程序时,经常需要输入除了可运行文件之外的其它的命令行参数,可以用传统的getopt函数来分析,本文基于面向对象,分析一种管理命令行参数方法 -- 来源于webrtc项目,在阅读过程中,大家分享一下。
一,传统命令行分析
包含头文件:#include
int getopt(int argc,char * const argv[ ],const char * optstring);
extern char *optarg;
extern int optind, opterr, optopt;
假设命令行的输入格式的规则如下:
2.1 参数的值封装---FlagValue
这个类对参数的值进行封装,如--prefix=/usr,作为一个命令行参数时,prefix为键,/usr为值。在参数中,在此定义值的类型为布尔、整型、浮点、字符串中的一种。
由于一个值在只能取四种的一种,所以此处用联合类型表示FlagValue。
union FlagValue {
static FlagValue New_BOOL(int b) {
FlagValue v;
v.b = (b != 0);
return v;
}
static FlagValue New_INT(int i) {
FlagValue v;
v.i = i;
return v;
}
static FlagValue New_FLOAT(float f) {
FlagValue v;
v.f = f;
return v;
}
static FlagValue New_STRING(const char* s) {
FlagValue v;
v.s = s;
return v;
}
bool b;
int i;
double f;
const char* s;
};
这个联合类型对命令行中键值对中的值进行封装,可以表示四种类型。
2.2 命令行中键值的表示 -- Flag
这个类是表示一对键值的抽象,包含下列元素:
class Flag {
public:
enum Type { BOOL, INT, FLOAT, STRING };
// Internal use only.
Flag(const char* file, const char* name, const char* comment,
Type type, void* variable, FlagValue default_);
// General flag information
const char* file() const { return file_; }
const char* name() const { return name_; }
const char* comment() const { return comment_; }
// Flag type
Type type() const { return type_; }
// Flag variables
bool* bool_variable() const {
assert(type_ == BOOL);
return &variable_->b;
}
int* int_variable() const {
assert(type_ == INT);
return &variable_->i;
}
double* float_variable() const {
assert(type_ == FLOAT);
return &variable_->f;
}
const char** string_variable() const {
assert(type_ == STRING);
return &variable_->s;
}
// Default values
bool bool_default() const {
assert(type_ == BOOL);
return default_.b;
}
int int_default() const {
assert(type_ == INT);
return default_.i;
}
double float_default() const {
assert(type_ == FLOAT);
return default_.f;
}
const char* string_default() const {
assert(type_ == STRING);
return default_.s;
}
// Resets a flag to its default value
void SetToDefault();
// Iteration support
Flag* next() const { return next_; }
// Prints flag information. The current flag value is only printed
// if print_current_value is set.
void Print(bool print_current_value);
private:
const char* file_;
const char* name_;
const char* comment_;
Type type_;
FlagValue* variable_;
FlagValue default_;
Flag* next_;
friend class FlagList; // accesses next_
};
这个类维护一个全局的链表,链表中每一项都是命令行参数解析的结果,如:--prefix=/usr --localstatedir=/var/data 这就表示两个Flag对象,通过Flag对象的next域来关联。
这个类的属性和方法都是静态的,属性只有Flag* list_,用于维护命令行所有输入的参数所组成的链表。
主要方法如下:
SetFkagsFromCommandLine:解析根据命令行的输入,这里传入的是所有的命令行输入。
SplitArgument:解析命令行中具体的一个可以被解析的键值对。
class FlagList {
public:
FlagList();
static Flag* list() { return list_; }
static void Print(const char* file, bool print_current_value);
static Flag* Lookup(const char* name);
static void SplitArgument(const char* arg,
char* buffer, int buffer_size,
const char** name, const char** value,
bool* is_bool);
static int SetFlagsFromCommandLine(int* argc,
const char** argv);
static inline int SetFlagsFromCommandLine(int* argc,
char** argv) {
return SetFlagsFromCommandLine(argc, const_cast(argv));
}
private:
static Flag* list_;
};
先看在链表中,查找指定的参数,这个实现比较简单。
Flag* FlagList::Lookup(const char* name) {
Flag* f = list_;
while (f != NULL && strcmp(name, f->name()) != 0)
f = f->next();
return f;
}
void FlagList::SplitArgument(const char* arg,
char* buffer, int buffer_size,
const char** name, const char** value,
bool* is_bool) {
*name = NULL;
*value = NULL;
*is_bool = false;
if (*arg == '-') {
// find the begin of the flag name
arg++; // remove 1st '-'
if (*arg == '-')
arg++; // remove 2nd '-'
if (arg[0] == 'n' && arg[1] == 'o') {
arg += 2; // remove "no"
*is_bool = true;
}
*name = arg;
// find the end of the flag name
while (*arg != '\0' && *arg != '=')
arg++;
// get the value if any
if (*arg == '=') {
// make a copy so we can NUL-terminate flag name
int n = static_cast(arg - *name);
if (n >= buffer_size)
Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size");
memcpy(buffer, *name, n * sizeof(char));
buffer[n] = '\0';
*name = buffer;
// get the value
*value = arg + 1;
}
}
}
上面的函数是对诸如--prefix=/usr 、--prefix、--prefix /usr之类的命令行进行解析,如果某项输入不以-开头,则不被解析。
流程如下:
int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv) {
// parse arguments
for (int i = 1; i < *argc; /* see below */) {
int j = i; // j > 0
const char* arg = argv[i++];
// split arg into flag components
char buffer[1024];
const char* name;
const char* value;
bool is_bool;
SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool);
if (name != NULL) {
// lookup the flag
Flag* flag = Lookup(name);
if (flag == NULL) {
fprintf(stderr, "Error: unrecognized flag %s\n", arg);
return j;
}
// if we still need a flag value, use the next argument if available
if (flag->type() != Flag::BOOL && value == NULL) {
if (i < *argc) {
value = argv[i++];
} else {
fprintf(stderr, "Error: missing value for flag %s of type %s\n",
arg, Type2String(flag->type()));
return j;
}
}
// set the flag
char empty[] = { '\0' };
char* endp = empty;
switch (flag->type()) {
case Flag::BOOL:
*flag->bool_variable() = !is_bool;
break;
case Flag::INT:
*flag->int_variable() = strtol(value, &endp, 10);
break;
case Flag::FLOAT:
*flag->float_variable() = strtod(value, &endp);
break;
case Flag::STRING:
*flag->string_variable() = value;
break;
}
// handle errors
if ((flag->type() == Flag::BOOL && value != NULL) ||
(flag->type() != Flag::BOOL && is_bool) ||
*endp != '\0') {
fprintf(stderr, "Error: illegal value for flag %s of type %s\n",
arg, Type2String(flag->type()));
return j;
}
}
}
return 0;
}
源码下载地址:http://download.csdn.net/detail/zmxiangde_88/4789141
粘贴如下:
flags.h
#ifndef TALK_BASE_FLAGS_H__
#define TALK_BASE_FLAGS_H__
#include
#include "talk/base/checks.h"
#include "talk/base/common.h"
// Internal use only.
union FlagValue {
// Note: Because in C++ non-bool values are silently converted into
// bool values ('bool b = "false";' results in b == true!), we pass
// and int argument to New_BOOL as this appears to be safer - sigh.
// In particular, it prevents the (not uncommon!) bug where a bool
// flag is defined via: DEFINE_bool(flag, "false", "some comment");.
static FlagValue New_BOOL(int b) {
FlagValue v;
v.b = (b != 0);
return v;
}
static FlagValue New_INT(int i) {
FlagValue v;
v.i = i;
return v;
}
static FlagValue New_FLOAT(float f) {
FlagValue v;
v.f = f;
return v;
}
static FlagValue New_STRING(const char* s) {
FlagValue v;
v.s = s;
return v;
}
bool b;
int i;
double f;
const char* s;
};
// Each flag can be accessed programmatically via a Flag object.
class Flag {
public:
enum Type { BOOL, INT, FLOAT, STRING };
// Internal use only.
Flag(const char* file, const char* name, const char* comment,
Type type, void* variable, FlagValue default_);
// General flag information
const char* file() const { return file_; }
const char* name() const { return name_; }
const char* comment() const { return comment_; }
// Flag type
Type type() const { return type_; }
// Flag variables
bool* bool_variable() const {
assert(type_ == BOOL);
return &variable_->b;
}
int* int_variable() const {
assert(type_ == INT);
return &variable_->i;
}
double* float_variable() const {
assert(type_ == FLOAT);
return &variable_->f;
}
const char** string_variable() const {
assert(type_ == STRING);
return &variable_->s;
}
// Default values
bool bool_default() const {
assert(type_ == BOOL);
return default_.b;
}
int int_default() const {
assert(type_ == INT);
return default_.i;
}
double float_default() const {
assert(type_ == FLOAT);
return default_.f;
}
const char* string_default() const {
assert(type_ == STRING);
return default_.s;
}
// Resets a flag to its default value
void SetToDefault();
// Iteration support
Flag* next() const { return next_; }
// Prints flag information. The current flag value is only printed
// if print_current_value is set.
void Print(bool print_current_value);
private:
const char* file_;
const char* name_;
const char* comment_;
Type type_;
FlagValue* variable_;
FlagValue default_;
Flag* next_;
friend class FlagList; // accesses next_
};
// Internal use only.
#define DEFINE_FLAG(type, c_type, name, default, comment) \
/* define and initialize the flag */ \
c_type FLAG_##name = (default); \
/* register the flag */ \
static Flag Flag_##name(__FILE__, #name, (comment), \
Flag::type, &FLAG_##name, \
FlagValue::New_##type(default))
// Internal use only.
#define DECLARE_FLAG(c_type, name) \
/* declare the external flag */ \
extern c_type FLAG_##name
// Use the following macros to define a new flag:
#define DEFINE_bool(name, default, comment) \
DEFINE_FLAG(BOOL, bool, name, default, comment)
#define DEFINE_int(name, default, comment) \
DEFINE_FLAG(INT, int, name, default, comment)
#define DEFINE_float(name, default, comment) \
DEFINE_FLAG(FLOAT, double, name, default, comment)
#define DEFINE_string(name, default, comment) \
DEFINE_FLAG(STRING, const char*, name, default, comment)
// Use the following macros to declare a flag defined elsewhere:
#define DECLARE_bool(name) DECLARE_FLAG(bool, name)
#define DECLARE_int(name) DECLARE_FLAG(int, name)
#define DECLARE_float(name) DECLARE_FLAG(double, name)
#define DECLARE_string(name) DECLARE_FLAG(const char*, name)
// The global list of all flags.
class FlagList {
public:
FlagList();
// The NULL-terminated list of all flags. Traverse with Flag::next().
static Flag* list() { return list_; }
// If file != NULL, prints information for all flags defined in file;
// otherwise prints information for all flags in all files. The current
// flag value is only printed if print_current_value is set.
static void Print(const char* file, bool print_current_value);
// Lookup a flag by name. Returns the matching flag or NULL.
static Flag* Lookup(const char* name);
// Helper function to parse flags: Takes an argument arg and splits it into
// a flag name and flag value (or NULL if they are missing). is_bool is set
// if the arg started with "-no" or "--no". The buffer may be used to NUL-
// terminate the name, it must be large enough to hold any possible name.
static void SplitArgument(const char* arg,
char* buffer, int buffer_size,
const char** name, const char** value,
bool* is_bool);
// Set the flag values by parsing the command line. If remove_flags
// is set, the flags and associated values are removed from (argc,
// argv). Returns 0 if no error occurred. Otherwise, returns the
// argv index > 0 for the argument where an error occurred. In that
// case, (argc, argv) will remain unchanged indepdendent of the
// remove_flags value, and no assumptions about flag settings should
// be made.
//
// The following syntax for flags is accepted (both '-' and '--' are ok):
//
// --flag (bool flags only)
// --noflag (bool flags only)
// --flag=value (non-bool flags only, no spaces around '=')
// --flag value (non-bool flags only)
static int SetFlagsFromCommandLine(int* argc,
const char** argv,
bool remove_flags);
static inline int SetFlagsFromCommandLine(int* argc,
char** argv,
bool remove_flags) {
return SetFlagsFromCommandLine(argc, const_cast(argv),
remove_flags);
}
// Registers a new flag. Called during program initialization. Not
// thread-safe.
static void Register(Flag* flag);
private:
static Flag* list_;
};
#ifdef WIN32
// A helper class to translate Windows command line arguments into UTF8,
// which then allows us to just pass them to the flags system.
// This encapsulates all the work of getting the command line and translating
// it to an array of 8-bit strings; all you have to do is create one of these,
// and then call argc() and argv().
class WindowsCommandLineArguments {
public:
WindowsCommandLineArguments();
~WindowsCommandLineArguments();
int argc() { return argc_; }
char **argv() { return argv_; }
private:
int argc_;
char **argv_;
private:
DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments);
};
#endif // WIN32
#endif // SHARED_COMMANDLINEFLAGS_FLAGS_H__
#include
#include
#include
#ifdef WIN32
#include "talk/base/win32.h"
#include
#endif
#include "talk/base/flags.h"
// -----------------------------------------------------------------------------
// Implementation of Flag
Flag::Flag(const char* file, const char* name, const char* comment,
Type type, void* variable, FlagValue default__)
: file_(file),
name_(name),
comment_(comment),
type_(type),
variable_(reinterpret_cast(variable)),
default_(default__) {
FlagList::Register(this);
}
void Flag::SetToDefault() {
// Note that we cannot simply do '*variable_ = default_;' since
// flag variables are not really of type FlagValue and thus may
// be smaller! The FlagValue union is simply 'overlayed' on top
// of a flag variable for convenient access. Since union members
// are guarantee to be aligned at the beginning, this works.
switch (type_) {
case Flag::BOOL:
variable_->b = default_.b;
return;
case Flag::INT:
variable_->i = default_.i;
return;
case Flag::FLOAT:
variable_->f = default_.f;
return;
case Flag::STRING:
variable_->s = default_.s;
return;
}
UNREACHABLE();
}
static const char* Type2String(Flag::Type type) {
switch (type) {
case Flag::BOOL: return "bool";
case Flag::INT: return "int";
case Flag::FLOAT: return "float";
case Flag::STRING: return "string";
}
UNREACHABLE();
return NULL;
}
static void PrintFlagValue(Flag::Type type, FlagValue* p) {
switch (type) {
case Flag::BOOL:
printf("%s", (p->b ? "true" : "false"));
return;
case Flag::INT:
printf("%d", p->i);
return;
case Flag::FLOAT:
printf("%f", p->f);
return;
case Flag::STRING:
printf("%s", p->s);
return;
}
UNREACHABLE();
}
void Flag::Print(bool print_current_value) {
printf(" --%s (%s) type: %s default: ", name_, comment_,
Type2String(type_));
PrintFlagValue(type_, &default_);
if (print_current_value) {
printf(" current value: ");
PrintFlagValue(type_, variable_);
}
printf("\n");
}
// -----------------------------------------------------------------------------
// Implementation of FlagList
Flag* FlagList::list_ = NULL;
FlagList::FlagList() {
list_ = NULL;
}
void FlagList::Print(const char* file, bool print_current_value) {
// Since flag registration is likely by file (= C++ file),
// we don't need to sort by file and still get grouped output.
const char* current = NULL;
for (Flag* f = list_; f != NULL; f = f->next()) {
if (file == NULL || file == f->file()) {
if (current != f->file()) {
printf("Flags from %s:\n", f->file());
current = f->file();
}
f->Print(print_current_value);
}
}
}
Flag* FlagList::Lookup(const char* name) {
Flag* f = list_;
while (f != NULL && strcmp(name, f->name()) != 0)
f = f->next();
return f;
}
void FlagList::SplitArgument(const char* arg,
char* buffer, int buffer_size,
const char** name, const char** value,
bool* is_bool) {
*name = NULL;
*value = NULL;
*is_bool = false;
if (*arg == '-') {
// find the begin of the flag name
arg++; // remove 1st '-'
if (*arg == '-')
arg++; // remove 2nd '-'
if (arg[0] == 'n' && arg[1] == 'o') {
arg += 2; // remove "no"
*is_bool = true;
}
*name = arg;
// find the end of the flag name
while (*arg != '\0' && *arg != '=')
arg++;
// get the value if any
if (*arg == '=') {
// make a copy so we can NUL-terminate flag name
int n = static_cast(arg - *name);
if (n >= buffer_size)
Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size");
memcpy(buffer, *name, n * sizeof(char));
buffer[n] = '\0';
*name = buffer;
// get the value
*value = arg + 1;
}
}
}
int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv,
bool remove_flags) {
// parse arguments
for (int i = 1; i < *argc; /* see below */) {
int j = i; // j > 0
const char* arg = argv[i++];
// split arg into flag components
char buffer[1024];
const char* name;
const char* value;
bool is_bool;
SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool);
if (name != NULL) {
// lookup the flag
Flag* flag = Lookup(name);
if (flag == NULL) {
fprintf(stderr, "Error: unrecognized flag %s\n", arg);
return j;
}
// if we still need a flag value, use the next argument if available
if (flag->type() != Flag::BOOL && value == NULL) {
if (i < *argc) {
value = argv[i++];
} else {
fprintf(stderr, "Error: missing value for flag %s of type %s\n",
arg, Type2String(flag->type()));
return j;
}
}
// set the flag
char empty[] = { '\0' };
char* endp = empty;
switch (flag->type()) {
case Flag::BOOL:
*flag->bool_variable() = !is_bool;
break;
case Flag::INT:
*flag->int_variable() = strtol(value, &endp, 10);
break;
case Flag::FLOAT:
*flag->float_variable() = strtod(value, &endp);
break;
case Flag::STRING:
*flag->string_variable() = value;
break;
}
// handle errors
if ((flag->type() == Flag::BOOL && value != NULL) ||
(flag->type() != Flag::BOOL && is_bool) ||
*endp != '\0') {
fprintf(stderr, "Error: illegal value for flag %s of type %s\n",
arg, Type2String(flag->type()));
return j;
}
// remove the flag & value from the command
if (remove_flags)
while (j < i)
argv[j++] = NULL;
}
}
// shrink the argument list
if (remove_flags) {
int j = 1;
for (int i = 1; i < *argc; i++) {
if (argv[i] != NULL)
argv[j++] = argv[i];
}
*argc = j;
}
// parsed all flags successfully
return 0;
}
void FlagList::Register(Flag* flag) {
assert(flag != NULL && strlen(flag->name()) > 0);
if (Lookup(flag->name()) != NULL)
Fatal(flag->file(), 0, "flag %s declared twice", flag->name());
flag->next_ = list_;
list_ = flag;
}
#ifdef WIN32
WindowsCommandLineArguments::WindowsCommandLineArguments() {
// start by getting the command line.
LPTSTR command_line = ::GetCommandLine();
// now, convert it to a list of wide char strings.
LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_);
// now allocate an array big enough to hold that many string pointers.
argv_ = new char*[argc_];
// iterate over the returned wide strings;
for(int i = 0; i < argc_; ++i) {
std::string s = talk_base::ToUtf8(wide_argv[i], wcslen(wide_argv[i]));
char *buffer = new char[s.length() + 1];
talk_base::strcpyn(buffer, s.length() + 1, s.c_str());
// make sure the argv array has the right string at this point.
argv_[i] = buffer;
}
LocalFree(wide_argv);
}
WindowsCommandLineArguments::~WindowsCommandLineArguments() {
// need to free each string in the array, and then the array.
for(int i = 0; i < argc_; i++) {
delete[] argv_[i];
}
delete[] argv_;
}
#endif // WIN32