C++实现的命令行参数管理

在编写可运行程序时,经常需要输入除了可运行文件之外的其它的命令行参数,可以用传统的getopt函数来分析,本文基于面向对象,分析一种管理命令行参数方法 -- 来源于webrtc项目,在阅读过程中,大家分享一下。


一,传统命令行分析

包含头文件:#include
  int getopt(int argc,char * const argv[ ],const char * optstring);
  extern char *optarg;
  extern int optind, opterr, optopt;

二,命令行参数管理

假设命令行的输入格式的规则如下:

  1. --flag           布尔类型。
  2. --noflag       布尔类型。
  3. --flag=value   等号周边没有空格。

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

    这个类是表示一对键值的抽象,包含下列元素:

  1. 键值对。包括name和variable_表示键和值。如--prefix=/usr中,name的值为prefix,variable_为/usr的一个表示。
  2. 链表维护域。Flag *next_用于指向下一个命令行参数。
  3. comment_表示该参数的解释。
  4. file表示和键值相关的外部文件。
  5. default_表示默认情况下,就是用户没有输入该参数的情况下默认的值。
  6. 定义友元类FlagList,因为FlagList需要访问Flag的next_域。

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_
};


2.3 命令行键值链表--- FlagList

这个类维护一个全局的链表,链表中每一项都是命令行参数解析的结果,如:--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_;
}; 


2.4 实现

先看在链表中,查找指定的参数,这个实现比较简单。

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之类的命令行进行解析,如果某项输入不以-开头,则不被解析。

流程如下:

  1. 如果以"-"开头,则去掉这个开头,接下来如果还是"-",还是把这个字符去掉。
  2. 如果接下来的字符是no,则表示这是一个布尔类型输入,此时将arg的值赋值给name。
  3. 然后一直跳过字符,直到遇到“=”号,或者arg的结尾。
  4. 如果后面是“=”号,则后面的部分为name所对应的value值。

解析所有的命令行输入
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__

flags.cc 

#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


你可能感兴趣的:(C/C++语言)