学习c++的第十五天

目录

预处理器

#define 预处理

条件编译

# 和 ## 运算符

C++ 中的预定义宏

信号处理

signal() 函数

raise() 函数

Sleep 函数

sleep_for 函数和Sleep 函数区别与联系


预处理器

预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。

所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。

我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。

C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。

#define 预处理

C++ 预处理器中的#define指令用于创建符号常量或宏,它可以让您在代码中定义一个标识符来代表一个值或一段代码。当编译器在预处理阶段遇到#define指令时,会将标识符替换为指定的值或代码,并在后续编译过程中使用这些替换。

例如,可以使用#define定义一个常量:

#define PI 3.14159

这样在代码中就可以使用PI代表3.14159。

另外,还可以使用#define定义一个简单的宏:

#define SQUARE(x) ((x) * (x))

这样在代码中就可以使用SQUARE(x)来表示计算x的平方。在这种情况下,预处理器会将代码中的SQUARE(x)替换为((x) * (x))。

需要注意的是,使用#define定义的常量或宏并不会进行类型检查,也不会对作用域进行限定,因此在使用时需要小心确保不会引发意外的行为。

条件编译

条件编译是一种在编译时根据指定条件选择性地包含或排除部分代码的技术。在C++中,条件编译通常使用预处理指令来实现,它的结构与 if 选择结构很像。其中最常见的指令是#ifdef、#ifndef、#if、#else、#elif和#endif。

下面是一些常见的条件编译指令的用法示例:

#ifdef DEBUG
    // 只有在定义了DEBUG宏的情况下才会包含这部分代码
    // 用于调试的代码可以放在这里
#else
    // 在没有定义DEBUG宏的情况下会包含这部分代码
    // 发布版本的代码可以放在这里
#endif
#ifdef DEBUG
    // 只有在定义了DEBUG宏的情况下才会包含这部分代码
    // 用于调试的代码可以放在这里
#else
    // 在没有定义DEBUG宏的情况下会包含这部分代码
    // 发布版本的代码可以放在这里
#endif
#if defined(DEBUG) && (VERSION >= 10)
    // 只有当DEBUG宏被定义并且VERSION大于等于10时才包含这部分代码
#endif

条件编译使得我们可以根据不同的情况选择性地包含或排除特定的代码,例如在调试和发布版本中包含不同的日志输出、测试代码或性能优化。然而,过度使用条件编译可能会导致代码可读性和维护性下降,因此需要谨慎使用。

下面是一个简单的示例,演示了如何在C++中使用条件编译:

#include 

#define DEBUG

int main() {
#ifdef DEBUG
    std::cout << "调试模式" << std::endl;
#else
    std::cout << "释放模式" << std::endl;
#endif

    return 0;
}

在这个例子中,我们通过#define DEBUG指令定义了一个名为DEBUG的宏。在main函数中,我们使用#ifdef DEBUG和#else指令来根据DEBUG宏的定义输出不同的信息。如果您在编译时加上了-DDEBUG选项,那么DEBUG宏就会被定义,相应地会输出"调试模式";否则会输出"释放模式"。

通过条件编译,可以灵活地根据需要选择包含或排除特定的代码块,以实现不同环境下的需求。

# 和 ## 运算符

在C++中,# 和 ## 运算符用于预处理阶段的宏定义和宏展开。这两个运算符都是为了在宏的展开过程中起到特殊作用。

1、运算符(字符串化操作符):
在宏替换中,# 运算符可以将参数转换为字符串。例如:

#define STRINGIZE(x) #x
std::cout << STRINGIZE(hello); // 输出 "hello"

在这个例子中,# 运算符将参数 x 转换为字符串。

2、运算符(连接操作符):
在宏替换中,## 运算符用于连接两个标识符。例如:

#define CONCAT(x, y) x##y
int xy = CONCAT(3, 4); // 展开为 int xy = 34;

在这个例子中,## 运算符将参数 x 和 y 连接在一起形成一个新的标识符。

