C语言预处理指令

1. 预定义符号

预定义符号都是语言内置的。
下面是一些常用的:

#include <iostream>
#include <stdio.h>

int main()
{
    printf("%s\n",__FILE__);        //进行编译的源文件路径,数据类型为字符串
    printf("%d\n", __LINE__);       //文件当前的行号,数据类型为整型
    printf("%s\n",__DATE__);        //文件被编译的日期,数据类型为字符串
    printf("%s\n",__TIME__);        //文件被编译的时间,数据类型为字符串
    printf("%s\n",__FUNCTION__);    //代码所在的函数
}

执行结果:

2. 宏定义预处理指令

2.1 #define

2.1.1 #define预定义标识符

语法规则:#define 标识符名 内容(标识符名通常为大写)
内容可以是值、关键字、甚至是一段代码:

#define MAX 1000
#define reg register            //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)        //用更形象的符号来替换一种实现
#define CASE break;case            //在写case语句的时候自动把 break写上。

如果定义的内容过长,可以分成几行写,除了最后一行外,每行的后面都加一个'\'(续行符)。

#define DEBUG_PRINT printf("file:%s\tline:%d\t        \
                            date:%s\ttime:%s\n" ,   \
                            __FILE__,__LINE__ ,     \
                            __DATE__,__TIME__ )

2.1.1 #define预定义宏

define 机制包括了一个规定,允许把参数替换到文本中(只是替换,不能传参),这种实现通常称为宏(macro)或定义宏(define macro)。

语法规则:#define 宏名(参数) 内容(宏名通常为大写)

#define SQUARE( x ) x * x

int a = SQUARE(5);    //等价于int a = 5*5;

预定义宏只是文本的替换,不是像函数一样可以传参:

#define SQUARE( x ) x * x
int b = 5;
int a = SQUARE(b+1);    //等价于int a = 5+1*5+1 = 11;(不是int a = 36;)

#define SQUARE( x ) (x) * (x)
int b = 5;
int a = SQUARE(b+1);    //等价于int a = (5+1)*(5+1) =36;

#define DOUBLE(x) (x) + (x)
int a = 5;
int b = 10 * DOUBLE(a);        //等价于int b = 10 * (a) + (a) = 55;

#define DOUBLE(x) ((x) + (x))
int a = 5;
int b = 10 * DOUBLE(a);        //等价于int b = 10 * ((a) + (a)) = 100;

用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
简单运算中宏相对函数的优点:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  • 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

简单运算中宏相对函数的缺点:

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。
  • 宏是不能调试的。
  • 宏由于类型无关,因此不够严谨。
  • 宏可能会带来运算符优先级的问题,导致程容易出现错。

一般来讲函数和宏的使用语法很相似。所以语言本身没法帮我们区分二者。
因此平时习惯将宏名全大写,函数名不是全大写,单词首字母大写。

2.1.3 #define替换规则

  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的标识符。如果是,它们首先被替换。
  • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  2. 当文本替换#define定义的标识符的时候,字符串常量的内容并不会被替换。

2.2 #与

和##的作用就是将参数插入到字符串中。

字符串是有自动连接特点:

    //三个都输出hello world
    printf("hello world\n");
    printf("hello " "world\n");
    printf("hel" "lo " "world\n");

那么可以实现如下代码:

#define PRINT(FORMAT, VALUE)\
printf("the value is "FORMAT"\n", VALUE)

int main()
{
    PRINT("%d", 10);    //printf("the value is ""%d"\n", 10)
    //the value is 10
}

还可以用#实现:

#define PRINT(VALUE)\
printf("the value is "#VALUE"\n")

int main()
{
    PRINT(10);  //printf("the value is "10"\n);
    //the value is 10
}

这里'#'的作用就是将10变成一个字符串。
也可以实现如下代码:

#define ADD_TO_SUM(num, value) \
sum##num += value

int main()
{
    int sum5=0;
    ADD_TO_SUM(5, 10); //sum5 += 10;
    printf("%d", sum5); 
}    

这里##可以把位于它两边的符号合成一个存在的标识符。

2.3 #undef

该指令用于移除一个#define定义的标识符或宏。
如果现存的一个#define定义的标识符或宏不再需要或者需要被重新定义,那么可以用该指令移除或移除后重定义。

这里在移除宏后仍使用,因此报错。

3. 条件编译预处理指令

程序中的一些代码删除可惜,保留又碍事,所以我们可以使用条件编译预处理指令选择性的编译。
条件编译预处理指令类似C程序中的条件分支语句if-else

3.1 #if

语法规则:#if 常量表达式 ··· #endif
如果常量表达式为1,就编译#if-#endif间的代码段,否则相当于没有#if-#endif间这段代码。

#include <iostream>
#include <stdio.h>
#define __DEBUG__ 1

int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
#if __DEBUG__
        printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
    }
    return 0;
}

