IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C/C++语言宏笔记 -> 正文阅读

[C++知识库]C/C++语言宏笔记

初学宏时遇到的问题

1. 编译器对宏进行处理发生在什么时期

c语言编译过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FOwe38a3-1628512552586)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621203253437.png)]

预处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxnGALMo-1628512637857)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621203434335.png)]

C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见,预处理之后得到的仍然是文本文件。
编译

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYxeRwFt-1628512681761)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621203949104.png)]

汇编

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyfMoNcO-1628512552592)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621204157773.png)]

链接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAbg5sFr-1628512552593)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621204238302.png)]

之后的过程,加载到内存等等行为,具体细节就涉及到操作系统相关知识。

所以,对宏的操作发生在程序处理的最初始的阶段—预处理阶段

2.头文件被多次引用带来的问题

  1. 变量, 函数等被重复定义,无法通过编译

经过测试发现,多次引用系统头文件是可以通过编译的,各系统头文件应该采取了解决措施。

  1. 如何采取解决措施

一般而言有两种解决办法

  1. 使用 #ifndef 条件编译
#ifndef __TEST_H__  /*如果该宏未被定义,则定义该宏和其他东西*/
#define __TEST_H__
//被定义的内容
#endif
  1. 使用#pragma once
#pragma once
// the code

两种方法的比较

#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心相同时,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况。

#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名相同引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,此时就不能保证他们不被重复包含。当然,相比宏名相同引发的“找不到声明”的问题,重复包含更容易被发现并正。

但是#pragma once 并非100%可靠,假设你调用a.h和b.h ,且a.h下又包含了b.h,就可能导致重定义问题

3.宏可以出现在程序的哪些部分

弄清楚上面的问题后,这个问题也逐渐清晰起来

答案是可以定义在任何地方,头部,尾部均可,甚至可以放在函数内部,但是需要放在使用这个宏的前面

比如下面这段代码,将定义移入main函数内部也是没问题的,但是放在PrintX函数后面就会报错

#include<iostream>
using namespace std;
int PrintX();
int main(){
    cout<<PrintX();
}
#define xx 100
int PrintX(){
    return xx;
}

宏的用法

1. 宏常量

好处都懂,就是修改时只需要更改一处即可

2. 宏函数

以宏充当函数,里面的坑比较多,把握住 宏只是进行文本替换即可

#define MAX(X,Y) ( (X) > (Y) ? (X) : (Y) )
#define ABS(X) ( (X)<0 )? -(X): (X) )

之所以要加括号,是因为避免优先级等等进行文本替换时发生的问题

#define MUL(X,Y) X*Y
/*假设此时要调用宏函数 MUL(2+3,3+5),则会变成*/
MUL(2+3,3+5) 2+3*3+5

但是还是避免不了一种情况++ 或–运算符

#define SQUARE(X) (X)*(X)
/*调用 SQUARE(x++),假设x为2 如果是函数,应当输出9,x变成3,而宏展开后:*/
SQUARE(x++)  (x++)*(x++)

3. #运算符

在类函数宏的替换体中,#号作为一个预处理符号,可以把记号替换为字符串 #x—> “x” (字符串化)

#define PRINTSQ(x) printf(#x"的平方为 %d",(x)*(x))
宏替换的过程:
printf("/*此处为具体的x*/""的平方为%d",(x)*(x))
之后字符串的串联功能将这两个合二为一   
/*分析*/
#include<iostream>
#define Print(x) printf(#x"的平方为%d",(x)*(x))
using namespace std;
#define xx 100
int main(){   
    Print(xx);
}
/*执行结果为:xx的平方为10000 ,而不是 100的平方为10000 
在xx被替换成100之前,xx已经被作为可替换的记号插入宏中了
换句话说,第一个宏比第二个宏要先被替换
*/

4. ##运算符

