文章目录
- 例一:`Foo(m);` 是定义名为 `m` 的对象
- 例二:`Foo(m).i;` 传入实参 `m`
- 例三:`func(Foo(m));` 传入实参 `m`
- 例四:`S(cout)(1)` 定义名为 `cout` 的对象
- 例五:`S(std::cout)(1)` 传入实参 `std::cout`
你知道吗,如果
Foo
是一个类,那么执行含有
Foo(m)
的语句时,可能并不会把
m
作为实参传给
Foo
的构造函数!还有一种可能是调用默认构造函数,去定义一个
Foo
的对象
m
!
那这两种情况分别在什么时候发生呢?我们知道,C++中圆括号 ()
有时候是多余的存在,比如 int i = (1+3);
,把括号去了 int i = 1+3;
,两条语句是等价的。对于含 Foo(m)
的语句,这对括号也可能是多余的。C++处理这种语句的原则是能简单就简单,如果能省略括号(无语法错误)那就省略!当然了,省略完括号得加上一个空格,变成 Foo m
。下面举五个例子。首先我们定义一个 Foo
类。
struct Foo
{
int i;
Foo(int i0) : i(i0) {}
};
例一:Foo(m);
是定义名为 m
的对象
对于 Foo(m);
括号省略后,语句变成 Foo m;
。语法没有任何问题,所以编译器决定把括号省略。
int main()
{
Foo(m);
return 0;
}
但是上述代码会在编译时产生错误,因为 Foo
没有默认构造函数啊,所以 Foo m;
当然是会报错的。
例二:Foo(m).i;
传入实参 m
对于Foo(m).i;
,我们把括号去掉,语句变成 Foo m.i;
,显然语法错误。编译时会报 m
未定义的错误。
这就说明括号是不能去的,即 m
会作为实参传给 Foo
的构造函数,创建一个匿名对象,然后访问其名为 i
的数据成员。
// main函数中
int m = 10;
cout << "Output:\t" << Foo(m).i << '\n';
Output: 10
例三:func(Foo(m));
传入实参 m
func
是一个函数,有一个 Foo
类型的形参。我们把括号去掉,语句变成 func(Foo m);
,显然是非法的,因为实参里面不能有类型名。故括号不能去,即 m
应作为参数传给 Foo
的构造函数。
int func(Foo foo) { return foo.i; }
int main()
{
int m = 10;
cout << "Output:\t" << func(Foo(m)) << '\n';
return 0;
}
Output: 10
为了说明下面两个例子,我们定义一个新的类 S
。S
重载了调用运算符 ()
,因此其对象是一个函数对象。
#include <iostream>
using std::cout;
using std::cerr;
struct S
{
ostream &os;
S(ostream &o = cerr) : os(o) {}
void operator()(int x)
{
os << "x = " << x << '\n';
}
};
例四:S(cout)(1)
定义名为 cout
的对象
cout
我们先来试试看 S()(1);
会有什么结果。它调用默认构造函数,创建一个可调用的函数对象,os
初始化为 cerr
。这个对象接受一个实参 1
然后被调用,由 cerr
输出 x = 1
。
如果我不想默认初始化,便传入 cout
给 S
的构造函数作为实参。此时语句变为 S(cout)(1)
。这能不能达到预期效果呢?我们试着把括号去掉,变成 S cout(1)
,这看着确实是语法正确的,意思是通过传入实参 1
,来定义一个名为 cout
的 S
对象。既然语法合法,则括号可去。
// main函数
S(cout)(1);
但编译显然是会报错的,为什么呢,因为 S
并没有接受一个 int
对象的构造函数。
这倒还算好的,因为报错了,我们能通过错误检查出我们代码的问题。如果 S
果真有接受一个 int
对象的构造函数,便不会报错,但达不到我们的预期效果,代码问题的排查也就更困难了。
那如果要达到预期效果,正确的做法是什么呢?请看例五。
例五:S(std::cout)(1)
传入实参 std::cout
加上了全局作用域,这括号可就去不得了:S std::cout(1)
显然非法,std::cout
不能作为我们自定义对象的名字。因此 std::cout
会如预期一样作为实参传入 S
的构造函数。
S(std::cout)(1);
x = 1
这就是为什么很多人建议用 std::cout
而非 cout
的原因,就是怕上述的情况。using std::cout;
倒还好了,只要我们不主动定义 名字叫 cout
的对象一般不会出错,除非是上述这种编译器去括号来定义的这种意想不到的错误。相较而言,using namespace std;
是风险最高的,你不知道啥时候,自己自定义的一个对象名字就把标准库的给隐藏了。
但是图省事,对于使用次数较多的名字,我还是会用 using std::cout;
;对于只用一两次的名字,我就不用 using
声明了吧,而是加上 std::
。另外自己要清楚 Foo(m)
的这些情况,如此写出与预期效果相符的代码也就容易多了。