??在C语言学习过程中,有些函数并不会怎么见,但是在工作以后经常会见到和使用。今天在这里和大家简单介绍一下。
??va_start()
和 va_end()
是 C 语言中的两个宏,用于在可变参数
函数中访问参数列表。
??可变参数
是指在函数声明中未指定参数数量或类型的参数。在 C 语言中,可变参数函数使用一组宏来解析参数列表。这些宏是 stdarg.h
头文件中定义的,并包含以下三个:
va_list
: 是一个指针,用于访问参数列表。
va_start()
: 初始化 va_list
指针,使其指向参数列表的第一个参数。
va_end()
: 清除 va_list
指针,结束参数列表的遍历。
这些宏的使用通常遵循以下通用模式:
#include <stdarg.h>
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
是一个用于解析命令行参数的函数,它定义在 unistd.h
头文件中。它能够解析传递给程序的命令行参数,识别指定的选项,并将非选项参数和选项参数分离开来。学会使用该函数,我们就不用再苦逼的使用scanf来解析参数了。
下面是 getopt()
函数的用法:
#include <unistd.h>
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 <stdio.h>
#include <unistd.h>
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 <getopt.h>
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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
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_only
与getopt_long
参数表和功能基本相同,主要差别的地方在于长选项参数的解析。
??在getopt_long
中,长选项参数需要由双横杠开始,即–name, 而-name会被解析成-n
,-a
,-m
和-e
在optstring
中匹配
??在getopt_long_only
中,–name和-name都被解析成长选项参数。
strtok()
strtok()
函数用于将字符串拆分为多个子字符串。它接收两个参数:要拆分的字符串和指定的分隔符。strtok()
函数通过在分隔符处将字符串截断,并返回一个指向拆分的子字符串的指针。第一次调用函数时,传入要拆分的字符串以及分隔符,随后每次调用函数时,第一个参数要设为 NULL
。strtok()
在原字符串上进行修改,将分隔符替换为 NULL
字符。示例代码:
#include <stdio.h>
#include <string.h>
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
strchr()
strchr()
函数用于在字符串中查找指定字符的位置,并返回一个指向该位置的指针。NULL
。示例代码:
#include <stdio.h>
#include <string.h>
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
strstr()
strstr()
函数用于在字符串中查找指定子字符串的位置,并返回一个指向该位置的指针。NULL
。示例代码:
#include <stdio.h>
#include <string.h>
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()
函数是C标准库(<string.h>
)中的一个函数,用于在内存中移动一段数据。与 memcpy()
类似,不同之处在于 memmove()
能够处理源内存和目标内存重叠的情况,而 memcpy()
则无法处理这种情况。
memmove()
函数的原型如下:
void *memmove(void *dest, const void *src, size_t n);
参数解释:
dest
:目标内存的指针,表示将数据移动到的位置。src
:源内存的指针,表示需要移动的数据的位置。n
:要移动的字节数。??memmove()
函数的作用是将 src
指针指向的内存区域的前 n
个字节的内容复制到 dest
指针指向的内存区域。它可以正确处理源内存和目标内存重叠的情况,因此在某些需要处理这种情况的场景下是非常有用的。
以下是一个简单的示例,展示了如何使用 memmove()
函数:
#include <stdio.h>
#include <string.h>
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()
更快,但在处理可能发生重叠的内存区域时,需要权衡使用哪个函数来保证程序的正确性和性能。
??欢迎大家指导和交流!如果我有任何错误或遗漏,请立即指正,我愿意学习改进。期待与大家一起进步!