[C语言]你真的了解C语言吗之main函数(二)

在上篇文章中我们简单讨论了一下main()函数,了解了一下正确的写法,在文章的最后我们简单分析了一下参数是怎么传递的,但是想要把这些参数规规范范的整明白还是需要花一点时间的,本片文章只针对命令行参数处理函数getopt()做一定讨论,并无太高深的技术实现,所以大神可以直接拉到底点个赞走人了~啊哈哈哈哈,开玩笑,对行业内的鄙视链还是了解的,处于鄙视链底层的我没反驳的底气,哎~努力吧!

一般开发具有GUI(Graphical User Interface)的程序是不怎么太关注一些命令行的,而现在大多的应用程序都是具有图形界面的,使用者也很少对命令行有所了解的,就像我现在系统标配是ubuntu,但是很多情况下也是使用图形界面去完成一定操作的,很多时候在命令行中操作效率是非常高的。

其实getopt()函数跟main()函数并无太大的直接关系,但是当你写一个功能比较多的程序时,或多或少都会处理一点参数,这时候我们一般都会在main()函数里面使用getopt()去处理,我们可以理解为跟main()函数是相互关联的。我们先看一下这个函数到底是什么:

函数说明 getopt()用来分析命令行参数。参数argc和argv分别代表参数个数和内容,跟main()函数的命令行参数是一样的。参数 optstring为选项字符串, 告知 getopt()可以处理哪个选项以及哪个选项需要参数,如果选项字符串里的字母后接着冒号“:”,则表示还有相关的参数,全域变量optarg 即会指向此额外参数。如果在处理期间遇到了不符合optstring指定的其他选项getopt()将显示一个错误消息,并将全域变量optopt设为“?”字符,如果不希望getopt()打印出错信息,则只要将全域变量opterr设为0即可。

以上引自百度百科-getopt()。

相关头文件:

#include  
/**
 *@brief UNIX Standard
 *@description unistd.h 是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的
               头文件的名称。该头文件由 POSIX.1 标准(单一UNIX规范的基础)提出,故所有遵循
               该标准的操作系统和编译器均应提供该头文件
**/

相关声明及相关变量说明:

extern char *optarg;      /** getopt(3) external variables */
extern int optind, opterr, optopt;
int   getopt(int argc, char * const argv[], const char *optstring);
extern int optreset;      /** getopt(3) external variable */
/**
*@author imliubo
*为防止混淆,在本篇文章中对命令行参数进行分类,例“-p 22”,称带“-”为选项,不带“-”为选项参数或参数,
 特例“-p22”,有时选项和参数之间没有空格,此时“-”后面第一个字母为选项,第一个字母之后和下一个空格
 之前的称为选项参数或参数
*这些变量声明可以看出都是extern的,我们是可以在main函数中使用的,这些变量的值会随着getopt()函数
 的不断调用而不断变化的
 * *optarg  option argument,指向选项后面的实际参数(比如“-p 22”,getopt解析完成后,
            会将“22”的值赋给optarg)
 * optind   option index,argv中要处理的下一个选项参数的索引,当我们处理完一个参数时,此时
            argv[optind]会索引到下一个命令行中的选项或参数
 * opterr   option error,当opterr不为0时,会将无效选项或缺少参数的选项的信息输出到stderr,
            此时会将错误的选项储存到optopt,并返回“?”,当opterr为0时,不会输出任何信息,但是
            错误还是错误,只是不输出了而已
 * optopt   option option,当选项或参数有错误时,储存无效的选项或缺少参数的选项,无效的选项
            就是我们没有在optstring定义的,缺少选项参数就是选项应该加参数却没加
 * optreset option reset,从字面意思就可以看出是复位的意思,就是我们调用getopt一次后,可以
            将optreset置1,并且将optind重新初始化,此时前一次调用getopt函数所改变的变量
            都会恢复到初始化状态,然后可以重新调用getopt函数
*这些变量是我们需要传给getopt(),argc,argv就是我们在main函数声明时定义的,其中optstring是
 我们需要根据我们自己定义的参数去定义的
 * argc     来自main函数的入参
 * argv[]   来自main函数的入参
 * optstring 选项字符的集合,例":p:lv:d::",其中选项字符后面带“:”或“::”的都需要加参数,比如
        其中“p”,“v”,“d”选项都需要参数,不同的是带一个“:”的选项和参数之间可以使用或者不
        使用空格,带“::”的选项和参数之间是不能使用空格分隔的,选项字符后面没有跟“:”的是
             不可以加选项参数的,比如选项“l”,其中第一个“:”字符有或者没有时,解析错误会有不同
             的返回值,见下面
*返回值
 * x   解析成功返回选项字符,x表示optstring中的任意选项字符
 * -1  解析完成,返回-1,可以用来判断是否还有选项去处理
  *optstring 第一个字符为“:”
   * ?  解析错误,无效选项,此时无效选项的值存储在optopt中
   * :   解析错误,选项缺少参数,此时缺少参数的选项的值存储在optopt中
  *optstring 第一个字符不为“:”
   * ?   解析错误,无效选项或者选项缺少参数都会返回“?”,此时无效选项的值或者缺少参数选项的值存储
         在optopt中
*/

