章节目录

提前退出程序

本节阅读量:

本节讨论的控制流语句是退出程序,这也是本章讨论的最后一类控制流语句。退出表示程序终止执行。在C++中,它通过函数(而不是关键字)实现,因此退出程序需要调用函数。

先绕一个小弯,回顾一下程序正常退出时会发生什么。当main()函数返回时(到达函数末尾或通过return语句返回),会发生许多事情。

首先,程序会离开main函数,因此所有局部变量和函数参数都会被销毁(通常情况下)。

接下来,调用一个名为std::exit()的特殊函数,并将main()函数的返回值作为参数传入。那么什么是std::exit()?


std::exit()函数

exit()是一个导致程序正常终止的函数。正常终止意味着程序以预期的方式退出。请注意,术语正常终止并不意味着程序执行符合预期。例如,假设您正在编写一个程序,希望用户输入要处理的文件名。如果用户键入了无效的文件名,则程序可能会返回非零的状态码来指示故障状态,但它仍然是正常的终止。

exit()会执行许多清理操作。首先,销毁具有静态存储期的对象。然后,如果使用了文件,则执行一些文件清理动作。最后,将传递给std::exit()的参数用作状态码,并把控制权交还给操作系统。


显式调用std::exit()

尽管在函数main()结束时隐式调用std::exit(),但也可以显式调用std::exit。当以这种方式调用std::exit()时,需要引用cstdlib头文件。

下面是显式使用std::exit()的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdlib> // 引入 std::exit()
#include <iostream>

void cleanup()
{
    // 这里来做一些清理工作
    std::cout << "cleanup!\n";
}

int main()
{
    std::cout << 1 << '\n';
    cleanup();

    std::exit(0); // 结束程序执行,并将0返回给操作系统

    // 下面的代码不会执行到
    std::cout << 2 << '\n';

    return 0;
}

该程序打印:

1
2
1
cleanup!

请注意,std::exit()后续的语句永远不会执行,因为程序已经终止。

在上面的程序中,从函数main()中调用std::exit(),但可以从任何函数中调用std::exit()来终止程序。

显式调用std::exit()不会清理任何局部变量(包括当前函数中,以及调用堆栈上其他函数中的局部变量)。因此,通常最好避免调用std::exit()。


std::atexit

由于std::exit()立即终止程序,因此您可能希望在终止之前手动进行一些清理。在这种情况下,清理意味着关闭数据库或网络连接、释放分配的任何内存、将信息写入日志文件等…

上面的示例调用cleanup()函数来处理清理任务。然而,在每次调用exit()之前手动调用清理函数,会给我们增加很多负担。

为了帮助实现这一点,C++提供了std::atexit()函数,它允许您指定一个函数,该函数将在程序终止时通过std::exit()自动调用。

下面是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdlib> // 引入 std::exit()
#include <iostream>

void cleanup()
{
    // 这里来做一些清理工作
    std::cout << "cleanup!\n";
}

int main()
{
    // 注册 cleanup(),这样当调用到std::exit()时,cleanup函数会被自动调用
    std::atexit(cleanup); // 注: 这里是cleanup,而不是cleanup(),因为这里需要的是一个函数,而不是函数调用
    std::cout << 1 << '\n';

    std::exit(0); // 结束程序执行,并将0返回给操作系统

    // 下面的代码不会执行到
    std::cout << 2 << '\n';

    return 0;
}

该程序的输出与前面的示例相同:

1
2
1
cleanup!

为什么要这么做?这允许我们在一个地方(可能在main中)指定清理函数,之后就不必担心在调用std::exit()之前忘记显式调用清理函数。

这里有几点关于std::atexit()和清理函数的注意事项:首先,由于main()终止时会隐式调用std::exit(),因此如果程序以这种方式退出,也会调用std::atexit()注册的所有函数。其次,被注册的函数必须没有参数,并且没有返回值。最后,如果需要,可以使用std::atexit()注册多个清理函数,它们将按注册顺序的相反顺序调用(最后注册的函数会最先调用)。


std::abort和std::terminate

C++包含另外两个与退出程序相关的函数。

std::abort()导致程序异常终止。异常终止意味着程序出现某种异常运行时错误,程序无法继续运行。例如,尝试除以0将导致异常终止。std::abort()不执行任何清理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <cstdlib> // 引入 std::abort()
#include <iostream>

int main()
{
    std::cout << 1 << '\n';
    std::abort();

    // 下面的代码不会执行到
    std::cout << 2 << '\n';

    return 0;
}

在本章后面的部分中,我们将看到隐式调用std::abort的情况(9.6–Assert和static_Assert)。

std::terminate()函数通常与异常一起使用(我们将在后面的一章中介绍异常)。尽管可以显式调用std::terminate,但在处理异常时(以及在其他一些与异常相关的情况下),它经常被隐式调用。默认情况下,std::terminate()会调用std::abort()。


什么时候需要手动退出程序?

简短的回答是“几乎从不”。销毁局部对象是C++的一个重要部分(特别是在使用class时),而上面提到的函数都不能清理局部变量。


8.10 break与continue

上一节

8.12 随机数生成简介

下一节