C语言:va_xx、getopt_xx、str_xx等函数

目录

  • 前言
  • 一、va_xx系列函数
  • 二、getopt、getopt_long函数
  • 三、strtok、strchr和 strstr
  • 四、memmove函数

前言

  在C语言学习过程中,有些函数并不会怎么见,但是在工作以后经常会见到和使用。今天在这里和大家简单介绍一下。

一、va_xx系列函数

  va_start()va_end() 是 C 语言中的两个宏,用于在可变参数函数中访问参数列表。

  可变参数是指在函数声明中未指定参数数量或类型的参数。在 C 语言中,可变参数函数使用一组宏来解析参数列表。这些宏是 stdarg.h 头文件中定义的,并包含以下三个:

  1. va_list: 是一个指针,用于访问参数列表。

  2. va_start(): 初始化 va_list 指针,使其指向参数列表的第一个参数。

  3. va_end(): 清除 va_list 指针,结束参数列表的遍历。

这些宏的使用通常遵循以下通用模式:

#include 

void func(char *fmt, ...)
{
  va_list args;
  int i, sum = 0;

  va_start(args, fmt);

  for (i = 0; fmt[i] != '\0'; i++) {
    switch (fmt[i]) {
      case 'd':
        sum += va_arg(args, int);   // 访问 int 类型的参数
        break;
      case 'f':
        sum += (int)va_arg(args, double);  // 访问 double 类型的参数
        break;
      default:
        break;
    }
  }

  va_end(args);
}

  在上面的例子中,va_start() 宏将 args 指针初始化为指向参数列表中的第一个参数。接下来,可以通过 va_arg() 宏访问参数列表中的每个参数。在访问完所有参数后,需要调用 va_end() 宏来关闭参数列表。

  需要注意的是,参数列表的格式与 fmt 字符串中的预期格式一致,这样才能正确地访问参数。fmt 字符串中需要指定参数的类型,并通过 switch 分支语句来处理每个参数的类型。va_arg() 宏的第一个参数是 va_list 指针,第二个参数是需要访问的参数类型,可以是任何合法的 C 语言数据类型。

  va_start()va_end() 宏是 C 语言支持可变参数函数的核心组成部分,在编写具有可变参数的函数时,这两个宏是必需的。

二、getopt、getopt_long函数

  getopt 是一个用于解析命令行参数的函数,它定义在 unistd.h 头文件中。它能够解析传递给程序的命令行参数,识别指定的选项,并将非选项参数和选项参数分离开来。学会使用该函数,我们就不用再苦逼的使用scanf来解析参数了。

下面是 getopt() 函数的用法:

#include 
extern char *optarg;
extern int optind, opterr, optopt;

int getopt(int argc, char * const argv[], const char *optstring);
  • argc:命令行参数个数。
  • argv:命令行参数列表。
  • optstring:包含所有支持选项的字符串,其中每个字符代表一个选项,有参数的选项在其字符后面带有冒号。例如,字符串 "ab:c" 表示选项 -a-c 没有参数,而选项 -b 有一个参数。

  getopt 函数按照以下方式逐个解析命令行参数:对于短选项(即单字符的选项),它会扫描命令行参数列表中的选项字符。

  如果没有更多的选项需要解析,getopt 将返回 -1,表示选项解析已经完成。如果解析出现错误,会返回 ?

  为了使用 getopt 函数,您需要在循环中调用它,直到返回 -1 为止。在每一次循环中,getopt 都会返回当前解析的选项,并将 optarg 置为该选项的参数(如果有的话)。同时,optind 变量会自动指向下一个未处理的命令行参数。下面是一个示例代码:

#include 
#include 