这两个运算符通常与宏定义一起使用,能够为编写更加灵活的宏提供便利。但是需要小心使用,因为滥用这些运算符可能会导致代码的可读性下降,并且不易调试。

 当使用#和##运算符时,可以创建一些有趣的宏以及用于生成代码的工具。以下是一个代码示例:

#include 
using namespace std;

// 使用#运算符将参数转换为字符串
#define STRINGIZE(x) #x

// 使用##运算符连接两个标识符
#define CONCAT(a, b) a##b

int main() {
    // 使用STRINGIZE宏将参数转换为字符串
    cout << STRINGIZE(hello) << endl; // 输出 "hello"

    // 使用CONCAT宏连接两个标识符
    int CONCAT(num, 1) = 10;
    cout << num1 << endl; // 输出 10

    return 0;
}

在这个示例中,我们定义了两个宏:STRINGIZE和CONCAT。STRINGIZE使用#运算符将传入的参数转换为字符串,而CONCAT使用##运算符将两个标识符连接在一起形成一个新的标识符。在main函数中,我们展示了如何使用这两个宏。

这种技术通常在需要根据参数生成标识符或字符串时很有用,例如生成函数名、变量名等。需要注意的是,使用宏时要小心,确保理解宏展开后的实际代码,并避免不必要的复杂性。

C++ 中的预定义宏

预定义宏在C++中是非常常用的,它们为程序员提供了在编译时获取有关源代码信息的便利途径。下面是对每个宏的简要说明:

  1. __LINE__:这个宏会在程序编译时被替换为当前的源文件行号。

  2. __FILE__:这个宏会在程序编译时被替换为当前的源文件名。

  3. __DATE__:这个宏会在程序编译时被替换为一个形式为 month/day/year 的字符串,表示源文件被转换为目标代码的日期。

  4. __TIME__:这个宏会在程序编译时被替换为一个形式为 hour:minute:second 的字符串,表示程序被编译的时间。

这些宏使得程序员可以在编译时获取有关代码的一些基本信息,例如调试信息、日志记录等。这对于开发和调试过程中的跟踪以及日志记录都是非常有用的。

当使用预定义宏时,可以在代码中直接使用这些宏来获取当前的行号、文件名以及编译的日期和时间。以下是一个简单的示例:

#include 
using namespace std;

int main() {
    cout << "Line: " << __LINE__ << endl; // 输出当前行号
    cout << "File: " << __FILE__ << endl; // 输出当前文件名
    cout << "Date: " << __DATE__ << endl; // 输出编译日期
    cout << "Time: " << __TIME__ << endl; // 输出编译时间
    return 0;
}

在这个示例中,我们使用了四个预定义宏:__LINE__、__FILE__、__DATE__ 和 __TIME__。当程序被编译时,这些宏会自动地被替换为对应的值,然后输出到控制台上。这些信息对于调试和日志记录非常有用,因为它们能够帮助您追踪代码的执行位置和编译时间。

信号处理

信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。

有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 中。

信号 描述
SIGABRT 程序的异常终止,如调用 abort
SIGFPE 错误的算术运算,比如除以零或导致溢出的操作。
SIGILL 检测非法指令。
SIGINT 程序终止(interrupt)信号。
SIGSEGV 非法访问内存。
SIGTERM 发送到程序的终止请求。

signal() 函数

signal() 函数是用于设置信号处理的函数。在C和C++中,它被定义在 头文件中。

该函数的原型如下:

void (*signal(int signum, void (*handler)(int)))(int);

其中,signum 是要处理的信号的编号,handler 是一个指向函数的指针,该函数将在接收到信号时被调用。

signal() 函数的返回类型是一个指向函数的指针,该函数接受一个整型参数并返回空值。

当调用 signal(signum, handler) 时,它将设置在接收到信号signum时调用 handler 函数。如果 handler 为 SIG_IGN,则表示忽略该信号;如果 handler 为 SIG_DFL,则表示使用默认的信号处理方式。

以下是一个简单的例子,演示了如何使用 signal() 函数来捕获SIGINT信号并进行相应的处理:

#include 
#include 
#include 

using namespace std;

// 信号处理函数
void signalHandler(int signum) {
    cout << "捕获到信号 " << signum << ",正在进行处理...\n";

    // 可以在此处添加适当的处理动作

    exit(signum);  // 终止程序
}

