argv#

指定形参默认值#

#include <iostream>
#include <cmath>

using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y, float z = 0);
float norm(float x, float y = 0, float z);

int main()
{
    cout << norm(3.0f) << endl;
    cout << norm(3.0f, 4.0f) << endl;
    cout << norm(3.0f, 4.0f, 5.0f) << endl;
    return 0;
}

float norm(float x, float y, float z)
{
    return sqrt(x * x + y * y + z * z);
}

指针作为参数#

#include <iostream>

using namespace std;

int foo1(int x)
{
    x += 10;
    return x;
}

int foo2(int *p)
{
    (*p) += 10;
    return *p;
}

int main()
{
    int num1 = 20;
    int num2 = foo1(num1);
    cout << "num1=" << num1 << endl;
    cout << "num2=" << num2 << endl;

    int *p = &num1;
    int num3 = foo2(p);
    cout << "num1=" << num1 << endl;
    cout << "*p=" << *p << endl;
    cout << "num3=" << num3 << endl;

    return 0;
}

引用作为参数#

#include <iostream>
#include <float.h>

struct Matrix
{
    int rows;
    int cols;
    float *pData;
};

float matrix_max(const struct Matrix &mat)
{
    float max = FLT_MIN;
    // find max value of mat
    for (int r = 0; r < mat.rows; r++)
        for (int c = 0; c < mat.cols; c++)
        {
            float val = mat.pData[r * mat.cols + c];
            max = (max > val ? max : val);
        }
    return max;
}

int main()
{
    using namespace std;

    Matrix matA = {3, 4};
    matA.pData = new float[matA.rows * matA.cols]{1.f, 2.f, 3.f};

    Matrix matB = {4, 8};
    matB.pData = new float[matB.rows * matB.cols]{10.f, 20.f, 30.f};

    Matrix matC = {4, 2};
    matC.pData = new float[matC.rows * matC.cols]{100.f, 200.f, 300.f};

    // some operations on the matrices

    float maxa = matrix_max(matA);
    float maxb = matrix_max(matB);
    float maxc = matrix_max(matC);

    cout << "max(matA) = " << maxa << endl;
    cout << "max(matB) = " << maxb << endl;
    cout << "max(matC) = " << maxc << endl;

    delete[] matA.pData;
    delete[] matB.pData;
    delete[] matC.pData;

    return 0;
}

getopt_long#

#include <getopt.h>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    /**
     * struct option
     * {
     *      const char * name;
     *      int          has_arg;
     *      int        * flag;
     *      int          val;
     * }
     */
    static struct option long_options[] = {
        {"reqarg", required_argument, NULL, 'r'},
        {"optarg", optional_argument, NULL, 'o'},
        {"noarg", no_argument, NULL, 'n'},
        {NULL, 0, NULL, 0},
    };

    while (1)
    {
        int option_index = 0, opt;

        /**
         * getopt_long 会遍历 argv 数组,在每次迭代中,它会返回下一个选项字符(如果有),
         * 然后,这个字符与长选项列表进行比较,如果匹配,则对应的操作会被执行,
         * 如果没有匹配,那么函数将返回 -1。
         * 只有一个字符,不带冒号,只表示选项,如 -c
         * 一个字符,后接一个冒号,表示选项后面带一个参数,如 -a 100
         * 一个字符,后接两个冒号——表示选项后面带一个可选参数,
         * 即参数可有可无,如果带参数,则选项与参数直接不能有空格,如 -b200
         */
        opt = getopt_long(argc, argv, "a::b:c:d", long_options, &option_index);

        if (opt == -1) // 选项遍历完毕,退出循环
            break;

        printf("opt = %c\t\t", opt);
        printf("optarg = %s\t\t", optarg);
        printf("optind = %d\t\t", optind);
        printf("argv[%d] = %s\t\t", optind, argv[optind]);
        printf("option_index = %d\n", option_index);
    }

    return 0;
}

这段代码是一个使用 getopt_long 函数进行命令行参数解析的例子,它演示了如何处理短选项(使用单个字符)和长选项(使用字符串)。

首先,代码定义了一个静态的 struct option 数组 long_options,用于描述长选项的信息。每个数组元素都是一个结构体,包含以下字段:

  • name:选项的名称,字符串类型。

  • has_arg:指定选项是否需要参数,有三个可能值:no_argument(0)表示不需要参数,required_argument(1)表示必须有参数,optional_argument(2)表示参数是可选的。

  • flag:如果不为 NULL,则指向一个整数变量,用于存储选项的值(即 val 字段),而不是返回选项字符。如果为 NULL,则 getopt_long 函数将返回选项字符。

  • val:选项的值,通常是一个字符。

main 函数中,使用一个 while 循环来遍历命令行参数。在循环中,调用 getopt_long 函数来获取下一个选项。getopt_long 的参数包括:

  • argcargv:分别是命令行参数的数量和数组。

  • "a::b:c:d":短选项的字符串表示,冒号表示需要参数的选项。

  • long_options:长选项的数组。

  • option_index:用于存储当前长选项在 long_options 数组中的索引。

在每次循环迭代中,打印出当前选项的相关信息,包括选项字符、选项参数、optind 的值、对应的参数值等。

g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out --reqarg 100 --optarg=200 --noarg
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out –reqarg=100 --optarg=200 --noarg
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out --reqarg 100 --optarg --noarg