int main(int argc, char *argv[])
{
  int opt;

  while ((opt = getopt(argc, argv, "ab:c:")) != -1) {
    switch (opt) {
      case 'a':
        printf("option a\n");
        break;
      case 'b':
        printf("option b with value '%s'\n", optarg);
        break;
      case 'c':
        printf("option c with value '%s'\n", optarg);
        break;
      case '?':
        printf("unknown option: %c\n", optopt);
        break;
      default:
        break;
    }
  }

  for (int i = optind; i < argc; i++) {
    printf("non-option argument: %s\n", argv[i]);
  }

  return 0;
}

  在上面的示例代码中,getopt 函数使用选项字符串 "ab:c:" 来解析命令行参数。这意味着程序支持 -a-b-c 选项,其中 -b-c 选项需要参数。在循环中,使用 switch 语句来处理每个选项,并使用 optarg 来获取选项的参数值。在循环结束后,第一个命令行参数的索引可以通过 optind 求得,可以将那些没有被使用的参数作为非选项参数进行处理。

#include 

int getopt_long(int argc, char * const argv[],
           const char *optstring,
           const struct option *longopts, int *longindex);

int getopt_long_only(int argc, char * const argv[],
           const char *optstring,
           const struct option *longopts, int *longindex);


argc:命令行参数个数。
argv:命令行参数数组。
optstring:短选项字符串,用于指定短选项和是否带参数。
long_options:长选项结构数组。
longindex:用于保存找到的长选项的索引。

  getopt() 函数用于解析命令行选项,对于长选项(即多个字符的选项),它会扫描以双短划线 – 开头的选项。下面是一个示例代码,演示如何使用 getopt() 函数处理长选项。

#include 
#include 
#include 
#include 

int main(int argc, char *argv[]) {
    int opt;

    // 设置长选项列表
    static struct option long_options[] = {
        {"input", required_argument, NULL, 'i'},
        {"output", required_argument, NULL, 'o'},
        {"verbose", no_argument, NULL, 'v'},
        {NULL, 0, NULL, 0} // 结束标志
    };

    while ((opt = getopt_long(argc, argv, "i:o:v", long_options, NULL)) != -1) {
        switch (opt) {
            case 'i':
                printf("输入文件:%s\n", optarg);
                break;
            case 'o':
                printf("输出文件:%s\n", optarg);
                break;
            case 'v':
                printf("启用详细输出\n");
                break;
            case '?':
                // 处理未知选项或缺少参数错误
                exit(1);
            default:
                exit(1);
        }
    }

    return 0;
}

  以上示例中,getopt_long() 函数用于解析命令行选项。long_options 数组定义了长选项的规则,每个长选项包含选项名称、参数要求等信息。在 while 循环中,通过遍历每个选项并使用 switch 语句处理不同的选项标识。

例如,运行以下命令行:

./program --input input.txt --output output.txt --verbose
./program -i input.txt -o output.txt --verbose

输出:

输入文件:input.txt
输出文件:output.txt
启用详细输出

  getopt_long_onlygetopt_long参数表和功能基本相同,主要差别的地方在于长选项参数的解析。

  在getopt_long中,长选项参数需要由双横杠开始,即–name, 而-name会被解析成-n,-a,-m-eoptstring中匹配

  在getopt_long_only中,–name和-name都被解析成长选项参数。

三、strtok、strchr和 strstr

  1. strtok()

    • strtok() 函数用于将字符串拆分为多个子字符串。它接收两个参数:要拆分的字符串和指定的分隔符。
    • strtok() 函数通过在分隔符处将字符串截断,并返回一个指向拆分的子字符串的指针。第一次调用函数时,传入要拆分的字符串以及分隔符,随后每次调用函数时,第一个参数要设为 NULL
    • 注意:strtok() 在原字符串上进行修改,将分隔符替换为 NULL 字符。

    示例代码:

    #include 
    #include 
    
    int main() {
        char str[] = "Hello,World,Example";
        char *token = strtok(str, ",");
        
        while (token != NULL) {
            printf("%s\n", token);
            token = strtok(NULL, ",");
        }
        
        return 0;
    }
    

    输出:

    Hello
    World
    Example
    
  2. strchr()

    • strchr() 函数用于在字符串中查找指定字符的位置,并返回一个指向该位置的指针。
    • 它接收两个参数:要查找的字符串和指定的字符。
    • 如果找到了指定字符,返回指向该字符的指针;否则返回 NULL

    示例代码:

    #include 
    #include 
    
    int main() {
        char str[] = "Hello, World!";
        char *ptr = strchr(str, 'W');
        
        if (ptr != NULL) {
            printf("找到字符 'W',其位置是:%ld\n", ptr - str);
        } else {
            printf("未找到字符 'W'\n");
        }
        
        return 0;
    }
    

    输出:

    找到字符 'W',其位置是:7
    
  3. strstr()

    • strstr() 函数用于在字符串中查找指定子字符串的位置,并返回一个指向该位置的指针。
    • 它接收两个参数:要查找的字符串和指定的子字符串。
    • 如果找到了指定子字符串,返回指向该子字符串的指针;否则返回 NULL

    示例代码:

    #include 
    #include 
    
    int main() {
        char str[] = "Hello, World!";
        char *ptr = strstr(str, "World");
        
        if (ptr != NULL) {
            printf("找到子字符串 'World',其起始位置是:%ld\n", ptr - str);
        } else {
            printf("未找到子字符串 'World'\n");
        }
        
        return 0;
    }
    

    输出:

    找到子字符串 'World',其起始位置是:7
    

  如果目标字符串中含有两个相同的子字符串,那么strstr()函数将返回第一个匹配的子字符串的位置。例如,如果目标字符串是"hellohello",而要查找的子字符串是"hello",那么strstr()函数将返回指向第一个"hello"的指针。

