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

结构体及其引用的成员选择

在第13.7节——结构体、成员及成员选择的介绍中,我们展示了如何使用成员选择运算符(.)从结构体对象中选择成员:

#include <iostream>

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

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

    // Use member selection operator (.) to select a member from struct object
    ++joe.age; // Joe had a birthday
    joe.wage = 68000.0; // Joe got a promotion

    return 0;
}

由于对象的引用与对象本身具有相同的行为,我们也可以使用成员选择运算符 (.) 从结构体引用中选择成员:

#include <iostream>

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

void printEmployee(const Employee& e)
{
    // Use member selection operator (.) to select member from reference to struct
    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;
}

image


结构体指针的成员选择

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

#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'; // Compile error: can't use operator. with pointers

    return 0;
}

image

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

#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'; // Not great but works: First dereference ptr, then use member selection

    return 0;
}

image

然而,这有点难看,特别是因为我们需要将取消引用操作放在括号中,因此它将优先于成员选择操作。

为了使语法更清晰,C++ 提供了成员选择指针运算符member selection from pointer operator (->)(有时也称为箭头运算符arrow operator),可用于从指向对象的指针中选择成员:

#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'; // Better: use -> to select member from pointer to object

    return 0;
}

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

箭头运算符不仅更易输入,且因隐式完成间接操作而大幅降低出错概率,无需顾虑运算符优先级问题。因此通过指针访问成员时,应始终使用->运算符而非.运算符。

最佳实践
使用指针访问成员时,请采用指针成员选择运算符(->)而非成员选择运算符(.)。


链式运算符->(Chaining operator->)

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

以下示例说明此特性(由读者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 is a pointer to a Triangle, which contains members that are pointers to a Point
    // To access member y of Point c of the Triangle pointed to by ptr, the following are equivalent:

    // access via operator.
    std::cout << (*(*ptr).c).y << '\n'; // ugly!

    // access via operator->
    std::cout << ptr -> c -> y << '\n'; // much nicer
}

image

当连续使用多个->运算符时(例如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 is a pointer, use ->
    // paw is not a pointer, use .

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

    return 0;
}

image

请注意,在 (ptr->paw).claws 的情况下,括号并非必需,因为运算符-> 和运算符. 都按从左到右的顺序求值,但括号确实能略微提高可读性。

posted @ 2025-12-24 10:57  游翔  阅读(5)  评论(0)    收藏  举报