C语言和C++中不定参数解析

在这里插入图片描述

你所看到的惊艳,都曾被平庸历练


简介

相信学过c/c++的佬们都知道,如果一个函数将自己的参数确定好后,要想使用该函数就必须传递合适的数量以及类型的参数,然而这样的函数非常具有局限性。例如有一个求和函数,我们并不知道我们要求几个数的和,这就导致在确定函数参数时无从下手,而这时候不定参数就派上了用场。

不定参数是指在函数定义时,允许接收可变数量的参数。这种机制允许函数在调用时接受不同数量的实际参数,使得函数更加灵活,能够适应不同的使用场景。例如C语言中的printf函数,scanf函数等。在C和C++中,不定参数的实现方式主要依赖于标准库中的stdarg.h头文件。

在这里插入图片描述

在使用不定参数后,sum函数可以接受不同数量的参数,并计算它们的总和。这种灵活性使得函数能够适应不同的求和需求,而不需要为每种情况都定义一个单独的函数。这样的设计使代码更简洁,更容易扩展,同时提供了更大的通用性。

不定参数的优势:

优势说明
灵活性不定参数函数允许在调用函数时传递不同数量的参数,提高了函数的灵活性和通用性。
适应变化在处理不确定数量的参数或者未来可能会新增参数的情况下,不定参数函数能够更好地适应变化。
简化代码使用不定参数可以简化函数定义,避免定义多个函数处理不同数量的参数情况。
可读性在某些情况下,使用不定参数可以使函数调用更加清晰和可读,特别是当参数的数量可能变化时。

C语言不定参数

在C语言中,不定参数是通过stdarg.h头文件提供的一组宏来实现的。这些宏主要包括va_listva_startva_argva_end

在这里插入图片描述

  1. va_list va_list是一个类型,用于声明一个变量,该变量将用于存储不定参数的信息。
    在这里插入图片描述

  2. va_start va_start宏用于初始化va_list,使其指向不定参数的起始位置。它接受两个参数,第一个是va_list对象,第二个是最后一个确定的参数,用于确定不定参数的起始位置。
    在这里插入图片描述

    void function_with_varargs(int fixed_arg, ...) {
        va_list args;
        va_start(args, fixed_arg); // 现在args指向第一个可变参数
        // ...
        va_end(args);
    }
    
  3. va_arg va_arg宏用于获取当前位置的值,并且在每次调用后,会将指针移动到下一个可变参数的位置。它接受两个参数,第一个是va_list对象,第二个是要获取的参数的类型。
    在这里插入图片描述

    int value = va_arg(args, int);
    
  4. va_end va_end宏用于清理va_list对象,确保在使用完不定参数后正确释放资源。
    在这里插入图片描述

    va_end(args);
    

如下是一个在C语言中使用不定参数的求和函数:

#include <stdio.h>
#include <stdarg.h>

int sum(int n, ...) {
    va_list args;   // 定义用于存储不定参数信息的变量
    va_start(args, n);   // 初始化 va_list,使其指向不定参数列表的起始位置
    int ret = 0;

    for (int i = 0; i < n; i++) {
        int tmp = va_arg(args, int);   // 获取当前位置的参数值
        ret += tmp;
    }

    va_end(args);   // 清理 va_list,确保资源正确释放
    return ret;
}

int main() {
    int ret = sum(5, 10, 24, 10, 34, 0);   // 调用 sum 函数,传递5个整数
    printf("%d\n", ret);   // 打印计算结果
    return 0;
}

sum 函数中,va_start 初始化了 va_list,并使其指向传递给函数的不定参数的起始位置。然后,通过使用 va_arg 获取每个参数的值,并将它们累加到 ret 变量中。最后,通过 va_end 清理 va_list,确保资源得到正确释放。

main 函数中,调用了 sum 函数,并将 5 个整数作为参数传递给它,然后打印计算结果。

在这里插入图片描述

不定参宏函数

除此之外,还可以在C语言中使用宏来创建带有不定参数的宏函数。不定参数的宏函数通常使用__VA_ARGS__来表示可变数量的参数。

#include <stdio.h>
#include <stdarg.h>

// 定义 LOG 宏,使用 __VA_ARGS__ 来表示可变参数
#define LOG(fmt, ...) printf("[%s : %d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

int main() {
    // 使用 LOG 宏输出日志信息,自动包含文件名和行号
    LOG("登陆失败");
    return 0;
}

在这个例子中,LOG 宏使用了 __VA_ARGS__ 来表示可变参数。在调用 LOG 时,传递的字符串会被输出,并自动包含文件名和行号。这样,通过这个宏,您可以轻松地在代码中输出带有文件名和行号的日志信息。

需要注意的是,在 ##__VA_ARGS__ 中的 ## 用于处理不带参数时的情况,以防止在宏展开时产生语法错误。在可变参数为空的情况下,##__VA_ARGS__ 将会被移除,避免了末尾逗号的问题。

在这里插入图片描述

其中 filename.c 是当前文件名,7 是当前行号,登陆失败 是通过 LOG 宏传递的字符串。不定参数宏函数的使用可以帮助简化代码,但也需要小心使用,确保在宏展开时没有意外的副作用。

C++不定参数

C++中的不定参函数通常使用可变参数模板来实现,这使得函数能够接受不定数量和类型的参数。使用可变参数模板的不定参函数是 C++11 引入的特性。如果你使用的是更早版本的 C++,可能需要使用 <cstdarg> 头文件中的宏来处理不定参数。

基本模板:

template<typename ...Args>
void print(Args... args) {}

又由于无法直接获取参数包args中的每个参数,所以只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以只能想一些特殊的办法来获取参数包的值。

方法一:递归展开