下面我们先来一个简单的测试程序尝试一下:

#include 
#include 
#include 

int main(int argc, char *argv[])
{
   int opt;
   while ((opt = getopt(argc, argv, ":ihm::b:")) != -1) {
       //printf("opt: %c  optarg: %s  optopt: %c  optind: %d\n",(char)opt,optarg,(char)optopt,optind);
       switch (opt) {
       case 'i':
          printf("作者:IAMLIUBO\n");
          break;
       case 'm':
          printf("专栏:%s\n",optarg);
          break;
       case 'b':
          printf("主页:%s\n",optarg);
          break;
       case 'h':
          fprintf(stderr,"Usage: [Options] [value]\n\n");
          fprintf(stderr,"  -i            printf author\n");
          fprintf(stderr,"  -h            printf help\n");
          fprintf(stderr,"  -m [value]    printf 专栏:value\n");
          fprintf(stderr,"  -b [value]    printf 主页:value\n");
          break;
       case ':':
          printf(">>%c<<选项缺少参数\n",(char)optopt);
          break;
       case '?':
          printf(">>%c<<是无效选项\n",(char)optopt);
          break;
       }
   }

   return 0;
}

这个是optstring第一个字符为":"的,所以错误信息应该会区分无效选项或选项缺少参数,其中"i","h"选项是不需要参数的,"m"选项后面有两个":",所以选项与参数之前不应该有空格,"b"选项后面有一个":",所以选项与参数之间有无空格都可以,我们看实际测试效果:

image
  • 第一个是正确的命令,可以看到打印都正确
  • 第二个是"m"选项与参数之间有空格,所以专栏的名称就没打印出来
  • 第三个是测试一个无效选项"o",可以看到optstring中是没有这个选项的,所以打印无效选项
  • 第四个是选项"b"没有跟上实际的参数,所以打印选项缺少参数
  • 第五个是打印帮助命令,在optstring中选项"h"后面没有":",所以不需要参数

我们再测试一下optstring第一个选项字符不为":"的,下面是测试程序:

#include 
#include 
#include 

int main(int argc, char *argv[])
{
   int opt;
   opterr = 0;
   while ((opt = getopt(argc, argv, "ihm::b:")) != -1) {
       //printf("opt: %c  optarg: %s  optopt: %c  optind: %d\n",(char)opt,optarg,(char)optopt,optind);
       switch (opt) {
       case 'i':
          printf("作者:IAMLIUBO\n");
          break;
       case 'm':
          printf("专栏:%s\n",optarg);
          break;
       case 'b':
          printf("主页:%s\n",optarg);
          break;
       case 'h':
          fprintf(stderr,"Usage: [Options] [value]\n\n");
          fprintf(stderr,"  -i            printf author\n");
          fprintf(stderr,"  -h            printf help\n");
          fprintf(stderr,"  -m [value]    printf 专栏:value\n");
          fprintf(stderr,"  -b [value]    printf 主页:value\n");
          break;
       case '?':
          fprintf(stderr, "Usage: %s [-i] [-h] [-m\"专栏名称\"] [-b \"主页网址\"]\n",
                   argv[0]);
          break;
       }
   }

   return 0;
}

image

可以看出与上面不同的是,不管是无效选项或者选项缺少参数都会返回"?",从而打印一样的内容(第三跟第四),所以这是optstring中第一个字符是否为":"的区别,我们可以利用这一点区别为使用程序的用户提供更精确的提示,所以建议大家还是使用第一种。

大家如果想看每解析一次各个变量值的变化可以将注释掉的内容取消注释,这样就会每解析一次便打印一次各个变量的值。

扩展:

  • POSIX标准

POSIX_百度百科​baike.baidu.com

图标

  • "-","--"," "的区别

我们有时候会见到命令行选项前面有的是一个"-"跟一个选项字符,有的是"--"跟一个单词,还有时候什么都没有直接跟选项字符,那么这到底有什么不同呢?其实本质是一样的,主要是风格不同,Unix风格是一个"-"跟上一个选项字符,GUN风格是"--"跟上一个单词,BSD风格是不加任何"-",但是他们之间都是兼容的,所以只是形式不同,本质上还是一样的。

对main函数的重新认识暂时就到这里,这里只是利用getopt函数简单写了个demo,其实只要解析出了选项的参数,后面大家对不同的参数做不同的处理应该也很简单了,比如写个命令行计算器?虽然人家有写好的,但是这也不妨碍大家写一个更好的嘛~

文章难免有错误,如果有不对之处,还请及时指出我好及时改正。

欢迎关注我的专栏,我会不定期分享一些开发经验。

你可能感兴趣的:([C语言]你真的了解C语言吗之main函数(二))