使用指针和引用进行成员选择

结构体和结构体引用的成员选择

在结构体、成员和成员选择的介绍中,我们展示了可以使用成员选择运算符(.)从结构体对象中选择一个成员:

#include <iostream>

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

int main()
{
    Employee joe { 1, 34, 65000.0 };

    // 使用成员选择运算符(.)从结构体对象中选择成员
    ++joe.age; // Joe又过了一岁生日
    joe.wage = 68000.0; // Joe升职了

    return 0;
}

由于对象的引用表现得就像对象本身一样,我们也可以使用成员选择运算符(.)从结构体的引用中选择成员:

#include <iostream>

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

void printEmployee(const Employee& e)
{
    // 使用成员选择运算符(.)从结构体引用中选择成员
    std::cout << "Id: " << e.id << '\n';
    std::cout << "Age: " << e.age << '\n';
    std::cout << "Wage: " << e.wage << '\n';
}

int main()
{
    Employee joe{ 1, 34, 65000.0 };

    ++joe.age;
    joe.wage = 68000.0;

    printEmployee(joe);

    return 0;
}

指向结构体的指针的成员选择

然而,成员选择运算符(.)不能直接用于指向结构体的指针:

#include <iostream>

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

int main()
{
    Employee joe{ 1, 34, 65000.0 };

    ++joe.age;
    joe.wage = 68000.0;

    Employee* ptr{ &joe };
    std::cout << ptr.id << '\n'; // 编译错误:不能对指针使用`.`运算符

    return 0;
}

对于普通变量或引用,我们可以直接访问对象。然而,由于指针存储的是地址,我们需要先对指针进行解引用,才能获取对象,然后才能对其进行任何操作。因此,访问指向结构体的指针的成员的一种方法如下:

#include <iostream>

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

int main()
{
    Employee joe{ 1, 34, 65000.0 };

    ++joe.age;
    joe.wage = 68000.0;

    Employee* ptr{ &joe };
    std::cout << (*ptr).id << '\n'; // 不太好,但可行:先解引用`ptr`,然后使用成员选择

    return 0;
}

然而,这种方法有些笨拙,尤其是因为我们需要对解引用操作进行括号括起来,以便它优先于成员选择操作。

为了提供更简洁的语法,C++提供了一个从指针选择成员的运算符(->)(有时也称为箭头运算符),可用于从指向对象的指针中选择成员:

#include <iostream>

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

int main()
{
    Employee joe{ 1, 34, 65000.0 };

    ++joe.age;
    joe.wage = 68000.0;

    Employee* ptr{ &joe };
    std::cout << ptr->id << '\n'; // 更好:使用`->`从指向对象的指针中选择成员

    return 0;
}

从指针选择成员的运算符(->)与成员选择运算符(.)的工作方式完全相同,但在选择成员之前会隐式地对指针对象进行解引用。因此,ptr->id等同于(*ptr).id

这种箭头运算符不仅更容易输入,而且由于隐式地完成了间接寻址,因此出错的可能性也小得多,因为无需担心优先级问题。因此,在通过指针进行成员访问时,始终使用->运算符,而不是.运算符。

最佳实践

当使用指针访问成员时,使用从指针选择成员的运算符(->),而不是成员选择运算符(.)。

运算符->的链式调用

如果通过运算符->访问的成员是指向类类型的指针,则可以在同一表达式中再次应用运算符->来访问该类类型的成员。

以下示例说明了这一点(感谢读者Luna提供):

#include <iostream>

struct Point
{
    double x {};
    double y {};
};

struct Triangle
{
    Point* a {};
    Point* b {};
    Point* c {};
};

int main()
{
    Point a {1,2};
    Point b {3,7};
    Point c {10,2};

    Triangle tr { &a, &b, &c };
    Triangle* ptr {&tr};

    // `ptr`是指向`Triangle`的指针,它包含指向`Point`的成员指针
    // 要访问`ptr`所指向的`Triangle`的`Point` c的成员`y`,以下两种方式是等价的:

    // 使用`.`运算符访问
    std::cout << (*(*ptr).c).y << '\n'; // 很丑陋!

    // 使用`->`运算符访问
    std::cout << ptr -> c -> y << '\n'; // 更优雅
}

当在序列中使用多个运算符->(例如ptr->c->y)时,表达式可能难以阅读。在成员和运算符->之间添加空格(例如ptr -> c -> y)可以使被访问的成员与运算符更易于区分。

混合使用指针和非指针成员

成员选择运算符始终应用于当前选择的变量。如果你有指针和普通成员变量的混合,你可以看到成员选择中.->按顺序使用的情况:

#include <iostream>
#include <string>

struct Paw
{
    int claws{};
};

struct Animal
{
    std::string name{};
    Paw paw{};
};

int main()
{
    Animal puma{ "Puma", { 5 } };

    Animal* ptr{ &puma };

    // `ptr`是指针,使用`->`
    // `paw`不是指针,使用`.`

    std::cout << (ptr->paw).claws << '\n';

    return 0;
}

(ptr->paw).claws的情况下,括号不是必需的,因为运算符->.都是从左到右求值的,但括号确实有助于略微提高可读性。

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

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

公众号二维码

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