预处理器 - 记号传递操作符##

2023-10-10 06:08

本文主要是介绍预处理器 - 记号传递操作符##,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文译至:http://complete-concrete-concise.com/programming/c/preprocessor-the-token-pasting-operator

##可能是最不为人所知,也最缺少文档的预处理器操作符了。

记号传递操作符(##) 仅仅删除它周围的所有空格和将非空的字符连接到一起。它只能用于一个宏定义中,它被用于创建新的记号。

它不能是替换文本里的第一个或最后一个字符。

如果有多个记号传递操作符(##)和/或字符串化操作符(#)那么处理的顺序是不定的。

有效的记号是:

  • 标识符 (变量名,函数名,等等)
  • 关键字 (int, while, volatile, etc)
  • 文字 (字符串, 数字, 字符, true 或 false)
  • 操作符和标点 (+, -, *, (, etc)

如果使用##的不是有效的记号,那么行为是未定义的。

考虑一下如下的宏定义:使用##操作符来在替换字符串中移除额外的空格。

#define macro_start i ## n ##t m         ##ain(void)


在预处理后macro_start 名将会被替换成:

int main(void)

因为预处理器对##作如下处理:
  1. 移除所有的##
  2. 删除周围所有的空格
  3. 将非空格字符连接到一起

虽然记号传递操作符可用于无参数的宏,但是这没有什么意义。因为你可以无须##直接打印你需要的东西。

##只是在你用它来连接你传递到宏中的参数时才发挥它的威力(只要是关于预处理器的,它处理的所有东西就是一堆文本).

通常情况下,它用于自动创建新的标志符。例如:

#define my_macro(x) x##_macrofied

将连接传递的参数( x) 并追加后缀  _macrofied到x后面.
my_macro(identifier)

将会被扩展为:
identifier_macrofied


编译器在处理##的不同点

GCC 和Visual C++按不同的方式处理  ## .

如果连接的结果不是一个有效的预处理记号,GCC 的处理是严格的 – 它在编译阶段会产生一个错误。

Visual C++, 在另一方面,重新处理了连接的结果并支持它,而它在GCC中被视为无效的。

这两个编译器的处理都可以正常工作,因为标准并没有定义一个无效的记号就如何被处理的(它只是说行为是未定义的)。拒绝它好点因为这重新处理了结果并被解析成有效的记号。


例子:

下面的例子在GCC中失败但是Visual C++里成功:

#define macro_start int main ## (void)

在预处理器连接main和(后,我们得到记号

main(

这不是一个有效的记号。

GCC 不支持它。

另一方面,Visual C++ 重新处理来生成两个记号: 1) 一个标志符 main 和 2) 标点 / 操作符(。

两个编译器都能正常处理以下的宏

#define macro_increment(x) x+ ## +

因为它解析成两个记号。第一个是记号x,第二个是操作符++。


为什么用##?

它主要是用于降低重复的(易于出错)输入。

下面的代码定义了一个宏来创建一个C/C++中的新标量类型,并创建了6个针对那个类型的名字定制的函数。(初始化,加,减,乘,除和原始值访问)。不使用##操作符的话,这就要手工输入(如果你定义许多类型的话,这将重复操作,令人厌烦,而且容易出错)或是通过拷贝/粘贴/查找和替换操作(仍旧是重复操作,令人厌烦,而且容易出错)。

#define new_scalar_type(name, type) \
typedef struct \
{ \type value; \
} name; \
inline name name##_(type v) \
{ \name t; \t.value = v; \return t; \
} \
inline name add_##name(name a, name b) \
{ \name t; \t.value = a.value + b.value; \return t; \
} \
inline name sub_##name(name a, name b) \
{ \name t; \t.value = a.value - b.value; \return t; \
} \
inline name mul_##name(name a, name b) \
{ \name t; \t.value = a.value * b.value; \return t; \
} \
inline name div_##name(name a, name b) \
{ \name t; \t.value = a.value / b.value; \return t; \
} \
inline type value_##name(name a) \
{ \return a.value; \
} \

当你在你的代码里使用宏:

new_scalar_type(age, int);

你会得到
  • 一个名叫age的新类型,含一个int型
  • 一个初始化函数age age_(int t)
  • 一个加函数 age add_age(age a, age b)
  • 一个减函数age sub_age(age a, age b)
  • 一个乘函数age mul_age(age a, age b)
  • 一个除函数age div_age(age a, age b)
  • 一个原始值访问函数int value_age(age a)

如果你定义了一个新类型,叫做 weight_lbs, 那么你将会得到新的类型和相关的定制的命名函数,你就可以操作这些函数。

你也能定义一个新类型,按下面的方式使用:

new_scalar_type(age, int);int main (void)
{age a = age_(42);age b = age_(24);age c = add_age(a, b);return value_age(c);
}


另外一个例子

下面的例子(被称作convert,能被用于自动生成一个将一个值转换成另一个值的函数 (例如, °F 到 °C 或英尺到英寸,等等):

#define convert(from, to, conversion, from_type, to_type) \
to_type convert_##from##_to_##to(from_type f) \
{ \return conversion; \
} \


它用到5个参数:

  1. from - a descriptive name of the unit we are converting from
  2. to - a descriptive name of the unit we are converting to
  3. conversion - the conversion equation (yes, macro parameters can be complex)
  4. from_type - the type we are converting from
  5. to_type - the type we are converting to

该宏可以这么使用:

convert(f, c, (f-32)*5.0/9.0, float, float);
convert(ft, in, ft * 12, int, int);


当宏被扩展时,我们得到两个叫做 convert_f_to_c 和 convert_ft_to_in的函数。这些可以按下面的方法使用:

int main (void)
{float a = 70.0;float b;int c = 3;int d;b = convert_f_to_c (a);d = convert_ft_to_in(c);return 0;
}


这篇关于预处理器 - 记号传递操作符##的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/178489

相关文章

异步线程traceId如何实现传递

《异步线程traceId如何实现传递》文章介绍了如何在异步请求中传递traceId,通过重写ThreadPoolTaskExecutor的方法和实现TaskDecorator接口来增强线程池,确保异步... 目录前言重写ThreadPoolTaskExecutor中方法线程池增强总结前言在日常问题排查中,

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

Java向kettle8.0传递参数的方式总结

《Java向kettle8.0传递参数的方式总结》介绍了如何在Kettle中传递参数到转换和作业中,包括设置全局properties、使用TransMeta和JobMeta的parameterValu... 目录1.传递参数到转换中2.传递参数到作业中总结1.传递参数到转换中1.1. 通过设置Trans的

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

JAVA基础:值传递和址传递

1 值传递和址传递 值传递 方法调用时,传递的实参是一个基本类型的数据 形参改变,实参不变 public static void doSum(int num1,int num2){}main(){doSum(10,20);int i = 10 ;int j = 20 ;doSum(i,j) ;}   public static void t1(int num){num = 20

C++可以被重载的操作符Overloadable operators

C++允许绝大多数操作符被重载,也就是重新定义操作符实现的功能,这样它们的行为可以被设计出来以适应所有的数据类型,包括类。 以下是C++可以被重载的操作符(Overloadable operators): //四则运算符+ - * / %+= -= *= /= %=//比较运算符> >= == != //赋值运算符= //位操作

六、Maven依赖管理、依赖传递和依赖冲突

1.Maven依赖管理 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题,使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中,避免出现版本冲突和依赖缺失等问题。 我们通过定义 POM 文件,Maven 能够自动解析项目的依赖关系,并通过 Maven 仓库自动下载和管理依赖,从而避免了手动

c++/《重载操作符》

为什么要对运算符进行重载:         C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。 <返回类型说明符> operator <运算符符号>(<参数表>) { <函数体> }

【鸿蒙HarmonyOS NEXT】页面之间相互传递参数

【鸿蒙HarmonyOS NEXT】页面之间相互传递参数 一、环境说明二、页面之间相互传参 一、环境说明 DevEco Studio 版本: API版本:以12为主 二、页面之间相互传参 说明: 页面间的导航可以通过页面路由router模块来实现。页面路由模块根据页面url找到目标页面,从而实现跳转。通过页面路由模块,可以使用不同的url访问不同的页面,包括跳转到U