if 语句

< cpp‎ | language

有条件地执行另一条语句。

用于需要基于运行时或编译时条件执行的代码。

语法

attr(可选) if ( 条件 ) true分支语句 (1) (C++17 前)
attr(可选) if constexpr(可选) ( 初始化语句(可选) 条件 ) true分支语句 (1) (C++17 起)
attr(可选) if ( 条件 ) true分支语句 else false分支语句 (2) (C++17 前)
attr(可选) if constexpr(可选) ( 初始化语句(可选) 条件 ) true分支语句 else false分支语句 (2) (C++17 起)
attr(C++11) - 任意数量的属性
条件 - 下列之一
初始化语句(C++17) - 下列之一
  • 一条表达式语句(可以是空语句;”)
  • 一条简单声明,典型的是带初始化器的变量声明,但它可以声明任意多变量,或是一条分解声明
注意,任何 初始化语句 必然以分号 ; 结束,这是它常被非正式地描述成后随分号的表达式或声明的原因。
true分支语句 - 任何语句(常为复合语句),当 条件 求值为 true 时执行
false分支语句 - 任何语句(常为复合语句),当 条件 求值为 false 时执行

解释

条件 在转换到 bool 后产生 true,则执行 true分支语句

若 if 语句的 else 部分存在,且 条件 在转换到 bool 后产生 false,则执行 false分支语句

在 if 语句的第二形式(包含 else)中,若 true分支语句 亦是 if 语句,则内层 if 语句必须也含有 else 部分(换言之,嵌套 if 语句中,else 关联到最近的尚未有 else 的 if)。

#include <iostream>
 
int main() {
    // 带 else 子句的简单 if 语句
    int i = 2;
    if (i > 2) {
        std::cout << i << " 大于 2\n";
    } else {
        std::cout << i << " 不大于 2\n";
    }
 
    // 嵌套 if 语句
    int j = 1;
    if (i > 1)
        if (j > 2)
            std::cout << i << " > 1 且 " << j << " > 2\n";
        else // 此 else 属于 if (j > 2), 而不是 if (i > 1)
            std::cout << i << " > 1 且 " << j << " <= 2\n";
 
   // 以下声明可用于含 dynamic_cast 的条件
   struct Base {
        virtual ~Base() {}
   };
   struct Derived : Base {
       void df() { std::cout << "df()\n"; }
   };
   Base* bp1 = new Base;
   Base* bp2 = new Derived;
 
   if (Derived* p = dynamic_cast<Derived*>(bp1)) // 转型失败,返回 nullptr
       p->df();  // 不执行
 
   if (auto p = dynamic_cast<Derived*>(bp2)) // 转型成功
       p->df();  // 执行
}

输出:

2 不大于 2
2 > 1 且 1 <= 2
df()


带初始化器的 if 语句

若使用 初始化语句,则 if 语句等价于

{
初始化语句
if constexpr(可选) ( 条件 )
true分支语句

}

{
初始化语句
if constexpr(可选) ( 条件 )
true分支语句
else
false分支语句

}

初始化语句 所声明的名字(若 初始化语句 是声明)和 条件 所声明的名字(若 条件 是声明)处于同一作用域中,同时也是两条 语句 所在的作用域。

std::map<int, std::string> m;
std::mutex mx;
extern bool shared_flag; // 由 mx 保证
int demo() {
   if (auto it = m.find(10); it != m.end()) { return it->second.size(); }
   if (char buf[10]; std::fgets(buf, 10, stdin)) { m[0] += buf; }
   if (std::lock_guard lock(mx); shared_flag) { unsafe_ping(); shared_flag = false; }
   if (int s; int count = ReadBytesWithSignal(&s)) { publish(count); raise(s); }
   if (auto keywords = {"if", "for", "while"};
       std::any_of(keywords.begin(), keywords.end(),
                   [&s](const char* kw) { return s == kw; })) {
     std::cerr << "Token 不能是关键词\n");
   }
}
(C++17 起)

constexpr if

if constexpr 开始的语句被称为constexpr if 语句

在 constexpr if 语句中,条件 的值必须是可按语境转换到 bool 类型的经转换常量表达式。若其值为 true,则舍弃 false分支语句(若存在),否则舍弃 true分支语句

被舍弃语句中的 return 语句不参与函数返回类型推导:

template <typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t; // 对 T = int* 推导返回类型为 int
    else
        return t;  // 对 T = int 推导返回类型为 int
}

被舍弃语句可以 ODR 式使用未定义的变量

extern int x; // 不需要 x 的定义
int f() {
if constexpr (true)
    return 0;
else if (x)
    return x;
else
    return -x;
}

若 constexpr if 语句出现于模板实体内,且若 条件 在实例化后不是值待决的,则外围模板被实例化时不会实例化被舍弃语句。

template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs) {
    // ... 处理 p
    if constexpr (sizeof...(rs) > 0)
        g(rs...); // 始终不会对空实参列表实例化。
}

在模板外,被舍弃语句受到完整的检查。if constexpr 不是 #if 预处理指令的替代品:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // 在被舍弃语句中仍为错误
    }
}

注意:实例化后仍为值待决的一个例子是嵌套模板,例如

template<class T> void g() {
    auto lm = [](auto p) {
        if constexpr (sizeof(T) == 1 && sizeof p == 1) {
           // 此条件在 g<T> 实例化后仍为值待决的
        }
    };
}

注意:被舍弃语句不能对所有特化均非良构:

template <typename T>
void f() {
     if constexpr (std::is_arithmetic_v<T>)
         // ...
     else
       static_assert(false, "必须是算术类型"); // 非良构:该语句对于所有 T 都非法
}

对这种万应语句的常用变通方案,是一条始终为 false 的类型待决表达式:

template<class T> struct dependent_false : std::false_type {};
template <typename T>
void f() {
     if constexpr (std::is_arithmetic_v<T>)
         // ...
     else
       static_assert(dependent_false<T>::value, "必须是算术类型"); // ok
}

在 constexpr if 的子语句中出现的标号(goto 目标case 标号和 default:),只能在同一子语句中(由 switchgoto)引用。

(C++17 起)

注解

true分支语句false分支语句 不是复合语句,则按如同是复合语句一样处理:

if (x)
    int i;
// i 不再在作用域中

与下面相同

if (x) {
    int i;
} // i 不再在作用域中

条件 若是声明,则其所引入的名字的作用域是两个语句体的合并作用域:

if (int x = f()) {
    int x; // 错误:重复声明了 x
} else {
    int x; // 错误:重复声明了 x
}

若通过 gotolongjmp 进入 true分支语句,则不执行 false分支语句

(C++14 起)

不允许 switch 和 goto 跳入 constexpr if 语句的分支。

(C++17 起)

关键词

if, else, constexpr

参阅