int main() {
    // 设置信号处理程序
    signal(SIGINT, signalHandler);

    cout << "进入一个无限循环,请使用 Ctrl+C 来发送 SIGINT 信号...\n";

    while (1) {
    }

    return 0;
}

在这个示例中,signal(SIGINT, signalHandler) 将在接收到SIGINT信号时调用 signalHandler 函数进行处理。

raise() 函数

raise() 函数用于在程序中显式地生成信号。它被定义在 头文件中。

raise() 函数的原型如下:

int raise(int signum);

其中,signum 是要生成的信号的编号。

调用 raise(signum) 将向当前进程发送信号 signum。这个函数可以用于模拟接收到某个特定信号的情况,从而触发相应的信号处理操作。

以下是一个简单的示例,演示了如何使用 raise() 函数来生成SIGINT信号:

#include 
#include 
#include 

using namespace std;

// 信号处理函数
void signalHandler(int signum) {
    cout << "捕获到信号 " << signum << ",正在进行处理...\n";

    // 可以在此处添加适当的处理动作

    exit(signum);  // 终止程序
}

int main() {
    // 设置信号处理程序
    signal(SIGINT, signalHandler);

    cout << "将使用 raise() 函数生成 SIGINT 信号...\n";

    raise(SIGINT);  // 生成 SIGINT 信号

    return 0;
}

在这个示例中,raise(SIGINT) 调用将模拟接收到了SIGINT信号,从而触发了 signalHandler 函数来处理该信号。

Sleep 函数

Sleep 函数通常是在Windows环境下使用的,用于让程序暂停执行一段时间。它会挂起当前线程并等待指定的时间后继续执行。在C++中,Sleep 函数是通过引入 头文件来调用的。

以下是一个简单的示例,演示了如何在C++中使用 Sleep 函数:

#include 
#include 

int main() {
    std::cout << "开始执行\n";

    // 暂停 2000 毫秒 (2秒)
    Sleep(2000);

    std::cout << "暂停结束,继续执行\n";

    return 0;
}

在这个示例中,Sleep(2000) 让程序暂停 2000 毫秒(即 2 秒)。

需要注意的是:

  • Sleep 是区分大小写的,需要根据实际情况来确定大小写;
  • 在Windows下,Sleep 函数的参数是以毫秒为单位,而在Linux下,其参数是以秒为单位。

sleep_for 函数和Sleep 函数区别与联系

sleep_for 函数和 Sleep 函数都用于在程序中暂停一段时间,但是它们存在一些区别和联系:

1、功能:

  • sleep_for:C++11标准引入的函数,可以在C++中实现跨平台的暂停功能,以纳秒、微秒、毫秒、秒等为单位进行暂停。
  • Sleep:Windows特定的函数,以毫秒为单位暂停程序的执行。

2、跨平台支持:

  • sleep_for:由于是C++标准库的一部分,因此在任何支持C++11标准的平台上都可以使用。
  • Sleep:是Windows特有的函数,在其他操作系统(如Linux)上不可用。

3、精度:

  • sleep_for:提供了更高的时间精度,并且可以灵活地指定纳秒、微秒、毫秒或秒。
  • Sleep:只能以毫秒为单位暂停程序的执行,精度相对较低。

4、头文件:

  • sleep_for:需要包含 头文件。
  • Sleep:需要包含 头文件。

5、用法:

sleep_for 示例:

#include 
#include 
#include 

int main() {
    std::cout << "开始执行\n";
    // 暂停 2 秒
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "暂停结束,继续执行\n";
    return 0;
}

Sleep 示例:

#include 
#include 

int main() {
    std::cout << "开始执行\n";
    // 暂停 2000 毫秒 (2秒)
    Sleep(2000);
    std::cout << "暂停结束,继续执行\n";
    return 0;
}

总之,sleep_for 是C++标准库提供的跨平台暂停函数,具有更高的时间精度和灵活性,而 Sleep 是Windows特有的函数,只能以毫秒为单位进行暂停。

你可能感兴趣的:(C++,学习,c++,开发语言)