在if语句的条件部分中定义变量?


76

我很震惊,这是允许的:

if( int* x = new int( 20 ) )
{
    std::cout << *x << "!\n";
    // delete x;
}
else
{
    std::cout << *x << "!!!\n";
    // delete x;
}
// std:cout << *x; // error - x is not defined in this scope

那么,这是标准允许的还是仅仅是编译器扩展?


附言:由于对此有很多评论,请忽略此示例“不好”或危险。我知道 例如,这只是我想到的第一件事。


1
很好的问题,似乎您的脑海中有C背景-为此+1。:)

@ H2CO3-谢谢:)但是那又如何C呢?您的意思是,那里不允许这样做,这可能使我认为,C ++不允许这样做?
Kiril Kirov 2012年

@ H2CO3-哈哈,谢谢:)我没有什么想法,我什至想知道C语言是否也一样:))
Kiril Kirov 2012年

2
因为它是C99中的唯一标准...

所需的new (std::nothrow)版本-否则,else由于抛出std::bad_alloc分配错误,您的示例将永远无法使用。
PiotrNycz

Answers:


82

从C ++ 98开始,规范允许这样做。

在第6.4节“选择语句”中:

由条件中的声明引入的名称(由类型说明符-seq或条件的声明者引入)从其声明点到该条件所控制的子语句的末尾都在范围内。

以下示例来自同一部分:

if (int x = f()) {
    int x;    // ill-formed, redeclaration of x
}
else {
    int x;    // ill-formed, redeclaration of x
}

21
天哪!我刚刚意识到(由于此引语)该名称也在范围之内else。我一直以某种方式一直以为它只能在本if节中使用
Matthieu M.

19

并不是一个真正的答案(但是注释并不适合于代码示例),更多的原因是它非常方便:

if (int* x = f()) {
    std::cout << *x << "\n";
}

每当API返回“选项”类型(也恰好有布尔转换可用)时,就可以利用这种类型的构造,以便仅在合理使用其值的上下文中访问该变量。这是一个非常强大的习语。


从选项类型的角度来看,我从未见过这种情况,并且在用C ++编写代码时总是会很想念它们。感谢您改变自己的生活!
hugomg

@MatthieuM。关于选项类型,我不太明白。您能否扩展上面提到的习惯用法或提供一些其他链接。听起来很有趣。目前,我对此类事情使用boost :: optional。您是要使用像可选类型这样的普通指针并且作用域有限还是在其后面?
Martin

1
@Martin:通过选项类型,我的意思是像指针(或boost::optional)一样具有“前哨”值的类型,以指示不存在实际值。如果它们还提供了转换为布尔值的转换,而布尔值仅取决于类型中所保留的实际值的存在/不存在,那么该成语就起作用了。例如:if (boost::optional<int> x = f()) { std::cout << *x << '\n'; }也可以。
Matthieu M.

18

即使在旧的C ++ 98版本的语言中,它也是标准的:

在此处输入图片说明


7

在的条件部分变量的定义whileifswitch声明都是标准配置。相关的子句是6.4 [stmt.select]段落1,它定义了条件的语法。

顺便说一句,您的使用毫无意义:如果new失败,它将引发std::bad_alloc异常。


3
这只是一个例子。我知道,这很糟糕。这只是我想到的第一件事。
Kiril Kirov 2012年

在“ while”部分中定义变量是否最优?
Julen 2012年

2

这是一个示例,说明在if条件中声明的变量的非典型用法。

变量的类型int &既可以转换为布尔值,又可以在thenelse分支中使用。

#include <string>
#include <map>
#include <vector>
using namespace std;

vector<string> names {"john", "john", "jack", "john", "jack"};
names.push_back("bill"); // without this push_back, my g++ generated exe fails :-(
map<string, int> ages;
int babies = 0;
for (const auto & name : names) {
    if (int & age = ages[name]) {
        cout << name << " is already " << age++ << " year-old" << endl;
    } else {
        cout << name << " was just born as baby #" << ++babies << endl;
        ++age;
    }
}

输出是

john was just born as baby #1
john is already 1 year-old
jack was just born as baby #2
john is already 2 year-old
jack is already 1 year-old
bill was just born as baby #3

不幸的是,条件中的变量只能用'='声明语法声明。

这排除了使用显式构造函数的其他可能有用的类型的情况。

例如,下一个使用的示例std::ifstream将不会编译...

if (std::ifstream is ("c:/tmp/input1.txt")) { // won't compile!
    std::cout << "true: " << is.rdbuf();
} else {
    is.open("c:/tmp/input2.txt");
    std::cout << "false: " << is.rdbuf();
}

编辑于2019年1月...您现在可以效仿我解释的无法完成的事情...

这适用于可移动类,例如C ++ 11中的ifstream,甚至适用于自带复制删除功能的C ++ 17起不可复制的类。

2019年5月编辑:使用自动缓解冗长

{
    if (auto is = std::ifstream ("missing.txt")) { // ok now !
        std::cout << "true: " << is.rdbuf();
    } else {
        is.open("main.cpp");
        std::cout << "false: " << is.rdbuf();
    }
}
struct NoCpy {
    int i;
    int j;
    NoCpy(int ii = 0, int jj = 0) : i (ii), j (jj) {}
    NoCpy(NoCpy&) = delete;
    NoCpy(NoCpy&&) = delete;
    operator bool() const {return i == j;}
    friend std::ostream & operator << (std::ostream & os, const NoCpy & x) {
        return os << "(" << x.i << ", " << x.j << ")";
    }
};
{
    auto x = NoCpy(); // ok compiles
    // auto y = x; // does not compile
    if (auto nocpy = NoCpy (7, 8)) {
        std::cout << "true: " << nocpy << std::endl;
    } else {
        std::cout << "false: " << nocpy << std::endl;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.