四、memmove函数

  memmove() 函数是C标准库()中的一个函数,用于在内存中移动一段数据。与 memcpy() 类似,不同之处在于 memmove() 能够处理源内存和目标内存重叠的情况,而 memcpy() 则无法处理这种情况。

memmove() 函数的原型如下:

void *memmove(void *dest, const void *src, size_t n);

参数解释:

  • dest:目标内存的指针,表示将数据移动到的位置。
  • src:源内存的指针,表示需要移动的数据的位置。
  • n:要移动的字节数。

  memmove() 函数的作用是将 src 指针指向的内存区域的前 n 个字节的内容复制到 dest 指针指向的内存区域。它可以正确处理源内存和目标内存重叠的情况,因此在某些需要处理这种情况的场景下是非常有用的。

以下是一个简单的示例,展示了如何使用 memmove() 函数:

#include 
#include 

int main() {
    char str[] = "Hello, World!";
    char buffer[20];

    // 将 str 中的内容移动到 buffer 中
    memmove(buffer, str, strlen(str) + 1);

    printf("buffer contains: %s\n", buffer);

    return 0;
}

  在这个示例中,memmove() 函数被用来将 str 中的内容移动到 buffer 中。它能够正确地处理源内存和目标内存重叠的情况,因此是一个比较安全的选择。

  需要注意的是,memmove()memcpy() 的性能可能会有所差异,尤其在处理非重叠内存区域时。因此,在选择使用哪一个函数时,需要根据具体情况进行权衡。

  一般情况下,memcpy() 的性能优于 memmove()。这是因为 memcpy() 在实现上通常会假设源内存区域和目标内存区域是不重叠的,从而可以对内存进行更加高效的复制操作。相比之下,memmove() 需要考虑到源内存和目标内存重叠的情况,因此需要进行更多的判断和处理,可能会导致性能上的一些损失。

  然而,在实际的使用中,性能差异可能并不会对整个程序的性能产生显著的影响,除非需要处理大量的数据。因此在选择使用 memcpy() 还是 memmove() 时,通常应该依据具体情况:

  • 如果确定源内存和目标内存不会重叠,而且性能要求比较高,可以选择使用 memcpy()
  • 如果源内存和目标内存可能出现重叠,或者对性能要求没有那么高,可以选择使用 memmove()

  在实际编程中,可以根据具体的需求和上下文来选择合适的函数,同时可以通过性能测试来评估不同函数的性能表现,并决定是否值得为了性能而牺牲代码的安全性。

  总的来说,memcpy() 在绝大多数情况下会比 memmove() 更快,但在处理可能发生重叠的内存区域时,需要权衡使用哪个函数来保证程序的正确性和性能。

  欢迎大家指导和交流!如果我有任何错误或遗漏,请立即指正,我愿意学习改进。期待与大家一起进步!

你可能感兴趣的:(C语言高级专题系列,从C高级到征服C++,c++,开发语言,C,嵌入式,linux,windows)