##也可以用于类函数宏的替换部分,而且可以用于对象宏的替换部分,充当粘合剂,将两个符号组合成一个符号

#define XNAMEX(n) x##n
#define PRINT_XN(n) printf("x"#n"=%d",x##n);
假设调用时
int XNAMEX(1)=12;
PRINT_XN(1);
则会输出 x1=12    

5. 变参宏

printf 和scanf都接受数量可变的参数 ,stdvar.h也有类似工具

在宏中, …用于表示可变,将宏参数列表中最后的参数写成省略号,用 _ _ VA_ARGS _ _放在替换部分,表明省略号省略了什么

#define PR(x,...) printf(#x":",__VA_ARGS__)
PR(2,"x=%.2f,y=%.4f\n", 2.3 , 4.5);

6. 宏的注意事项

  1. 宏名不能有空格,但是替换字符串可以有空格
  2. 宏函数一般都是大写,与函数区分
  3. 宏即生成内联代码,没有函数调用时的开销,故选择函数or宏时,如果表达内容简单,则宏更有优势。所以只使用一次的宏也无法明显减少程序运行时间
  4. 宏的标识符和c语言规则一样,由大写字母,小写字母,数字和下划线组成,且首字母不能是数字

7. #include指令

  1. 文件名在尖括号中代表预处理器在标准系统目录里查找该文件
  2. 文件名在双引号中代表预处理器在当前目录里查找该文件,如未找到再查找系统目录
  3. 文件名也可以包含路径名
#include<iostream>
#include"complex.h"
#include"/usr/biff/complex.h"

8. #undef指令

用于取消已经定义的#define指令

使得被定义的#define生效的范围为,从定义开始到取消定义之间的区域

不取消定义的情况下,作用域为从定义开始到文件尾

#define _SIZE 200
#undef _SIZE

9. 条件编译指令

  1. #ifdef 如果已经定义了某宏 则xxx
  2. #else 否则xxx
  3. #endif 结束条件编译
  4. #ifndef 如果没有定义某宏,则xxx
#ifdef HELLO
    #include"complex.h"
	#define MAXM 10
#else 
    #define "String.h"
    #define MAXM 20
#endif
/*-------------------------*/
#ifndef _COMPLEX_
#define _COMPLEX_
/* code */
#endif
  1. #if 和#elif指令

    如果表达式为真则xxx

    defined是一个预处理符 defined(XX)如果顶一个则返回1

    #if SYS==1
    	#include<stdio.h>
    #elif SYS==2
        #include<stdlib.h>
    #elif defined(_HELLO_)
    #else
        #include<string.h>
    #endif
    

10. 系统中的预定义宏

含义
_ _DATE _ _预处理的日期(Mmm dd yyyy)
_ _FILE _ _当前源代码文件名的字符串字面量
_ _ LINE_ _当前源代码文件中行号的整形常量
_ _STDC _ _设为1表示实现遵循C标准
_ _ STDC_HOSTED _ _本机环境设置为1否则为0
_ _STDC_VERSION _ _支持C99设置为 199901L ;支持C11标准设置为201112L
_ _TIME _ _翻译代码的时间 (hh:mm:ss)

11. #line和#error

#line重置__ LINE__ 和 __ FILE__的行号和文件名,#error让预处理器发出一条错误信息

#line 100
#line 10 "hello.c"

#if SIZE==10
#error size too small

12. #pragma

#pragma指示使每个编译程序在保留C和C++语言的整体兼容性时提供不同机器和操作系统特定的功能。编译指示被定义为机器或操作系统特定的,并且通常每种编译程序是不同的。

具体细节参见下面博客

https://blog.csdn.net/lmhuanying1012/article/details/78549763?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162434109316780261977038%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162434109316780261977038&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-78549763.first_rank_v2_pc_rank_v29&utm_term=%23prama&spm=1018.2226.3001.4187

?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:14:33  更:2021-08-10 13:15:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 19:53:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码