C/C++,不废话的宏使用技巧

news2024/12/26 1:58:29

经典废话

下面的所有内容全是我在欣赏一串代码时发出的疑问,之前对宏的了解不多,导致在刚看到下面的这串代码的时候是“地铁   老人   手机”,具体代码如下,如果有对这里解读有问题的欢迎在评论区留言。

 一、预定义宏

编译一个程序涉及很多的步骤

第一个就是预处理阶段
预处理器就是在源码编译之前进行一些文本性质的操作
主要任务比如: 删除注释,插入被include 包含的头文件的内容,替换由define定义的符号,以及确认根据条件编译进行编译

Visual c + + 编译器预定义某些预处理器宏,具体取决于语言 (C 或 C + +)、 编译目标,以及选择的编译器选项。

Visual c + + 支持 ANSI/ISO C99 标准和 ISO C + + 14 标准所指定的所需预定义的预处理器宏。 该实现还支持几个更多特定于 Microsoft 的预处理器宏。 仅针对特定的生成环境或编译器选项定义一些宏,宏。 除非另有说明,宏的定义整个翻译单元如同它们指定为 /D 编译器选项参数。 在定义时,宏是由预处理器在编译前扩展为指定的值。 预定义的宏不采用任何参数,并且不能重新定义。

常见的预定义宏:

还有更多的一些看如下网址:

https://learn.microsoft.com/zh-cn/cpp/preprocessor/predefined-macros?view=msvc-170

在顶部的代码里,由于不同环境下的语言可能有所不同,为了统一书写,需要根据不同版本的特性来做不同的调整,而这就是通过每个版本特殊的预定义宏来实现的。

二、decltype(x)

以下参考至http://c.biancheng.net/view/7151.html

什么是decltype

decltype 是 C++11 新增的一个关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导。

decltype 是“declare type”的缩写,译为“声明类型”。

既然已经有了 auto 关键字,为什么还需要 decltype 关键字呢?因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下 auto 用起来非常不方便,甚至压根无法使用,所以 decltype 关键字也被引入到 C++11 中。

auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:

auto varname = value;
decltype(exp) varname = value;

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。

auto 根据=右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

另外,auto 要求变量必须初始化,而 decltype 不要求。这很容易理解,auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。decltype 可以写成下面的形式:

decltype(exp) varname;

auto 的语法格式比 decltype 简单,所以在一般的类型推导中,使用 auto 比使用 decltype 更加方便.

我们知道,auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了。

exp 注意事项

原则上讲,exp 就是一个普通的表达式,它可以是任意复杂的形式,但是我们必须要保证 exp 的结果是有类型的,不能是 void;例如,当 exp 调用一个返回值类型为 void 的函数时,exp 的结果也是 void 类型,此时就会导致编译错误。

C++ decltype 用法举例:

int a = 0;
decltype(a) b = 1;  //b 被推导成了 int
decltype(10.8) x = 5.5;  //x 被推导成了 double
decltype(x + 100) y;  //y 被推导成了 double

实际应用

下面有一个模板定义

#include <vector>
using namespace std;
template <typename T>
class Base {
public:
    void func(T& container) {
        m_it = container.begin();
    }
private:
    typename T::iterator m_it;  //注意这里
};
int main()
{
    const vector<int> v;
    Base<const vector<int>> obj;
    obj.func(v);
    return 0;
}

单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出一大堆错误信息。原因就在于,T::iterator并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。

要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:

template <typename T>
class Base {
public:
    void func(T& container) {
        m_it = container.begin();
    }
private:
    decltype(T().begin()) m_it;  //注意这里
};

看起来是不是很清爽?

注意,有些低版本的编译器不支持T().begin()这种写法,以上代码在 VS2019 下可以通过,在 VS2015 下失败。

decltype(x) 和decltype((x))的不同之处

以下参考至文章  decltype(x) 和decltype((x))的不同

两者同为左值

如果“是否保留引用”是按照“左值性”来区分的话,那么 int x; 这样的定义, decltype(x) 也要返回 int& 了。这显然会造成很大的困扰, decltype 做函数的返回值就很容易悬垂引用。所以 decltype 的考虑是:参数如果是变量名,就返回其声明的类型;而参数是表达式,再根据表达式的值类型来判断是否保留引用。 decltype(x) 是变量名的规则,而 decltype((x)) 是表达式的规则,井水不犯河水。

实际上,标准并没有把 (x) 单独出来,而是把 (x) 合进去了。

在 N1607 [Decltype and auto (revision 3)] 中,没有提到 decltype((e)) 这种情况。而在 N1705 [Decltype and auto (revision 4)] 中,第一条改动就指出了:

