常见的语义错误
  本节阅读量: 
  
  在前面我们介绍了语法错误,当您编写不符合C++语言语法的无效代码时会发生语法错误。编译器将通知您此类错误,因此它们很容易捕获,并且通常很容易修复。
我们还讨论了语义错误,这些错误发生在您编写的代码没有达到预期目的时。编译器通常不会捕获语义错误(尽管在某些情况下,智能编译器可能能够生成警告)。
语义错误会导致程序出现非预期的结果,例如导致程序产生错误的结果、导致不稳定的行为、损坏程序数据、导致程序崩溃——或者可能根本没有任何影响。
在编写程序时,几乎不可避免地会出现语义错误。在使用程序时,可能会有明显的问题:例如,如果您正在编写迷宫游戏,您的角色能够穿墙。测试程序也可以帮助发现语义错误。
但还有一件事可以帮助我们——那就是知道哪种类型的语义错误最常见,所以你可以花更多的时间来确保避免这类问题。
在本课中,将讨论C++中发生的一系列最常见类型的语义错误(其中大多数都与控制语句有关)。
条件逻辑错误
最常见的语义错误类型之一是条件逻辑错误。当程序员错误地编写条件语句或循环条件的逻辑时,会发生条件逻辑错误。下面是一个简单的示例:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 | #include <iostream>
int main()
{
    std::cout << "Enter an integer: ";
    int x{};
    std::cin >> x;
    if (x >= 5) // oops, 错误的使用 >= 而不是 >
        std::cout << x << " is greater than 5\n";
    return 0;
}
 | 
 
下面是条件逻辑错误的程序的运行:
| 1
2
 | Enter an integer: 5
5 is greater than 5
 | 
 
当用户输入5时,条件表达式x>=5的计算结果为true,因此执行关联的语句。
下面是另一个使用for循环的示例:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 | #include <iostream>
int main()
{
    std::cout << "Enter an integer: ";
    int x{};
    std::cin >> x;
    // oops, 使用了 > 而不是 <
    for (int count{ 1 }; count > x; ++count)
    {
        std::cout << count << ' ';
    }
    std::cout << '\n';
    return 0;
}
 | 
 
这个程序应该打印1和用户输入的数字之间的所有数字。但它实际上是这样做的:
它没有打印任何内容。发生这种情况是因为在for循环的入口,count > x 是false,因此循环内的代码根本不会执行。
死循环
如下这个示例:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 | #include <iostream>
 
int main()
{
    int count{ 1 };
    while (count <= 10) // 这个条件语句永远不会是 false
    {
        std::cout << count << ' '; // 这一行会一直重复执行
    }
 
    std::cout << '\n'; // 这一行不会执行到
    return 0; // 这一行不会执行到
}
 | 
 
在这种情况下,忘记了递增count变量,因此循环条件永远不会为false,循环将一直打印:
| 1
 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 | 
 
直到用户关闭程序。
这里有另一个例子。下面的代码有什么问题?
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 | #include <iostream>
int main()
{
    for (unsigned int count{ 5 }; count >= 0; --count)
    {
        if (count == 0)
            std::cout << "blastoff! ";
        else
          std::cout << count << ' ';
    }
    std::cout << '\n';
    return 0;
}
 | 
 
这个程序应该打印5 4 3 2 1 blastoff!,确实如此,但它并没有就此止步。实际上,它打印:
| 1
 | 5 4 3 2 1 blastoff! 4294967295 4294967294 4294967293 4294967292 4294967291
 | 
 
然后继续递减。程序永远不会终止,因为当count是无符号整数时,count>=0永远不能为false。
循环迭代次数错误
当循环执行多一次或少一次时会发生错误。例如:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 | #include <iostream>
int main()
{
    for (int count{ 1 }; count < 5; ++count)
    {
        std::cout << count << ' ';
    }
    std::cout << '\n';
    return 0;
}
 | 
 
程序员打算打印1 2 3 4 5。然而,使用了错误的关系运算符(<而不是<=),因此循环的执行次数比预期的少一次,打印1 2 3 4。
运算符优先级不正确
以下程序会犯运算符优先级错误:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 | #include <iostream>
int main()
{
    int x{ 5 };
    int y{ 7 };
    if (!x > y) // oops: 运算符优先级问题
        std::cout << x << " is not greater than " << y << '\n';
    else
        std::cout << x << " is greater than " << y << '\n';
    return 0;
}
 | 
 
由于逻辑NOT的优先级高于运算符>,因此条件的求值方式就像它是(!x)> y 一样,这不是我们想要的。
因此,该程序打印:
在同一表达式中混合逻辑OR和逻辑AND时也可能发生这种情况(逻辑AND优先于逻辑OR)。使用显式括号来避免这类错误。
浮点类型的精度问题
以下浮点变量的精度不足,无法存储整个数字:
| 1
2
3
4
5
6
7
8
9
 | #include <iostream>
int main()
{
    float f{ 0.123456789f };
    std::cout << f << '\n';
    return 0;
}
 | 
 
由于缺乏精度,数字稍微四舍五入:
在前面,我们讨论了,运算符==和运算符!=,由于舍入错误(以及如何处理它),比较浮点数可能会有问题。下面是一个示例:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 | #include <iostream>
int main()
{
    double d{ 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 }; // should sum to 1.0
    if (d == 1.0)
        std::cout << "equal\n";
    else
        std::cout << "not equal\n";
    return 0;
}
 | 
 
该程序打印:
对浮点数进行的算术运算越多,它积累的小舍入错误就越多。
整数除法
在下面的示例中,打算进行浮点除法,但由于两个操作数都是整数,因此最终只能进行整数除法:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 | #include <iostream>
int main()
{
    int x{ 5 };
    int y{ 3 };
    std::cout << x << " divided by " << y << " is: " << x / y << '\n'; // 整数除法
    return 0;
}
 | 
 
这将打印:
在前面,我们展示了可以使用static_cast将一个整数操作数转换为浮点值,以便进行浮点除法。
意外的空语句
在if语句学习中,讨论了空语句,它们是不做任何事情的语句。
在下面的程序中,我们只想在获得用户许可的情况下炸毁世界:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 | #include <iostream>
void blowUpWorld()
{
    std::cout << "Kaboom!\n";
} 
int main()
{
    std::cout << "Should we blow up the world again? (y/n): ";
    char c{};
    std::cin >> c;
    if (c=='y'); // 意外的空语句
        blowUpWorld(); // 所以这一行会执行到,因为它不是if语句的一部分
 
    return 0;
}
 | 
 
然而,由于意外的空语句,始终会执行对blowUpWorld()的函数调用,因此不管怎样,都会执行到 Kaboom! :
| 1
2
 | Should we blow up the world again? (y/n): n
Kaboom!
 | 
 
需要复合语句时不使用复合语句
上述程序的另一个变体也总是会炸毁世界:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 | #include <iostream>
void blowUpWorld()
{
    std::cout << "Kaboom!\n";
} 
int main()
{
    std::cout << "Should we blow up the world again? (y/n): ";
    char c{};
    std::cin >> c;
    if (c=='y')
        std::cout << "Okay, here we go...\n";
        blowUpWorld(); // oops, 这一行也会永远执行到
 
    return 0;
}
 | 
 
该程序打印:
| 1
2
 | Should we blow up the world again? (y/n): n
Kaboom!
 | 
 
悬空的else也属于这一类问题。
还有什么?
上面是新C++程序员倾向于犯的最常见的一些语义错误,但还有更多。读者们,如果您有任何其他认为是常见陷阱的问题,请在评论留言。
        
        
        