这里DEBUG为1,那么就编译#if-#endif间的代码段。
这个预编译指令与C程序中的if-else相似,也可以多分支和嵌套使用。

3.2 #if defined()&&#ifdef

语法规则:#if defined(标识符) ··· #endif
如果标识符被定义过,就编译#if defined(标识符)-#endif间的代码段,否则相当于没有#if defined(标识符)-#endif间这段代码。
与此相对应的是#if !defined(标识符) ··· #endif,与#if defined(标识符) ··· #endif,其功能正好相反。

#include <iostream>
#include <stdio.h>
#define __DEBUG__ 

int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
#if defined(__DEBUG__)
        printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
    }
    return 0;
}

#include <iostream>
#include <stdio.h>

int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
#if !defined __DEBUG__
        printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
    }
    return 0;
}

这里DEBUG被定义过,那么就编译代码段。
这里也可以使用#ifdef实现相同的功能,当然也有与此相对应的ifndef,其功能与#ifdef是相反的。
语法规则:#ifdef 标识符 ··· #endif

#include <iostream>
#include <stdio.h>
#define __DEBUG__ 

int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
#ifdef __DEBUG__
        printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
    }
    return 0;
}

#include <iostream>
#include <stdio.h>

int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
#ifndef __DEBUG__
        printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
    }
    return 0;
}

同样的,这个预编译指令与C程序中的if-else相似,也可以多分支和嵌套使用。

4. 文件包含预处理指令

4.1 头文件被包含的方式

  • 本地文件包含: #include "文件名"。
    查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
  • 库文件包含:#include <文件名>。
    查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

当然用本地文件包含也可以查找库文件中的文件,只是本地文件包含会优先查找源文件所在目录,然后再查找库文件中的文件。这样效率会低一些

4.2 条件编译头文件

在程序编译时,会先预编译,预编译会将#define宏定义的内容替换和包含的头文件替换,当一个文件中包含多个相同的头文件,那么头文件就会被替换多次,相当于多份头文件中的内容出现在文件中。这样会影响编译的效率。
因此使用条件编译的方式解决,即在每个头文件首部写这样代码段:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__

或者如下代码段:

#pragma once

这样就可以避免头文件的重复引入。

​
```python
class BertPooler(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.activation = nn.Tanh()

    def forward(self, hidden_states):
        # We "pool" the model by simply taking the hidden state corresponding
        # to the first token.
        first_token_tensor = hidden_states[:, 0]
        pooled_output = self.dense(first_token_tensor)
        pooled_output = self.activation(pooled_output)
        return pooled_output
from transformers.models.bert.configuration_bert import *
import torch
config = BertConfig.from_pretrained("bert-base-uncased")
bert_pooler = BertPooler(config=config)
print("input to bert pooler size: {}".format(config.hidden_size))
batch_size = 1
seq_len = 2
hidden_size = 768
x = torch.rand(batch_size, seq_len, hidden_size)
y = bert_pooler(x)
print(y.size())
```

​