Following is the list of changes from proposal N1607.
* The decltype rules now explicitly state that decltype((e)) == decltype(e) (as suggested by EWG).

于是,到了 N1978 [Decltype (revision 5)] 中, decltype ( expression ) 完整的说明是这样的:

1. If e is of the form (e1), decltype(e) is defined as decltype(e1).
2. If e is a name of a variable or non-overloaded function, decltype(e) is defined as the type used in the declaration of that variable or function. If e is a name of an overloaded function, the program is ill-formed.
3. If e is an invocation of a user-defined function or operator, decltype(e) is the return type of that function or operation.
4. Otherwise, where T is the type of e, if T is void or e is an rvalue, decltype(e) is defined as T, otherwise decltype(e) is defined as T&.

可以看到,revision 3 中的 decltype((e)) == decltype(e) 作为第 4 条规则的特例,有排面地成为了 decltype 说明的第 1 条。

然而到了 N2115 [Decltype (revision 6)] , decltype ( expression ) 的说明就变了,并且认真考虑了括号的问题:

1. If e is an id-expression or a class member access (5.2.5 [expr.ref]), decltype(e) is defined as the type of the entity named by e. If there is no such entity, or e names a set of overloaded functions, the program is ill-formed.
2. If e is a function call (5.2.2 [expr.call]) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is defined as the return type of that function.
3. Otherwise, where T is the type of e, if e is an lvalue, decltype(e) is defined as T&, otherwise decltype(e) is defined as T.

到了这儿, decltype((e)) 的定义已经被包含在了第 3 条规则中。并且 N2115 [Decltype (revision 6)] 特意举例:

Note that parentheses matter:
int a;
decltype(a) // int
decltype((a)) // int&

综上, (x) 本来就是一个左值表达式, decltype 就应该返回引用。然而有人考虑到 decltype((x)) 的特殊性,强行让他和 decltype(x) 挂钩,给它整了一条去掉括号的特殊规则,不过最后又整回去了。大概是这样一个故事。

其实早期关于 decltype 的提出、讨论、到最后确定下来,有很多都是变来变去的。说白了, decltype 就是在“在哪些情况下保留引用”做权衡。无非就是人为规定而已,没有为什么,也不一定合理。

三、宏define的各种用法 

参考至文章  https://zhuanlan.zhihu.com/p/367761694

1. #define命令

#define命令是C语言中的一个宏定义命令,它用来讲一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。该命令有两种格式:一种是简单的宏定义(不带参数的宏定义),另一种是带参数的宏定义。

(1) 简单的宏定义

格式:#define <宏名/标识符> <字符串>

例子:define PI 3.1415926

说明:

①宏名一般用大写

②宏定义末尾不加分好;

③可以用#undef命令终止宏定义的作用域

④宏定义可以嵌套

⑤字符串“”中永远不包含宏

⑥宏替换在编译前进行,不分配内存,变量定义分配内存,函数调用在编译后程序运行时进行,并且分配内存

⑦预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查

⑧使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义

(2) 带参数的宏定义(除了一般的字符串替换,还要做参数代换)

格式:#define <宏名>(<参数表>) <字符串>

例子:#define S(a,b) a*b

area=S(3,2);

第一步被换为area=a*b;第二步换为area=3*2;

一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。

说明:

①实参如果是表达式容易出问题

#define S(r) r*r

area=S(a+b);第一步换为area=r*r;第二步换成area=a+b*a+b;

当定义为#define S(r)((r)*(r))时area=((a+b)*(a+b))

②宏名和参数的括号间不能有空格

③宏替换之作替换不做计算,不做表达式求解

④宏的哑实结合不存在类型,也没有类型转换

⑤宏展开不占用运行时间,只占用编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)

2. 宏定义易错点示例总结

(1)“”内的东西不会被宏替换

#define NAMEzhang

程序中有"NAME"则,它会不会被替换呢?

答:否

(2)宏定义前面的那个必须是合法的用户标识符(可以使关键字)

#define 0x abcd

可以吗?也就是说,可不可以用把标识符的字母替换成别的东西?

答:否

(3)第二位置如果有字符串,必须“”配对

#define NAME "zhang

这个可以吗?

答:否

(4)只替换与第一位置完全相同的标识符

#define NAME "zhangyuncong"

程序中有上面的宏定义,并且,程序里有句:NAMELIST这样,会 不会被替换成"zhangyuncong"LIST

答:否

(5)带参数宏的一般用法

例如:

