0%

C++ 迷你系列(二)链式调用

引言

大部分语言支持链式调用,但是在 C++ 中,链式调用有几个坑需要我们注意。本篇文章就是介绍一下 C++ 中链式调用的注意事项。

本次例子中,我们把链式调用分为三类:普通对象的链式调用、对象引用的链式调用、指针的链式调用!

普通对象的链式调用

为了将焦点放到链式调用上,这里所有的代码全部放到 main.cpp 文件中,就不分割文件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

using namespace std;

class Person
{
private:
int age;

public:
// 这里未加 explicit 关键字,为了简化,我们需要隐式转换
Person(int age) : age(age)
{};

Person incr_age_val(const Person &person)
{
this->age += person.age;
return *this;
}

Person &incr_age_ref(const Person &person)
{
this->age += person.age;
return *this;
}

Person *incr_age_ptr(const Person &person)
{
this->age += person.age;
return this;
}

void print_age() const
{
cout << "age is : " << this->age << endl;
}
};

场景一

1
2
3
4
5
6
7
8
int main()
{
Person p(10);
// 这里用了隐式转换,将 1、2、3 分别转换为对应 age 的 Person 对象
p.incr_age_val(1).incr_age_val(2).incr_age_val(3);
p.print_age();
return 0;
}

incr_age_val 方法中,在更新了当前对象的 age 字段之后,返回了 *this,也就是 person 对象。

我们创建 Person p(10) 对象,然后链式调用之后,获取 age 属性。大家不妨猜一猜此时 age 为多少?

答案:11。

为什么是 11 而不是 16 呢?关键在于 incr_age_val 方法的返回值为 Person 对象,也就是 p 的副本。我们看下图:

incr_age_val 三次调用分别返回了三个不同的 Person 实例。我们输出 age 时,输出的只是 p 实例对应的 age。所以结果为 11。

如果想得出 age = 16 的结果,只需要获取它的返回值即可:

1
2
3
4
5
6
7
8
int main()
{
Person p(10);

const Person p2 = p.incr_age_val(1).incr_age_val(2).incr_age_val(3);
p2.print_age();
return 0;
}

场景二

1
2
3
4
5
6
7
8
9
10
int main()
{
Person p(10);
Person p1(20);

const Person p2 = p.incr_age_val(p1).incr_age_val(p1).incr_age_val(p);
p.print_age();
p2.print_age();
return 0;
}

以上代码创建两个对象 pp1,然后在获取对象 pp2age。猜一猜它们的结果是什么?

答案是:30、80。

我们继续看图,看一看整个调用过程:

每次函数调用之后都会发生值复制,所以第一次调用之后返回的 person 对象值为 30 也就是对象 p 的值。第二次调用 age 此时为 30 然后加上 p1 的 20 结果为 50 生成副本对象参与第三次调用,但是此时的 p 为 30,所以 50 + 30 得到最后的结果 80。

对象引用的链式调用

场景一

1
2
3
4
5
6
7
int main()
{
Person p(10);
p.incr_age_ref(1).incr_age_ref(2).incr_age_ref(3);
p.print_age();
return 0;
}

换成引用之后,结果为我们期待的结果 16 了。因为函数调用之后返回的不再是副本,而是第一个对象的引用。也就是说三次调用递增的都是同一个对象。

场景二

1
2
3
4
5
6
7
8
9
int main()
{
Person p(10);
Person p1(20);

p.incr_age_ref(p1).incr_age_ref(p1).incr_age_ref(p);
p.print_age();
return 0;
}

猜一猜结果是多少?我们来分析一下:

  1. 由于返回的是引用,所以 p.incr_age_ref(p1) 之后,p 为 30。
  2. 再次调用 p.incr_age_ref(p1) 之后,p 为 50。
  3. 最后调用 incr_age_ref(p) 时,p 为 50,所以两者相加之后为 100。

最终答案:100。

对象指针的链式调用

由于引用的本质就是指针,所以指针的行为跟引用应该一致,不过我们依然测试一下。

场景一

1
2
3
4
5
6
7
int main()
{
Person p(10);
p.incr_age_ptr(1)->incr_age_ptr(2)->incr_age_ptr(3);
p.print_age();
return 0;
}

我们得到的结果与预期的一直为 16。引用返回的是对象的假名,而指针返回的是对象的地址,所以二者可以等价。结果自然而然一样。

1
2
3
4
5
6
7
8
9
int main()
{
Person p(10);
Person p1(20);

p.incr_age_ptr(p1)->incr_age_ptr(p1)->incr_age_ptr(p);
p.print_age();
return 0;
}

由于指针获取的都是当前的最新值,所以跟引用一致,答案为 100!


尾声

链式调用虽然好用,但是我们一定要清楚,函数返回的值是什么以及每次调用中,入参的当前值又是什么?