<think>好的,我现在需要回答用户关于C/C++预处理指令的使用方法和示例的问题。用户已经提供了三个引用资料,我需要结合这些资料来生成一个结构清晰、内容准确的回答,并且按照用户的格式要求,添加相关问题。 首先,我要回忆一下预处理指令的基本知识。预处理指令是在编译前由预处理器处理的指令,常见的包括#define、#include、条件编译指令如#ifdef、#ifndef、#endif等。用户提供的引用中,引用[2]和[3]详细说明了这些指令的作用和应用场景,而引用[1]提到了#T的用法,这属于宏定义中的字符串化操作符。 接下来,我需要按照用户的要求,将回答分为几个主要部分:预处理指令概述、常用指令的使用方法及示例、实际应用场景,然后生成相关问题。每个部分需要结合引用中的信息,确保正确性。 在预处理指令概述部分,我需要简要说明预处理指令的作用和它们在编译过程中的位置。引用[2]提到预处理指令是在编译前处理的,进行文本替换、条件编译等操作,这可以作为概述的基础。 然后是常用的预处理指令,如#define、#include、条件编译和#pragma。每个指令需要给出用途、语法和示例代码。例如,#define用于宏定义,可以分对象宏和函数宏。例如引用[3]中提到了使用#define求最大值和最小值的宏,这里可以举一个例子,但要注意避免宏的副作用,比如参数没有括号导致的错误。例如: #define MAX(a, b) ((a) > (b) ? (a) : (b)) 这样的宏在替换时,由于参数可能包含表达式,需要用括号包裹以避免运算符优先级的问题。另外,引用[1]中的例子#T是字符串化操作符,将宏参数转换为字符串,这在调试中很有用,可以生成变量名作为字符串。 接下来是#include,用于包含头文件,需要区分尖括号和双引号的区别,引用[3]可能提到这一点。 条件编译部分需要解释#ifdef、#ifndef、#else、#elif等指令,并给出示例,比如根据不同的平台定义不同的宏,或者调试模式下输出日志信息。例如: #ifdef DEBUG printf("Debug info: %d\n", value); #endif 这里引用[3]中提到的使用宏来调试代码的情况,可以举例说明。 然后是#pragma指令,这部分可能需要说明常见的用法,如#pragma once用于防止头文件重复包含,或者#pragma pack设置结构体对齐方式。引用[3]可能没有详细提到,但根据常识补充。 实际应用场景部分,需要结合引用中的例子,比如调试代码、跨平台兼容和自定义语法。例如,调试代码中使用宏来条件编译调试信息,跨平台时使用不同的宏定义包含不同的头文件,或者使用宏简化代码结构,如引用[1]中的CEREAL_NVP宏,使用#T将变量名转为字符串。 在生成相关问题的时候,需要确保问题与预处理指令相关,并且覆盖用户可能感兴趣的方面。例如,宏和函数的区别、防止头文件重复包含的方法、条件编译的实际应用、字符串化操作符的其他用法等。 需要注意的是,所有行内数学表达式要用$...$格式,但在这个回答中可能不需要数学公式,所以可能可以忽略。但如果有变量名或代码,要正确使用代码块。另外,回答要使用中文,结构清晰,分点说明,并添加引用标识。 现在需要检查是否有遗漏的重要预处理指令,比如#undef、#error、#line等,但根据引用[3]的内容,可能用户主要关注常用指令,因此可以简要提及或在相关问题中提到。 最后,确保示例代码正确,语法正确,并且符合用户提供的引用内容。例如,在字符串化操作符的例子中,正确使用#: #define PRINT_VAR(var) printf(#var " = %d\n", var) 当调用PRINT_VAR(count);时,会展开为printf("count" " = %d\n", count);,合并成"count = %d\n"。 总结,整个回答需要覆盖: 1. 预处理指令概述 2. 常用指令及示例 - #define - #include - 条件编译 - #pragma 3. 应用场景 4. 相关问题 确保每个部分都有引用支持,比如引用[3]中的内容,并在适当位置添加引用标识,如[^2]、[^3]等。</think>### C/C++ 预处理指令使用方法与示例 #### 1. 预处理指令概述 预处理指令在编译前由**预处理器**处理,用于文本替换、条件编译、文件包含等操作。它们是代码工程化的重要工具,例如实现跨平台兼容或调试优化。 --- #### 2. 常用预处理指令及示例 ##### (1) `#define`:定义宏 - **用途**:定义常量、函数式宏或简化代码。 - **语法**: ```c #define 宏名 替换内容 ``` - **示例**: ```c // 定义常量 #define PI 3.1415926 // 函数式宏(需注意参数括号) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define SQUARE(x) ((x) * (x)) // 字符串化操作符#(将参数转为字符串) #define PRINT_VAR(var) printf(#var " = %d\n", var) // 调用 PRINT_VAR(count) 会展开为:printf("count = %d\n", count); // 连接符##(拼接标识符) #define MAKE_FUNC(name) int func_##name() { return 0; } // 调用 MAKE_FUNC(test) 会生成:int func_test() { return 0; } ``` **注意**:函数式宏可能因参数副作用导致错误,如`SQUARE(++x)`会展开为`(++x * ++x)`。 --- ##### (2) `#include`:文件包含 - **用途**:插入头文件或自定义代码。 - **语法**: ```c #include <标准头文件> // 从系统目录查找 #include "自定义头文件" // 从当前目录查找 ``` --- ##### (3) 条件编译 通过宏定义控制代码是否参与编译。 - **常见指令**: - `#ifdef` / `#ifndef`:检查宏是否已定义。 - `#else` / `#elif`:分支逻辑。 - `#endif`:结束条件块。 - **示例**: ```c #define DEBUG // 定义调试模式 #ifdef DEBUG #define LOG(msg) printf("[DEBUG] %s\n", msg) #else #define LOG(msg) #endif // 跨平台兼容 #if defined(_WIN32) #include <windows.h> #elif defined(__linux__) #include <unistd.h> #endif ``` --- ##### (4) `#pragma`:编译器指令 - **用途**:指定编译器特殊行为。 - **常见用法**: ```c #pragma once // 防止头文件重复包含(非标准但广泛支持) #pragma pack(1) // 设置结构体对齐为1字节 #pragma warning(disable: 4996) // 禁用特定警告(MSVC) ``` --- #### 3. 实际应用场景 1. **调试代码**:通过条件编译开关日志输出。 2. **跨平台兼容**:根据平台选择不同代码分支。 3. **自定义语法**:如序列化库中使用宏简化代码(参考`CEREAL_NVP`的`#T`用法[^1])。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值