这条指令表示使用 C++20 标准,进行优化,启用所有警告,使用多线程,编译 main.cpp 文件,然后运行生成的可执行文件 a.out 并传递一些命令行参数。

在不同的命令行调用中,通过使用 --reqarg--optarg--noarg 等选项来测试程序的输出。程序会解析这些选项,并打印相关的信息。

va_listva_arg#

#include <cstdarg>
#include <iostream>

void log(char *fmt, ...)
{
    char buf[512] = {0};
    va_list ap;

    va_start(ap, fmt);
    (void)vsnprintf(buf, sizeof(buf) - 2, fmt, ap);
    va_end(ap);

    printf("%s\n", buf);
}

int main()
{
    log((char *)"%s, %d, %s", "hello", 100, "world");
}
#include <cstdarg>
#include <iostream>

double sum(int num, ...)
{
    va_list valist; // 创建参数列表
    double ret = 0.0;

    va_start(valist, num); // 初始化参数列表

    for (int i = 0; i < num; i++)
    {
        ret += va_arg(valist, double); // 访问参数列表中的项
    }

    va_end(valist); // 清理参数列表占用的内存

    return ret;
}

int main()
{
    std::cout << "Sum of 2, 3 is " << sum(2, 2.0, 3.0) << std::endl;
    std::cout << "Sum of 2, 3, 4, 5 is " << sum(4, 2.0, 3.0, 4.0, 5.0) << std::endl;
}
  • sum 函数接受一个整数参数 num,表示后面可变参数的数量。

  • va_list 是用于存储可变参数的类型,它是一个指向参数列表的对象。

  • va_start 宏用于初始化 valist,将其指向参数列表的起始位置。

  • 使用 va_arg 宏可以依次访问参数列表中的项,本例中假设参数都是 double 类型。

  • va_end 宏用于清理参数列表占用的内存。

main 函数中分别调用了 sum 函数两次,每次传递不同数量的参数。这展示了可变参数函数的灵活性。

当程序运行时,sum 函数通过可变参数的方式计算传递给它的数字的总和,然后 printf 语句输出这个总和。在第一个调用中,传递了两个参数(2 和 3),在第二个调用中,传递了四个参数(2、3、4、5)。

使用 C 风格的可变参数函数,需要在运行时做类型转换,可能存在类型安全的隐患。

<typename... Args>#

在 C++ 中,你可以使用递归或者使用 C++17 引入的折叠表达式(fold expression)来访问可变参数列表中的每个参数。

#include <iostream>

// 递归终止条件
void printArgs() {
    std::cout << std::endl;
}

// 递归步骤
template<typename T, typename... Args>
void printArgs(T first, Args... args) {
    std::cout << first << " ";
    printArgs(args...); // 递归调用
}

int main() {
    printArgs(1, "Hello", 3.14, 'A');
    return 0;
}

在这个例子中,printArgs 函数通过递归的方式遍历可变参数列表,打印每个参数的值。递归终止条件是一个没有参数的版本。

#include <iostream>

template<typename... Args>
void printArgs(Args... args) {
    (std::cout << ... << args) << std::endl; // 折叠表达式
}

int main() {
    printArgs(1, "Hello", 3.14, 'A');
    return 0;
}

在这个例子中,使用了 C++17 引入的折叠表达式。(std::cout << ... << args) 表示将所有参数展开成一个表达式,然后通过 << 运算符连接起来,最后加上 std::endl 进行换行。

这两种方法都允许你访问可变参数列表中的每个参数,具体选择取决于你的编译环境和代码的需求。折叠表达式提供了一种更简洁和直观的语法,但需要 C++17 及以上的编译器支持。

包展开(args…)和模式(args)

例 1:字面量作为实参

template <typename... Us>
void f(Us... pargs) {}

template <typename... Ts>
void g(Ts... args)
{
    // &args... 是包展开
    // &args    是它的模式
    f(&args...);
}

int main()
{
    // Ts... args   会展开成 int E1, double E2, const char* E3
    // &args...     会展开成 &E1, &E2, &E3
    // Us...        会展开成 int* E1, double* E2, const char** E3
    g(1, 0.2, "a");
}

例 2:数组作为实参

// 接受任意数量的模板参数(用 Ts... 表示)
template <typename... Ts>
void f(Ts...) {}

// 接受一个模板参数包 Ts 和一个非类型参数包 N
// Ts (&...arr)[N] 表示 arr 是一个引用数组,数组元素的类型是 Ts
// 数组的大小是 N
template <typename... Ts, int... N>
void g(Ts (&...arr)[N]) {}

int main()
{
    // Ts... 会展开成 void f(char, int)
    f('a', 1);

    // Ts... 会展开成 void f(double)
    f(0.1);

    // Ts (&...arr)[N] 会展开成 const char (&)[2], int (&)[1]
    // 模板参数 Ts 被展开为 const char
    // 非类型参数 N 被展开为数组大小,即 2 和 1
    int n[1];
    g<const char, int>("a", n);
}

例 3:调整可变参数的位置

template <typename A, typename B, typename... C>
void func(A arg1, B arg2, C... arg3)
{
    container<A, B, C...> t1; // 展开成 container<A, B, E1, E2, E3>
    container<C..., A, B> t2; // 展开成 container<E1, E2, E3, A, B>
    container<A, C..., B> t3; // 展开成 container<A, E1, E2, E3, B>
}