意想不到的结果:Foo(m)可能是在定义名为m的对象


你知道吗,如果 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

为了说明下面两个例子,我们定义一个新的类 SS 重载了调用运算符 (),因此其对象是一个函数对象。

#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

如果我不想默认初始化,便传入 coutS 的构造函数作为实参。此时语句变为 S(cout)(1)。这能不能达到预期效果呢?我们试着把括号去掉,变成 S cout(1),这看着确实是语法正确的,意思是通过传入实参 1 ,来定义一个名为 coutS 对象。既然语法合法,则括号可去。

// 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) 的这些情况,如此写出与预期效果相符的代码也就容易多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值