传递和返回结构体

考虑一个由3个独立变量表示的员工:

int main()
{
    int id { 1 };
    int age { 24 };
    double wage { 52400.0 };

    return 0;
}

如果我们要将这个员工传递给一个函数,我们需要传递3个变量:

#include <iostream>

void printEmployee(int id, int age, double wage)
{
    std::cout << "ID:   " << id << '\n';
    std::cout << "Age:  " << age << '\n';
    std::cout << "Wage: " << wage << '\n';
}

int main()
{
    int id { 1 };
    int age { 24 };
    double wage { 52400.0 };

    printEmployee(id, age, wage);

    return 0;
}

虽然传递3个独立的员工变量还不算太糟糕,但试想一个需要传递10个或12个员工变量的函数。独立传递每个变量不仅耗时,而且容易出错。此外,如果我们为员工添加一个新的属性(例如名字),我们就需要修改所有函数的声明、定义以及函数调用,以接受新的参数和参数值!

通过引用传递结构体

使用结构体而不是独立变量的一个巨大优势是,我们可以将整个结构体传递给需要操作其成员的函数。结构体通常通过引用传递(通常是通过const引用),以避免复制。

#include <iostream>

struct Employee
{
    int id {};
    int age {};
    double wage {};
};

void printEmployee(const Employee& employee) // 注意这里通过引用传递
{
    std::cout << "ID:   " << employee.id << '\n';
    std::cout << "Age:  " << employee.age << '\n';
    std::cout << "Wage: " << employee.wage << '\n';
}

int main()
{
    Employee joe { 14, 32, 24.15 };
    Employee frank { 15, 28, 18.27 };

    // 打印Joe的信息
    printEmployee(joe);

    std::cout << '\n';

    // 打印Frank的信息
    printEmployee(frank);

    return 0;
}

在上述示例中,我们将整个Employee对象传递给printEmployee()(两次,一次传递joe,一次传递frank)。

上述程序的输出为:

ID:   14
Age:  32
Wage: 24.15

ID:   15
Age:  28
Wage: 18.27

因为我们传递的是整个结构体对象(而不是单独的成员),所以无论结构体对象有多少成员,我们只需要一个参数。而且,将来如果我们决定为Employee结构体添加新成员,我们就不必更改函数的声明或函数调用了!新成员将自动包含在内。

相关内容

我们在第12.6课——通过const左值引用传递中讨论了何时通过值传递结构体,何时通过引用传递结构体。

传递临时结构体

在前面的示例中,我们在将joe传递给printEmployee()函数之前创建了Employee变量joe。这允许我们为Employee变量命名,这在文档编写方面可能会很有用。但它也需要两条语句(一条用于创建joe,一条用于使用joe)。

在某些情况下,我们只使用一个变量一次,那么给变量命名并将变量的创建和使用分开会增加复杂性。在这种情况下,使用临时对象可能更可取。临时对象不是变量,因此它没有标识符。

以下是与上面相同的示例,但我们用临时对象替换了变量joefrank

#include <iostream>

struct Employee
{
    int id {};
    int age {};
    double wage {};
};

void printEmployee(const Employee& employee) // 注意这里通过引用传递
{
    std::cout << "ID:   " << employee.id << '\n';
    std::cout << "Age:  " << employee.age << '\n';
    std::cout << "Wage: " << employee.wage << '\n';
}

int main()
{
    // 打印Joe的信息
    printEmployee(Employee { 14, 32, 24.15 }); // 创建一个临时Employee对象传递给函数(明确指定类型)(推荐)

    std::cout << '\n';

    // 打印Frank的信息
    printEmployee({ 15, 28, 18.27 }); // 创建一个临时Employee对象传递给函数(类型从参数推导)

    return 0;
}

我们可以用两种方式创建一个临时Employee对象。在第一次调用中,我们使用Employee { 14, 32, 24.15 }的语法。这告诉编译器创建一个Employee对象,并用提供的初始化器初始化它。这是推荐的语法,因为它清楚地表明我们正在创建什么类型的临时对象,编译器不可能误解我们的意图。

在第二次调用中,我们使用{ 15, 28, 18.27 }的语法。编译器足够聪明,能够理解提供的参数必须转换为一个Employee对象,以便函数调用能够成功。请注意,这种形式被认为是一种隐式转换,因此在只接受显式转换的情况下,它将无法工作。

相关内容

我们在第14.13课——临时类对象中更详细地讨论了类类型的临时对象和转换。

关于临时对象的其他几点:它们在定义点被创建和初始化,并在创建它们的完整表达式的末尾被销毁。临时对象的求值是一个右值表达式,它只能用在接受右值的地方。当临时对象作为函数参数使用时,它只会绑定到接受右值的参数。这包括按值传递和按const引用传递,不包括按非常量引用传递和按地址传递。

返回结构体

考虑一个函数需要返回三维笛卡尔空间中的一个点的情况。这样的点有3个属性:一个x坐标、一个y坐标和一个z坐标。但函数只能返回一个值。那么我们如何将所有3个坐标返回给用户呢?

一种常见的方式是返回一个结构体:

#include <iostream>

struct Point3d
{
    double x { 0.0 };
    double y { 0.0 };
    double z { 0.0 };
};

Point3d getZeroPoint()
{
    // 我们可以创建一个变量并返回该变量(我们稍后会改进这一点)
    Point3d temp { 0.0, 0.0, 0.0 };
    return temp;
}

int main()
{
    Point3d zero{ getZeroPoint() };

    if (zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0)
        std::cout << "The point is zero\n";
    else
        std::cout << "The point is not zero\n";

    return 0;
}

这将打印:

The point is zero

在函数内部定义的结构体通常按值返回,以避免返回悬挂引用。

在上面的getZeroPoint()函数中,我们创建了一个新的命名对象(temp),只是为了返回它:

Point3d getZeroPoint()
{
    // 我们可以创建一个变量并返回该变量(我们稍后会改进这一点)
    Point3d temp { 0.0, 0.0, 0.0 };
    return temp;
}

对象的名称(temp)在这里并没有提供任何文档价值。

我们可以通过返回一个临时(无名/匿名)对象来使函数稍微更好一些:

Point3d getZeroPoint()
{
    return Point3d { 0.0, 0.0, 0.0 }; // 返回一个无名的Point3d
}

在这种情况下,一个临时的Point3d对象被构造,复制回调用者,然后在表达式的末尾被销毁。注意这有多简洁(一行对两行,且无需理解temp是否被使用超过一次)。

相关内容

我们在第14.13课——临时类对象中更详细地讨论了匿名对象。

推导返回类型

在函数有一个显式的返回类型(例如Point3d)的情况下,我们甚至可以在返回语句中省略类型:

Point3d getZeroPoint()
{
    // 我们已经在函数声明中指定了类型
    // 因此这里不需要再指定
    return { 0.0, 0.0, 0.0 }; // 返回一个无名的Point3d
}

这被认为是一种隐式转换。

另外请注意,由于在这种情况下我们返回的都是零值,我们可以使用空的大括号来值初始化一个Point3d

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"