①#define MAX(a,b) ((a)>(b)?(a):(b))

则遇到MAX(1+2,value)则会把它替换成:

((1+2)>(value)?(1+2):(value))

②#define FUN(a) "a"

则,输入FUN(345)会被替换成什么?

其实,如果这么写,无论宏的实参是什么,都不会影响其被替换成 "a"。也就是说,""内的字符不被当成形参,即使它和一模一样。

③#define N 2+2

#define N 2+2

void main()
{
    int a=N*N;
    printf(“%d”,a);
}

问题解析:如1节所述,宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8。

④多行宏定义

#define doit (m,n) for(inti=0;i<(n);++i) { m+=i; }

3. 其他宏定义

#define Conn(x,y) x##y

#define ToChar(x) #@x

#define ToString(x) #x

x##y表示什么?表示x连接y,举例说:

int n = Conn(123,456); 结果就是n=123456;

char* str = Conn("asdf","adf")结果就是 str = "asdfadf";

#@x,其实就是给x加上单引号,结果返回是一个constchar,举例说:

char a = ToChar(1);结果就是a='1';

做个越界试验char a = ToChar(123);结果是a='3';

但是如果你的参数超过四个字符,编译器就给给你报错了!error C2015:too many characters in constant :P

#x是给x加双引号

char* str = ToString(123132);就成了str="123132";

如果有#define FUN(a,b) vo##a##b()那么FUN(idma,in)会被替换成void main()

4、宏定义其他概念

① 预处理功能:

(1)文件包含:可以把源程序中的#define扩展为文件正文,即把包含的.h文件找到并展开到#include所在处。

(2)条件编译:预处理器根据#if和#ifdef等编译命令及其后的条件,把源程序中的某些部分包含进来或排除在外,通常把排除在外的语句转换成空行。

(3)宏展开:预处理器将源程序文件中出现的对宏的引用展开成相应的宏定义,经过预处理器处理的源程序与之前的源程序有所不同,在这个阶段所进行的工作只是纯粹的替换和展开,没有任何计算功能。

②使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。

正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽略,但如果子函数完成的功能比较少,甚至只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在线就能制作活动邀请函,一键生成链接

今天小编教你如何在线制作一个活动邀请函&#xff0c;不需要下载软件&#xff0c;也不需要编程代码&#xff0c;只需使用乔拓云工具在线一键就能生成活动邀请函和邀请函链接&#xff0c;下面就跟着小编的教学开始学习如何在线制作活动邀请函&#xff01;第一步&#xff1a;打开…

[附源码]java毕业设计SSM归途中流浪动物收容与领养管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

OSPF高级配置——虚接口,NSSA

作者介绍&#xff1a; 作者&#xff1a;小刘在C站 每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生&#xff01; 夕阳下&#xff0c;是最美的绽放。 目录 一.ospf 虚链路 二.虚链路的目的 三.配置虚链路的规则及特点 四.虚链路的配置&#xff1a; nssa …