递归展开可变参数包是使用可变参数模板的一种常见技术,通常用于在编译时展开不定数量的参数。下面是一个递归展开可变参数包的示例:

#include <iostream>

void print() {
    // 终止条件,处理空参数包
}

template <typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << "Cur : " << first << " args size : " << sizeof...(args) << std::endl;
    print(args...);  // 递归调用,展开参数包
}

int main() {
    print("你干嘛", "哎呦", 100, 345);
    return 0;
}

这个程序定义了一个函数模板 print,该函数接受任意数量和类型的参数,并递归展开这些参数。

  • print 函数有两个版本:一个是终止条件版本(空参数包时调用),另一个是递归版本。

  • 在递归版本中,首先打印第一个参数 first 和参数包的大小(使用 sizeof...(args)),然后通过 print(args...) 递归地调用自己,展开参数包。

  • main 函数中,你调用了 print 函数,传递了不同类型的参数。

这样,当你运行程序时,print 函数将递归展开参数包,打印每个参数的值和参数包的大小。

在这里插入图片描述

这样,通过递归调用,编译器会在编译时展开参数包,使得函数能够处理不定数量和类型的参数。这是一种在编译时进行模板展开的方式,相比运行时的展开更加高效。

方式二:折叠表达式

除了递归展开参数包的方式,C++17 引入了折叠表达式(fold expressions)的特性,这是一种更简洁的方式来展开参数包。折叠表达式在处理可变参数包时可以提供更紧凑的语法。如下代码:

#include <iostream>

// 使用折叠表达式展开参数包
template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // 折叠表达式展开参数包
}

int main() {
    print("你干嘛", "哎呦", 100, 345);
    return 0;
}

在这里插入图片描述

在这个例子中,(std::cout << ... << args) 是一个折叠表达式,用于展开参数包 args。这个表达式实际上将所有参数连接到 std::cout 流中,然后通过 << std::endl 添加换行符。

当需要使用折叠表达式求和时,也可以如下代码:

#include <iostream>

template<class ...Args>
int sum(Args... args) {
    return (args + ...);
}

int main() {
    std::cout << sum(25, 34, 59) << std::endl;
    return 0;
}

代码中使用了 C++17 中引入的折叠表达式 (args + ...) 来计算参数包中所有参数的和。在这个例子中,折叠表达式展开了 args 中的参数,并使用 + 运算符对它们求和。

在这里插入图片描述

使用折叠表达式的好处是语法更为紧凑,而且可以在一个表达式中完成参数包的展开。这是在 C++17 及更高版本中的一种更现代的方式。

总体而言,递归展开和折叠表达式是两种常见的方式,具体选择取决于个人或团队的偏好以及编译器的支持版本。在 C++17 及更高版本中,折叠表达式通常被认为是更好的选择。

方法三:逗号表达式

除了递归展开和折叠表达式,还有一种使用逗号表达式的方式展开参数包。这种方式通常结合逗号运算符 , 和初始化列表来实现。

以下是一个示例:

#include <iostream>

// 使用逗号表达式展开参数包
template <typename... Args>
void print(Args... args) {
    int dummy[] = {(std::cout << args << " ", 0)...};  // 逗号表达式展开参数包
    (void)dummy;  // 防止编译器警告未使用的变量
    std::cout << std::endl;
}

int main() {
    print("你干嘛", "哎呦", 100, 345);
    return 0;
}

在这个例子中,逗号表达式 (std::cout << args << " ", 0) 被用于展开参数包 args。逗号表达式的返回值是初始化列表中的最后一个表达式的值(在这里是 0),而初始化列表的目的是为了创建一个临时数组 dummy

这种方式相对较独特,通常在一些特殊情况下使用。一般而言,递归展开和折叠表达式更为常见和直观,但了解这种逗号表达式的方法也是有益的。选择展开参数包的方法通常取决于个人或团队的偏好、代码可读性和编译器支持。

总结

在C语言和C++中,可变参数是一种强大的特性,它允许函数接受可变数量和类型的参数。在C语言中,我们通常使用<stdarg.h>头文件提供的宏,如va_startva_arg、和va_end,来处理可变参数函数。而在C++中,我们有更灵活和类型安全的可变参数模板。总结起来,可变参数的使用有以下关键点:

  1. C语言中的可变参数:

    • 使用<stdarg.h>头文件提供的宏,例如va_startva_arg、和va_end
    • 缺点是类型不安全,需要手动管理参数的类型和数量。
  2. C++中的可变参数模板:

    • 引入了可变参数模板,允许在编译时处理可变数量和类型的参数。
    • 提高了类型安全性,无需手动管理参数的类型和数量。
    • 可以使用递归展开、折叠表达式、逗号表达式等多种方式展开可变参数包。
  3. 递归展开:

    • 使用递归函数模板,逐一处理参数包中的每个参数。
    • 通过终止条件和递归调用来展开参数包。
  4. 折叠表达式:

    • 在C++17及以后版本引入,提供了更紧凑的语法。
    • 使用(expression op ...)的形式,其中op可以是二元运算符。
  5. 逗号表达式:

    • 使用逗号表达式展开参数包时,返回值不能直接用于数组初始化,通常使用0作为占位符。
    • 可以通过逗号表达式在一行中执行多个操作。

在选择可变参数的处理方式时,应根据具体情况和编程需求来选择最合适的方法。C++中的可变参数模板提供了更强大、类型安全和灵活的处理方式,但对于特定情况,C语言中的可变参数也可能是一种合适的选择。最终,可变参数的使用应该以代码的清晰性、可读性和维护性为重要考虑因素。

最后,码文不易,如果文章对你有所帮助的话,不妨点上一个👍,感谢大佬的支持!

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拖拉机厂第一代码手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值