HTML小游戏6 —— 《高达战争》横版射击游戏(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的计算机相关知…

奥密克戎 (Omicron) 知多少m?| MedCheExpress

这个冬天 Omicron 已迅速超越其他变种&#xff0c;成为主要的 SARS-CoV-2 毒株&#xff0c;尽管该变体在体内引起的病毒水平与其“竞争对手” Delta 相比更低&#xff0c;但威力不容小觑。 ■ 第五大变异关注病毒株&#xff0c;有何神奇之处&#xff1f; 2021 年 11 月 24 日&…

深度自定义mybatis

> 回顾mybatis的操作的核心步骤 > > 编写核心类SqlSessionFacotryBuild进行解析配置文件 > 深度分析解析SqlSessionFacotryBuild干的核心工作 > > 编写核心类SqlSessionFacotry > 深度分析解析SqlSessionFacotry干的核心工作 > 编写核心类SqlSession &…

【面试官让我十分钟实现一个链表?一个双向带头循环链表甩给面试官】

我们在面试中面试官一般都会让我们现场写代码&#xff0c;如果你面试的时候面试官让你十分钟写一个链表&#xff0c;你是不是懵逼了&#xff1f;十分钟写一个链表&#xff0c;怎么可能&#xff1f;事实上是有可能的&#xff0c;十分钟写出的链表也能震惊面试官。 我们学习单链…

《红楼梦》诗词大全

前言&#xff1a; 博主最近二读红楼&#xff0c;幼时只觉此书开篇便人物繁杂、莺莺燕燕似多混乱&#xff0c;开篇只看黛玉哭闹了几次&#xff0c;便弃书不读&#xff0c;只觉困惑&#xff0c;其何敢称六大奇书或四大名著&#xff1f; 今日书荒&#xff0c;偶然间再次拾起红楼…

3.2 网络协议

0 socket协议 访问 Internet 使用得最广泛的方法&#xff1b;所谓socket通常也称作"套接字"&#xff0c;用于描述IP地址和端口&#xff0c;是一个通信链的句柄&#xff1b;应用程序通常通过"套接字"向网络发出请求或者应答网络请求&#xff1b;Socket接口…

六六大顺 马蹄集

六六大顺 难度&#xff1a;白银 0时间限制&#xff1a;1秒 巴占用内存&#xff1a;64M 输入正整数N,输出N以内&#xff08;含N),6的倍数&#xff0c;并且包含6的数字&#xff0c;比如36等。 格式 输入格式&#xff1a;输入整型 输出格式&#xff1a;输出整型&#xff0c;空格分…

SI24R1国产低功耗2.4GHz收发一体射频遥控工控答题卡方案芯片替代NRF24L01+

目录SI24R1简介芯片特性硬件设计参考2.4GHz射频芯片选型参考应用领域SI24R1简介 Si24R1 2.4GHz收发一体芯片量产于2012年&#xff0c;由于其一致性稳定性高、低功耗、远距离、兼容替代NRF24L01&#xff0c;兼容NORDIC 2.4GHz协议等特点&#xff0c;一直广泛应用于各物联网场景…

牛客竞赛每日俩题 - 动态规划2

目录 经典DP - 走方格 走方格2.0 分割回文串 分割回文串 - 回文优化 经典DP - 走方格 不同路径的数目(一)_牛客题霸_牛客网 状态&#xff1a; 子状态&#xff1a;从(0,0)到达(1,0),(1,1),(2,1),...(m-1,n-1)的路径数 F(i,j): 从(0,0)到达F(i,j)的路径数 状态递推&#xff1a…

【23届秋招总结系列】一个普本23届小学弟的秋招总结,上岸金山云开发(云计算方向)

大家好&#xff0c;我是路飞~ 正值秋招收尾阶段&#xff0c;今天很荣幸请来了交流qun小分队里的一位23届本科上岸 金山云开发工程师-云计算方向的同学&#xff0c;给大家分享一下他在秋招过程中的总结和心得体会。 他的博客链接&#xff1a;团子的守护 一、秋招收获 2022.1…

计算机毕业设计SSM大学生创新创业项目活动管理平台【附源码数据库】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【微服务】Nacos服务发现源码分析

&#x1f496;Spring家族及微服务系列文章 ✨【微服务】SpringBoot监听器机制以及在Nacos中的应用 ✨【微服务】Nacos服务端完成微服务注册以及健康检查流程 ✨【微服务】Nacos客户端微服务注册原理流程 ✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理 ✨【微服务】Sp…

Ubuntu20.04安装k8s v1.21.0

1. 禁用swap分区, 修改网络配置 sudo vim /etc/fstab 把有swap的那一行注释掉即可&#xff0c;如下&#xff1a; 然后执行如下命令 cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables 1 net.bridge.bridge-nf-call-iptables 1 EOF …

12.帖子模块——使用peewee创建多表关联的结构,使用Tornado创建查询接口、增加接口

1.模型建立与数据初始化 1.1分析建立表所需要的字段 本次主要是添加一个帖子展示时&#xff0c;所需要的内容&#xff0c;这里就得创建一个mysql的数据表去存储它的内容。 1.2 使用peewee创建多表关联结构Model 模型建立 # forum/models.py # 用于创建数据表模型from peewe…

企业自研业务系统的登录如何添加动态口令,实施MFA双因子认证?

一、背景需求 不少企业因业务需要会自己研发业务系统&#xff0c;为保护业务数据安全&#xff0c;首先要确保能访问到业务数据的人员“身份”安全可信。 企业自研业务系统的账号密码基本是 IT 管理员单独管理维护&#xff0c;员工为了方便记忆&#xff0c;通常设置与其他商采系…

函数绘图仪 MathGrafix 12.1 Crack

函数绘图仪 MathGrafix 12.1 MatheGrafix 12.1于 2022 年 8 月 1 日发布&#xff0c;包含两个新模块&#xff1a; 公式函数模块支持具有一个变量和最多十个参数的函数方程。每个参数都可以使用自动运行的滑块进行调整。 在数据模块中&#xff0c;记录数据后&#xff0c;使用回…

网页制作基础大二dw作业HTML+CSS+JavaScript云南我的家乡旅游景点

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…